commit adf0d174978b7900c866d229cbb403b20135de80
Author: wehub-resource-sync ` in Chatbot messages. Thanks [@jaywonchung](https://github.com/jaywonchung)!
+- [#5005](https://github.com/gradio-app/gradio/pull/5005) [`f5539c76`](https://github.com/gradio-app/gradio/commit/f5539c7618e31451420bd3228754774da14dc65f) - Enhancement: Add focus event to textbox and number component. Thanks [@JodyZ0203](https://github.com/JodyZ0203)!
+- [#5104](https://github.com/gradio-app/gradio/pull/5104) [`34f6b22e`](https://github.com/gradio-app/gradio/commit/34f6b22efbfedfa569d452f3f99ed2e6593e3c21) - Strip leading and trailing spaces from username in login route. Thanks [@sweep-ai](https://github.com/apps/sweep-ai)!
+- [#5149](https://github.com/gradio-app/gradio/pull/5149) [`144df459`](https://github.com/gradio-app/gradio/commit/144df459a3b7895e524defcfc4c03fbb8b083aca) - Add `show_edit_button` param to `gr.Audio`. Thanks [@hannahblair](https://github.com/hannahblair)!
+- [#5136](https://github.com/gradio-app/gradio/pull/5136) [`eaa1ce14`](https://github.com/gradio-app/gradio/commit/eaa1ce14ac41de1c23321e93f11f1b03a2f3c7f4) - Enhancing Tamil Translation: Language Refinement 🌟. Thanks [@sanjaiyan-dev](https://github.com/sanjaiyan-dev)!
+- [#5035](https://github.com/gradio-app/gradio/pull/5035) [`8b4eb8ca`](https://github.com/gradio-app/gradio/commit/8b4eb8cac9ea07bde31b44e2006ca2b7b5f4de36) - JS Client: Fixes cannot read properties of null (reading 'is_file'). Thanks [@raymondtri](https://github.com/raymondtri)!
+- [#5023](https://github.com/gradio-app/gradio/pull/5023) [`e6317d77`](https://github.com/gradio-app/gradio/commit/e6317d77f87d3dad638acca3dbc4a9228570e63c) - Update dependency extendable-media-recorder to v8. Thanks [@renovate](https://github.com/apps/renovate)!
+- [#5085](https://github.com/gradio-app/gradio/pull/5085) [`13e47835`](https://github.com/gradio-app/gradio/commit/13e478353532c4af18cfa50772f8b6fb3c6c9818) - chore(deps): update dependency extendable-media-recorder to v8. Thanks [@renovate](https://github.com/apps/renovate)!
+- [#5080](https://github.com/gradio-app/gradio/pull/5080) [`37caa2e0`](https://github.com/gradio-app/gradio/commit/37caa2e0fe95d6cab8beb174580fb557904f137f) - Add icon and link params to `gr.Button`. Thanks [@hannahblair](https://github.com/hannahblair)!
+
+### Fixes
+
+- [#5062](https://github.com/gradio-app/gradio/pull/5062) [`7d897165`](https://github.com/gradio-app/gradio/commit/7d89716519d0751072792c9bbda668ffeb597296) - `gr.Dropdown` now has correct behavior in static mode as well as when an option is selected. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5077](https://github.com/gradio-app/gradio/pull/5077) [`667875b2`](https://github.com/gradio-app/gradio/commit/667875b2441753e74d25bd9d3c8adedd8ede11cd) - Live audio streaming output
+- [#5118](https://github.com/gradio-app/gradio/pull/5118) [`1b017e68`](https://github.com/gradio-app/gradio/commit/1b017e68f6a9623cc2ec085bd20e056229552028) - Add `interactive` args to `gr.ColorPicker`. Thanks [@hannahblair](https://github.com/hannahblair)!
+- [#5114](https://github.com/gradio-app/gradio/pull/5114) [`56d2609d`](https://github.com/gradio-app/gradio/commit/56d2609de93387a75dc82b1c06c1240c5b28c0b8) - Reset textbox value to empty string when value is None. Thanks [@hannahblair](https://github.com/hannahblair)!
+- [#5075](https://github.com/gradio-app/gradio/pull/5075) [`67265a58`](https://github.com/gradio-app/gradio/commit/67265a58027ef1f9e4c0eb849a532f72eaebde48) - Allow supporting >1000 files in `gr.File()` and `gr.UploadButton()`. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5135](https://github.com/gradio-app/gradio/pull/5135) [`80727bbe`](https://github.com/gradio-app/gradio/commit/80727bbe2c6d631022054edf01515017691b3bdd) - Fix dataset features and dataset preview for HuggingFaceDatasetSaver. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+- [#5039](https://github.com/gradio-app/gradio/pull/5039) [`620e4645`](https://github.com/gradio-app/gradio/commit/620e46452729d6d4877b3fab84a65daf2f2b7bc6) - `gr.Dropdown()` now supports values with arbitrary characters and doesn't clear value when re-focused. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5061](https://github.com/gradio-app/gradio/pull/5061) [`136adc9c`](https://github.com/gradio-app/gradio/commit/136adc9ccb23e5cb4d02d2e88f23f0b850041f98) - Ensure `gradio_client` is backwards compatible with `gradio==3.24.1`. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5129](https://github.com/gradio-app/gradio/pull/5129) [`97d804c7`](https://github.com/gradio-app/gradio/commit/97d804c748be9acfe27b8369dd2d64d61f43c2e7) - [Spaces] ZeroGPU Queue fix. Thanks [@cbensimon](https://github.com/cbensimon)!
+- [#5140](https://github.com/gradio-app/gradio/pull/5140) [`cd1353fa`](https://github.com/gradio-app/gradio/commit/cd1353fa3eb1b015f5860ca5d5a8e8d1aa4a831c) - Fixes the display of minutes in the video player. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5111](https://github.com/gradio-app/gradio/pull/5111) [`b84a35b7`](https://github.com/gradio-app/gradio/commit/b84a35b7b91eca947f787648ceb361b1d023427b) - Add icon and link to DuplicateButton. Thanks [@aliabd](https://github.com/aliabd)!
+- [#5030](https://github.com/gradio-app/gradio/pull/5030) [`f6c491b0`](https://github.com/gradio-app/gradio/commit/f6c491b079d335af633dd854c68eb26f9e61c552) - highlightedtext throws an error basing on model. Thanks [@rajeunoia](https://github.com/rajeunoia)!
+
+## 3.39.0
+
+### Highlights
+
+#### Create Discord Bots from Gradio Apps 🤖 ([#4960](https://github.com/gradio-app/gradio/pull/4960) [`46e4ef67`](https://github.com/gradio-app/gradio/commit/46e4ef67d287dd68a91473b73172b29cbad064bc))
+
+We're excited to announce that Gradio can now automatically create a discord bot from any `gr.ChatInterface` app.
+
+It's as easy as importing `gradio_client`, connecting to the app, and calling `deploy_discord`!
+
+_🦙 Turning Llama 2 70b into a discord bot 🦙_
+
+```python
+import gradio_client as grc
+grc.Client("ysharma/Explore_llamav2_with_TGI").deploy_discord(to_id="llama2-70b-discord-bot")
+```
+
+
+
+#### Getting started with template spaces
+
+To help get you started, we have created an organization on Hugging Face called [gradio-discord-bots](https://huggingface.co/gradio-discord-bots) with template spaces you can use to turn state of the art LLMs powered by Gradio to discord bots.
+
+Currently we have template spaces for:
+
+- [Llama-2-70b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/Llama-2-70b-chat-hf) powered by a FREE Hugging Face Inference Endpoint!
+- [Llama-2-13b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/Llama-2-13b-chat-hf) powered by Hugging Face Inference Endpoints.
+- [Llama-2-13b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/llama-2-13b-chat-transformers) powered by Hugging Face transformers.
+- [falcon-7b-instruct](https://huggingface.co/spaces/gradio-discord-bots/falcon-7b-instruct) powered by Hugging Face Inference Endpoints.
+- [gpt-3.5-turbo](https://huggingface.co/spaces/gradio-discord-bots/gpt-35-turbo), powered by openai. Requires an OpenAI key.
+
+But once again, you can deploy ANY `gr.ChatInterface` app exposed on the internet! So don't hesitate to try it on your own Chatbots.
+
+❗️ Additional Note ❗️: Technically, any gradio app that exposes an api route that takes in a single string and outputs a single string can be deployed to discord. But `gr.ChatInterface` apps naturally lend themselves to discord's chat functionality so we suggest you start with those.
+
+Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+### Features
+
+- [#4995](https://github.com/gradio-app/gradio/pull/4995) [`3f8c210b`](https://github.com/gradio-app/gradio/commit/3f8c210b01ef1ceaaf8ee73be4bf246b5b745bbf) - Implement left and right click in `Gallery` component and show implicit images in `Gallery` grid. Thanks [@hannahblair](https://github.com/hannahblair)!
+- [#4993](https://github.com/gradio-app/gradio/pull/4993) [`dc07a9f9`](https://github.com/gradio-app/gradio/commit/dc07a9f947de44b419d8384987a02dcf94977851) - Bringing back the "Add download button for audio" PR by [@leuryr](https://github.com/leuryr). Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#4979](https://github.com/gradio-app/gradio/pull/4979) [`44ac8ad0`](https://github.com/gradio-app/gradio/commit/44ac8ad08d82ea12c503dde5c78f999eb0452de2) - Allow setting sketch color default. Thanks [@aliabid94](https://github.com/aliabid94)!
+- [#4985](https://github.com/gradio-app/gradio/pull/4985) [`b74f8453`](https://github.com/gradio-app/gradio/commit/b74f8453034328f0e42da8e41785f5eb039b45d7) - Adds `additional_inputs` to `gr.ChatInterface`. Thanks [@abidlabs](https://github.com/abidlabs)!
+
+### Fixes
+
+- [#4997](https://github.com/gradio-app/gradio/pull/4997) [`41c83070`](https://github.com/gradio-app/gradio/commit/41c83070b01632084e7d29123048a96c1e261407) - Add CSS resets and specifiers to play nice with HF blog. Thanks [@aliabid94](https://github.com/aliabid94)!
+
+## 3.38
+
+### New Features:
+
+- Provide a parameter `animate` (`False` by default) in `gr.make_waveform()` which animates the overlayed waveform by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4918](https://github.com/gradio-app/gradio/pull/4918)
+- Add `show_download_button` param to allow the download button in static Image components to be hidden by [@hannahblair](https://github.com/hannahblair) in [PR 4959](https://github.com/gradio-app/gradio/pull/4959)
+- Added autofocus argument to Textbox by [@aliabid94](https://github.com/aliabid94) in [PR 4978](https://github.com/gradio-app/gradio/pull/4978)
+- The `gr.ChatInterface` UI now converts the "Submit" button to a "Stop" button in ChatInterface while streaming, which can be used to pause generation. By [@abidlabs](https://github.com/abidlabs) in [PR 4971](https://github.com/gradio-app/gradio/pull/4971).
+- Add a `border_color_accent_subdued` theme variable to add a subdued border color to accented items. This is used by chatbot user messages. Set the value of this variable in `Default` theme to `*primary_200`. By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4989](https://github.com/gradio-app/gradio/pull/4989)
+- Add default sketch color argument `brush_color`. Also, masks drawn on images are now slightly translucent (and mask color can also be set via brush_color). By [@aliabid94](https://github.com/aliabid94) in [PR 4979](https://github.com/gradio-app/gradio/pull/4979)
+
+### Bug Fixes:
+
+- Fixes `cancels` for generators so that if a generator is canceled before it is complete, subsequent runs of the event do not continue from the previous iteration, but rather start from the beginning. By [@abidlabs](https://github.com/abidlabs) in [PR 4969](https://github.com/gradio-app/gradio/pull/4969).
+- Use `gr.State` in `gr.ChatInterface` to reduce latency by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4976](https://github.com/gradio-app/gradio/pull/4976)
+- Fix bug with `gr.Interface` where component labels inferred from handler parameters were including special args like `gr.Request` or `gr.EventData`. By [@cbensimon](https://github.com/cbensimon) in [PR 4956](https://github.com/gradio-app/gradio/pull/4956)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Other Changes:
+
+- Apply pyright to the `components` directory by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4948](https://github.com/gradio-app/gradio/pull/4948)
+- Improved look of ChatInterface by [@aliabid94](https://github.com/aliabid94) in [PR 4978](https://github.com/gradio-app/gradio/pull/4978)
+
+## 3.37
+
+### New Features:
+
+Introducing a new `gr.ChatInterface` abstraction, which allows Gradio users to build fully functioning Chat interfaces very easily. The only required parameter is a chat function `fn`, which accepts a (string) user input `message` and a (list of lists) chat `history` and returns a (string) response. Here's a toy example:
+
+```py
+import gradio as gr
+
+def echo(message, history):
+ return message
+
+demo = gr.ChatInterface(fn=echo, examples=["hello", "hola", "merhaba"], title="Echo Bot")
+demo.launch()
+```
+
+Which produces:
+
+
+
+And a corresponding easy-to-use API at `/chat`:
+
+
+
+The `gr.ChatInterface` abstraction works nicely with various LLM libraries, such as `langchain`. See the [dedicated guide](https://gradio.app/guides/creating-a-chatbot-fast) for more examples using `gr.ChatInterface`. Collective team effort in [PR 4869](https://github.com/gradio-app/gradio/pull/4869)
+
+- Chatbot messages now show hyperlinks to download files uploaded to `gr.Chatbot()` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4848](https://github.com/gradio-app/gradio/pull/4848)
+- Cached examples now work with generators and async generators by [@abidlabs](https://github.com/abidlabs) in [PR 4927](https://github.com/gradio-app/gradio/pull/4927)
+- Add RTL support to `gr.Markdown`, `gr.Chatbot`, `gr.Textbox` (via the `rtl` boolean parameter) and text-alignment to `gr.Textbox`(via the string `text_align` parameter) by [@abidlabs](https://github.com/abidlabs) in [PR 4933](https://github.com/gradio-app/gradio/pull/4933)
+
+Examples of usage:
+
+```py
+with gr.Blocks() as demo:
+ gr.Textbox(interactive=True, text_align="right")
+demo.launch()
+```
+
+```py
+with gr.Blocks() as demo:
+ gr.Markdown("سلام", rtl=True)
+demo.launch()
+```
+
+- The `get_api_info` method of `Blocks` now supports layout output components [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4871](https://github.com/gradio-app/gradio/pull/4871)
+
+- Added the support for the new command `gradio environment`to make it easier for people to file bug reports if we shipped an easy command to list the OS, gradio version, and versions of gradio/gradio-client dependencies. bu [@varshneydevansh](https://github.com/varshneydevansh) in [PR 4915](https://github.com/gradio-app/gradio/pull/4915).
+
+### Bug Fixes:
+
+- The `.change()` event is fixed in `Video` and `Image` so that it only fires once by [@abidlabs](https://github.com/abidlabs) in [PR 4793](https://github.com/gradio-app/gradio/pull/4793)
+- The `.change()` event is fixed in `Audio` so that fires when the component value is programmatically updated by [@abidlabs](https://github.com/abidlabs) in [PR 4793](https://github.com/gradio-app/gradio/pull/4793)
+
+* Add missing `display: flex` property to `Row` so that flex styling is applied to children by [@hannahblair] in [PR 4896](https://github.com/gradio-app/gradio/pull/4896)
+* Fixed bug where `gr.Video` could not preprocess urls by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4904](https://github.com/gradio-app/gradio/pull/4904)
+* Fixed copy button rendering in API page on Safari by [@aliabid94](https://github.com/aliabid94) in [PR 4924](https://github.com/gradio-app/gradio/pull/4924)
+* Fixed `gr.Group` and `container=False`. `container` parameter only available for `Textbox`, `Number`, and `Dropdown`, the only elements where it makes sense. By [@aliabid94](https://github.com/aliabid94) in [PR 4916](https://github.com/gradio-app/gradio/pull/4916)
+* Fixed broken image link in auto-generated `app.py` from `ThemeClass.push_to_hub` by [@deepkyu](https://github.com/deepkyu) in [PR 4944](https://github.com/gradio-app/gradio/pull/4944)
+
+### Other Changes:
+
+- Warning on mobile that if a user leaves the tab, websocket connection may break. On broken connection, tries to rejoin queue and displays error conveying connection broke. By [@aliabid94](https://github.com/aliabid94) in [PR 4742](https://github.com/gradio-app/gradio/pull/4742)
+- Remove blocking network calls made before the local URL gets printed - these slow down the display of the local URL, especially when no internet is available. [@aliabid94](https://github.com/aliabid94) in [PR 4905](https://github.com/gradio-app/gradio/pull/4905).
+- Pinned dependencies to major versions to reduce the likelihood of a broken `gradio` due to changes in downstream dependencies by [@abidlabs](https://github.com/abidlabs) in [PR 4885](https://github.com/gradio-app/gradio/pull/4885)
+- Queue `max_size` defaults to parent Blocks `max_thread` when running on Spaces with ZeroGPU hardware. By [@cbensimon](https://github.com/cbensimon) in [PR 4937](https://github.com/gradio-app/gradio/pull/4937)
+
+### Breaking Changes:
+
+Motivated by the release of `pydantic==2.0`, which included breaking changes that broke a large number of Gradio apps, we've pinned many gradio dependencies. Note that pinned dependencies can cause downstream conflicts, so this may be a breaking change. That being said, we've kept the pins pretty loose, and we're expecting change to be better for the long-term stability of Gradio apps.
+
+## 3.36.1
+
+### New Features:
+
+- Hotfix to support pydantic v1 and v2 by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4835](https://github.com/gradio-app/gradio/pull/4835)
+
+### Bug Fixes:
+
+- Fix bug where `gr.File` change event was not triggered when the value was changed by another event by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4811](https://github.com/gradio-app/gradio/pull/4811)
+
+### Other Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.36.0
+
+### New Features:
+
+- The `gr.Video`, `gr.Audio`, `gr.Image`, `gr.Chatbot`, and `gr.Gallery` components now include a share icon when deployed on Spaces. This behavior can be modified by setting the `show_share_button` parameter in the component classes. by [@aliabid94](https://github.com/aliabid94) in [PR 4651](https://github.com/gradio-app/gradio/pull/4651)
+- Allow the web component `space`, `src`, and `host` attributes to be updated dynamically by [@pngwn](https://github.com/pngwn) in [PR 4461](https://github.com/gradio-app/gradio/pull/4461)
+- Suggestion for Spaces Duplication built into Gradio, by [@aliabid94](https://github.com/aliabid94) in [PR 4458](https://github.com/gradio-app/gradio/pull/4458)
+- The `api_name` parameter now accepts `False` as a value, which means it does not show up in named or unnamed endpoints. By [@abidlabs](https://github.com/aliabid94) in [PR 4683](https://github.com/gradio-app/gradio/pull/4683)
+- Added support for `pathlib.Path` in `gr.Video`, `gr.Gallery`, and `gr.Chatbot` by [sunilkumardash9](https://github.com/sunilkumardash9) in [PR 4581](https://github.com/gradio-app/gradio/pull/4581).
+
+### Bug Fixes:
+
+- Updated components with `info` attribute to update when `update()` is called on them. by [@jebarpg](https://github.com/jebarpg) in [PR 4715](https://github.com/gradio-app/gradio/pull/4715).
+- Ensure the `Image` components undo button works mode is `mask` or `color-sketch` by [@amyorz](https://github.com/AmyOrz) in [PR 4692](https://github.com/gradio-app/gradio/pull/4692)
+- Load the iframe resizer external asset asynchronously, by [@akx](https://github.com/akx) in [PR 4336](https://github.com/gradio-app/gradio/pull/4336)
+- Restored missing imports in `gr.components` by [@abidlabs](https://github.com/abidlabs) in [PR 4566](https://github.com/gradio-app/gradio/pull/4566)
+- Fix bug where `select` event was not triggered in `gr.Gallery` if `height` was set to be large with `allow_preview=False` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4551](https://github.com/gradio-app/gradio/pull/4551)
+- Fix bug where setting `visible=False` in `gr.Group` event did not work by [@abidlabs](https://github.com/abidlabs) in [PR 4567](https://github.com/gradio-app/gradio/pull/4567)
+- Fix `make_waveform` to work with paths that contain spaces [@akx](https://github.com/akx) in [PR 4570](https://github.com/gradio-app/gradio/pull/4570) & [PR 4578](https://github.com/gradio-app/gradio/pull/4578)
+- Send captured data in `stop_recording` event for `gr.Audio` and `gr.Video` components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4554](https://github.com/gradio-app/gradio/pull/4554)
+- Fix bug in `gr.Gallery` where `height` and `object_fit` parameters where being ignored by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4576](https://github.com/gradio-app/gradio/pull/4576)
+- Fixes an HTML sanitization issue in DOMPurify where links in markdown were not opening in a new window by [@hannahblair] in [PR 4577](https://github.com/gradio-app/gradio/pull/4577)
+- Fixed Dropdown height rendering in Columns by [@aliabid94](https://github.com/aliabid94) in [PR 4584](https://github.com/gradio-app/gradio/pull/4584)
+- Fixed bug where `AnnotatedImage` css styling was causing the annotation masks to not be displayed correctly by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4628](https://github.com/gradio-app/gradio/pull/4628)
+- Ensure that Gradio does not silently fail when running on a port that is occupied by [@abidlabs](https://github.com/abidlabs) in [PR 4624](https://github.com/gradio-app/gradio/pull/4624).
+- Fix double upload bug that caused lag in file uploads by [@aliabid94](https://github.com/aliabid94) in [PR 4661](https://github.com/gradio-app/gradio/pull/4661)
+- `Progress` component now appears even when no `iterable` is specified in `tqdm` constructor by [@itrushkin](https://github.com/itrushkin) in [PR 4475](https://github.com/gradio-app/gradio/pull/4475)
+- Deprecation warnings now point at the user code using those deprecated features, instead of Gradio internals, by (https://github.com/akx) in [PR 4694](https://github.com/gradio-app/gradio/pull/4694)
+- Adapt column widths in gr.Examples based on content by [@pngwn](https://github.com/pngwn) & [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4700](https://github.com/gradio-app/gradio/pull/4700)
+- The `plot` parameter deprecation warnings should now only be emitted for `Image` components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4709](https://github.com/gradio-app/gradio/pull/4709)
+- Removed uncessessary `type` deprecation warning by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4709](https://github.com/gradio-app/gradio/pull/4709)
+- Ensure Audio autoplays works when `autoplay=True` and the video source is dynamically updated [@pngwn](https://github.com/pngwn) in [PR 4705](https://github.com/gradio-app/gradio/pull/4705)
+- When an error modal is shown in spaces, ensure we scroll to the top so it can be seen by [@pngwn](https://github.com/pngwn) in [PR 4712](https://github.com/gradio-app/gradio/pull/4712)
+- Update depedencies by [@pngwn](https://github.com/pngwn) in [PR 4675](https://github.com/gradio-app/gradio/pull/4675)
+- Fixes `gr.Dropdown` being cutoff at the bottom by [@abidlabs](https://github.com/abidlabs) in [PR 4691](https://github.com/gradio-app/gradio/pull/4691).
+- Scroll top when clicking "View API" in spaces by [@pngwn](https://github.com/pngwn) in [PR 4714](https://github.com/gradio-app/gradio/pull/4714)
+- Fix bug where `show_label` was hiding the entire component for `gr.Label` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4713](https://github.com/gradio-app/gradio/pull/4713)
+- Don't crash when uploaded image has broken EXIF data, by [@akx](https://github.com/akx) in [PR 4764](https://github.com/gradio-app/gradio/pull/4764)
+- Place toast messages at the top of the screen by [@pngwn](https://github.com/pngwn) in [PR 4796](https://github.com/gradio-app/gradio/pull/4796)
+- Fix regressed styling of Login page when auth is enabled by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4797](https://github.com/gradio-app/gradio/pull/4797)
+- Prevent broken scrolling to output on Spaces by [@aliabid94](https://github.com/aliabid94) in [PR 4822](https://github.com/gradio-app/gradio/pull/4822)
+
+### Other Changes:
+
+- Add `.git-blame-ignore-revs` by [@akx](https://github.com/akx) in [PR 4586](https://github.com/gradio-app/gradio/pull/4586)
+- Update frontend dependencies in [PR 4601](https://github.com/gradio-app/gradio/pull/4601)
+- Use `typing.Literal` where possible in gradio library and client by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4608](https://github.com/gradio-app/gradio/pull/4608)
+- Remove unnecessary mock json files for frontend E2E tests by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4625](https://github.com/gradio-app/gradio/pull/4625)
+- Update dependencies by [@pngwn](https://github.com/pngwn) in [PR 4643](https://github.com/gradio-app/gradio/pull/4643)
+- The theme builder now launches successfully, and the API docs are cleaned up. By [@abidlabs](https://github.com/aliabid94) in [PR 4683](https://github.com/gradio-app/gradio/pull/4683)
+- Remove `cleared_value` from some components as its no longer used internally by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4685](https://github.com/gradio-app/gradio/pull/4685)
+- Better errors when you define two Blocks and reference components in one Blocks from the events in the other Blocks [@abidlabs](https://github.com/abidlabs) in [PR 4738](https://github.com/gradio-app/gradio/pull/4738).
+- Better message when share link is not created by [@abidlabs](https://github.com/abidlabs) in [PR 4773](https://github.com/gradio-app/gradio/pull/4773).
+- Improve accessibility around selected images in gr.Gallery component by [@hannahblair](https://github.com/hannahblair) in [PR 4790](https://github.com/gradio-app/gradio/pull/4790)
+
+### Breaking Changes:
+
+[PR 4683](https://github.com/gradio-app/gradio/pull/4683) removes the explict named endpoint "load_examples" from gr.Interface that was introduced in [PR 4456](https://github.com/gradio-app/gradio/pull/4456).
+
+## 3.35.2
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fix chatbot streaming by [@aliabid94](https://github.com/aliabid94) in [PR 4537](https://github.com/gradio-app/gradio/pull/4537)
+- Fix chatbot height and scrolling by [@aliabid94](https://github.com/aliabid94) in [PR 4540](https://github.com/gradio-app/gradio/pull/4540)
+
+### Other Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.35.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fix chatbot streaming by [@aliabid94](https://github.com/aliabid94) in [PR 4537](https://github.com/gradio-app/gradio/pull/4537)
+- Fix error modal position and text size by [@pngwn](https://github.com/pngwn) in [PR 4538](https://github.com/gradio-app/gradio/pull/4538).
+
+### Other Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.35.0
+
+### New Features:
+
+- A `gr.ClearButton` which allows users to easily clear the values of components by [@abidlabs](https://github.com/abidlabs) in [PR 4456](https://github.com/gradio-app/gradio/pull/4456)
+
+Example usage:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ chatbot = gr.Chatbot([("Hello", "How are you?")])
+ with gr.Row():
+ textbox = gr.Textbox(scale=3, interactive=True)
+ gr.ClearButton([textbox, chatbot], scale=1)
+
+demo.launch()
+```
+
+- Min and max value for gr.Number by [@artegoser](https://github.com/artegoser) and [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3991](https://github.com/gradio-app/gradio/pull/3991)
+- Add `start_recording` and `stop_recording` events to `Video` and `Audio` components by [@pngwn](https://github.com/pngwn) in [PR 4422](https://github.com/gradio-app/gradio/pull/4422)
+- Allow any function to generate an error message and allow multiple messages to appear at a time. Other error modal improvements such as auto dismiss after a time limit and a new layout on mobile [@pngwn](https://github.com/pngwn) in [PR 4459](https://github.com/gradio-app/gradio/pull/4459).
+- Add `autoplay` kwarg to `Video` and `Audio` components by [@pngwn](https://github.com/pngwn) in [PR 4453](https://github.com/gradio-app/gradio/pull/4453)
+- Add `allow_preview` parameter to `Gallery` to control whether a detailed preview is displayed on click by
+ [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4470](https://github.com/gradio-app/gradio/pull/4470)
+- Add `latex_delimiters` parameter to `Chatbot` to control the delimiters used for LaTeX and to disable LaTeX in the `Chatbot` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4516](https://github.com/gradio-app/gradio/pull/4516)
+- Can now issue `gr.Warning` and `gr.Info` modals. Simply put the code `gr.Warning("Your warning message")` or `gr.Info("Your info message")` as a standalone line in your function. By [@aliabid94](https://github.com/aliabid94) in [PR 4518](https://github.com/gradio-app/gradio/pull/4518).
+
+Example:
+
+```python
+def start_process(name):
+ gr.Info("Starting process")
+ if name is None:
+ gr.Warning("Name is empty")
+ ...
+ if success == False:
+ raise gr.Error("Process failed")
+```
+
+### Bug Fixes:
+
+- Add support for PAUSED state in the JS client by [@abidlabs](https://github.com/abidlabs) in [PR 4438](https://github.com/gradio-app/gradio/pull/4438)
+- Ensure Tabs only occupy the space required by [@pngwn](https://github.com/pngwn) in [PR 4419](https://github.com/gradio-app/gradio/pull/4419)
+- Ensure components have the correct empty sizes to prevent empty containers from collapsing by [@pngwn](https://github.com/pngwn) in [PR 4447](https://github.com/gradio-app/gradio/pull/4447).
+- Frontend code no longer crashes when there is a relative URL in an `` element, by [@akx](https://github.com/akx) in [PR 4449](https://github.com/gradio-app/gradio/pull/4449).
+- Fix bug where setting `format='mp4'` on a video component would cause the function to error out if the uploaded video was not playable by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4467](https://github.com/gradio-app/gradio/pull/4467)
+- Fix `_js` parameter to work even without backend function, by [@aliabid94](https://github.com/aliabid94) in [PR 4486](https://github.com/gradio-app/gradio/pull/4486).
+- Fix new line issue with `gr.Chatbot()` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4491](https://github.com/gradio-app/gradio/pull/4491)
+- Fixes issue with Clear button not working for `Label` component by [@abidlabs](https://github.com/abidlabs) in [PR 4456](https://github.com/gradio-app/gradio/pull/4456)
+- Restores the ability to pass in a tuple (sample rate, audio array) to gr.Audio() by [@abidlabs](https://github.com/abidlabs) in [PR 4525](https://github.com/gradio-app/gradio/pull/4525)
+- Ensure code is correctly formatted and copy button is always present in Chatbot by [@pngwn](https://github.com/pngwn) in [PR 4527](https://github.com/gradio-app/gradio/pull/4527)
+- `show_label` will not automatically be set to `True` in `gr.BarPlot.update` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4531](https://github.com/gradio-app/gradio/pull/4531)
+- `gr.BarPlot` group text now respects darkmode by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4531](https://github.com/gradio-app/gradio/pull/4531)
+- Fix dispatched errors from within components [@aliabid94](https://github.com/aliabid94) in [PR 4786](https://github.com/gradio-app/gradio/pull/4786)
+
+### Other Changes:
+
+- Change styling of status and toast error components by [@hannahblair](https://github.com/hannahblair) in [PR 4454](https://github.com/gradio-app/gradio/pull/4454).
+- Clean up unnecessary `new Promise()`s by [@akx](https://github.com/akx) in [PR 4442](https://github.com/gradio-app/gradio/pull/4442).
+- Minor UI cleanup for Examples and Dataframe components [@aliabid94](https://github.com/aliabid94) in [PR 4455](https://github.com/gradio-app/gradio/pull/4455).
+- Minor UI cleanup for Examples and Dataframe components [@aliabid94](https://github.com/aliabid94) in [PR 4455](https://github.com/gradio-app/gradio/pull/4455).
+- Add Catalan translation [@jordimas](https://github.com/jordimas) in [PR 4483](https://github.com/gradio-app/gradio/pull/4483).
+- The API endpoint that loads examples upon click has been given an explicit name ("/load_examples") by [@abidlabs](https://github.com/abidlabs) in [PR 4456](https://github.com/gradio-app/gradio/pull/4456).
+- Allows configuration of FastAPI app when calling `mount_gradio_app`, by [@charlesfrye](https://github.com/charlesfrye) in [PR4519](https://github.com/gradio-app/gradio/pull/4519).
+
+### Breaking Changes:
+
+- The behavior of the `Clear` button has been changed for `Slider`, `CheckboxGroup`, `Radio`, `Dropdown` components by [@abidlabs](https://github.com/abidlabs) in [PR 4456](https://github.com/gradio-app/gradio/pull/4456). The Clear button now sets the value of these components to be empty as opposed to the original default set by the developer. This is to make them in line with the rest of the Gradio components.
+- Python 3.7 end of life is June 27 2023. Gradio will no longer support python 3.7 by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4484](https://github.com/gradio-app/gradio/pull/4484)
+- Removed `$` as a default LaTeX delimiter for the `Chatbot` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4516](https://github.com/gradio-app/gradio/pull/4516). The specific LaTeX delimeters can be set using the new `latex_delimiters` parameter in `Chatbot`.
+
+## 3.34.0
+
+### New Features:
+
+- The `gr.UploadButton` component now supports the `variant` and `interactive` parameters by [@abidlabs](https://github.com/abidlabs) in [PR 4436](https://github.com/gradio-app/gradio/pull/4436).
+
+### Bug Fixes:
+
+- Remove target="\_blank" override on anchor tags with internal targets by [@hannahblair](https://github.com/hannahblair) in [PR 4405](https://github.com/gradio-app/gradio/pull/4405)
+- Fixed bug where `gr.File(file_count='multiple')` could not be cached as output by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4421](https://github.com/gradio-app/gradio/pull/4421)
+- Restricts the domains that can be proxied via `/proxy` route by [@abidlabs](https://github.com/abidlabs) in [PR 4406](https://github.com/gradio-app/gradio/pull/4406).
+- Fixes issue where `gr.UploadButton` could not be used to upload the same file twice by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4437](https://github.com/gradio-app/gradio/pull/4437)
+- Fixes bug where `/proxy` route was being incorrectly constructed by the frontend by [@abidlabs](https://github.com/abidlabs) in [PR 4430](https://github.com/gradio-app/gradio/pull/4430).
+- Fix z-index of status component by [@hannahblair](https://github.com/hannahblair) in [PR 4429](https://github.com/gradio-app/gradio/pull/4429)
+- Fix video rendering in Safari by [@aliabid94](https://github.com/aliabid94) in [PR 4433](https://github.com/gradio-app/gradio/pull/4433).
+- The output directory for files downloaded when calling Blocks as a function is now set to a temporary directory by default (instead of the working directory in some cases) by [@abidlabs](https://github.com/abidlabs) in [PR 4501](https://github.com/gradio-app/gradio/pull/4501)
+
+### Other Changes:
+
+- When running on Spaces, handler functions will be transformed by the [PySpaces](https://pypi.org/project/spaces/) library in order to make them work with specific hardware. It will have no effect on standalone Gradio apps or regular Gradio Spaces and can be globally deactivated as follows : `import spaces; spaces.disable_gradio_auto_wrap()` by [@cbensimon](https://github.com/cbensimon) in [PR 4389](https://github.com/gradio-app/gradio/pull/4389).
+- Deprecated `.style` parameter and moved arguments to constructor. Added support for `.update()` to all arguments initially in style. Added `scale` and `min_width` support to every Component. By [@aliabid94](https://github.com/aliabid94) in [PR 4374](https://github.com/gradio-app/gradio/pull/4374)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.33.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Allow `every` to work with generators by [@dkjshk](https://github.com/dkjshk) in [PR 4434](https://github.com/gradio-app/gradio/pull/4434)
+- Fix z-index of status component by [@hannahblair](https://github.com/hannahblair) in [PR 4429](https://github.com/gradio-app/gradio/pull/4429)
+- Allow gradio to work offline, by [@aliabid94](https://github.com/aliabid94) in [PR 4398](https://github.com/gradio-app/gradio/pull/4398).
+- Fixed `validate_url` to check for 403 errors and use a GET request in place of a HEAD by [@alvindaiyan](https://github.com/alvindaiyan) in [PR 4388](https://github.com/gradio-app/gradio/pull/4388).
+
+### Other Changes:
+
+- More explicit error message when share link binary is blocked by antivirus by [@abidlabs](https://github.com/abidlabs) in [PR 4380](https://github.com/gradio-app/gradio/pull/4380).
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.33.0
+
+### New Features:
+
+- Introduced `gradio deploy` to launch a Gradio app to Spaces directly from your terminal. By [@aliabid94](https://github.com/aliabid94) in [PR 4033](https://github.com/gradio-app/gradio/pull/4033).
+- Introduce `show_progress='corner'` argument to event listeners, which will not cover the output components with the progress animation, but instead show it in the corner of the components. By [@aliabid94](https://github.com/aliabid94) in [PR 4396](https://github.com/gradio-app/gradio/pull/4396).
+
+### Bug Fixes:
+
+- Fix bug where Label change event was triggering itself by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4371](https://github.com/gradio-app/gradio/pull/4371)
+- Make `Blocks.load` behave like other event listeners (allows chaining `then` off of it) [@anentropic](https://github.com/anentropic/) in [PR 4304](https://github.com/gradio-app/gradio/pull/4304)
+- Respect `interactive=True` in output components of a `gr.Interface` by [@abidlabs](https://github.com/abidlabs) in [PR 4356](https://github.com/gradio-app/gradio/pull/4356).
+- Remove unused frontend code by [@akx](https://github.com/akx) in [PR 4275](https://github.com/gradio-app/gradio/pull/4275)
+- Fixes favicon path on Windows by [@abidlabs](https://github.com/abidlabs) in [PR 4369](https://github.com/gradio-app/gradio/pull/4369).
+- Prevent path traversal in `/file` routes by [@abidlabs](https://github.com/abidlabs) in [PR 4370](https://github.com/gradio-app/gradio/pull/4370).
+- Do not send HF token to other domains via `/proxy` route by [@abidlabs](https://github.com/abidlabs) in [PR 4368](https://github.com/gradio-app/gradio/pull/4368).
+- Replace default `markedjs` sanitize function with DOMPurify sanitizer for `gr.Chatbot()` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4360](https://github.com/gradio-app/gradio/pull/4360)
+- Prevent the creation of duplicate copy buttons in the chatbot and ensure copy buttons work in non-secure contexts by [@binary-husky](https://github.com/binary-husky) in [PR 4350](https://github.com/gradio-app/gradio/pull/4350).
+
+### Other Changes:
+
+- Remove flicker of loading bar by adding opacity transition, by [@aliabid94](https://github.com/aliabid94) in [PR 4349](https://github.com/gradio-app/gradio/pull/4349).
+- Performance optimization in the frontend's Blocks code by [@akx](https://github.com/akx) in [PR 4334](https://github.com/gradio-app/gradio/pull/4334)
+- Upgrade the pnpm lock file format version from v6.0 to v6.1 by [@whitphx](https://github.com/whitphx) in [PR 4393](https://github.com/gradio-app/gradio/pull/4393)
+
+### Breaking Changes:
+
+- The `/file=` route no longer allows accessing dotfiles or files in "dot directories" by [@akx](https://github.com/akx) in [PR 4303](https://github.com/gradio-app/gradio/pull/4303)
+
+## 3.32.0
+
+### New Features:
+
+- `Interface.launch()` and `Blocks.launch()` now accept an `app_kwargs` argument to allow customizing the configuration of the underlying FastAPI app, by [@akx](https://github.com/akx) in [PR 4282](https://github.com/gradio-app/gradio/pull/4282)
+
+### Bug Fixes:
+
+- Fixed Gallery/AnnotatedImage components not respecting GRADIO_DEFAULT_DIR variable by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4256](https://github.com/gradio-app/gradio/pull/4256)
+- Fixed Gallery/AnnotatedImage components resaving identical images by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4256](https://github.com/gradio-app/gradio/pull/4256)
+- Fixed Audio/Video/File components creating empty tempfiles on each run by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4256](https://github.com/gradio-app/gradio/pull/4256)
+- Fixed the behavior of the `run_on_click` parameter in `gr.Examples` by [@abidlabs](https://github.com/abidlabs) in [PR 4258](https://github.com/gradio-app/gradio/pull/4258).
+- Ensure error modal displays when the queue is enabled by [@pngwn](https://github.com/pngwn) in [PR 4273](https://github.com/gradio-app/gradio/pull/4273)
+- Ensure js client respcts the full root when making requests to the server by [@pngwn](https://github.com/pngwn) in [PR 4271](https://github.com/gradio-app/gradio/pull/4271)
+
+### Other Changes:
+
+- Refactor web component `initial_height` attribute by [@whitphx](https://github.com/whitphx) in [PR 4223](https://github.com/gradio-app/gradio/pull/4223)
+- Relocate `mount_css` fn to remove circular dependency [@whitphx](https://github.com/whitphx) in [PR 4222](https://github.com/gradio-app/gradio/pull/4222)
+- Upgrade Black to 23.3 by [@akx](https://github.com/akx) in [PR 4259](https://github.com/gradio-app/gradio/pull/4259)
+- Add frontend LaTeX support in `gr.Chatbot()` using `KaTeX` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4285](https://github.com/gradio-app/gradio/pull/4285).
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.31.0
+
+### New Features:
+
+- The reloader command (`gradio app.py`) can now accept command line arguments by [@micky2be](https://github.com/micky2be) in [PR 4119](https://github.com/gradio-app/gradio/pull/4119)
+- Added `format` argument to `Audio` component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4178](https://github.com/gradio-app/gradio/pull/4178)
+- Add JS client code snippets to use via api page by [@aliabd](https://github.com/aliabd) in [PR 3927](https://github.com/gradio-app/gradio/pull/3927).
+- Update to the JS client by [@pngwn](https://github.com/pngwn) in [PR 4202](https://github.com/gradio-app/gradio/pull/4202)
+
+### Bug Fixes:
+
+- Fix "TypeError: issubclass() arg 1 must be a class" When use Optional[Types] by [@lingfengchencn](https://github.com/lingfengchencn) in [PR 4200](https://github.com/gradio-app/gradio/pull/4200).
+- Gradio will no longer send any analytics or call home if analytics are disabled with the GRADIO_ANALYTICS_ENABLED environment variable. By [@akx](https://github.com/akx) in [PR 4194](https://github.com/gradio-app/gradio/pull/4194) and [PR 4236](https://github.com/gradio-app/gradio/pull/4236)
+- The deprecation warnings for kwargs now show the actual stack level for the invocation, by [@akx](https://github.com/akx) in [PR 4203](https://github.com/gradio-app/gradio/pull/4203).
+- Fix "TypeError: issubclass() arg 1 must be a class" When use Optional[Types] by [@lingfengchencn](https://github.com/lingfengchencn) in [PR 4200](https://github.com/gradio-app/gradio/pull/4200).
+- Ensure cancelling functions work correctly by [@pngwn](https://github.com/pngwn) in [PR 4225](https://github.com/gradio-app/gradio/pull/4225)
+- Fixes a bug with typing.get_type_hints() on Python 3.9 by [@abidlabs](https://github.com/abidlabs) in [PR 4228](https://github.com/gradio-app/gradio/pull/4228).
+- Fixes JSONDecodeError by [@davidai](https://github.com/davidai) in [PR 4241](https://github.com/gradio-app/gradio/pull/4241)
+- Fix `chatbot_dialogpt` demo by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4238](https://github.com/gradio-app/gradio/pull/4238).
+
+### Other Changes:
+
+- Change `gr.Chatbot()` markdown parsing to frontend using `marked` library and `prism` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4150](https://github.com/gradio-app/gradio/pull/4150)
+- Update the js client by [@pngwn](https://github.com/pngwn) in [PR 3899](https://github.com/gradio-app/gradio/pull/3899)
+- Fix documentation for the shape of the numpy array produced by the `Image` component by [@der3318](https://github.com/der3318) in [PR 4204](https://github.com/gradio-app/gradio/pull/4204).
+- Updates the timeout for websocket messaging from 1 second to 5 seconds by [@abidlabs](https://github.com/abidlabs) in [PR 4235](https://github.com/gradio-app/gradio/pull/4235)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.30.0
+
+### New Features:
+
+- Adds a `root_path` parameter to `launch()` that allows running Gradio applications on subpaths (e.g. www.example.com/app) behind a proxy, by [@abidlabs](https://github.com/abidlabs) in [PR 4133](https://github.com/gradio-app/gradio/pull/4133)
+- Fix dropdown change listener to trigger on change when updated as an output by [@aliabid94](https://github.com/aliabid94) in [PR 4128](https://github.com/gradio-app/gradio/pull/4128).
+- Add `.input` event listener, which is only triggered when a user changes the component value (as compared to `.change`, which is also triggered when a component updates as the result of a function trigger), by [@aliabid94](https://github.com/aliabid94) in [PR 4157](https://github.com/gradio-app/gradio/pull/4157).
+
+### Bug Fixes:
+
+- Records username when flagging by [@abidlabs](https://github.com/abidlabs) in [PR 4135](https://github.com/gradio-app/gradio/pull/4135)
+- Fix website build issue by [@aliabd](https://github.com/aliabd) in [PR 4142](https://github.com/gradio-app/gradio/pull/4142)
+- Fix lang agnostic type info for `gr.File(file_count='multiple')` output components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4153](https://github.com/gradio-app/gradio/pull/4153)
+
+### Other Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.29.0
+
+### New Features:
+
+- Returning language agnostic types in the `/info` route by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4039](https://github.com/gradio-app/gradio/pull/4039)
+
+### Bug Fixes:
+
+- Allow users to upload audio files in Audio component on iOS by by [@aliabid94](https://github.com/aliabid94) in [PR 4071](https://github.com/gradio-app/gradio/pull/4071).
+- Fixes the gradio theme builder error that appeared on launch by [@aliabid94](https://github.com/aliabid94) and [@abidlabs](https://github.com/abidlabs) in [PR 4080](https://github.com/gradio-app/gradio/pull/4080)
+- Keep Accordion content in DOM by [@aliabid94](https://github.com/aliabid94) in [PR 4070](https://github.com/gradio-app/gradio/pull/4073)
+- Fixed bug where type hints in functions caused the event handler to crash by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4068](https://github.com/gradio-app/gradio/pull/4068)
+- Fix dropdown default value not appearing by [@aliabid94](https://github.com/aliabid94) in [PR 4072](https://github.com/gradio-app/gradio/pull/4072).
+- Soft theme label color fix by [@aliabid94](https://github.com/aliabid94) in [PR 4070](https://github.com/gradio-app/gradio/pull/4070)
+- Fix `gr.Slider` `release` event not triggering on mobile by [@space-nuko](https://github.com/space-nuko) in [PR 4098](https://github.com/gradio-app/gradio/pull/4098)
+- Removes extraneous `State` component info from the `/info` route by [@abidlabs](https://github.com/freddyaboulton) in [PR 4107](https://github.com/gradio-app/gradio/pull/4107)
+- Make .then() work even if first event fails by [@aliabid94](https://github.com/aliabid94) in [PR 4115](https://github.com/gradio-app/gradio/pull/4115).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Allow users to submit with enter in Interfaces with textbox / number inputs [@aliabid94](https://github.com/aliabid94) in [PR 4090](https://github.com/gradio-app/gradio/pull/4090).
+- Updates gradio's requirements.txt to requires uvicorn>=0.14.0 by [@abidlabs](https://github.com/abidlabs) in [PR 4086](https://github.com/gradio-app/gradio/pull/4086)
+- Updates some error messaging by [@abidlabs](https://github.com/abidlabs) in [PR 4086](https://github.com/gradio-app/gradio/pull/4086)
+- Renames simplified Chinese translation file from `zh-cn.json` to `zh-CN.json` by [@abidlabs](https://github.com/abidlabs) in [PR 4086](https://github.com/gradio-app/gradio/pull/4086)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.28.3
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fixes issue with indentation in `gr.Code()` component with streaming by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4043](https://github.com/gradio-app/gradio/pull/4043)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.28.2
+
+### Bug Fixes
+
+- Code component visual updates by [@pngwn](https://github.com/pngwn) in [PR 4051](https://github.com/gradio-app/gradio/pull/4051)
+
+### New Features:
+
+- Add support for `visual-question-answering`, `document-question-answering`, and `image-to-text` using `gr.Interface.load("models/...")` and `gr.Interface.from_pipeline` by [@osanseviero](https://github.com/osanseviero) in [PR 3887](https://github.com/gradio-app/gradio/pull/3887)
+- Add code block support in `gr.Chatbot()`, by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4048](https://github.com/gradio-app/gradio/pull/4048)
+- Adds the ability to blocklist filepaths (and also improves the allowlist mechanism) by [@abidlabs](https://github.com/abidlabs) in [PR 4047](https://github.com/gradio-app/gradio/pull/4047).
+- Adds the ability to specify the upload directory via an environment variable by [@abidlabs](https://github.com/abidlabs) in [PR 4047](https://github.com/gradio-app/gradio/pull/4047).
+
+### Bug Fixes:
+
+- Fixes issue with `matplotlib` not rendering correctly if the backend was not set to `Agg` by [@abidlabs](https://github.com/abidlabs) in [PR 4029](https://github.com/gradio-app/gradio/pull/4029)
+- Fixes bug where rendering the same `gr.State` across different Interfaces/Blocks within larger Blocks would not work by [@abidlabs](https://github.com/abidlabs) in [PR 4030](https://github.com/gradio-app/gradio/pull/4030)
+- Code component visual updates by [@pngwn](https://github.com/pngwn) in [PR 4051](https://github.com/gradio-app/gradio/pull/4051)
+
+### Documentation Changes:
+
+- Adds a Guide on how to use the Python Client within a FastAPI app, by [@abidlabs](https://github.com/abidlabs) in [PR 3892](https://github.com/gradio-app/gradio/pull/3892)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+- `gr.HuggingFaceDatasetSaver` behavior changed internally. The `flagging/` folder is not a `.git/` folder anymore when using it. `organization` parameter is now ignored in favor of passing a full dataset id as `dataset_name` (e.g. `"username/my-dataset"`).
+- New lines (`\n`) are not automatically converted to `
` in `gr.Markdown()` or `gr.Chatbot()`. For multiple new lines, a developer must add multiple `
` tags.
+
+### Full Changelog:
+
+- Safer version of `gr.HuggingFaceDatasetSaver` using HTTP methods instead of git pull/push by [@Wauplin](https://github.com/Wauplin) in [PR 3973](https://github.com/gradio-app/gradio/pull/3973)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.28.1
+
+### New Features:
+
+- Add a "clear mask" button to `gr.Image` sketch modes, by [@space-nuko](https://github.com/space-nuko) in [PR 3615](https://github.com/gradio-app/gradio/pull/3615)
+
+### Bug Fixes:
+
+- Fix dropdown default value not appearing by [@aliabid94](https://github.com/aliabid94) in [PR 3996](https://github.com/gradio-app/gradio/pull/3996).
+- Fix faded coloring of output textboxes in iOS / Safari by [@aliabid94](https://github.com/aliabid94) in [PR 3993](https://github.com/gradio-app/gradio/pull/3993)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+- CI: Simplified Python CI workflow by [@akx](https://github.com/akx) in [PR 3982](https://github.com/gradio-app/gradio/pull/3982)
+- Upgrade pyright to 1.1.305 by [@akx](https://github.com/akx) in [PR 4042](https://github.com/gradio-app/gradio/pull/4042)
+- More Ruff rules are enabled and lint errors fixed by [@akx](https://github.com/akx) in [PR 4038](https://github.com/gradio-app/gradio/pull/4038)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.28.0
+
+### Bug Fixes:
+
+- Fix duplicate play commands in full-screen mode of 'video'. by [@tomchang25](https://github.com/tomchang25) in [PR 3968](https://github.com/gradio-app/gradio/pull/3968).
+- Fix the issue of the UI stuck caused by the 'selected' of DataFrame not being reset. by [@tomchang25](https://github.com/tomchang25) in [PR 3916](https://github.com/gradio-app/gradio/pull/3916).
+- Fix issue where `gr.Video()` would not work inside a `gr.Tab()` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3891](https://github.com/gradio-app/gradio/pull/3891)
+- Fixed issue with old_value check in File. by [@tomchang25](https://github.com/tomchang25) in [PR 3859](https://github.com/gradio-app/gradio/pull/3859).
+- Fixed bug where all bokeh plots appeared in the same div by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3896](https://github.com/gradio-app/gradio/pull/3896)
+- Fixed image outputs to automatically take full output image height, unless explicitly set, by [@aliabid94](https://github.com/aliabid94) in [PR 3905](https://github.com/gradio-app/gradio/pull/3905)
+- Fix issue in `gr.Gallery()` where setting height causes aspect ratio of images to collapse by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3830](https://github.com/gradio-app/gradio/pull/3830)
+- Fix issue where requesting for a non-existing file would trigger a 500 error by [@micky2be](https://github.com/micky2be) in `[PR 3895](https://github.com/gradio-app/gradio/pull/3895)`.
+- Fix bugs with abspath about symlinks, and unresolvable path on Windows by [@micky2be](https://github.com/micky2be) in `[PR 3895](https://github.com/gradio-app/gradio/pull/3895)`.
+- Fixes type in client `Status` enum by [@10zinten](https://github.com/10zinten) in [PR 3931](https://github.com/gradio-app/gradio/pull/3931)
+- Fix `gr.ChatBot` to handle image url [tye-singwa](https://github.com/tye-signwa) in [PR 3953](https://github.com/gradio-app/gradio/pull/3953)
+- Move Google Tag Manager related initialization code to analytics-enabled block by [@akx](https://github.com/akx) in [PR 3956](https://github.com/gradio-app/gradio/pull/3956)
+- Fix bug where port was not reused if the demo was closed and then re-launched by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3896](https://github.com/gradio-app/gradio/pull/3959)
+- Fixes issue where dropdown does not position itself at selected element when opened [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3639](https://github.com/gradio-app/gradio/pull/3639)
+
+### Documentation Changes:
+
+- Make use of `gr` consistent across the docs by [@duerrsimon](https://github.com/duerrsimon) in [PR 3901](https://github.com/gradio-app/gradio/pull/3901)
+- Fixed typo in theming-guide.md by [@eltociear](https://github.com/eltociear) in [PR 3952](https://github.com/gradio-app/gradio/pull/3952)
+
+### Testing and Infrastructure Changes:
+
+- CI: Python backend lint is only run once, by [@akx](https://github.com/akx) in [PR 3960](https://github.com/gradio-app/gradio/pull/3960)
+- Format invocations and concatenations were replaced by f-strings where possible by [@akx](https://github.com/akx) in [PR 3984](https://github.com/gradio-app/gradio/pull/3984)
+- Linting rules were made more strict and issues fixed by [@akx](https://github.com/akx) in [PR 3979](https://github.com/gradio-app/gradio/pull/3979).
+
+### Breaking Changes:
+
+- Some re-exports in `gradio.themes` utilities (introduced in 3.24.0) have been eradicated.
+ By [@akx](https://github.com/akx) in [PR 3958](https://github.com/gradio-app/gradio/pull/3958)
+
+### Full Changelog:
+
+- Add DESCRIPTION.md to image_segmentation demo by [@aliabd](https://github.com/aliabd) in [PR 3866](https://github.com/gradio-app/gradio/pull/3866)
+- Fix error in running `gr.themes.builder()` by [@deepkyu](https://github.com/deepkyu) in [PR 3869](https://github.com/gradio-app/gradio/pull/3869)
+- Fixed a JavaScript TypeError when loading custom JS with `_js` and setting `outputs` to `None` in `gradio.Blocks()` by [@DavG25](https://github.com/DavG25) in [PR 3883](https://github.com/gradio-app/gradio/pull/3883)
+- Fixed bg_background_fill theme property to expand to whole background, block_radius to affect form elements as well, and added block_label_shadow theme property by [@aliabid94](https://github.com/aliabid94) in [PR 3590](https://github.com/gradio-app/gradio/pull/3590)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.27.0
+
+### New Features:
+
+###### AnnotatedImage Component
+
+New AnnotatedImage component allows users to highlight regions of an image, either by providing bounding boxes, or 0-1 pixel masks. This component is useful for tasks such as image segmentation, object detection, and image captioning.
+
+
+
+Example usage:
+
+```python
+with gr.Blocks() as demo:
+ img = gr.Image()
+ img_section = gr.AnnotatedImage()
+ def mask(img):
+ top_left_corner = [0, 0, 20, 20]
+ random_mask = np.random.randint(0, 2, img.shape[:2])
+ return (img, [(top_left_corner, "left corner"), (random_mask, "random")])
+ img.change(mask, img, img_section)
+```
+
+See the [image_segmentation demo](https://github.com/gradio-app/gradio/tree/main/demo/image_segmentation) for a full example. By [@aliabid94](https://github.com/aliabid94) in [PR 3836](https://github.com/gradio-app/gradio/pull/3836)
+
+### Bug Fixes:
+
+No changes to highlight.
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.26.0
+
+### New Features:
+
+###### `Video` component supports subtitles
+
+- Allow the video component to accept subtitles as input, by [@tomchang25](https://github.com/tomchang25) in [PR 3673](https://github.com/gradio-app/gradio/pull/3673). To provide subtitles, simply return a tuple consisting of `(path_to_video, path_to_subtitles)` from your function. Both `.srt` and `.vtt` formats are supported:
+
+```py
+with gr.Blocks() as demo:
+ gr.Video(("video.mp4", "captions.srt"))
+```
+
+### Bug Fixes:
+
+- Fix code markdown support in `gr.Chatbot()` component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3816](https://github.com/gradio-app/gradio/pull/3816)
+
+### Documentation Changes:
+
+- Updates the "view API" page in Gradio apps to use the `gradio_client` library by [@aliabd](https://github.com/aliabd) in [PR 3765](https://github.com/gradio-app/gradio/pull/3765)
+
+- Read more about how to use the `gradio_client` library here: https://gradio.app/getting-started-with-the-python-client/
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.25.0
+
+### New Features:
+
+- Improve error messages when number of inputs/outputs to event handlers mismatch, by [@space-nuko](https://github.com/space-nuko) in [PR 3519](https://github.com/gradio-app/gradio/pull/3519)
+
+- Add `select` listener to Images, allowing users to click on any part of an image and get the coordinates of the click by [@aliabid94](https://github.com/aliabid94) in [PR 3786](https://github.com/gradio-app/gradio/pull/3786).
+
+```python
+with gr.Blocks() as demo:
+ img = gr.Image()
+ textbox = gr.Textbox()
+
+ def select_handler(img, evt: gr.SelectData):
+ selected_pixel = img[evt.index[1], evt.index[0]]
+ return f"Selected pixel: {selected_pixel}"
+
+ img.select(select_handler, img, textbox)
+```
+
+
+
+### Bug Fixes:
+
+- Increase timeout for sending analytics data by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3647](https://github.com/gradio-app/gradio/pull/3647)
+- Fix bug where http token was not accessed over websocket connections by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3735](https://github.com/gradio-app/gradio/pull/3735)
+- Add ability to specify `rows`, `columns` and `object-fit` in `style()` for `gr.Gallery()` component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3586](https://github.com/gradio-app/gradio/pull/3586)
+- Fix bug where recording an audio file through the microphone resulted in a corrupted file name by [@abidlabs](https://github.com/abidlabs) in [PR 3770](https://github.com/gradio-app/gradio/pull/3770)
+- Added "ssl_verify" to blocks.launch method to allow for use of self-signed certs by [@garrettsutula](https://github.com/garrettsutula) in [PR 3873](https://github.com/gradio-app/gradio/pull/3873)
+- Fix bug where iterators where not being reset for processes that terminated early by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3777](https://github.com/gradio-app/gradio/pull/3777)
+- Fix bug where the upload button was not properly handling the `file_count='multiple'` case by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3782](https://github.com/gradio-app/gradio/pull/3782)
+- Fix bug where use Via API button was giving error by [@Devang-C](https://github.com/Devang-C) in [PR 3783](https://github.com/gradio-app/gradio/pull/3783)
+
+### Documentation Changes:
+
+- Fix invalid argument docstrings, by [@akx](https://github.com/akx) in [PR 3740](https://github.com/gradio-app/gradio/pull/3740)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fixed IPv6 listening to work with bracket [::1] notation, by [@dsully](https://github.com/dsully) in [PR 3695](https://github.com/gradio-app/gradio/pull/3695)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.24.1
+
+### New Features:
+
+- No changes to highlight.
+
+### Bug Fixes:
+
+- Fixes Chatbot issue where new lines were being created every time a message was sent back and forth by [@aliabid94](https://github.com/aliabid94) in [PR 3717](https://github.com/gradio-app/gradio/pull/3717).
+- Fixes data updating in DataFrame invoking a `select` event once the dataframe has been selected. By [@yiyuezhuo](https://github.com/yiyuezhuo) in [PR 3861](https://github.com/gradio-app/gradio/pull/3861)
+- Fixes false positive warning which is due to too strict type checking by [@yiyuezhuo](https://github.com/yiyuezhuo) in [PR 3837](https://github.com/gradio-app/gradio/pull/3837).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.24.0
+
+### New Features:
+
+- Trigger the release event when Slider number input is released or unfocused by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3589](https://github.com/gradio-app/gradio/pull/3589)
+- Created Theme Builder, which allows users to create themes without writing any code, by [@aliabid94](https://github.com/aliabid94) in [PR 3664](https://github.com/gradio-app/gradio/pull/3664). Launch by:
+
+ ```python
+ import gradio as gr
+ gr.themes.builder()
+ ```
+
+ 
+
+- The `Dropdown` component now has a `allow_custom_value` parameter that lets users type in custom values not in the original list of choices.
+- The `Colorpicker` component now has a `.blur()` event
+
+###### Added a download button for videos! 📥
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3581](https://github.com/gradio-app/gradio/pull/3581).
+
+- Trigger the release event when Slider number input is released or unfocused by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3589](https://github.com/gradio-app/gradio/pull/3589)
+
+### Bug Fixes:
+
+- Fixed bug where text for altair plots was not legible in dark mode by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3555](https://github.com/gradio-app/gradio/pull/3555)
+- Fixes `Chatbot` and `Image` components so that files passed during processing are added to a directory where they can be served from, by [@abidlabs](https://github.com/abidlabs) in [PR 3523](https://github.com/gradio-app/gradio/pull/3523)
+- Use Gradio API server to send telemetry using `huggingface_hub` [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3488](https://github.com/gradio-app/gradio/pull/3488)
+- Fixes an an issue where if the Blocks scope was not exited, then State could be shared across sessions, by [@abidlabs](https://github.com/abidlabs) in [PR 3600](https://github.com/gradio-app/gradio/pull/3600)
+- Ensures that `gr.load()` loads and applies the upstream theme, by [@abidlabs](https://github.com/abidlabs) in [PR 3641](https://github.com/gradio-app/gradio/pull/3641)
+- Fixed bug where "or" was not being localized in file upload text by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3599](https://github.com/gradio-app/gradio/pull/3599)
+- Fixed bug where chatbot does not autoscroll inside of a tab, row or column by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3637](https://github.com/gradio-app/gradio/pull/3637)
+- Fixed bug where textbox shrinks when `lines` set to larger than 20 by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3637](https://github.com/gradio-app/gradio/pull/3637)
+- Ensure CSS has fully loaded before rendering the application, by [@pngwn](https://github.com/pngwn) in [PR 3573](https://github.com/gradio-app/gradio/pull/3573)
+- Support using an empty list as `gr.Dataframe` value, by [@space-nuko](https://github.com/space-nuko) in [PR 3646](https://github.com/gradio-app/gradio/pull/3646)
+- Fixed `gr.Image` not filling the entire element size, by [@space-nuko](https://github.com/space-nuko) in [PR 3649](https://github.com/gradio-app/gradio/pull/3649)
+- Make `gr.Code` support the `lines` property, by [@space-nuko](https://github.com/space-nuko) in [PR 3651](https://github.com/gradio-app/gradio/pull/3651)
+- Fixes certain `_js` return values being double wrapped in an array, by [@space-nuko](https://github.com/space-nuko) in [PR 3594](https://github.com/gradio-app/gradio/pull/3594)
+- Correct the documentation of `gr.File` component to state that its preprocessing method converts the uploaded file to a temporary file, by @RussellLuo in [PR 3660](https://github.com/gradio-app/gradio/pull/3660)
+- Fixed bug in Serializer ValueError text by [@osanseviero](https://github.com/osanseviero) in [PR 3669](https://github.com/gradio-app/gradio/pull/3669)
+- Fix default parameter argument and `gr.Progress` used in same function, by [@space-nuko](https://github.com/space-nuko) in [PR 3671](https://github.com/gradio-app/gradio/pull/3671)
+- Hide `Remove All` button in `gr.Dropdown` single-select mode by [@space-nuko](https://github.com/space-nuko) in [PR 3678](https://github.com/gradio-app/gradio/pull/3678)
+- Fix broken spaces in docs by [@aliabd](https://github.com/aliabd) in [PR 3698](https://github.com/gradio-app/gradio/pull/3698)
+- Fix items in `gr.Dropdown` besides the selected item receiving a checkmark, by [@space-nuko](https://github.com/space-nuko) in [PR 3644](https://github.com/gradio-app/gradio/pull/3644)
+- Fix several `gr.Dropdown` issues and improve usability, by [@space-nuko](https://github.com/space-nuko) in [PR 3705](https://github.com/gradio-app/gradio/pull/3705)
+
+### Documentation Changes:
+
+- Makes some fixes to the Theme Guide related to naming of variables, by [@abidlabs](https://github.com/abidlabs) in [PR 3561](https://github.com/gradio-app/gradio/pull/3561)
+- Documented `HuggingFaceDatasetJSONSaver` by [@osanseviero](https://github.com/osanseviero) in [PR 3604](https://github.com/gradio-app/gradio/pull/3604)
+- Makes some additions to documentation of `Audio` and `State` components, and fixes the `pictionary` demo by [@abidlabs](https://github.com/abidlabs) in [PR 3611](https://github.com/gradio-app/gradio/pull/3611)
+- Fix outdated sharing your app guide by [@aliabd](https://github.com/aliabd) in [PR 3699](https://github.com/gradio-app/gradio/pull/3699)
+
+### Testing and Infrastructure Changes:
+
+- Removed heavily-mocked tests related to comet_ml, wandb, and mlflow as they added a significant amount of test dependencies that prevented installation of test dependencies on Windows environments. By [@abidlabs](https://github.com/abidlabs) in [PR 3608](https://github.com/gradio-app/gradio/pull/3608)
+- Added Windows continuous integration, by [@space-nuko](https://github.com/space-nuko) in [PR 3628](https://github.com/gradio-app/gradio/pull/3628)
+- Switched linting from flake8 + isort to `ruff`, by [@akx](https://github.com/akx) in [PR 3710](https://github.com/gradio-app/gradio/pull/3710)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Mobile responsive iframes in themes guide by [@aliabd](https://github.com/aliabd) in [PR 3562](https://github.com/gradio-app/gradio/pull/3562)
+- Remove extra $demo from theme guide by [@aliabd](https://github.com/aliabd) in [PR 3563](https://github.com/gradio-app/gradio/pull/3563)
+- Set the theme name to be the upstream repo name when loading from the hub by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3595](https://github.com/gradio-app/gradio/pull/3595)
+- Copy everything in website Dockerfile, fix build issues by [@aliabd](https://github.com/aliabd) in [PR 3659](https://github.com/gradio-app/gradio/pull/3659)
+- Raise error when an event is queued but the queue is not configured by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3640](https://github.com/gradio-app/gradio/pull/3640)
+- Allows users to apss in a string name for a built-in theme, by [@abidlabs](https://github.com/abidlabs) in [PR 3641](https://github.com/gradio-app/gradio/pull/3641)
+- Added `orig_name` to Video output in the backend so that the front end can set the right name for downloaded video files by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3700](https://github.com/gradio-app/gradio/pull/3700)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.23.0
+
+### New Features:
+
+###### Theme Sharing!
+
+Once you have created a theme, you can upload it to the HuggingFace Hub to let others view it, use it, and build off of it! You can also download, reuse, and remix other peoples' themes. See https://gradio.app/theming-guide/ for more details.
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3428](https://github.com/gradio-app/gradio/pull/3428)
+
+### Bug Fixes:
+
+- Removes leading spaces from all lines of code uniformly in the `gr.Code()` component. By [@abidlabs](https://github.com/abidlabs) in [PR 3556](https://github.com/gradio-app/gradio/pull/3556)
+- Fixed broken login page, by [@aliabid94](https://github.com/aliabid94) in [PR 3529](https://github.com/gradio-app/gradio/pull/3529)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fix rendering of dropdowns to take more space, and related bugs, by [@aliabid94](https://github.com/aliabid94) in [PR 3549](https://github.com/gradio-app/gradio/pull/3549)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.22.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Restore label bars by [@aliabid94](https://github.com/aliabid94) in [PR 3507](https://github.com/gradio-app/gradio/pull/3507)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.22.0
+
+### New Features:
+
+###### Official Theme release
+
+Gradio now supports a new theme system, which allows you to customize the look and feel of your app. You can now use the `theme=` kwarg to pass in a prebuilt theme, or customize your own! See https://gradio.app/theming-guide/ for more details. By [@aliabid94](https://github.com/aliabid94) in [PR 3470](https://github.com/gradio-app/gradio/pull/3470) and [PR 3497](https://github.com/gradio-app/gradio/pull/3497)
+
+###### `elem_classes`
+
+Add keyword argument `elem_classes` to Components to control class names of components, in the same manner as existing `elem_id`.
+By [@aliabid94](https://github.com/aliabid94) in [PR 3466](https://github.com/gradio-app/gradio/pull/3466)
+
+### Bug Fixes:
+
+- Fixes the File.upload() event trigger which broke as part of the change in how we uploaded files by [@abidlabs](https://github.com/abidlabs) in [PR 3462](https://github.com/gradio-app/gradio/pull/3462)
+- Fixed issue with `gr.Request` object failing to handle dictionaries when nested keys couldn't be converted to variable names [#3454](https://github.com/gradio-app/gradio/issues/3454) by [@radames](https://github.com/radames) in [PR 3459](https://github.com/gradio-app/gradio/pull/3459)
+- Fixed bug where css and client api was not working properly when mounted in a subpath by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3482](https://github.com/gradio-app/gradio/pull/3482)
+
+### Documentation Changes:
+
+- Document gr.Error in the docs by [@aliabd](https://github.com/aliabd) in [PR 3465](https://github.com/gradio-app/gradio/pull/3465)
+
+### Testing and Infrastructure Changes:
+
+- Pinned `pyright==1.1.298` for stability by [@abidlabs](https://github.com/abidlabs) in [PR 3475](https://github.com/gradio-app/gradio/pull/3475)
+- Removed `IOComponent.add_interactive_to_config()` by [@space-nuko](https://github.com/space-nuko) in [PR 3476](https://github.com/gradio-app/gradio/pull/3476)
+- Removed `IOComponent.generate_sample()` by [@space-nuko](https://github.com/space-nuko) in [PR 3475](https://github.com/gradio-app/gradio/pull/3483)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Revert primary button background color in dark mode by [@aliabid94](https://github.com/aliabid94) in [PR 3468](https://github.com/gradio-app/gradio/pull/3468)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.21.0
+
+### New Features:
+
+###### Theme Sharing 🎨 🤝
+
+You can now share your gradio themes with the world!
+
+After creating a theme, you can upload it to the HuggingFace Hub to let others view it, use it, and build off of it!
+
+###### Uploading
+
+There are two ways to upload a theme, via the theme class instance or the command line.
+
+1. Via the class instance
+
+```python
+my_theme.push_to_hub(repo_name="my_theme",
+ version="0.2.0",
+ hf_token="...")
+```
+
+2. Via the command line
+
+First save the theme to disk
+
+```python
+my_theme.dump(filename="my_theme.json")
+```
+
+Then use the `upload_theme` command:
+
+```bash
+upload_theme\
+"my_theme.json"\
+"my_theme"\
+"0.2.0"\
+"
+
+Note: images were previously supported via Markdown syntax and that is still supported for backwards compatibility. By [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3413](https://github.com/gradio-app/gradio/pull/3413)
+
+- Allow consecutive function triggers with `.then` and `.success` by [@aliabid94](https://github.com/aliabid94) in [PR 3430](https://github.com/gradio-app/gradio/pull/3430)
+
+- New code component allows you to enter, edit and display code with full syntax highlighting by [@pngwn](https://github.com/pngwn) in [PR 3421](https://github.com/gradio-app/gradio/pull/3421)
+
+
+
+- Added the `.select()` event listener, which also includes event data that can be passed as an argument to a function with type hint `gr.SelectData`. The following components support the `.select()` event listener: Chatbot, CheckboxGroup, Dataframe, Dropdown, File, Gallery, HighlightedText, Label, Radio, TabItem, Tab, Textbox. Example usage:
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ gallery = gr.Gallery(["images/1.jpg", "images/2.jpg", "images/3.jpg"])
+ selected_index = gr.Textbox()
+
+ def on_select(evt: gr.SelectData):
+ return evt.index
+
+ gallery.select(on_select, None, selected_index)
+```
+
+By [@aliabid94](https://github.com/aliabid94) in [PR 3399](https://github.com/gradio-app/gradio/pull/3399)
+
+- The `Textbox` component now includes a copy button by [@abidlabs](https://github.com/abidlabs) in [PR 3452](https://github.com/gradio-app/gradio/pull/3452)
+
+### Bug Fixes:
+
+- Use `huggingface_hub` to send telemetry on `interface` and `blocks`; eventually to replace segment by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3342](https://github.com/gradio-app/gradio/pull/3342)
+- Ensure load events created by components (randomize for slider, callable values) are never queued unless every is passed by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3391](https://github.com/gradio-app/gradio/pull/3391)
+- Prevent in-place updates of `generic_update` by shallow copying by [@gitgithan](https://github.com/gitgithan) in [PR 3405](https://github.com/gradio-app/gradio/pull/3405) to fix [#3282](https://github.com/gradio-app/gradio/issues/3282)
+- Fix bug caused by not importing `BlockContext` in `utils.py` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3424](https://github.com/gradio-app/gradio/pull/3424)
+- Ensure dropdown does not highlight partial matches by [@pngwn](https://github.com/pngwn) in [PR 3421](https://github.com/gradio-app/gradio/pull/3421)
+- Fix mic button display by [@aliabid94](https://github.com/aliabid94) in [PR 3456](https://github.com/gradio-app/gradio/pull/3456)
+
+### Documentation Changes:
+
+- Added a section on security and access when sharing Gradio apps by [@abidlabs](https://github.com/abidlabs) in [PR 3408](https://github.com/gradio-app/gradio/pull/3408)
+- Add Chinese README by [@uanu2002](https://github.com/uanu2002) in [PR 3394](https://github.com/gradio-app/gradio/pull/3394)
+- Adds documentation for web components by [@abidlabs](https://github.com/abidlabs) in [PR 3407](https://github.com/gradio-app/gradio/pull/3407)
+- Fixed link in Chinese readme by [@eltociear](https://github.com/eltociear) in [PR 3417](https://github.com/gradio-app/gradio/pull/3417)
+- Document Blocks methods by [@aliabd](https://github.com/aliabd) in [PR 3427](https://github.com/gradio-app/gradio/pull/3427)
+- Fixed bug where event handlers were not showing up in documentation by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3434](https://github.com/gradio-app/gradio/pull/3434)
+
+### Testing and Infrastructure Changes:
+
+- Fixes tests that were failing locally but passing on CI by [@abidlabs](https://github.com/abidlabs) in [PR 3411](https://github.com/gradio-app/gradio/pull/3411)
+- Remove codecov from the repo by [@aliabd](https://github.com/aliabd) in [PR 3415](https://github.com/gradio-app/gradio/pull/3415)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Prevent in-place updates of `generic_update` by shallow copying by [@gitgithan](https://github.com/gitgithan) in [PR 3405](https://github.com/gradio-app/gradio/pull/3405) to fix [#3282](https://github.com/gradio-app/gradio/issues/3282)
+- Persist file names of files uploaded through any Gradio component by [@abidlabs](https://github.com/abidlabs) in [PR 3412](https://github.com/gradio-app/gradio/pull/3412)
+- Fix markdown embedded component in docs by [@aliabd](https://github.com/aliabd) in [PR 3410](https://github.com/gradio-app/gradio/pull/3410)
+- Clean up event listeners code by [@aliabid94](https://github.com/aliabid94) in [PR 3420](https://github.com/gradio-app/gradio/pull/3420)
+- Fix css issue with spaces logo by [@aliabd](https://github.com/aliabd) in [PR 3422](https://github.com/gradio-app/gradio/pull/3422)
+- Makes a few fixes to the `JSON` component (show_label parameter, icons) in [@abidlabs](https://github.com/abidlabs) in [PR 3451](https://github.com/gradio-app/gradio/pull/3451)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.20.1
+
+### New Features:
+
+- Add `height` kwarg to style in `gr.Chatbot()` component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3369](https://github.com/gradio-app/gradio/pull/3369)
+
+```python
+chatbot = gr.Chatbot().style(height=500)
+```
+
+### Bug Fixes:
+
+- Ensure uploaded images are always shown in the sketch tool by [@pngwn](https://github.com/pngwn) in [PR 3386](https://github.com/gradio-app/gradio/pull/3386)
+- Fixes bug where when if fn is a non-static class member, then self should be ignored as the first param of the fn by [@or25](https://github.com/or25) in [PR #3227](https://github.com/gradio-app/gradio/pull/3227)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.20.0
+
+### New Features:
+
+###### Release event for Slider
+
+Now you can trigger your python function to run when the slider is released as opposed to every slider change value!
+
+Simply use the `release` method on the slider
+
+```python
+slider.release(function, inputs=[...], outputs=[...], api_name="predict")
+```
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3353](https://github.com/gradio-app/gradio/pull/3353)
+
+###### Dropdown Component Updates
+
+The standard dropdown component now supports searching for choices. Also when `multiselect` is `True`, you can specify `max_choices` to set the maximum number of choices you want the user to be able to select from the dropdown component.
+
+```python
+gr.Dropdown(label="Choose your favorite colors", choices=["red", "blue", "green", "yellow", "orange"], multiselect=True, max_choices=2)
+```
+
+by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3211](https://github.com/gradio-app/gradio/pull/3211)
+
+###### Download button for images 🖼️
+
+Output images will now automatically have a download button displayed to make it easier to save and share
+the results of Machine Learning art models.
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3297](https://github.com/gradio-app/gradio/pull/3297)
+
+- Updated image upload component to accept all image formats, including lossless formats like .webp by [@fienestar](https://github.com/fienestar) in [PR 3225](https://github.com/gradio-app/gradio/pull/3225)
+- Adds a disabled mode to the `gr.Button` component by setting `interactive=False` by [@abidlabs](https://github.com/abidlabs) in [PR 3266](https://github.com/gradio-app/gradio/pull/3266) and [PR 3288](https://github.com/gradio-app/gradio/pull/3288)
+- Adds visual feedback to the when the Flag button is clicked, by [@abidlabs](https://github.com/abidlabs) in [PR 3289](https://github.com/gradio-app/gradio/pull/3289)
+- Adds ability to set `flagging_options` display text and saved flag separately by [@abidlabs](https://github.com/abidlabs) in [PR 3289](https://github.com/gradio-app/gradio/pull/3289)
+- Allow the setting of `brush_radius` for the `Image` component both as a default and via `Image.update()` by [@pngwn](https://github.com/pngwn) in [PR 3277](https://github.com/gradio-app/gradio/pull/3277)
+- Added `info=` argument to form components to enable extra context provided to users, by [@aliabid94](https://github.com/aliabid94) in [PR 3291](https://github.com/gradio-app/gradio/pull/3291)
+- Allow developers to access the username of a logged-in user from the `gr.Request()` object using the `.username` attribute by [@abidlabs](https://github.com/abidlabs) in [PR 3296](https://github.com/gradio-app/gradio/pull/3296)
+- Add `preview` option to `Gallery.style` that launches the gallery in preview mode when first loaded by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3345](https://github.com/gradio-app/gradio/pull/3345)
+
+### Bug Fixes:
+
+- Ensure `mirror_webcam` is always respected by [@pngwn](https://github.com/pngwn) in [PR 3245](https://github.com/gradio-app/gradio/pull/3245)
+- Fix issue where updated markdown links were not being opened in a new tab by [@gante](https://github.com/gante) in [PR 3236](https://github.com/gradio-app/gradio/pull/3236)
+- API Docs Fixes by [@aliabd](https://github.com/aliabd) in [PR 3287](https://github.com/gradio-app/gradio/pull/3287)
+- Added a timeout to queue messages as some demos were experiencing infinitely growing queues from active jobs waiting forever for clients to respond by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3196](https://github.com/gradio-app/gradio/pull/3196)
+- Fixes the height of rendered LaTeX images so that they match the height of surrounding text by [@abidlabs](https://github.com/abidlabs) in [PR 3258](https://github.com/gradio-app/gradio/pull/3258) and in [PR 3276](https://github.com/gradio-app/gradio/pull/3276)
+- Fix bug where matplotlib images where always too small on the front end by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3274](https://github.com/gradio-app/gradio/pull/3274)
+- Remove embed's `initial_height` when loading is complete so the embed finds its natural height once it is loaded [@pngwn](https://github.com/pngwn) in [PR 3292](https://github.com/gradio-app/gradio/pull/3292)
+- Prevent Sketch from crashing when a default image is provided by [@pngwn](https://github.com/pngwn) in [PR 3277](https://github.com/gradio-app/gradio/pull/3277)
+- Respect the `shape` argument on the front end when creating Image Sketches by [@pngwn](https://github.com/pngwn) in [PR 3277](https://github.com/gradio-app/gradio/pull/3277)
+- Fix infinite loop caused by setting `Dropdown's` value to be `[]` and adding a change event on the dropdown by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3295](https://github.com/gradio-app/gradio/pull/3295)
+- Fix change event listed twice in image docs by [@aliabd](https://github.com/aliabd) in [PR 3318](https://github.com/gradio-app/gradio/pull/3318)
+- Fix bug that cause UI to be vertically centered at all times by [@pngwn](https://github.com/pngwn) in [PR 3336](https://github.com/gradio-app/gradio/pull/3336)
+- Fix bug where `height` set in `Gallery.style` was not respected by the front-end by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3343](https://github.com/gradio-app/gradio/pull/3343)
+- Ensure markdown lists are rendered correctly by [@pngwn](https://github.com/pngwn) in [PR 3341](https://github.com/gradio-app/gradio/pull/3341)
+- Ensure that the initial empty value for `gr.Dropdown(Multiselect=True)` is an empty list and the initial value for `gr.Dropdown(Multiselect=False)` is an empty string by [@pngwn](https://github.com/pngwn) in [PR 3338](https://github.com/gradio-app/gradio/pull/3338)
+- Ensure uploaded images respect the shape property when the canvas is also enabled by [@pngwn](https://github.com/pngwn) in [PR 3351](https://github.com/gradio-app/gradio/pull/3351)
+- Ensure that Google Analytics works correctly when gradio apps are created with `analytics_enabled=True` by [@abidlabs](https://github.com/abidlabs) in [PR 3349](https://github.com/gradio-app/gradio/pull/3349)
+- Fix bug where files were being re-uploaded after updates by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3375](https://github.com/gradio-app/gradio/pull/3375)
+- Fix error when using backen_fn and custom js at the same time by [@jialeicui](https://github.com/jialeicui) in [PR 3358](https://github.com/gradio-app/gradio/pull/3358)
+- Support new embeds for huggingface spaces subdomains by [@pngwn](https://github.com/pngwn) in [PR 3367](https://github.com/gradio-app/gradio/pull/3367)
+
+### Documentation Changes:
+
+- Added the `types` field to the dependency field in the config by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3315](https://github.com/gradio-app/gradio/pull/3315)
+- Gradio Status Page by [@aliabd](https://github.com/aliabd) in [PR 3331](https://github.com/gradio-app/gradio/pull/3331)
+- Adds a Guide on setting up a dashboard from Supabase data using the `gr.BarPlot`
+ component by [@abidlabs](https://github.com/abidlabs) in [PR 3275](https://github.com/gradio-app/gradio/pull/3275)
+
+### Testing and Infrastructure Changes:
+
+- Adds a script to benchmark the performance of the queue and adds some instructions on how to use it. By [@freddyaboulton](https://github.com/freddyaboulton) and [@abidlabs](https://github.com/abidlabs) in [PR 3272](https://github.com/gradio-app/gradio/pull/3272)
+- Flaky python tests no longer cancel non-flaky tests by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3344](https://github.com/gradio-app/gradio/pull/3344)
+
+### Breaking Changes:
+
+- Chatbot bubble colors can no longer be set by `chatbot.style(color_map=)` by [@aliabid94] in [PR 3370](https://github.com/gradio-app/gradio/pull/3370)
+
+### Full Changelog:
+
+- Fixed comment typo in components.py by [@eltociear](https://github.com/eltociear) in [PR 3235](https://github.com/gradio-app/gradio/pull/3235)
+- Cleaned up chatbot ui look and feel by [@aliabid94] in [PR 3370](https://github.com/gradio-app/gradio/pull/3370)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.19.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- UI fixes including footer and API docs by [@aliabid94](https://github.com/aliabid94) in [PR 3242](https://github.com/gradio-app/gradio/pull/3242)
+- Updated image upload component to accept all image formats, including lossless formats like .webp by [@fienestar](https://github.com/fienestar) in [PR 3225](https://github.com/gradio-app/gradio/pull/3225)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Added backend support for themes by [@aliabid94](https://github.com/aliabid94) in [PR 2931](https://github.com/gradio-app/gradio/pull/2931)
+- Added support for button sizes "lg" (default) and "sm".
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.19.0
+
+### New Features:
+
+###### Improved embedding experience
+
+When embedding a spaces-hosted gradio app as a web component, you now get an improved UI linking back to the original space, better error handling and more intelligent load performance. No changes are required to your code to benefit from this enhanced experience; simply upgrade your gradio SDK to the latest version.
+
+
+
+This behaviour is configurable. You can disable the info panel at the bottom by passing `info="false"`. You can disable the container entirely by passing `container="false"`.
+
+Error statuses are reported in the UI with an easy way for end-users to report problems to the original space author via the community tab of that Hugginface space:
+
+
+
+By default, gradio apps are lazy loaded, vastly improving performance when there are several demos on the page. Metadata is loaded ahead of time, but the space will only be loaded and rendered when it is in view.
+
+This behaviour is configurable. You can pass `eager="true"` to load and render the space regardless of whether or not it is currently on the screen.
+
+by [@pngwn](https://github.com/pngwn) in [PR 3205](https://github.com/gradio-app/gradio/pull/3205)
+
+###### New `gr.BarPlot` component! 📊
+
+Create interactive bar plots from a high-level interface with `gr.BarPlot`.
+No need to remember matplotlib syntax anymore!
+
+Example usage:
+
+```python
+import gradio as gr
+import pandas as pd
+
+simple = pd.DataFrame({
+ 'a': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],
+ 'b': [28, 55, 43, 91, 81, 53, 19, 87, 52]
+})
+
+with gr.Blocks() as demo:
+ gr.BarPlot(
+ simple,
+ x="a",
+ y="b",
+ title="Simple Bar Plot with made up data",
+ tooltip=['a', 'b'],
+ )
+
+demo.launch()
+```
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3157](https://github.com/gradio-app/gradio/pull/3157)
+
+###### Bokeh plots are back! 🌠
+
+Fixed a bug that prevented bokeh plots from being displayed on the front end and extended support for both 2.x and 3.x versions of bokeh!
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3212](https://github.com/gradio-app/gradio/pull/3212)
+
+### Bug Fixes:
+
+- Adds ability to add a single message from the bot or user side. Ex: specify `None` as the second value in the tuple, to add a single message in the chatbot from the "bot" side.
+
+```python
+gr.Chatbot([("Hi, I'm DialoGPT. Try asking me a question.", None)])
+```
+
+By [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3165](https://github.com/gradio-app/gradio/pull/3165)
+
+- Fixes `gr.utils.delete_none` to only remove props whose values are `None` from the config by [@abidlabs](https://github.com/abidlabs) in [PR 3188](https://github.com/gradio-app/gradio/pull/3188)
+- Fix bug where embedded demos were not loading files properly by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3177](https://github.com/gradio-app/gradio/pull/3177)
+- The `change` event is now triggered when users click the 'Clear All' button of the multiselect DropDown component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3195](https://github.com/gradio-app/gradio/pull/3195)
+- Stops File component from freezing when a large file is uploaded by [@aliabid94](https://github.com/aliabid94) in [PR 3191](https://github.com/gradio-app/gradio/pull/3191)
+- Support Chinese pinyin in Dataframe by [@aliabid94](https://github.com/aliabid94) in [PR 3206](https://github.com/gradio-app/gradio/pull/3206)
+- The `clear` event is now triggered when images are cleared by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3218](https://github.com/gradio-app/gradio/pull/3218)
+- Fix bug where auth cookies where not sent when connecting to an app via http by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3223](https://github.com/gradio-app/gradio/pull/3223)
+- Ensure latext CSS is always applied in light and dark mode by [@pngwn](https://github.com/pngwn) in [PR 3233](https://github.com/gradio-app/gradio/pull/3233)
+
+### Documentation Changes:
+
+- Sort components in docs by alphabetic order by [@aliabd](https://github.com/aliabd) in [PR 3152](https://github.com/gradio-app/gradio/pull/3152)
+- Changes to W&B guide by [@scottire](https://github.com/scottire) in [PR 3153](https://github.com/gradio-app/gradio/pull/3153)
+- Keep pnginfo metadata for gallery by [@wfng92](https://github.com/wfng92) in [PR 3150](https://github.com/gradio-app/gradio/pull/3150)
+- Add a section on how to run a Gradio app locally [@osanseviero](https://github.com/osanseviero) in [PR 3170](https://github.com/gradio-app/gradio/pull/3170)
+- Fixed typos in gradio events function documentation by [@vidalmaxime](https://github.com/vidalmaxime) in [PR 3168](https://github.com/gradio-app/gradio/pull/3168)
+- Added an example using Gradio's batch mode with the diffusers library by [@abidlabs](https://github.com/abidlabs) in [PR 3224](https://github.com/gradio-app/gradio/pull/3224)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fix demos page css and add close demos button by [@aliabd](https://github.com/aliabd) in [PR 3151](https://github.com/gradio-app/gradio/pull/3151)
+- Caches temp files from base64 input data by giving them a deterministic path based on the contents of data by [@abidlabs](https://github.com/abidlabs) in [PR 3197](https://github.com/gradio-app/gradio/pull/3197)
+- Better warnings (when there is a mismatch between the number of output components and values returned by a function, or when the `File` component or `UploadButton` component includes a `file_types` parameter along with `file_count=="dir"`) by [@abidlabs](https://github.com/abidlabs) in [PR 3194](https://github.com/gradio-app/gradio/pull/3194)
+- Raises a `gr.Error` instead of a regular Python error when you use `gr.Interface.load()` to load a model and there's an error querying the HF API by [@abidlabs](https://github.com/abidlabs) in [PR 3194](https://github.com/gradio-app/gradio/pull/3194)
+- Fixed gradio share links so that they are persistent and do not reset if network
+ connection is disrupted by by [XciD](https://github.com/XciD), [Wauplin](https://github.com/Wauplin), and [@abidlabs](https://github.com/abidlabs) in [PR 3149](https://github.com/gradio-app/gradio/pull/3149) and a follow-up to allow it to work for users upgrading from a previous Gradio version in [PR 3221](https://github.com/gradio-app/gradio/pull/3221)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.18.0
+
+### New Features:
+
+###### Revamped Stop Button for Interfaces 🛑
+
+If your Interface function is a generator, there used to be a separate `Stop` button displayed next
+to the `Submit` button.
+
+We've revamed the `Submit` button so that it turns into a `Stop` button during the generation process.
+Clicking on the `Stop` button will cancel the generation and turn it back to a `Submit` button.
+The `Stop` button will automatically turn back to a `Submit` button at the end of the generation if you don't use it!
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3124](https://github.com/gradio-app/gradio/pull/3124)
+
+###### Queue now works with reload mode!
+
+You can now call `queue` on your `demo` outside of the `if __name__ == "__main__"` block and
+run the script in reload mode with the `gradio` command.
+
+Any changes to the `app.py` file will be reflected in the webpage automatically and the queue will work
+properly!
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3089](https://github.com/gradio-app/gradio/pull/3089)
+
+###### Allow serving files from additional directories
+
+```python
+demo = gr.Interface(...)
+demo.launch(
+ file_directories=["/var/lib/demo/path/to/resources"]
+)
+```
+
+By [@maxaudron](https://github.com/maxaudron) in [PR 3075](https://github.com/gradio-app/gradio/pull/3075)
+
+### Bug Fixes:
+
+- Fixes URL resolution on Windows by [@abidlabs](https://github.com/abidlabs) in [PR 3108](https://github.com/gradio-app/gradio/pull/3108)
+- Example caching now works with components without a label attribute (e.g. `Column`) by [@abidlabs](https://github.com/abidlabs) in [PR 3123](https://github.com/gradio-app/gradio/pull/3123)
+- Ensure the Video component correctly resets the UI state when a new video source is loaded and reduce choppiness of UI by [@pngwn](https://github.com/abidlabs) in [PR 3117](https://github.com/gradio-app/gradio/pull/3117)
+- Fixes loading private Spaces by [@abidlabs](https://github.com/abidlabs) in [PR 3068](https://github.com/gradio-app/gradio/pull/3068)
+- Added a warning when attempting to launch an `Interface` via the `%%blocks` jupyter notebook magic command by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3126](https://github.com/gradio-app/gradio/pull/3126)
+- Fixes bug where interactive output image cannot be set when in edit mode by [@dawoodkhan82](https://github.com/@dawoodkhan82) in [PR 3135](https://github.com/gradio-app/gradio/pull/3135)
+- A share link will automatically be created when running on Sagemaker notebooks so that the front-end is properly displayed by [@abidlabs](https://github.com/abidlabs) in [PR 3137](https://github.com/gradio-app/gradio/pull/3137)
+- Fixes a few dropdown component issues; hide checkmark next to options as expected, and keyboard hover is visible by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3145]https://github.com/gradio-app/gradio/pull/3145)
+- Fixed bug where example pagination buttons were not visible in dark mode or displayed under the examples table. By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3144](https://github.com/gradio-app/gradio/pull/3144)
+- Fixed bug where the font color of axis labels and titles for native plots did not respond to dark mode preferences. By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3146](https://github.com/gradio-app/gradio/pull/3146)
+
+### Documentation Changes:
+
+- Added a guide on the 4 kinds of Gradio Interfaces by [@yvrjsharma](https://github.com/yvrjsharma) and [@abidlabs](https://github.com/abidlabs) in [PR 3003](https://github.com/gradio-app/gradio/pull/3003)
+- Explained that the parameters in `launch` will not be respected when using reload mode, e.g. `gradio` command by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3089](https://github.com/gradio-app/gradio/pull/3089)
+- Added a demo to show how to set up variable numbers of outputs in Gradio by [@abidlabs](https://github.com/abidlabs) in [PR 3127](https://github.com/gradio-app/gradio/pull/3127)
+- Updated docs to reflect that the `equal_height` parameter should be passed to the `.style()` method of `gr.Row()` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3125](https://github.com/gradio-app/gradio/pull/3125)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Changed URL of final image for `fake_diffusion` demos by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3120](https://github.com/gradio-app/gradio/pull/3120)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.17.1
+
+### New Features:
+
+###### iOS image rotation fixed 🔄
+
+Previously photos uploaded via iOS would be rotated after processing. This has been fixed by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3089](https://github.com/gradio-app/gradio/pull/3091)
+
+######### Before
+
+
+
+######### After
+
+
+
+###### Run on Kaggle kernels 🧪
+
+A share link will automatically be created when running on Kaggle kernels (notebooks) so that the front-end is properly displayed.
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3101](https://github.com/gradio-app/gradio/pull/3101)
+
+### Bug Fixes:
+
+- Fix bug where examples were not rendered correctly for demos created with Blocks api that had multiple input compinents by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3090](https://github.com/gradio-app/gradio/pull/3090)
+- Fix change event listener for JSON, HighlightedText, Chatbot by [@aliabid94](https://github.com/aliabid94) in [PR 3095](https://github.com/gradio-app/gradio/pull/3095)
+- Fixes bug where video and file change event not working [@tomchang25](https://github.com/tomchang25) in [PR 3098](https://github.com/gradio-app/gradio/pull/3098)
+- Fixes bug where static_video play and pause event not working [@tomchang25](https://github.com/tomchang25) in [PR 3098](https://github.com/gradio-app/gradio/pull/3098)
+- Fixed `Gallery.style(grid=...)` by by [@aliabd](https://github.com/aliabd) in [PR 3107](https://github.com/gradio-app/gradio/pull/3107)
+
+### Documentation Changes:
+
+- Update chatbot guide to include blocks demo and markdown support section by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3023](https://github.com/gradio-app/gradio/pull/3023)
+
+* Fix a broken link in the Quick Start guide, by [@cakiki](https://github.com/cakiki) in [PR 3109](https://github.com/gradio-app/gradio/pull/3109)
+* Better docs navigation on mobile by [@aliabd](https://github.com/aliabd) in [PR 3112](https://github.com/gradio-app/gradio/pull/3112)
+* Add a guide on using Gradio with [Comet](https://comet.com/), by [@DN6](https://github.com/DN6/) in [PR 3058](https://github.com/gradio-app/gradio/pull/3058)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Set minimum `markdown-it-py` version to `2.0.0` so that the dollar math plugin is compatible by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3102](https://github.com/gradio-app/gradio/pull/3102)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.17.0
+
+### New Features:
+
+###### Extended support for Interface.load! 🏗️
+
+You can now load `image-to-text` and `conversational` pipelines from the hub!
+
+###### Image-to-text Demo
+
+```python
+io = gr.Interface.load("models/nlpconnect/vit-gpt2-image-captioning",
+ api_key="
+
+###### conversational Demo
+
+```python
+chatbot = gr.Interface.load("models/microsoft/DialoGPT-medium",
+ api_key="
+
+By [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3014](https://github.com/gradio-app/gradio/pull/3014)
+
+###### Fixing Auth on Spaces 🔑
+
+Authentication on spaces works now! Third party cookies must be enabled on your browser to be able
+to log in. Some browsers disable third party cookies by default (Safari, Chrome Incognito).
+
+
+
+### Bug Fixes:
+
+- Fixes bug where interpretation event was not configured correctly by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2993](https://github.com/gradio-app/gradio/pull/2993)
+- Fix relative import bug in reload mode by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2992](https://github.com/gradio-app/gradio/pull/2992)
+- Fixes bug where png files were not being recognized when uploading images by [@abidlabs](https://github.com/abidlabs) in [PR 3002](https://github.com/gradio-app/gradio/pull/3002)
+- Fixes bug where external Spaces could not be loaded and used as functions if they returned files by [@abidlabs](https://github.com/abidlabs) in [PR 3004](https://github.com/gradio-app/gradio/pull/3004)
+- Fix bug where file serialization output was not JSON serializable by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2999](https://github.com/gradio-app/gradio/pull/2999)
+- Fixes bug where png files were not being recognized when uploading images by [@abidlabs](https://github.com/abidlabs) in [PR 3002](https://github.com/gradio-app/gradio/pull/3002)
+- Fixes bug where temporary uploaded files were not being added to temp sets by [@abidlabs](https://github.com/abidlabs) in [PR 3005](https://github.com/gradio-app/gradio/pull/3005)
+- Fixes issue where markdown support in chatbot breaks older demos [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3006](https://github.com/gradio-app/gradio/pull/3006)
+- Fixes the `/file/` route that was broken in a recent change in [PR 3010](https://github.com/gradio-app/gradio/pull/3010)
+- Fix bug where the Image component could not serialize image urls by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2957](https://github.com/gradio-app/gradio/pull/2957)
+- Fix forwarding for guides after SEO renaming by [@aliabd](https://github.com/aliabd) in [PR 3017](https://github.com/gradio-app/gradio/pull/3017)
+- Switch all pages on the website to use latest stable gradio by [@aliabd](https://github.com/aliabd) in [PR 3016](https://github.com/gradio-app/gradio/pull/3016)
+- Fix bug related to deprecated parameters in `huggingface_hub` for the HuggingFaceDatasetSaver in [PR 3025](https://github.com/gradio-app/gradio/pull/3025)
+- Added better support for symlinks in the way absolute paths are resolved by [@abidlabs](https://github.com/abidlabs) in [PR 3037](https://github.com/gradio-app/gradio/pull/3037)
+- Fix several minor frontend bugs (loading animation, examples as gallery) frontend [@aliabid94](https://github.com/3026) in [PR 2961](https://github.com/gradio-app/gradio/pull/3026).
+- Fixes bug that the chatbot sample code does not work with certain input value by [@petrov826](https://github.com/petrov826) in [PR 3039](https://github.com/gradio-app/gradio/pull/3039).
+- Fix shadows for form element and ensure focus styles more visible in dark mode [@pngwn](https://github.com/pngwn) in [PR 3042](https://github.com/gradio-app/gradio/pull/3042).
+- Fixed bug where the Checkbox and Dropdown change events were not triggered in response to other component changes by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3045](https://github.com/gradio-app/gradio/pull/3045)
+- Fix bug where the queue was not properly restarted after launching a `closed` app by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3022](https://github.com/gradio-app/gradio/pull/3022)
+- Adding missing embedded components on docs by [@aliabd](https://github.com/aliabd) in [PR 3027](https://github.com/gradio-app/gradio/pull/3027)
+- Fixes bug where app would crash if the `file_types` parameter of `gr.File` or `gr.UploadButton` was not a list by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3048](https://github.com/gradio-app/gradio/pull/3048)
+- Ensure CSS mounts correctly regardless of how many Gradio instances are on the page [@pngwn](https://github.com/pngwn) in [PR 3059](https://github.com/gradio-app/gradio/pull/3059).
+- Fix bug where input component was not hidden in the frontend for `UploadButton` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3053](https://github.com/gradio-app/gradio/pull/3053)
+- Fixes issue where after clicking submit or undo, the sketch output wouldn't clear. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3047](https://github.com/gradio-app/gradio/pull/3047)
+- Ensure spaces embedded via the web component always use the correct URLs for server requests and change ports for testing to avoid strange collisions when users are working with embedded apps locally by [@pngwn](https://github.com/pngwn) in [PR 3065](https://github.com/gradio-app/gradio/pull/3065)
+- Preserve selected image of Gallery through updated by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3061](https://github.com/gradio-app/gradio/pull/3061)
+- Fix bug where auth was not respected on HF spaces by [@freddyaboulton](https://github.com/freddyaboulton) and [@aliabid94](https://github.com/aliabid94) in [PR 3049](https://github.com/gradio-app/gradio/pull/3049)
+- Fixes bug where tabs selected attribute not working if manually change tab by [@tomchang25](https://github.com/tomchang25) in [3055](https://github.com/gradio-app/gradio/pull/3055)
+- Change chatbot to show dots on progress, and fix bug where chatbot would not stick to bottom in the case of images by [@aliabid94](https://github.com/aliabid94) in [PR 3067](https://github.com/gradio-app/gradio/pull/3079)
+
+### Documentation Changes:
+
+- SEO improvements to guides by[@aliabd](https://github.com/aliabd) in [PR 2915](https://github.com/gradio-app/gradio/pull/2915)
+- Use `gr.LinePlot` for the `blocks_kinematics` demo by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2998](https://github.com/gradio-app/gradio/pull/2998)
+- Updated the `interface_series_load` to include some inline markdown code by [@abidlabs](https://github.com/abidlabs) in [PR 3051](https://github.com/gradio-app/gradio/pull/3051)
+
+### Testing and Infrastructure Changes:
+
+- Adds a GitHub action to test if any large files (> 5MB) are present by [@abidlabs](https://github.com/abidlabs) in [PR 3013](https://github.com/gradio-app/gradio/pull/3013)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Rewrote frontend using CSS variables for themes by [@pngwn](https://github.com/pngwn) in [PR 2840](https://github.com/gradio-app/gradio/pull/2840)
+- Moved telemetry requests to run on background threads by [@abidlabs](https://github.com/abidlabs) in [PR 3054](https://github.com/gradio-app/gradio/pull/3054)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.16.2
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fixed file upload fails for files with zero size by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2923](https://github.com/gradio-app/gradio/pull/2923)
+- Fixed bug where `mount_gradio_app` would not launch if the queue was enabled in a gradio app by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2939](https://github.com/gradio-app/gradio/pull/2939)
+- Fix custom long CSS handling in Blocks by [@anton-l](https://github.com/anton-l) in [PR 2953](https://github.com/gradio-app/gradio/pull/2953)
+- Recovers the dropdown change event by [@abidlabs](https://github.com/abidlabs) in [PR 2954](https://github.com/gradio-app/gradio/pull/2954).
+- Fix audio file output by [@aliabid94](https://github.com/aliabid94) in [PR 2961](https://github.com/gradio-app/gradio/pull/2961).
+- Fixed bug where file extensions of really long files were not kept after download by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2929](https://github.com/gradio-app/gradio/pull/2929)
+- Fix bug where outputs for examples where not being returned by the backend by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2955](https://github.com/gradio-app/gradio/pull/2955)
+- Fix bug in `blocks_plug` demo that prevented switching tabs programmatically with python [@TashaSkyUp](https://github.com/https://github.com/TashaSkyUp) in [PR 2971](https://github.com/gradio-app/gradio/pull/2971).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.16.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fix audio file output by [@aliabid94](https://github.com/aliabid94) in [PR 2950](https://github.com/gradio-app/gradio/pull/2950).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.16.0
+
+### New Features:
+
+###### Send custom progress updates by adding a `gr.Progress` argument after the input arguments to any function. Example:
+
+```python
+def reverse(word, progress=gr.Progress()):
+ progress(0, desc="Starting")
+ time.sleep(1)
+ new_string = ""
+ for letter in progress.tqdm(word, desc="Reversing"):
+ time.sleep(0.25)
+ new_string = letter + new_string
+ return new_string
+
+demo = gr.Interface(reverse, gr.Text(), gr.Text())
+```
+
+Progress indicator bar by [@aliabid94](https://github.com/aliabid94) in [PR 2750](https://github.com/gradio-app/gradio/pull/2750).
+
+- Added `title` argument to `TabbedInterface` by @MohamedAliRashad in [#2888](https://github.com/gradio-app/gradio/pull/2888)
+- Add support for specifying file extensions for `gr.File` and `gr.UploadButton`, using `file_types` parameter (e.g `gr.File(file_count="multiple", file_types=["text", ".json", ".csv"])`) by @dawoodkhan82 in [#2901](https://github.com/gradio-app/gradio/pull/2901)
+- Added `multiselect` option to `Dropdown` by @dawoodkhan82 in [#2871](https://github.com/gradio-app/gradio/pull/2871)
+
+###### With `multiselect` set to `true` a user can now select multiple options from the `gr.Dropdown` component.
+
+```python
+gr.Dropdown(["angola", "pakistan", "canada"], multiselect=True, value=["angola"])
+```
+
+
+
+### Bug Fixes:
+
+- Fixed bug where an error opening an audio file led to a crash by [@FelixDombek](https://github.com/FelixDombek) in [PR 2898](https://github.com/gradio-app/gradio/pull/2898)
+- Fixed bug where setting `default_enabled=False` made it so that the entire queue did not start by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2876](https://github.com/gradio-app/gradio/pull/2876)
+- Fixed bug where csv preview for DataFrame examples would show filename instead of file contents by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2877](https://github.com/gradio-app/gradio/pull/2877)
+- Fixed bug where an error raised after yielding iterative output would not be displayed in the browser by
+ [@JaySmithWpg](https://github.com/JaySmithWpg) in [PR 2889](https://github.com/gradio-app/gradio/pull/2889)
+- Fixed bug in `blocks_style` demo that was preventing it from launching by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2890](https://github.com/gradio-app/gradio/pull/2890)
+- Fixed bug where files could not be downloaded by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2926](https://github.com/gradio-app/gradio/pull/2926)
+- Fixed bug where cached examples were not displaying properly by [@a-rogalska](https://github.com/a-rogalska) in [PR 2974](https://github.com/gradio-app/gradio/pull/2974)
+
+### Documentation Changes:
+
+- Added a Guide on using Google Sheets to create a real-time dashboard with Gradio's `DataFrame` and `LinePlot` component, by [@abidlabs](https://github.com/abidlabs) in [PR 2816](https://github.com/gradio-app/gradio/pull/2816)
+- Add a components - events matrix on the docs by [@aliabd](https://github.com/aliabd) in [PR 2921](https://github.com/gradio-app/gradio/pull/2921)
+
+### Testing and Infrastructure Changes:
+
+- Deployed PRs from forks to spaces by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2895](https://github.com/gradio-app/gradio/pull/2895)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- The `default_enabled` parameter of the `Blocks.queue` method has no effect by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2876](https://github.com/gradio-app/gradio/pull/2876)
+- Added typing to several Python files in codebase by [@abidlabs](https://github.com/abidlabs) in [PR 2887](https://github.com/gradio-app/gradio/pull/2887)
+- Excluding untracked files from demo notebook check action by [@aliabd](https://github.com/aliabd) in [PR 2897](https://github.com/gradio-app/gradio/pull/2897)
+- Optimize images and gifs by [@aliabd](https://github.com/aliabd) in [PR 2922](https://github.com/gradio-app/gradio/pull/2922)
+- Updated typing by [@1nF0rmed](https://github.com/1nF0rmed) in [PR 2904](https://github.com/gradio-app/gradio/pull/2904)
+
+### Contributors Shoutout:
+
+- @JaySmithWpg for making their first contribution to gradio!
+- @MohamedAliRashad for making their first contribution to gradio!
+
+## 3.15.0
+
+### New Features:
+
+Gradio's newest plotting component `gr.LinePlot`! 📈
+
+With this component you can easily create time series visualizations with customizable
+appearance for your demos and dashboards ... all without having to know an external plotting library.
+
+For an example of the api see below:
+
+```python
+gr.LinePlot(stocks,
+ x="date",
+ y="price",
+ color="symbol",
+ color_legend_position="bottom",
+ width=600, height=400, title="Stock Prices")
+```
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2807](https://github.com/gradio-app/gradio/pull/2807)
+
+### Bug Fixes:
+
+- Fixed bug where the `examples_per_page` parameter of the `Examples` component was not passed to the internal `Dataset` component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2861](https://github.com/gradio-app/gradio/pull/2861)
+- Fixes loading Spaces that have components with default values by [@abidlabs](https://github.com/abidlabs) in [PR 2855](https://github.com/gradio-app/gradio/pull/2855)
+- Fixes flagging when `allow_flagging="auto"` in `gr.Interface()` by [@abidlabs](https://github.com/abidlabs) in [PR 2695](https://github.com/gradio-app/gradio/pull/2695)
+- Fixed bug where passing a non-list value to `gr.CheckboxGroup` would crash the entire app by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2866](https://github.com/gradio-app/gradio/pull/2866)
+
+### Documentation Changes:
+
+- Added a Guide on using BigQuery with Gradio's `DataFrame` and `ScatterPlot` component,
+ by [@abidlabs](https://github.com/abidlabs) in [PR 2794](https://github.com/gradio-app/gradio/pull/2794)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fixed importing gradio can cause PIL.Image.registered_extensions() to break by `[@aliencaocao](https://github.com/aliencaocao)` in `[PR 2846](https://github.com/gradio-app/gradio/pull/2846)`
+- Fix css glitch and navigation in docs by [@aliabd](https://github.com/aliabd) in [PR 2856](https://github.com/gradio-app/gradio/pull/2856)
+- Added the ability to set `x_lim`, `y_lim` and legend positions for `gr.ScatterPlot` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2807](https://github.com/gradio-app/gradio/pull/2807)
+- Remove footers and min-height the correct way by [@aliabd](https://github.com/aliabd) in [PR 2860](https://github.com/gradio-app/gradio/pull/2860)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.14.0
+
+### New Features:
+
+###### Add Waveform Visual Support to Audio
+
+Adds a `gr.make_waveform()` function that creates a waveform video by combining an audio and an optional background image by [@dawoodkhan82](http://github.com/dawoodkhan82) and [@aliabid94](http://github.com/aliabid94) in [PR 2706](https://github.com/gradio-app/gradio/pull/2706. Helpful for making audio outputs much more shareable.
+
+
+
+###### Allows Every Component to Accept an `every` Parameter
+
+When a component's initial value is a function, the `every` parameter re-runs the function every `every` seconds. By [@abidlabs](https://github.com/abidlabs) in [PR 2806](https://github.com/gradio-app/gradio/pull/2806). Here's a code example:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ df = gr.DataFrame(run_query, every=60*60)
+
+demo.queue().launch()
+```
+
+### Bug Fixes:
+
+- Fixed issue where too many temporary files were created, all with randomly generated
+ filepaths. Now fewer temporary files are created and are assigned a path that is a
+ hash based on the file contents by [@abidlabs](https://github.com/abidlabs) in [PR 2758](https://github.com/gradio-app/gradio/pull/2758)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.13.2
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+\*No changes to highlight.
+
+-
+
+### Documentation Changes:
+
+- Improves documentation of several queuing-related parameters by [@abidlabs](https://github.com/abidlabs) in [PR 2825](https://github.com/gradio-app/gradio/pull/2825)
+
+### Testing and Infrastructure Changes:
+
+- Remove h11 pinning by [@ecederstrand](https://github.com/ecederstrand) in [PR 2820](https://github.com/gradio-app/gradio/pull/2820)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.13.1
+
+### New Features:
+
+###### New Shareable Links
+
+Replaces tunneling logic based on ssh port-forwarding to that based on `frp` by [XciD](https://github.com/XciD) and [Wauplin](https://github.com/Wauplin) in [PR 2509](https://github.com/gradio-app/gradio/pull/2509)
+
+You don't need to do anything differently, but when you set `share=True` in `launch()`,
+you'll get this message and a public link that look a little bit different:
+
+```bash
+Setting up a public link... we have recently upgraded the way public links are generated. If you encounter any problems, please downgrade to gradio version 3.13.0
+.
+Running on public URL: https://bec81a83-5b5c-471e.gradio.live
+```
+
+These links are a more secure and scalable way to create shareable demos!
+
+### Bug Fixes:
+
+- Allows `gr.Dataframe()` to take a `pandas.DataFrame` that includes numpy array and other types as its initial value, by [@abidlabs](https://github.com/abidlabs) in [PR 2804](https://github.com/gradio-app/gradio/pull/2804)
+- Add `altair` to requirements.txt by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2811](https://github.com/gradio-app/gradio/pull/2811)
+- Added aria-labels to icon buttons that are built into UI components by [@emilyuhde](http://github.com/emilyuhde) in [PR 2791](https://github.com/gradio-app/gradio/pull/2791)
+
+### Documentation Changes:
+
+- Fixed some typos in the "Plot Component for Maps" guide by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2811](https://github.com/gradio-app/gradio/pull/2811)
+
+### Testing and Infrastructure Changes:
+
+- Fixed test for IP address by [@abidlabs](https://github.com/abidlabs) in [PR 2808](https://github.com/gradio-app/gradio/pull/2808)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fixed typo in parameter `visible` in classes in `templates.py` by [@abidlabs](https://github.com/abidlabs) in [PR 2805](https://github.com/gradio-app/gradio/pull/2805)
+- Switched external service for getting IP address from `https://api.ipify.org` to `https://checkip.amazonaws.com/` by [@abidlabs](https://github.com/abidlabs) in [PR 2810](https://github.com/gradio-app/gradio/pull/2810)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+- Fixed typo in parameter `visible` in classes in `templates.py` by [@abidlabs](https://github.com/abidlabs) in [PR 2805](https://github.com/gradio-app/gradio/pull/2805)
+- Switched external service for getting IP address from `https://api.ipify.org` to `https://checkip.amazonaws.com/` by [@abidlabs](https://github.com/abidlabs) in [PR 2810](https://github.com/gradio-app/gradio/pull/2810)
+
+## 3.13.0
+
+### New Features:
+
+###### Scatter plot component
+
+It is now possible to create a scatter plot natively in Gradio!
+
+The `gr.ScatterPlot` component accepts a pandas dataframe and some optional configuration parameters
+and will automatically create a plot for you!
+
+This is the first of many native plotting components in Gradio!
+
+For an example of how to use `gr.ScatterPlot` see below:
+
+```python
+import gradio as gr
+from vega_datasets import data
+
+cars = data.cars()
+
+with gr.Blocks() as demo:
+ gr.ScatterPlot(show_label=False,
+ value=cars,
+ x="Horsepower",
+ y="Miles_per_Gallon",
+ color="Origin",
+ tooltip="Name",
+ title="Car Data",
+ y_title="Miles per Gallon",
+ color_legend_title="Origin of Car").style(container=False)
+
+demo.launch()
+```
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2764](https://github.com/gradio-app/gradio/pull/2764)
+
+###### Support for altair plots
+
+The `Plot` component can now accept altair plots as values!
+Simply return an altair plot from your event listener and gradio will display it in the front-end.
+See the example below:
+
+```python
+import gradio as gr
+import altair as alt
+from vega_datasets import data
+
+cars = data.cars()
+chart = (
+ alt.Chart(cars)
+ .mark_point()
+ .encode(
+ x="Horsepower",
+ y="Miles_per_Gallon",
+ color="Origin",
+ )
+)
+
+with gr.Blocks() as demo:
+ gr.Plot(value=chart)
+demo.launch()
+```
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2741](https://github.com/gradio-app/gradio/pull/2741)
+
+###### Set the background color of a Label component
+
+The `Label` component now accepts a `color` argument by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2736](https://github.com/gradio-app/gradio/pull/2736).
+The `color` argument should either be a valid css color name or hexadecimal string.
+You can update the color with `gr.Label.update`!
+
+This lets you create Alert and Warning boxes with the `Label` component. See below:
+
+```python
+import gradio as gr
+import random
+
+def update_color(value):
+ if value < 0:
+ # This is bad so use red
+ return "#FF0000"
+ elif 0 <= value <= 20:
+ # Ok but pay attention (use orange)
+ return "#ff9966"
+ else:
+ # Nothing to worry about
+ return None
+
+def update_value():
+ choice = random.choice(['good', 'bad', 'so-so'])
+ color = update_color(choice)
+ return gr.Label.update(value=choice, color=color)
+
+
+with gr.Blocks() as demo:
+ label = gr.Label(value=-10)
+ demo.load(lambda: update_value(), inputs=None, outputs=[label], every=1)
+demo.queue().launch()
+```
+
+
+
+###### Add Brazilian Portuguese translation
+
+Add Brazilian Portuguese translation (pt-BR.json) by [@pstwh](http://github.com/pstwh) in [PR 2753](https://github.com/gradio-app/gradio/pull/2753):
+
+
+
+### Bug Fixes:
+
+- Fixed issue where image thumbnails were not showing when an example directory was provided
+ by [@abidlabs](https://github.com/abidlabs) in [PR 2745](https://github.com/gradio-app/gradio/pull/2745)
+- Fixed bug loading audio input models from the hub by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2779](https://github.com/gradio-app/gradio/pull/2779).
+- Fixed issue where entities were not merged when highlighted text was generated from the
+ dictionary inputs [@payoto](https://github.com/payoto) in [PR 2767](https://github.com/gradio-app/gradio/pull/2767)
+- Fixed bug where generating events did not finish running even if the websocket connection was closed by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2783](https://github.com/gradio-app/gradio/pull/2783).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Images in the chatbot component are now resized if they exceed a max width by [@abidlabs](https://github.com/abidlabs) in [PR 2748](https://github.com/gradio-app/gradio/pull/2748)
+- Missing parameters have been added to `gr.Blocks().load()` by [@abidlabs](https://github.com/abidlabs) in [PR 2755](https://github.com/gradio-app/gradio/pull/2755)
+- Deindex share URLs from search by [@aliabd](https://github.com/aliabd) in [PR 2772](https://github.com/gradio-app/gradio/pull/2772)
+- Redirect old links and fix broken ones by [@aliabd](https://github.com/aliabd) in [PR 2774](https://github.com/gradio-app/gradio/pull/2774)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.12.0
+
+### New Features:
+
+###### The `Chatbot` component now supports a subset of Markdown (including bold, italics, code, images)
+
+You can now pass in some Markdown to the Chatbot component and it will show up,
+meaning that you can pass in images as well! by [@abidlabs](https://github.com/abidlabs) in [PR 2731](https://github.com/gradio-app/gradio/pull/2731)
+
+Here's a simple example that references a local image `lion.jpg` that is in the same
+folder as the Python script:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ gr.Chatbot([("hi", "hello **abubakar**"), ("", "cool pic")])
+
+demo.launch()
+```
+
+
+
+To see a more realistic example, see the new demo `/demo/chatbot_multimodal/run.py`.
+
+###### Latex support
+
+Added mathtext (a subset of latex) support to gr.Markdown. Added by [@kashif](https://github.com/kashif) and [@aliabid94](https://github.com/aliabid94) in [PR 2696](https://github.com/gradio-app/gradio/pull/2696).
+
+Example of how it can be used:
+
+```python
+gr.Markdown(
+ r"""
+ # Hello World! $\frac{\sqrt{x + y}}{4}$ is today's lesson.
+ """)
+```
+
+###### Update Accordion properties from the backend
+
+You can now update the Accordion `label` and `open` status with `gr.Accordion.update` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2690](https://github.com/gradio-app/gradio/pull/2690)
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Accordion(label="Open for greeting", open=False) as accordion:
+ gr.Textbox("Hello!")
+ open_btn = gr.Button(value="Open Accordion")
+ close_btn = gr.Button(value="Close Accordion")
+ open_btn.click(
+ lambda: gr.Accordion.update(open=True, label="Open Accordion"),
+ inputs=None,
+ outputs=[accordion],
+ )
+ close_btn.click(
+ lambda: gr.Accordion.update(open=False, label="Closed Accordion"),
+ inputs=None,
+ outputs=[accordion],
+ )
+demo.launch()
+```
+
+
+
+### Bug Fixes:
+
+- Fixed bug where requests timeout is missing from utils.version_check() by [@yujiehecs](https://github.com/yujiehecs) in [PR 2729](https://github.com/gradio-app/gradio/pull/2729)
+- Fixed bug where so that the `File` component can properly preprocess files to "binary" byte-string format by [CoffeeVampir3](https://github.com/CoffeeVampir3) in [PR 2727](https://github.com/gradio-app/gradio/pull/2727)
+- Fixed bug to ensure that filenames are less than 200 characters even for non-English languages by [@SkyTNT](https://github.com/SkyTNT) in [PR 2685](https://github.com/gradio-app/gradio/pull/2685)
+
+### Documentation Changes:
+
+- Performance improvements to docs on mobile by [@aliabd](https://github.com/aliabd) in [PR 2730](https://github.com/gradio-app/gradio/pull/2730)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Make try examples button more prominent by [@aliabd](https://github.com/aliabd) in [PR 2705](https://github.com/gradio-app/gradio/pull/2705)
+- Fix id clashes in docs by [@aliabd](https://github.com/aliabd) in [PR 2713](https://github.com/gradio-app/gradio/pull/2713)
+- Fix typos in guide docs by [@andridns](https://github.com/andridns) in [PR 2722](https://github.com/gradio-app/gradio/pull/2722)
+- Add option to `include_audio` in Video component. When `True`, for `source="webcam"` this will record audio and video, for `source="upload"` this will retain the audio in an uploaded video by [@mandargogate](https://github.com/MandarGogate) in [PR 2721](https://github.com/gradio-app/gradio/pull/2721)
+
+### Contributors Shoutout:
+
+- [@andridns](https://github.com/andridns) made their first contribution in [PR 2722](https://github.com/gradio-app/gradio/pull/2722)!
+
+## 3.11.0
+
+### New Features:
+
+###### Upload Button
+
+There is now a new component called the `UploadButton` which is a file upload component but in button form! You can also specify what file types it should accept in the form of a list (ex: `image`, `video`, `audio`, `text`, or generic `file`). Added by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2591](https://github.com/gradio-app/gradio/pull/2591).
+
+Example of how it can be used:
+
+```python
+import gradio as gr
+
+def upload_file(files):
+ file_paths = [file.name for file in files]
+ return file_paths
+
+with gr.Blocks() as demo:
+ file_output = gr.File()
+ upload_button = gr.UploadButton("Click to Upload a File", file_types=["image", "video"], file_count="multiple")
+ upload_button.upload(upload_file, upload_button, file_output)
+
+demo.launch()
+```
+
+###### Revamped API documentation page
+
+New API Docs page with in-browser playground and updated aesthetics. [@gary149](https://github.com/gary149) in [PR 2652](https://github.com/gradio-app/gradio/pull/2652)
+
+###### Revamped Login page
+
+Previously our login page had its own CSS, had no dark mode, and had an ugly json message on the wrong credentials. Made the page more aesthetically consistent, added dark mode support, and a nicer error message. [@aliabid94](https://github.com/aliabid94) in [PR 2684](https://github.com/gradio-app/gradio/pull/2684)
+
+###### Accessing the Requests Object Directly
+
+You can now access the Request object directly in your Python function by [@abidlabs](https://github.com/abidlabs) in [PR 2641](https://github.com/gradio-app/gradio/pull/2641). This means that you can access request headers, the client IP address, and so on. In order to use it, add a parameter to your function and set its type hint to be `gr.Request`. Here's a simple example:
+
+```py
+import gradio as gr
+
+def echo(name, request: gr.Request):
+ if request:
+ print("Request headers dictionary:", request.headers)
+ print("IP address:", request.client.host)
+ return name
+
+io = gr.Interface(echo, "textbox", "textbox").launch()
+```
+
+### Bug Fixes:
+
+- Fixed bug that limited files from being sent over websockets to 16MB. The new limit
+ is now 1GB by [@abidlabs](https://github.com/abidlabs) in [PR 2709](https://github.com/gradio-app/gradio/pull/2709)
+
+### Documentation Changes:
+
+- Updated documentation for embedding Gradio demos on Spaces as web components by
+ [@julien-c](https://github.com/julien-c) in [PR 2698](https://github.com/gradio-app/gradio/pull/2698)
+- Updated IFrames in Guides to use the host URL instead of the Space name to be consistent with the new method for embedding Spaces, by
+ [@julien-c](https://github.com/julien-c) in [PR 2692](https://github.com/gradio-app/gradio/pull/2692)
+- Colab buttons on every demo in the website! Just click open in colab, and run the demo there.
+
+https://user-images.githubusercontent.com/9021060/202878400-cb16ed47-f4dd-4cb0-b2f0-102a9ff64135.mov
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Better warnings and error messages for `gr.Interface.load()` by [@abidlabs](https://github.com/abidlabs) in [PR 2694](https://github.com/gradio-app/gradio/pull/2694)
+- Add open in colab buttons to demos in docs and /demos by [@aliabd](https://github.com/aliabd) in [PR 2608](https://github.com/gradio-app/gradio/pull/2608)
+- Apply different formatting for the types in component docstrings by [@aliabd](https://github.com/aliabd) in [PR 2707](https://github.com/gradio-app/gradio/pull/2707)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.10.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Passes kwargs into `gr.Interface.load()` by [@abidlabs](https://github.com/abidlabs) in [PR 2669](https://github.com/gradio-app/gradio/pull/2669)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Clean up printed statements in Embedded Colab Mode by [@aliabid94](https://github.com/aliabid94) in [PR 2612](https://github.com/gradio-app/gradio/pull/2612)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.10.0
+
+- Add support for `'password'` and `'email'` types to `Textbox`. [@pngwn](https://github.com/pngwn) in [PR 2653](https://github.com/gradio-app/gradio/pull/2653)
+- `gr.Textbox` component will now raise an exception if `type` is not "text", "email", or "password" [@pngwn](https://github.com/pngwn) in [PR 2653](https://github.com/gradio-app/gradio/pull/2653). This will cause demos using the deprecated `gr.Textbox(type="number")` to raise an exception.
+
+### Bug Fixes:
+
+- Updated the minimum FastApi used in tests to version 0.87 by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2647](https://github.com/gradio-app/gradio/pull/2647)
+- Fixed bug where interfaces with examples could not be loaded with `gr.Interface.load` by [@freddyaboulton](https://github.com/freddyaboulton) [PR 2640](https://github.com/gradio-app/gradio/pull/2640)
+- Fixed bug where the `interactive` property of a component could not be updated by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2639](https://github.com/gradio-app/gradio/pull/2639)
+- Fixed bug where some URLs were not being recognized as valid URLs and thus were not
+ loading correctly in various components by [@abidlabs](https://github.com/abidlabs) in [PR 2659](https://github.com/gradio-app/gradio/pull/2659)
+
+### Documentation Changes:
+
+- Fix some typos in the embedded demo names in "05_using_blocks_like_functions.md" by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2656](https://github.com/gradio-app/gradio/pull/2656)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Add support for `'password'` and `'email'` types to `Textbox`. [@pngwn](https://github.com/pngwn) in [PR 2653](https://github.com/gradio-app/gradio/pull/2653)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.9.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Only set a min height on md and html when loading by [@pngwn](https://github.com/pngwn) in [PR 2623](https://github.com/gradio-app/gradio/pull/2623)
+
+### Documentation Changes:
+
+- See docs for the latest gradio commit to main as well the latest pip release:
+
+
+
+- Modified the "Connecting To a Database Guide" to use `pd.read_sql` as opposed to low-level postgres connector by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2604](https://github.com/gradio-app/gradio/pull/2604)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Dropdown for seeing docs as latest or main by [@aliabd](https://github.com/aliabd) in [PR 2544](https://github.com/gradio-app/gradio/pull/2544)
+- Allow `gr.Templates` to accept parameters to override the defaults by [@abidlabs](https://github.com/abidlabs) in [PR 2600](https://github.com/gradio-app/gradio/pull/2600)
+- Components now throw a `ValueError()` if constructed with invalid parameters for `type` or `source` (for components that take those parameters) in [PR 2610](https://github.com/gradio-app/gradio/pull/2610)
+- Allow auth with using queue by [@GLGDLY](https://github.com/GLGDLY) in [PR 2611](https://github.com/gradio-app/gradio/pull/2611)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.9
+
+### New Features:
+
+- Gradio is now embedded directly in colab without requiring the share link by [@aliabid94](https://github.com/aliabid94) in [PR 2455](https://github.com/gradio-app/gradio/pull/2455)
+
+###### Calling functions by api_name in loaded apps
+
+When you load an upstream app with `gr.Blocks.load`, you can now specify which fn
+to call with the `api_name` parameter.
+
+```python
+import gradio as gr
+english_translator = gr.Blocks.load(name="spaces/gradio/english-translator")
+german = english_translator("My name is Freddy", api_name='translate-to-german')
+```
+
+The `api_name` parameter will take precedence over the `fn_index` parameter.
+
+### Bug Fixes:
+
+- Fixed bug where None could not be used for File,Model3D, and Audio examples by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2588](https://github.com/gradio-app/gradio/pull/2588)
+- Fixed links in Plotly map guide + demo by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2578](https://github.com/gradio-app/gradio/pull/2578)
+- `gr.Blocks.load()` now correctly loads example files from Spaces [@abidlabs](https://github.com/abidlabs) in [PR 2594](https://github.com/gradio-app/gradio/pull/2594)
+- Fixed bug when image clear started upload dialog [@mezotaken](https://github.com/mezotaken) in [PR 2577](https://github.com/gradio-app/gradio/pull/2577)
+
+### Documentation Changes:
+
+- Added a Guide on how to configure the queue for maximum performance by [@abidlabs](https://github.com/abidlabs) in [PR 2558](https://github.com/gradio-app/gradio/pull/2558)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Add `api_name` to `Blocks.__call__` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2593](https://github.com/gradio-app/gradio/pull/2593)
+- Update queue with using deque & update requirements by [@GLGDLY](https://github.com/GLGDLY) in [PR 2428](https://github.com/gradio-app/gradio/pull/2428)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.8.2
+
+### Bug Fixes:
+
+- Ensure gradio apps embedded via spaces use the correct endpoint for predictions. [@pngwn](https://github.com/pngwn) in [PR 2567](https://github.com/gradio-app/gradio/pull/2567)
+- Ensure gradio apps embedded via spaces use the correct websocket protocol. [@pngwn](https://github.com/pngwn) in [PR 2571](https://github.com/gradio-app/gradio/pull/2571)
+
+### New Features:
+
+###### Running Events Continuously
+
+Gradio now supports the ability to run an event continuously on a fixed schedule. To use this feature,
+pass `every=# of seconds` to the event definition. This will run the event every given number of seconds!
+
+This can be used to:
+
+- Create live visualizations that show the most up to date data
+- Refresh the state of the frontend automatically in response to changes in the backend
+
+Here is an example of a live plot that refreshes every half second:
+
+```python
+import math
+import gradio as gr
+import plotly.express as px
+import numpy as np
+
+
+plot_end = 2 * math.pi
+
+
+def get_plot(period=1):
+ global plot_end
+ x = np.arange(plot_end - 2 * math.pi, plot_end, 0.02)
+ y = np.sin(2*math.pi*period * x)
+ fig = px.line(x=x, y=y)
+ plot_end += 2 * math.pi
+ return fig
+
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("Change the value of the slider to automatically update the plot")
+ period = gr.Slider(label="Period of plot", value=1, minimum=0, maximum=10, step=1)
+ plot = gr.Plot(label="Plot (updates every half second)")
+
+ dep = demo.load(get_plot, None, plot, every=0.5)
+ period.change(get_plot, period, plot, every=0.5, cancels=[dep])
+
+demo.queue().launch()
+```
+
+
+
+### Bug Fixes:
+
+No changes to highlight.
+
+### Documentation Changes:
+
+- Explained how to set up `queue` and `auth` when working with reload mode by by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3089](https://github.com/gradio-app/gradio/pull/3089)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Allows loading private Spaces by passing an an `api_key` to `gr.Interface.load()`
+ by [@abidlabs](https://github.com/abidlabs) in [PR 2568](https://github.com/gradio-app/gradio/pull/2568)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.8
+
+### New Features:
+
+- Allows event listeners to accept a single dictionary as its argument, where the keys are the components and the values are the component values. This is set by passing the input components in the event listener as a set instead of a list. [@aliabid94](https://github.com/aliabid94) in [PR 2550](https://github.com/gradio-app/gradio/pull/2550)
+
+### Bug Fixes:
+
+- Fix whitespace issue when using plotly. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2548](https://github.com/gradio-app/gradio/pull/2548)
+- Apply appropriate alt text to all gallery images. [@camenduru](https://github.com/camenduru) in [PR 2358](https://github.com/gradio-app/gradio/pull/2538)
+- Removed erroneous tkinter import in gradio.blocks by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2555](https://github.com/gradio-app/gradio/pull/2555)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Added the `every` keyword to event listeners that runs events on a fixed schedule by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2512](https://github.com/gradio-app/gradio/pull/2512)
+- Fix whitespace issue when using plotly. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2548](https://github.com/gradio-app/gradio/pull/2548)
+- Apply appropriate alt text to all gallery images. [@camenduru](https://github.com/camenduru) in [PR 2358](https://github.com/gradio-app/gradio/pull/2538)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.7
+
+### New Features:
+
+###### Batched Functions
+
+Gradio now supports the ability to pass _batched_ functions. Batched functions are just
+functions which take in a list of inputs and return a list of predictions.
+
+For example, here is a batched function that takes in two lists of inputs (a list of
+words and a list of ints), and returns a list of trimmed words as output:
+
+```py
+import time
+
+def trim_words(words, lens):
+ trimmed_words = []
+ time.sleep(5)
+ for w, l in zip(words, lens):
+ trimmed_words.append(w[:l])
+ return [trimmed_words]
+```
+
+The advantage of using batched functions is that if you enable queuing, the Gradio
+server can automatically _batch_ incoming requests and process them in parallel,
+potentially speeding up your demo. Here's what the Gradio code looks like (notice
+the `batch=True` and `max_batch_size=16` -- both of these parameters can be passed
+into event triggers or into the `Interface` class)
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ word = gr.Textbox(label="word", value="abc")
+ leng = gr.Number(label="leng", precision=0, value=1)
+ output = gr.Textbox(label="Output")
+ with gr.Row():
+ run = gr.Button()
+
+ event = run.click(trim_words, [word, leng], output, batch=True, max_batch_size=16)
+
+demo.queue()
+demo.launch()
+```
+
+In the example above, 16 requests could be processed in parallel (for a total inference
+time of 5 seconds), instead of each request being processed separately (for a total
+inference time of 80 seconds).
+
+###### Upload Event
+
+`Video`, `Audio`, `Image`, and `File` components now support a `upload()` event that is triggered when a user uploads a file into any of these components.
+
+Example usage:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ input_video = gr.Video()
+ output_video = gr.Video()
+
+ # Clears the output video when an input video is uploaded
+ input_video.upload(lambda : None, None, output_video)
+```
+
+### Bug Fixes:
+
+- Fixes issue where plotly animations, interactivity, titles, legends, were not working properly. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2486](https://github.com/gradio-app/gradio/pull/2486)
+- Prevent requests to the `/api` endpoint from skipping the queue if the queue is enabled for that event by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2493](https://github.com/gradio-app/gradio/pull/2493)
+- Fixes a bug with `cancels` in event triggers so that it works properly if multiple
+ Blocks are rendered by [@abidlabs](https://github.com/abidlabs) in [PR 2530](https://github.com/gradio-app/gradio/pull/2530)
+- Prevent invalid targets of events from crashing the whole application. [@pngwn](https://github.com/pngwn) in [PR 2534](https://github.com/gradio-app/gradio/pull/2534)
+- Properly dequeue cancelled events when multiple apps are rendered by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2540](https://github.com/gradio-app/gradio/pull/2540)
+- Fixes videos being cropped due to height/width params not being used [@hannahblair](https://github.com/hannahblair) in [PR 4946](https://github.com/gradio-app/gradio/pull/4946)
+
+### Documentation Changes:
+
+- Added an example interactive dashboard to the "Tabular & Plots" section of the Demos page by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2508](https://github.com/gradio-app/gradio/pull/2508)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fixes the error message if a user builds Gradio locally and tries to use `share=True` by [@abidlabs](https://github.com/abidlabs) in [PR 2502](https://github.com/gradio-app/gradio/pull/2502)
+- Allows the render() function to return self by [@Raul9595](https://github.com/Raul9595) in [PR 2514](https://github.com/gradio-app/gradio/pull/2514)
+- Fixes issue where plotly animations, interactivity, titles, legends, were not working properly. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2486](https://github.com/gradio-app/gradio/pull/2486)
+- Gradio now supports batched functions by [@abidlabs](https://github.com/abidlabs) in [PR 2218](https://github.com/gradio-app/gradio/pull/2218)
+- Add `upload` event for `Video`, `Audio`, `Image`, and `File` components [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2448](https://github.com/gradio-app/gradio/pull/2456)
+- Changes websocket path for Spaces as it is no longer necessary to have a different URL for websocket connections on Spaces by [@abidlabs](https://github.com/abidlabs) in [PR 2528](https://github.com/gradio-app/gradio/pull/2528)
+- Clearer error message when events are defined outside of a Blocks scope, and a warning if you
+ try to use `Series` or `Parallel` with `Blocks` by [@abidlabs](https://github.com/abidlabs) in [PR 2543](https://github.com/gradio-app/gradio/pull/2543)
+- Adds support for audio samples that are in `float64`, `float16`, or `uint16` formats by [@abidlabs](https://github.com/abidlabs) in [PR 2545](https://github.com/gradio-app/gradio/pull/2545)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.6
+
+### New Features:
+
+###### Cancelling Running Events
+
+Running events can be cancelled when other events are triggered! To test this feature, pass the `cancels` parameter to the event listener.
+For this feature to work, the queue must be enabled.
+
+
+
+Code:
+
+```python
+import time
+import gradio as gr
+
+def fake_diffusion(steps):
+ for i in range(steps):
+ time.sleep(1)
+ yield str(i)
+
+def long_prediction(*args, **kwargs):
+ time.sleep(10)
+ return 42
+
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column():
+ n = gr.Slider(1, 10, value=9, step=1, label="Number Steps")
+ run = gr.Button()
+ output = gr.Textbox(label="Iterative Output")
+ stop = gr.Button(value="Stop Iterating")
+ with gr.Column():
+ prediction = gr.Number(label="Expensive Calculation")
+ run_pred = gr.Button(value="Run Expensive Calculation")
+ with gr.Column():
+ cancel_on_change = gr.Textbox(label="Cancel Iteration and Expensive Calculation on Change")
+
+ click_event = run.click(fake_diffusion, n, output)
+ stop.click(fn=None, inputs=None, outputs=None, cancels=[click_event])
+ pred_event = run_pred.click(fn=long_prediction, inputs=None, outputs=prediction)
+
+ cancel_on_change.change(None, None, None, cancels=[click_event, pred_event])
+
+
+demo.queue(concurrency_count=1, max_size=20).launch()
+```
+
+For interfaces, a stop button will be added automatically if the function uses a `yield` statement.
+
+```python
+import gradio as gr
+import time
+
+def iteration(steps):
+ for i in range(steps):
+ time.sleep(0.5)
+ yield i
+
+gr.Interface(iteration,
+ inputs=gr.Slider(minimum=1, maximum=10, step=1, value=5),
+ outputs=gr.Number()).queue().launch()
+```
+
+
+
+### Bug Fixes:
+
+- Add loading status tracker UI to HTML and Markdown components. [@pngwn](https://github.com/pngwn) in [PR 2474](https://github.com/gradio-app/gradio/pull/2474)
+- Fixed videos being mirrored in the front-end if source is not webcam by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2475](https://github.com/gradio-app/gradio/pull/2475)
+- Add clear button for timeseries component [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2487](https://github.com/gradio-app/gradio/pull/2487)
+- Removes special characters from temporary filenames so that the files can be served by components [@abidlabs](https://github.com/abidlabs) in [PR 2480](https://github.com/gradio-app/gradio/pull/2480)
+- Fixed infinite reload loop when mounting gradio as a sub application by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2477](https://github.com/gradio-app/gradio/pull/2477)
+
+### Documentation Changes:
+
+- Adds a demo to show how a sound alert can be played upon completion of a prediction by [@abidlabs](https://github.com/abidlabs) in [PR 2478](https://github.com/gradio-app/gradio/pull/2478)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Enable running events to be cancelled from other events by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2433](https://github.com/gradio-app/gradio/pull/2433)
+- Small fix for version check before reuploading demos by [@aliabd](https://github.com/aliabd) in [PR 2469](https://github.com/gradio-app/gradio/pull/2469)
+- Add loading status tracker UI to HTML and Markdown components. [@pngwn](https://github.com/pngwn) in [PR 2400](https://github.com/gradio-app/gradio/pull/2474)
+- Add clear button for timeseries component [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2487](https://github.com/gradio-app/gradio/pull/2487)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.5
+
+### Bug Fixes:
+
+- Ensure that Gradio does not take control of the HTML page title when embedding a gradio app as a web component, this behaviour flipped by adding `control_page_title="true"` to the webcomponent. [@pngwn](https://github.com/pngwn) in [PR 2400](https://github.com/gradio-app/gradio/pull/2400)
+- Decreased latency in iterative-output demos by making the iteration asynchronous [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2409](https://github.com/gradio-app/gradio/pull/2409)
+- Fixed queue getting stuck under very high load by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2374](https://github.com/gradio-app/gradio/pull/2374)
+- Ensure that components always behave as if `interactive=True` were set when the following conditions are true:
+
+ - no default value is provided,
+ - they are not set as the input or output of an event,
+ - `interactive` kwarg is not set.
+
+ [@pngwn](https://github.com/pngwn) in [PR 2459](https://github.com/gradio-app/gradio/pull/2459)
+
+### New Features:
+
+- When an `Image` component is set to `source="upload"`, it is now possible to drag and drop and image to replace a previously uploaded image by [@pngwn](https://github.com/pngwn) in [PR 1711](https://github.com/gradio-app/gradio/issues/1711)
+- The `gr.Dataset` component now accepts `HTML` and `Markdown` components by [@abidlabs](https://github.com/abidlabs) in [PR 2437](https://github.com/gradio-app/gradio/pull/2437)
+
+### Documentation Changes:
+
+- Improved documentation for the `gr.Dataset` component by [@abidlabs](https://github.com/abidlabs) in [PR 2437](https://github.com/gradio-app/gradio/pull/2437)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+- The `Carousel` component is officially deprecated. Since gradio 3.0, code containing the `Carousel` component would throw warnings. As of the next release, the `Carousel` component will raise an exception.
+
+### Full Changelog:
+
+- Speeds up Gallery component by using temporary files instead of base64 representation in the front-end by [@proxyphi](https://github.com/proxyphi), [@pngwn](https://github.com/pngwn), and [@abidlabs](https://github.com/abidlabs) in [PR 2265](https://github.com/gradio-app/gradio/pull/2265)
+- Fixed some embedded demos in the guides by not loading the gradio web component in some guides by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2403](https://github.com/gradio-app/gradio/pull/2403)
+- When an `Image` component is set to `source="upload"`, it is now possible to drag and drop and image to replace a previously uploaded image by [@pngwn](https://github.com/pngwn) in [PR 2400](https://github.com/gradio-app/gradio/pull/2410)
+- Improve documentation of the `Blocks.load()` event by [@abidlabs](https://github.com/abidlabs) in [PR 2413](https://github.com/gradio-app/gradio/pull/2413)
+- Decreased latency in iterative-output demos by making the iteration asynchronous [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2409](https://github.com/gradio-app/gradio/pull/2409)
+- Updated share link message to reference new Spaces Hardware [@abidlabs](https://github.com/abidlabs) in [PR 2423](https://github.com/gradio-app/gradio/pull/2423)
+- Automatically restart spaces if they're down by [@aliabd](https://github.com/aliabd) in [PR 2405](https://github.com/gradio-app/gradio/pull/2405)
+- Carousel component is now deprecated by [@abidlabs](https://github.com/abidlabs) in [PR 2434](https://github.com/gradio-app/gradio/pull/2434)
+- Build Gradio from source in ui tests by by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2440](https://github.com/gradio-app/gradio/pull/2440)
+- Change "return ValueError" to "raise ValueError" by [@vzakharov](https://github.com/vzakharov) in [PR 2445](https://github.com/gradio-app/gradio/pull/2445)
+- Add guide on creating a map demo using the `gr.Plot()` component [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2402](https://github.com/gradio-app/gradio/pull/2402)
+- Add blur event for `Textbox` and `Number` components [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2448](https://github.com/gradio-app/gradio/pull/2448)
+- Stops a gradio launch from hogging a port even after it's been killed [@aliabid94](https://github.com/aliabid94) in [PR 2453](https://github.com/gradio-app/gradio/pull/2453)
+- Fix embedded interfaces on touch screen devices by [@aliabd](https://github.com/aliabd) in [PR 2457](https://github.com/gradio-app/gradio/pull/2457)
+- Upload all demos to spaces by [@aliabd](https://github.com/aliabd) in [PR 2281](https://github.com/gradio-app/gradio/pull/2281)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.4.1
+
+### New Features:
+
+###### 1. See Past and Upcoming Changes in the Release History 👀
+
+You can now see gradio's release history directly on the website, and also keep track of upcoming changes. Just go [here](https://gradio.app/changelog/).
+
+
+
+### Bug Fixes:
+
+1. Fix typo in guide image path by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2357](https://github.com/gradio-app/gradio/pull/2357)
+2. Raise error if Blocks has duplicate component with same IDs by [@abidlabs](https://github.com/abidlabs) in [PR 2359](https://github.com/gradio-app/gradio/pull/2359)
+3. Catch the permission exception on the audio component by [@Ian-GL](https://github.com/Ian-GL) in [PR 2330](https://github.com/gradio-app/gradio/pull/2330)
+4. Fix image_classifier_interface_load demo by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2365](https://github.com/gradio-app/gradio/pull/2365)
+5. Fix combining adjacent components without gaps by introducing `gr.Row(variant="compact")` by [@aliabid94](https://github.com/aliabid94) in [PR 2291](https://github.com/gradio-app/gradio/pull/2291) This comes with deprecation of the following arguments for `Component.style`: `round`, `margin`, `border`.
+6. Fix audio streaming, which was previously choppy in [PR 2351](https://github.com/gradio-app/gradio/pull/2351). Big thanks to [@yannickfunk](https://github.com/yannickfunk) for the proposed solution.
+7. Fix bug where new typeable slider doesn't respect the minimum and maximum values [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2380](https://github.com/gradio-app/gradio/pull/2380)
+
+### Documentation Changes:
+
+1. New Guide: Connecting to a Database 🗄️
+
+ A new guide by [@freddyaboulton](https://github.com/freddyaboulton) that explains how you can use Gradio to connect your app to a database. Read more [here](https://gradio.app/connecting_to_a_database/).
+
+2. New Guide: Running Background Tasks 🥷
+
+ A new guide by [@freddyaboulton](https://github.com/freddyaboulton) that explains how you can run background tasks from your gradio app. Read more [here](https://gradio.app/running_background_tasks/).
+
+3. Small fixes to docs for `Image` component by [@abidlabs](https://github.com/abidlabs) in [PR 2372](https://github.com/gradio-app/gradio/pull/2372)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Create a guide on how to connect an app to a database hosted on the cloud by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2341](https://github.com/gradio-app/gradio/pull/2341)
+- Removes `analytics` dependency by [@abidlabs](https://github.com/abidlabs) in [PR 2347](https://github.com/gradio-app/gradio/pull/2347)
+- Add guide on launching background tasks from your app by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2350](https://github.com/gradio-app/gradio/pull/2350)
+- Fix typo in guide image path by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2357](https://github.com/gradio-app/gradio/pull/2357)
+- Raise error if Blocks has duplicate component with same IDs by [@abidlabs](https://github.com/abidlabs) in [PR 2359](https://github.com/gradio-app/gradio/pull/2359)
+- Hotfix: fix version back to 3.4 by [@abidlabs](https://github.com/abidlabs) in [PR 2361](https://github.com/gradio-app/gradio/pull/2361)
+- Change version.txt to 3.4 instead of 3.4.0 by [@aliabd](https://github.com/aliabd) in [PR 2363](https://github.com/gradio-app/gradio/pull/2363)
+- Catch the permission exception on the audio component by [@Ian-GL](https://github.com/Ian-GL) in [PR 2330](https://github.com/gradio-app/gradio/pull/2330)
+- Fix image_classifier_interface_load demo by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2365](https://github.com/gradio-app/gradio/pull/2365)
+- Small fixes to docs for `Image` component by [@abidlabs](https://github.com/abidlabs) in [PR 2372](https://github.com/gradio-app/gradio/pull/2372)
+- Automated Release Notes by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2306](https://github.com/gradio-app/gradio/pull/2306)
+- Fixed small typos in the docs [@julien-c](https://github.com/julien-c) in [PR 2373](https://github.com/gradio-app/gradio/pull/2373)
+- Adds ability to disable pre/post-processing for examples [@abidlabs](https://github.com/abidlabs) in [PR 2383](https://github.com/gradio-app/gradio/pull/2383)
+- Copy changelog file in website docker by [@aliabd](https://github.com/aliabd) in [PR 2384](https://github.com/gradio-app/gradio/pull/2384)
+- Lets users provide a `gr.update()` dictionary even if post-processing is disabled [@abidlabs](https://github.com/abidlabs) in [PR 2385](https://github.com/gradio-app/gradio/pull/2385)
+- Fix bug where errors would cause apps run in reload mode to hang forever by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2394](https://github.com/gradio-app/gradio/pull/2394)
+- Fix bug where new typeable slider doesn't respect the minimum and maximum values [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2380](https://github.com/gradio-app/gradio/pull/2380)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.4
+
+### New Features:
+
+###### 1. Gallery Captions 🖼️
+
+You can now pass captions to images in the Gallery component. To do so you need to pass a {List} of (image, {str} caption) tuples. This is optional and the component also accepts just a list of the images.
+
+Here's an example:
+
+```python
+import gradio as gr
+
+images_with_captions = [
+ ("https://images.unsplash.com/photo-1551969014-7d2c4cddf0b6", "Cheetah by David Groves"),
+ ("https://images.unsplash.com/photo-1546182990-dffeafbe841d", "Lion by Francesco"),
+ ("https://images.unsplash.com/photo-1561731216-c3a4d99437d5", "Tiger by Mike Marrah")
+ ]
+
+with gr.Blocks() as demo:
+ gr.Gallery(value=images_with_captions)
+
+demo.launch()
+```
+
+
+
+###### 2. Type Values into the Slider 🔢
+
+You can now type values directly on the Slider component! Here's what it looks like:
+
+
+
+###### 3. Better Sketching and Inpainting 🎨
+
+We've made a lot of changes to our Image component so that it can support better sketching and inpainting.
+
+Now supports:
+
+- A standalone black-and-white sketch
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x: x, gr.Sketchpad(), gr.Image())
+demo.launch()
+```
+
+
+
+- A standalone color sketch
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x: x, gr.Paint(), gr.Image())
+demo.launch()
+```
+
+
+
+- An uploadable image with black-and-white or color sketching
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x: x, gr.Image(source='upload', tool='color-sketch'), gr.Image()) # for black and white, tool = 'sketch'
+demo.launch()
+```
+
+
+
+- Webcam with black-and-white or color sketching
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x: x, gr.Image(source='webcam', tool='color-sketch'), gr.Image()) # for black and white, tool = 'sketch'
+demo.launch()
+```
+
+
+
+As well as other fixes
+
+### Bug Fixes:
+
+1. Fix bug where max concurrency count is not respected in queue by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2286](https://github.com/gradio-app/gradio/pull/2286)
+2. fix : queue could be blocked by [@SkyTNT](https://github.com/SkyTNT) in [PR 2288](https://github.com/gradio-app/gradio/pull/2288)
+3. Supports `gr.update()` in example caching by [@abidlabs](https://github.com/abidlabs) in [PR 2309](https://github.com/gradio-app/gradio/pull/2309)
+4. Clipboard fix for iframes by [@abidlabs](https://github.com/abidlabs) in [PR 2321](https://github.com/gradio-app/gradio/pull/2321)
+5. Fix: Dataframe column headers are reset when you add a new column by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2318](https://github.com/gradio-app/gradio/pull/2318)
+6. Added support for URLs for Video, Audio, and Image by [@abidlabs](https://github.com/abidlabs) in [PR 2256](https://github.com/gradio-app/gradio/pull/2256)
+7. Add documentation about how to create and use the Gradio FastAPI app by [@abidlabs](https://github.com/abidlabs) in [PR 2263](https://github.com/gradio-app/gradio/pull/2263)
+
+### Documentation Changes:
+
+1. Adding a Playground Tab to the Website by [@aliabd](https://github.com/aliabd) in [PR 1860](https://github.com/gradio-app/gradio/pull/1860)
+2. Gradio for Tabular Data Science Workflows Guide by [@merveenoyan](https://github.com/merveenoyan) in [PR 2199](https://github.com/gradio-app/gradio/pull/2199)
+3. Promotes `postprocess` and `preprocess` to documented parameters by [@abidlabs](https://github.com/abidlabs) in [PR 2293](https://github.com/gradio-app/gradio/pull/2293)
+4. Update 2)key_features.md by [@voidxd](https://github.com/voidxd) in [PR 2326](https://github.com/gradio-app/gradio/pull/2326)
+5. Add docs to blocks context postprocessing function by [@Ian-GL](https://github.com/Ian-GL) in [PR 2332](https://github.com/gradio-app/gradio/pull/2332)
+
+### Testing and Infrastructure Changes
+
+1. Website fixes and refactoring by [@aliabd](https://github.com/aliabd) in [PR 2280](https://github.com/gradio-app/gradio/pull/2280)
+2. Don't deploy to spaces on release by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2313](https://github.com/gradio-app/gradio/pull/2313)
+
+### Full Changelog:
+
+- Website fixes and refactoring by [@aliabd](https://github.com/aliabd) in [PR 2280](https://github.com/gradio-app/gradio/pull/2280)
+- Fix bug where max concurrency count is not respected in queue by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2286](https://github.com/gradio-app/gradio/pull/2286)
+- Promotes `postprocess` and `preprocess` to documented parameters by [@abidlabs](https://github.com/abidlabs) in [PR 2293](https://github.com/gradio-app/gradio/pull/2293)
+- Raise warning when trying to cache examples but not all inputs have examples by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2279](https://github.com/gradio-app/gradio/pull/2279)
+- fix : queue could be blocked by [@SkyTNT](https://github.com/SkyTNT) in [PR 2288](https://github.com/gradio-app/gradio/pull/2288)
+- Don't deploy to spaces on release by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2313](https://github.com/gradio-app/gradio/pull/2313)
+- Supports `gr.update()` in example caching by [@abidlabs](https://github.com/abidlabs) in [PR 2309](https://github.com/gradio-app/gradio/pull/2309)
+- Respect Upstream Queue when loading interfaces/blocks from Spaces by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2294](https://github.com/gradio-app/gradio/pull/2294)
+- Clipboard fix for iframes by [@abidlabs](https://github.com/abidlabs) in [PR 2321](https://github.com/gradio-app/gradio/pull/2321)
+- Sketching + Inpainting Capabilities to Gradio by [@abidlabs](https://github.com/abidlabs) in [PR 2144](https://github.com/gradio-app/gradio/pull/2144)
+- Update 2)key_features.md by [@voidxd](https://github.com/voidxd) in [PR 2326](https://github.com/gradio-app/gradio/pull/2326)
+- release 3.4b3 by [@abidlabs](https://github.com/abidlabs) in [PR 2328](https://github.com/gradio-app/gradio/pull/2328)
+- Fix: Dataframe column headers are reset when you add a new column by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2318](https://github.com/gradio-app/gradio/pull/2318)
+- Start queue when gradio is a sub application by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2319](https://github.com/gradio-app/gradio/pull/2319)
+- Fix Web Tracker Script by [@aliabd](https://github.com/aliabd) in [PR 2308](https://github.com/gradio-app/gradio/pull/2308)
+- Add docs to blocks context postprocessing function by [@Ian-GL](https://github.com/Ian-GL) in [PR 2332](https://github.com/gradio-app/gradio/pull/2332)
+- Fix typo in iterator variable name in run_predict function by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2340](https://github.com/gradio-app/gradio/pull/2340)
+- Add captions to galleries by [@aliabid94](https://github.com/aliabid94) in [PR 2284](https://github.com/gradio-app/gradio/pull/2284)
+- Typeable value on gradio.Slider by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2329](https://github.com/gradio-app/gradio/pull/2329)
+
+### Contributors Shoutout:
+
+- [@SkyTNT](https://github.com/SkyTNT) made their first contribution in [PR 2288](https://github.com/gradio-app/gradio/pull/2288)
+- [@voidxd](https://github.com/voidxd) made their first contribution in [PR 2326](https://github.com/gradio-app/gradio/pull/2326)
+
+## 3.3
+
+### New Features:
+
+###### 1. Iterative Outputs ⏳
+
+You can now create an iterative output simply by having your function return a generator!
+
+Here's (part of) an example that was used to generate the interface below it. [See full code](https://colab.research.google.com/drive/1m9bWS6B82CT7bw-m4L6AJR8za7fEK7Ov?usp=sharing).
+
+```python
+def predict(steps, seed):
+ generator = torch.manual_seed(seed)
+ for i in range(1,steps):
+ yield pipeline(generator=generator, num_inference_steps=i)["sample"][0]
+```
+
+
+
+###### 2. Accordion Layout 🆕
+
+This version of Gradio introduces a new layout component to Blocks: the Accordion. Wrap your elements in a neat, expandable layout that allows users to toggle them as needed.
+
+Usage: ([Read the docs](https://gradio.app/docs/#accordion))
+
+```python
+with gr.Accordion("open up"):
+# components here
+```
+
+
+
+###### 3. Skops Integration 📈
+
+Our new integration with [skops](https://huggingface.co/blog/skops) allows you to load tabular classification and regression models directly from the [hub](https://huggingface.co/models).
+
+Here's a classification example showing how quick it is to set up an interface for a [model](https://huggingface.co/scikit-learn/tabular-playground).
+
+```python
+import gradio as gr
+gr.Interface.load("models/scikit-learn/tabular-playground").launch()
+```
+
+
+
+### Bug Fixes:
+
+No changes to highlight.
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- safari fixes by [@pngwn](https://github.com/pngwn) in [PR 2138](https://github.com/gradio-app/gradio/pull/2138)
+- Fix roundedness and form borders by [@aliabid94](https://github.com/aliabid94) in [PR 2147](https://github.com/gradio-app/gradio/pull/2147)
+- Better processing of example data prior to creating dataset component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2147](https://github.com/gradio-app/gradio/pull/2147)
+- Show error on Connection drops by [@aliabid94](https://github.com/aliabid94) in [PR 2147](https://github.com/gradio-app/gradio/pull/2147)
+- 3.2 release! by [@abidlabs](https://github.com/abidlabs) in [PR 2139](https://github.com/gradio-app/gradio/pull/2139)
+- Fixed Named API Requests by [@abidlabs](https://github.com/abidlabs) in [PR 2151](https://github.com/gradio-app/gradio/pull/2151)
+- Quick Fix: Cannot upload Model3D image after clearing it by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2168](https://github.com/gradio-app/gradio/pull/2168)
+- Fixed misleading log when server_name is '0.0.0.0' by [@lamhoangtung](https://github.com/lamhoangtung) in [PR 2176](https://github.com/gradio-app/gradio/pull/2176)
+- Keep embedded PngInfo metadata by [@cobryan05](https://github.com/cobryan05) in [PR 2170](https://github.com/gradio-app/gradio/pull/2170)
+- Skops integration: Load tabular classification and regression models from the hub by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2126](https://github.com/gradio-app/gradio/pull/2126)
+- Respect original filename when cached example files are downloaded by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2145](https://github.com/gradio-app/gradio/pull/2145)
+- Add manual trigger to deploy to pypi by [@abidlabs](https://github.com/abidlabs) in [PR 2192](https://github.com/gradio-app/gradio/pull/2192)
+- Fix bugs with gr.update by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2157](https://github.com/gradio-app/gradio/pull/2157)
+- Make queue per app by [@aliabid94](https://github.com/aliabid94) in [PR 2193](https://github.com/gradio-app/gradio/pull/2193)
+- Preserve Labels In Interpretation Components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2166](https://github.com/gradio-app/gradio/pull/2166)
+- Quick Fix: Multiple file download not working by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2169](https://github.com/gradio-app/gradio/pull/2169)
+- use correct MIME type for js-script file by [@daspartho](https://github.com/daspartho) in [PR 2200](https://github.com/gradio-app/gradio/pull/2200)
+- Add accordion component by [@aliabid94](https://github.com/aliabid94) in [PR 2208](https://github.com/gradio-app/gradio/pull/2208)
+
+### Contributors Shoutout:
+
+- [@lamhoangtung](https://github.com/lamhoangtung) made their first contribution in [PR 2176](https://github.com/gradio-app/gradio/pull/2176)
+- [@cobryan05](https://github.com/cobryan05) made their first contribution in [PR 2170](https://github.com/gradio-app/gradio/pull/2170)
+- [@daspartho](https://github.com/daspartho) made their first contribution in [PR 2200](https://github.com/gradio-app/gradio/pull/2200)
+
+## 3.2
+
+### New Features:
+
+###### 1. Improvements to Queuing 🥇
+
+We've implemented a brand new queuing system based on **web sockets** instead of HTTP long polling. Among other things, this allows us to manage queue sizes better on Hugging Face Spaces. There are also additional queue-related parameters you can add:
+
+- Now supports concurrent workers (parallelization)
+
+```python
+demo = gr.Interface(...)
+demo.queue(concurrency_count=3)
+demo.launch()
+```
+
+- Configure a maximum queue size
+
+```python
+demo = gr.Interface(...)
+demo.queue(max_size=100)
+demo.launch()
+```
+
+- If a user closes their tab / browser, they leave the queue, which means the demo will run faster for everyone else
+
+###### 2. Fixes to Examples
+
+- Dataframe examples will render properly, and look much clearer in the UI: (thanks to PR #2125)
+
+
+
+- Image and Video thumbnails are cropped to look neater and more uniform: (thanks to PR #2109)
+
+
+
+- Other fixes in PR #2131 and #2064 make it easier to design and use Examples
+
+###### 3. Component Fixes 🧱
+
+- Specify the width and height of an image in its style tag (thanks to PR #2133)
+
+```python
+components.Image().style(height=260, width=300)
+```
+
+- Automatic conversion of videos so they are playable in the browser (thanks to PR #2003). Gradio will check if a video's format is playable in the browser and, if it isn't, will automatically convert it to a format that is (mp4).
+- Pass in a json filepath to the Label component (thanks to PR #2083)
+- Randomize the default value of a Slider (thanks to PR #1935)
+
+
+
+- Improvements to State in PR #2100
+
+###### 4. Ability to Randomize Input Sliders and Reload Data whenever the Page Loads
+
+- In some cases, you want to be able to show a different set of input data to every user as they load the page app. For example, you might want to randomize the value of a "seed" `Slider` input. Or you might want to show a `Textbox` with the current date. We now supporting passing _functions_ as the default value in input components. When you pass in a function, it gets **re-evaluated** every time someone loads the demo, allowing you to reload / change data for different users.
+
+Here's an example loading the current date time into an input Textbox:
+
+```python
+import gradio as gr
+import datetime
+
+with gr.Blocks() as demo:
+ gr.Textbox(datetime.datetime.now)
+
+demo.launch()
+```
+
+Note that we don't evaluate the function -- `datetime.datetime.now()` -- we pass in the function itself to get this behavior -- `datetime.datetime.now`
+
+Because randomizing the initial value of `Slider` is a common use case, we've added a `randomize` keyword argument you can use to randomize its initial value:
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x:x, gr.Slider(0, 10, randomize=True), "number")
+demo.launch()
+```
+
+###### 5. New Guide 🖊️
+
+- [Gradio and W&B Integration](https://gradio.app/Gradio_and_Wandb_Integration/)
+
+### Full Changelog:
+
+- Reset components to original state by setting value to None by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2044](https://github.com/gradio-app/gradio/pull/2044)
+- Cleaning up the way data is processed for components by [@abidlabs](https://github.com/abidlabs) in [PR 1967](https://github.com/gradio-app/gradio/pull/1967)
+- version 3.1.8b by [@abidlabs](https://github.com/abidlabs) in [PR 2063](https://github.com/gradio-app/gradio/pull/2063)
+- Wandb guide by [@AK391](https://github.com/AK391) in [PR 1898](https://github.com/gradio-app/gradio/pull/1898)
+- Add a flagging callback to save json files to a hugging face dataset by [@chrisemezue](https://github.com/chrisemezue) in [PR 1821](https://github.com/gradio-app/gradio/pull/1821)
+- Add data science demos to landing page by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2067](https://github.com/gradio-app/gradio/pull/2067)
+- Hide time series + xgboost demos by default by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2079](https://github.com/gradio-app/gradio/pull/2079)
+- Encourage people to keep trying when queue full by [@apolinario](https://github.com/apolinario) in [PR 2076](https://github.com/gradio-app/gradio/pull/2076)
+- Updated our analytics on creation of Blocks/Interface by [@abidlabs](https://github.com/abidlabs) in [PR 2082](https://github.com/gradio-app/gradio/pull/2082)
+- `Label` component now accepts file paths to `.json` files by [@abidlabs](https://github.com/abidlabs) in [PR 2083](https://github.com/gradio-app/gradio/pull/2083)
+- Fix issues related to demos in Spaces by [@abidlabs](https://github.com/abidlabs) in [PR 2086](https://github.com/gradio-app/gradio/pull/2086)
+- Fix TimeSeries examples not properly displayed in UI by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2064](https://github.com/gradio-app/gradio/pull/2064)
+- Fix infinite requests when doing tab item select by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2070](https://github.com/gradio-app/gradio/pull/2070)
+- Accept deprecated `file` route as well by [@abidlabs](https://github.com/abidlabs) in [PR 2099](https://github.com/gradio-app/gradio/pull/2099)
+- Allow frontend method execution on Block.load event by [@codedealer](https://github.com/codedealer) in [PR 2108](https://github.com/gradio-app/gradio/pull/2108)
+- Improvements to `State` by [@abidlabs](https://github.com/abidlabs) in [PR 2100](https://github.com/gradio-app/gradio/pull/2100)
+- Catch IndexError, KeyError in video_is_playable by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2113](https://github.com/gradio-app/gradio/pull/2113)
+- Fix: Download button does not respect the filepath returned by the function by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2073](https://github.com/gradio-app/gradio/pull/2073)
+- Refactoring Layout: Adding column widths, forms, and more. by [@aliabid94](https://github.com/aliabid94) in [PR 2097](https://github.com/gradio-app/gradio/pull/2097)
+- Update CONTRIBUTING.md by [@abidlabs](https://github.com/abidlabs) in [PR 2118](https://github.com/gradio-app/gradio/pull/2118)
+- 2092 df ex by [@pngwn](https://github.com/pngwn) in [PR 2125](https://github.com/gradio-app/gradio/pull/2125)
+- feat(samples table/gallery): Crop thumbs to square by [@ronvoluted](https://github.com/ronvoluted) in [PR 2109](https://github.com/gradio-app/gradio/pull/2109)
+- Some enhancements to `gr.Examples` by [@abidlabs](https://github.com/abidlabs) in [PR 2131](https://github.com/gradio-app/gradio/pull/2131)
+- Image size fix by [@aliabid94](https://github.com/aliabid94) in [PR 2133](https://github.com/gradio-app/gradio/pull/2133)
+
+### Contributors Shoutout:
+
+- [@chrisemezue](https://github.com/chrisemezue) made their first contribution in [PR 1821](https://github.com/gradio-app/gradio/pull/1821)
+- [@apolinario](https://github.com/apolinario) made their first contribution in [PR 2076](https://github.com/gradio-app/gradio/pull/2076)
+- [@codedealer](https://github.com/codedealer) made their first contribution in [PR 2108](https://github.com/gradio-app/gradio/pull/2108)
+
+## 3.1
+
+### New Features:
+
+###### 1. Embedding Demos on Any Website 💻
+
+With PR #1444, Gradio is now distributed as a web component. This means demos can be natively embedded on websites. You'll just need to add two lines: one to load the gradio javascript, and one to link to the demos backend.
+
+Here's a simple example that embeds the demo from a Hugging Face space:
+
+```html
+
+
+
+Read more in the [Embedding Gradio Demos](https://gradio.app/embedding_gradio_demos) guide.
+
+###### 2. Reload Mode 👨💻
+
+Reload mode helps developers create gradio demos faster by automatically reloading the demo whenever the code changes. It can support development on Python IDEs (VS Code, PyCharm, etc), the terminal, as well as Jupyter notebooks.
+
+If your demo code is in a script named `app.py`, instead of running `python app.py` you can now run `gradio app.py` and that will launch the demo in reload mode:
+
+```bash
+Launching in reload mode on: http://127.0.0.1:7860 (Press CTRL+C to quit)
+Watching...
+WARNING: The --reload flag should not be used in production on Windows.
+```
+
+If you're working from a Jupyter or Colab Notebook, use these magic commands instead: `%load_ext gradio` when you import gradio, and `%%blocks` in the top of the cell with the demo code. Here's an example that shows how much faster the development becomes:
+
+
+
+###### 3. Inpainting Support on `gr.Image()` 🎨
+
+We updated the Image component to add support for inpainting demos. It works by adding `tool="sketch"` as a parameter, that passes both an image and a sketchable mask to your prediction function.
+
+Here's an example from the [LAMA space](https://huggingface.co/spaces/akhaliq/lama):
+
+
+
+###### 4. Markdown and HTML support in Dataframes 🔢
+
+We upgraded the Dataframe component in PR #1684 to support rendering Markdown and HTML inside the cells.
+
+This means you can build Dataframes that look like the following:
+
+
+
+###### 5. `gr.Examples()` for Blocks 🧱
+
+We've added the `gr.Examples` component helper to allow you to add examples to any Blocks demo. This class is a wrapper over the `gr.Dataset` component.
+
+
+
+gr.Examples takes two required parameters:
+
+- `examples` which takes in a nested list
+- `inputs` which takes in a component or list of components
+
+You can read more in the [Examples docs](https://gradio.app/docs/#examples) or the [Adding Examples to your Demos guide](https://gradio.app/adding_examples_to_your_app/).
+
+###### 6. Fixes to Audio Streaming
+
+With [PR 1828](https://github.com/gradio-app/gradio/pull/1828) we now hide the status loading animation, as well as remove the echo in streaming. Check out the [stream_audio](https://github.com/gradio-app/gradio/blob/main/demo/stream_audio/run.py) demo for more or read through our [Real Time Speech Recognition](https://gradio.app/real_time_speech_recognition/) guide.
+
+
+
+### Full Changelog:
+
+- File component: list multiple files and allow for download #1446 by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1681](https://github.com/gradio-app/gradio/pull/1681)
+- Add ColorPicker to docs by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1768](https://github.com/gradio-app/gradio/pull/1768)
+- Mock out requests in TestRequest unit tests by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1794](https://github.com/gradio-app/gradio/pull/1794)
+- Add requirements.txt and test_files to source dist by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1817](https://github.com/gradio-app/gradio/pull/1817)
+- refactor: f-string for tunneling.py by [@nhankiet](https://github.com/nhankiet) in [PR 1819](https://github.com/gradio-app/gradio/pull/1819)
+- Miscellaneous formatting improvements to website by [@aliabd](https://github.com/aliabd) in [PR 1754](https://github.com/gradio-app/gradio/pull/1754)
+- `integrate()` method moved to `Blocks` by [@abidlabs](https://github.com/abidlabs) in [PR 1776](https://github.com/gradio-app/gradio/pull/1776)
+- Add python-3.7 tests by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1818](https://github.com/gradio-app/gradio/pull/1818)
+- Copy test dir in website dockers by [@aliabd](https://github.com/aliabd) in [PR 1827](https://github.com/gradio-app/gradio/pull/1827)
+- Add info to docs on how to set default values for components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1788](https://github.com/gradio-app/gradio/pull/1788)
+- Embedding Components on Docs by [@aliabd](https://github.com/aliabd) in [PR 1726](https://github.com/gradio-app/gradio/pull/1726)
+- Remove usage of deprecated gr.inputs and gr.outputs from website by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1796](https://github.com/gradio-app/gradio/pull/1796)
+- Some cleanups to the docs page by [@abidlabs](https://github.com/abidlabs) in [PR 1822](https://github.com/gradio-app/gradio/pull/1822)
+
+### Contributors Shoutout:
+
+- [@nhankiet](https://github.com/nhankiet) made their first contribution in [PR 1819](https://github.com/gradio-app/gradio/pull/1819)
+
+## 3.0
+
+###### 🔥 Gradio 3.0 is the biggest update to the library, ever.
+
+### New Features:
+
+###### 1. Blocks 🧱
+
+Blocks is a new, low-level API that allows you to have full control over the data flows and layout of your application. It allows you to build very complex, multi-step applications. For example, you might want to:
+
+- Group together related demos as multiple tabs in one web app
+- Change the layout of your demo instead of just having all of the inputs on the left and outputs on the right
+- Have multi-step interfaces, in which the output of one model becomes the input to the next model, or have more flexible data flows in general
+- Change a component's properties (for example, the choices in a Dropdown) or its visibility based on user input
+
+Here's a simple example that creates the demo below it:
+
+```python
+import gradio as gr
+
+def update(name):
+ return f"Welcome to Gradio, {name}!"
+
+demo = gr.Blocks()
+
+with demo:
+ gr.Markdown(
+ """
+ # Hello World!
+ Start typing below to see the output.
+ """)
+ inp = gr.Textbox(placeholder="What is your name?")
+ out = gr.Textbox()
+
+ inp.change(fn=update,
+ inputs=inp,
+ outputs=out)
+
+demo.launch()
+```
+
+
+
+Read our [Introduction to Blocks](http://gradio.app/introduction_to_blocks/) guide for more, and join the 🎈 [Gradio Blocks Party](https://huggingface.co/spaces/Gradio-Blocks/README)!
+
+###### 2. Our Revamped Design 🎨
+
+We've upgraded our design across the entire library: from components, and layouts all the way to dark mode.
+
+
+
+###### 3. A New Website 💻
+
+We've upgraded [gradio.app](https://gradio.app) to make it cleaner, faster and easier to use. Our docs now come with components and demos embedded directly on the page. So you can quickly get up to speed with what you're looking for.
+
+
+
+###### 4. New Components: Model3D, Dataset, and More..
+
+We've introduced a lot of new components in `3.0`, including `Model3D`, `Dataset`, `Markdown`, `Button` and `Gallery`. You can find all the components and play around with them [here](https://gradio.app/docs/#components).
+
+
+
+### Full Changelog:
+
+- Gradio dash fe by [@pngwn](https://github.com/pngwn) in [PR 807](https://github.com/gradio-app/gradio/pull/807)
+- Blocks components by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 765](https://github.com/gradio-app/gradio/pull/765)
+- Blocks components V2 by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 843](https://github.com/gradio-app/gradio/pull/843)
+- Blocks-Backend-Events by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 844](https://github.com/gradio-app/gradio/pull/844)
+- Interfaces from Blocks by [@aliabid94](https://github.com/aliabid94) in [PR 849](https://github.com/gradio-app/gradio/pull/849)
+- Blocks dev by [@aliabid94](https://github.com/aliabid94) in [PR 853](https://github.com/gradio-app/gradio/pull/853)
+- Started updating demos to use the new `gradio.components` syntax by [@abidlabs](https://github.com/abidlabs) in [PR 848](https://github.com/gradio-app/gradio/pull/848)
+- add test infra + add browser tests to CI by [@pngwn](https://github.com/pngwn) in [PR 852](https://github.com/gradio-app/gradio/pull/852)
+- 854 textbox by [@pngwn](https://github.com/pngwn) in [PR 859](https://github.com/gradio-app/gradio/pull/859)
+- Getting old Python unit tests to pass on `blocks-dev` by [@abidlabs](https://github.com/abidlabs) in [PR 861](https://github.com/gradio-app/gradio/pull/861)
+- initialise chatbot with empty array of messages by [@pngwn](https://github.com/pngwn) in [PR 867](https://github.com/gradio-app/gradio/pull/867)
+- add test for output to input by [@pngwn](https://github.com/pngwn) in [PR 866](https://github.com/gradio-app/gradio/pull/866)
+- More Interface -> Blocks features by [@aliabid94](https://github.com/aliabid94) in [PR 864](https://github.com/gradio-app/gradio/pull/864)
+- Fixing external.py in blocks-dev to reflect the new HF Spaces paths by [@abidlabs](https://github.com/abidlabs) in [PR 879](https://github.com/gradio-app/gradio/pull/879)
+- backend_default_value_refactoring by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 871](https://github.com/gradio-app/gradio/pull/871)
+- fix default_value by [@pngwn](https://github.com/pngwn) in [PR 869](https://github.com/gradio-app/gradio/pull/869)
+- fix buttons by [@aliabid94](https://github.com/aliabid94) in [PR 883](https://github.com/gradio-app/gradio/pull/883)
+- Checking and updating more demos to use 3.0 syntax by [@abidlabs](https://github.com/abidlabs) in [PR 892](https://github.com/gradio-app/gradio/pull/892)
+- Blocks Tests by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 902](https://github.com/gradio-app/gradio/pull/902)
+- Interface fix by [@pngwn](https://github.com/pngwn) in [PR 901](https://github.com/gradio-app/gradio/pull/901)
+- Quick fix: Issue 893 by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 907](https://github.com/gradio-app/gradio/pull/907)
+- 3d Image Component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 775](https://github.com/gradio-app/gradio/pull/775)
+- fix endpoint url in prod by [@pngwn](https://github.com/pngwn) in [PR 911](https://github.com/gradio-app/gradio/pull/911)
+- rename Model3d to Image3D by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 912](https://github.com/gradio-app/gradio/pull/912)
+- update pypi to 2.9.1 by [@abidlabs](https://github.com/abidlabs) in [PR 916](https://github.com/gradio-app/gradio/pull/916)
+- blocks-with-fix by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 917](https://github.com/gradio-app/gradio/pull/917)
+- Restore Interpretation, Live, Auth, Queueing by [@aliabid94](https://github.com/aliabid94) in [PR 915](https://github.com/gradio-app/gradio/pull/915)
+- Allow `Blocks` instances to be used like a `Block` in other `Blocks` by [@abidlabs](https://github.com/abidlabs) in [PR 919](https://github.com/gradio-app/gradio/pull/919)
+- Redesign 1 by [@pngwn](https://github.com/pngwn) in [PR 918](https://github.com/gradio-app/gradio/pull/918)
+- blocks-components-tests by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 904](https://github.com/gradio-app/gradio/pull/904)
+- fix unit + browser tests by [@pngwn](https://github.com/pngwn) in [PR 926](https://github.com/gradio-app/gradio/pull/926)
+- blocks-move-test-data by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 927](https://github.com/gradio-app/gradio/pull/927)
+- remove debounce from form inputs by [@pngwn](https://github.com/pngwn) in [PR 932](https://github.com/gradio-app/gradio/pull/932)
+- reimplement webcam video by [@pngwn](https://github.com/pngwn) in [PR 928](https://github.com/gradio-app/gradio/pull/928)
+- blocks-move-test-data by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 941](https://github.com/gradio-app/gradio/pull/941)
+- allow audio components to take a string value by [@pngwn](https://github.com/pngwn) in [PR 930](https://github.com/gradio-app/gradio/pull/930)
+- static mode for textbox by [@pngwn](https://github.com/pngwn) in [PR 929](https://github.com/gradio-app/gradio/pull/929)
+- fix file upload text by [@pngwn](https://github.com/pngwn) in [PR 931](https://github.com/gradio-app/gradio/pull/931)
+- tabbed-interface-rewritten by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 958](https://github.com/gradio-app/gradio/pull/958)
+- Gan demo fix by [@abidlabs](https://github.com/abidlabs) in [PR 965](https://github.com/gradio-app/gradio/pull/965)
+- Blocks analytics by [@abidlabs](https://github.com/abidlabs) in [PR 947](https://github.com/gradio-app/gradio/pull/947)
+- Blocks page load by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 963](https://github.com/gradio-app/gradio/pull/963)
+- add frontend for page load events by [@pngwn](https://github.com/pngwn) in [PR 967](https://github.com/gradio-app/gradio/pull/967)
+- fix i18n and some tweaks by [@pngwn](https://github.com/pngwn) in [PR 966](https://github.com/gradio-app/gradio/pull/966)
+- add jinja2 to reqs by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 969](https://github.com/gradio-app/gradio/pull/969)
+- Cleaning up `Launchable()` by [@abidlabs](https://github.com/abidlabs) in [PR 968](https://github.com/gradio-app/gradio/pull/968)
+- Fix #944 by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 971](https://github.com/gradio-app/gradio/pull/971)
+- New Blocks Demo: neural instrument cloning by [@abidlabs](https://github.com/abidlabs) in [PR 975](https://github.com/gradio-app/gradio/pull/975)
+- Add huggingface_hub client library by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 973](https://github.com/gradio-app/gradio/pull/973)
+- State and variables by [@aliabid94](https://github.com/aliabid94) in [PR 977](https://github.com/gradio-app/gradio/pull/977)
+- update-components by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 986](https://github.com/gradio-app/gradio/pull/986)
+- ensure dataframe updates as expected by [@pngwn](https://github.com/pngwn) in [PR 981](https://github.com/gradio-app/gradio/pull/981)
+- test-guideline by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 990](https://github.com/gradio-app/gradio/pull/990)
+- Issue #785: add footer by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 972](https://github.com/gradio-app/gradio/pull/972)
+- indentation fix by [@abidlabs](https://github.com/abidlabs) in [PR 993](https://github.com/gradio-app/gradio/pull/993)
+- missing quote by [@aliabd](https://github.com/aliabd) in [PR 996](https://github.com/gradio-app/gradio/pull/996)
+- added interactive parameter to components by [@abidlabs](https://github.com/abidlabs) in [PR 992](https://github.com/gradio-app/gradio/pull/992)
+- custom-components by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 985](https://github.com/gradio-app/gradio/pull/985)
+- Refactor component shortcuts by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 995](https://github.com/gradio-app/gradio/pull/995)
+- Plot Component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 805](https://github.com/gradio-app/gradio/pull/805)
+- updated PyPi version to 2.9.2 by [@abidlabs](https://github.com/abidlabs) in [PR 1002](https://github.com/gradio-app/gradio/pull/1002)
+- Release 2.9.3 by [@abidlabs](https://github.com/abidlabs) in [PR 1003](https://github.com/gradio-app/gradio/pull/1003)
+- Image3D Examples Fix by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1001](https://github.com/gradio-app/gradio/pull/1001)
+- release 2.9.4 by [@abidlabs](https://github.com/abidlabs) in [PR 1006](https://github.com/gradio-app/gradio/pull/1006)
+- templates import hotfix by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1008](https://github.com/gradio-app/gradio/pull/1008)
+- Progress indicator bar by [@aliabid94](https://github.com/aliabid94) in [PR 997](https://github.com/gradio-app/gradio/pull/997)
+- Fixed image input for absolute path by [@JefferyChiang](https://github.com/JefferyChiang) in [PR 1004](https://github.com/gradio-app/gradio/pull/1004)
+- Model3D + Plot Components by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1010](https://github.com/gradio-app/gradio/pull/1010)
+- Gradio Guides: Creating CryptoPunks with GANs by [@NimaBoscarino](https://github.com/NimaBoscarino) in [PR 1000](https://github.com/gradio-app/gradio/pull/1000)
+- [BIG PR] Gradio blocks & redesigned components by [@abidlabs](https://github.com/abidlabs) in [PR 880](https://github.com/gradio-app/gradio/pull/880)
+- fixed failing test on main by [@abidlabs](https://github.com/abidlabs) in [PR 1023](https://github.com/gradio-app/gradio/pull/1023)
+- Use smaller ASR model in external test by [@abidlabs](https://github.com/abidlabs) in [PR 1024](https://github.com/gradio-app/gradio/pull/1024)
+- updated PyPi version to 2.9.0b by [@abidlabs](https://github.com/abidlabs) in [PR 1026](https://github.com/gradio-app/gradio/pull/1026)
+- Fixing import issues so that the package successfully installs on colab notebooks by [@abidlabs](https://github.com/abidlabs) in [PR 1027](https://github.com/gradio-app/gradio/pull/1027)
+- Update website tracker slackbot by [@aliabd](https://github.com/aliabd) in [PR 1037](https://github.com/gradio-app/gradio/pull/1037)
+- textbox-autoheight by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1009](https://github.com/gradio-app/gradio/pull/1009)
+- Model3D Examples fixes by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1035](https://github.com/gradio-app/gradio/pull/1035)
+- GAN Gradio Guide: Adjustments to iframe heights by [@NimaBoscarino](https://github.com/NimaBoscarino) in [PR 1042](https://github.com/gradio-app/gradio/pull/1042)
+- added better default labels to form components by [@abidlabs](https://github.com/abidlabs) in [PR 1040](https://github.com/gradio-app/gradio/pull/1040)
+- Slackbot web tracker fix by [@aliabd](https://github.com/aliabd) in [PR 1043](https://github.com/gradio-app/gradio/pull/1043)
+- Plot fixes by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1044](https://github.com/gradio-app/gradio/pull/1044)
+- Small fixes to the demos by [@abidlabs](https://github.com/abidlabs) in [PR 1030](https://github.com/gradio-app/gradio/pull/1030)
+- fixing demo issue with website by [@aliabd](https://github.com/aliabd) in [PR 1047](https://github.com/gradio-app/gradio/pull/1047)
+- [hotfix] HighlightedText by [@aliabid94](https://github.com/aliabid94) in [PR 1046](https://github.com/gradio-app/gradio/pull/1046)
+- Update text by [@ronvoluted](https://github.com/ronvoluted) in [PR 1050](https://github.com/gradio-app/gradio/pull/1050)
+- Update CONTRIBUTING.md by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1052](https://github.com/gradio-app/gradio/pull/1052)
+- fix(ui): Increase contrast for footer by [@ronvoluted](https://github.com/ronvoluted) in [PR 1048](https://github.com/gradio-app/gradio/pull/1048)
+- UI design update by [@gary149](https://github.com/gary149) in [PR 1041](https://github.com/gradio-app/gradio/pull/1041)
+- updated PyPi version to 2.9.0b8 by [@abidlabs](https://github.com/abidlabs) in [PR 1059](https://github.com/gradio-app/gradio/pull/1059)
+- Running, testing, and fixing demos by [@abidlabs](https://github.com/abidlabs) in [PR 1060](https://github.com/gradio-app/gradio/pull/1060)
+- Form layout by [@pngwn](https://github.com/pngwn) in [PR 1054](https://github.com/gradio-app/gradio/pull/1054)
+- inputless-interfaces by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1038](https://github.com/gradio-app/gradio/pull/1038)
+- Update PULL_REQUEST_TEMPLATE.md by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1068](https://github.com/gradio-app/gradio/pull/1068)
+- Upgrading node memory to 4gb in website Docker by [@aliabd](https://github.com/aliabd) in [PR 1069](https://github.com/gradio-app/gradio/pull/1069)
+- Website reload error by [@aliabd](https://github.com/aliabd) in [PR 1079](https://github.com/gradio-app/gradio/pull/1079)
+- fixed favicon issue by [@abidlabs](https://github.com/abidlabs) in [PR 1064](https://github.com/gradio-app/gradio/pull/1064)
+- remove-queue-from-events by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1056](https://github.com/gradio-app/gradio/pull/1056)
+- Enable vertex colors for OBJs files by [@radames](https://github.com/radames) in [PR 1074](https://github.com/gradio-app/gradio/pull/1074)
+- Dark text by [@ronvoluted](https://github.com/ronvoluted) in [PR 1049](https://github.com/gradio-app/gradio/pull/1049)
+- Scroll to output by [@pngwn](https://github.com/pngwn) in [PR 1077](https://github.com/gradio-app/gradio/pull/1077)
+- Explicitly list pnpm version 6 in contributing guide by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1085](https://github.com/gradio-app/gradio/pull/1085)
+- hotfix for encrypt issue by [@abidlabs](https://github.com/abidlabs) in [PR 1096](https://github.com/gradio-app/gradio/pull/1096)
+- Release 2.9b9 by [@abidlabs](https://github.com/abidlabs) in [PR 1098](https://github.com/gradio-app/gradio/pull/1098)
+- tweak node circleci settings by [@pngwn](https://github.com/pngwn) in [PR 1091](https://github.com/gradio-app/gradio/pull/1091)
+- Website Reload Error by [@aliabd](https://github.com/aliabd) in [PR 1099](https://github.com/gradio-app/gradio/pull/1099)
+- Website Reload: README in demos docker by [@aliabd](https://github.com/aliabd) in [PR 1100](https://github.com/gradio-app/gradio/pull/1100)
+- Flagging fixes by [@abidlabs](https://github.com/abidlabs) in [PR 1081](https://github.com/gradio-app/gradio/pull/1081)
+- Backend for optional labels by [@abidlabs](https://github.com/abidlabs) in [PR 1080](https://github.com/gradio-app/gradio/pull/1080)
+- Optional labels fe by [@pngwn](https://github.com/pngwn) in [PR 1105](https://github.com/gradio-app/gradio/pull/1105)
+- clean-deprecated-parameters by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1090](https://github.com/gradio-app/gradio/pull/1090)
+- Blocks rendering fix by [@abidlabs](https://github.com/abidlabs) in [PR 1102](https://github.com/gradio-app/gradio/pull/1102)
+- Redos #1106 by [@abidlabs](https://github.com/abidlabs) in [PR 1112](https://github.com/gradio-app/gradio/pull/1112)
+- Interface types: handle input-only, output-only, and unified interfaces by [@abidlabs](https://github.com/abidlabs) in [PR 1108](https://github.com/gradio-app/gradio/pull/1108)
+- Hotfix + New pypi release 2.9b11 by [@abidlabs](https://github.com/abidlabs) in [PR 1118](https://github.com/gradio-app/gradio/pull/1118)
+- issue-checkbox by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1122](https://github.com/gradio-app/gradio/pull/1122)
+- issue-checkbox-hotfix by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1127](https://github.com/gradio-app/gradio/pull/1127)
+- Fix demos in website by [@aliabd](https://github.com/aliabd) in [PR 1130](https://github.com/gradio-app/gradio/pull/1130)
+- Guide for Gradio ONNX model zoo on Huggingface by [@AK391](https://github.com/AK391) in [PR 1073](https://github.com/gradio-app/gradio/pull/1073)
+- ONNX guide fixes by [@aliabd](https://github.com/aliabd) in [PR 1131](https://github.com/gradio-app/gradio/pull/1131)
+- Stacked form inputs css by [@gary149](https://github.com/gary149) in [PR 1134](https://github.com/gradio-app/gradio/pull/1134)
+- made default value in textbox empty string by [@abidlabs](https://github.com/abidlabs) in [PR 1135](https://github.com/gradio-app/gradio/pull/1135)
+- Examples UI by [@gary149](https://github.com/gary149) in [PR 1121](https://github.com/gradio-app/gradio/pull/1121)
+- Chatbot custom color support by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1092](https://github.com/gradio-app/gradio/pull/1092)
+- highlighted text colors by [@pngwn](https://github.com/pngwn) in [PR 1119](https://github.com/gradio-app/gradio/pull/1119)
+- pin to pnpm 6 for now by [@pngwn](https://github.com/pngwn) in [PR 1147](https://github.com/gradio-app/gradio/pull/1147)
+- Restore queue in Blocks by [@aliabid94](https://github.com/aliabid94) in [PR 1137](https://github.com/gradio-app/gradio/pull/1137)
+- add select event for tabitems by [@pngwn](https://github.com/pngwn) in [PR 1154](https://github.com/gradio-app/gradio/pull/1154)
+- max_lines + autoheight for textbox by [@pngwn](https://github.com/pngwn) in [PR 1153](https://github.com/gradio-app/gradio/pull/1153)
+- use color palette for chatbot by [@pngwn](https://github.com/pngwn) in [PR 1152](https://github.com/gradio-app/gradio/pull/1152)
+- Timeseries improvements by [@pngwn](https://github.com/pngwn) in [PR 1149](https://github.com/gradio-app/gradio/pull/1149)
+- move styling for interface panels to frontend by [@pngwn](https://github.com/pngwn) in [PR 1146](https://github.com/gradio-app/gradio/pull/1146)
+- html tweaks by [@pngwn](https://github.com/pngwn) in [PR 1145](https://github.com/gradio-app/gradio/pull/1145)
+- Issue #768: Support passing none to resize and crop image by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1144](https://github.com/gradio-app/gradio/pull/1144)
+- image gallery component + img css by [@aliabid94](https://github.com/aliabid94) in [PR 1140](https://github.com/gradio-app/gradio/pull/1140)
+- networking tweak by [@abidlabs](https://github.com/abidlabs) in [PR 1143](https://github.com/gradio-app/gradio/pull/1143)
+- Allow enabling queue per event listener by [@aliabid94](https://github.com/aliabid94) in [PR 1155](https://github.com/gradio-app/gradio/pull/1155)
+- config hotfix and v. 2.9b23 by [@abidlabs](https://github.com/abidlabs) in [PR 1158](https://github.com/gradio-app/gradio/pull/1158)
+- Custom JS calls by [@aliabid94](https://github.com/aliabid94) in [PR 1082](https://github.com/gradio-app/gradio/pull/1082)
+- Small fixes: queue default fix, ffmpeg installation message by [@abidlabs](https://github.com/abidlabs) in [PR 1159](https://github.com/gradio-app/gradio/pull/1159)
+- formatting by [@abidlabs](https://github.com/abidlabs) in [PR 1161](https://github.com/gradio-app/gradio/pull/1161)
+- enable flex grow for gr-box by [@radames](https://github.com/radames) in [PR 1165](https://github.com/gradio-app/gradio/pull/1165)
+- 1148 loading by [@pngwn](https://github.com/pngwn) in [PR 1164](https://github.com/gradio-app/gradio/pull/1164)
+- Put enable_queue kwarg back in launch() by [@aliabid94](https://github.com/aliabid94) in [PR 1167](https://github.com/gradio-app/gradio/pull/1167)
+- A few small fixes by [@abidlabs](https://github.com/abidlabs) in [PR 1171](https://github.com/gradio-app/gradio/pull/1171)
+- Hotfix for dropdown component by [@abidlabs](https://github.com/abidlabs) in [PR 1172](https://github.com/gradio-app/gradio/pull/1172)
+- use secondary buttons in interface by [@pngwn](https://github.com/pngwn) in [PR 1173](https://github.com/gradio-app/gradio/pull/1173)
+- 1183 component height by [@pngwn](https://github.com/pngwn) in [PR 1185](https://github.com/gradio-app/gradio/pull/1185)
+- 962 dataframe by [@pngwn](https://github.com/pngwn) in [PR 1186](https://github.com/gradio-app/gradio/pull/1186)
+- update-contributing by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1188](https://github.com/gradio-app/gradio/pull/1188)
+- Table tweaks by [@pngwn](https://github.com/pngwn) in [PR 1195](https://github.com/gradio-app/gradio/pull/1195)
+- wrap tab content in column by [@pngwn](https://github.com/pngwn) in [PR 1200](https://github.com/gradio-app/gradio/pull/1200)
+- WIP: Add dark mode support by [@gary149](https://github.com/gary149) in [PR 1187](https://github.com/gradio-app/gradio/pull/1187)
+- Restored /api/predict/ endpoint for Interfaces by [@abidlabs](https://github.com/abidlabs) in [PR 1199](https://github.com/gradio-app/gradio/pull/1199)
+- hltext-label by [@pngwn](https://github.com/pngwn) in [PR 1204](https://github.com/gradio-app/gradio/pull/1204)
+- add copy functionality to json by [@pngwn](https://github.com/pngwn) in [PR 1205](https://github.com/gradio-app/gradio/pull/1205)
+- Update component config by [@aliabid94](https://github.com/aliabid94) in [PR 1089](https://github.com/gradio-app/gradio/pull/1089)
+- fix placeholder prompt by [@pngwn](https://github.com/pngwn) in [PR 1215](https://github.com/gradio-app/gradio/pull/1215)
+- ensure webcam video value is propagated correctly by [@pngwn](https://github.com/pngwn) in [PR 1218](https://github.com/gradio-app/gradio/pull/1218)
+- Automatic word-break in highlighted text, combine_adjacent support by [@aliabid94](https://github.com/aliabid94) in [PR 1209](https://github.com/gradio-app/gradio/pull/1209)
+- async-function-support by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1190](https://github.com/gradio-app/gradio/pull/1190)
+- Sharing fix for assets by [@aliabid94](https://github.com/aliabid94) in [PR 1208](https://github.com/gradio-app/gradio/pull/1208)
+- Hotfixes for course demos by [@abidlabs](https://github.com/abidlabs) in [PR 1222](https://github.com/gradio-app/gradio/pull/1222)
+- Allow Custom CSS by [@aliabid94](https://github.com/aliabid94) in [PR 1170](https://github.com/gradio-app/gradio/pull/1170)
+- share-hotfix by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1226](https://github.com/gradio-app/gradio/pull/1226)
+- tweaks by [@pngwn](https://github.com/pngwn) in [PR 1229](https://github.com/gradio-app/gradio/pull/1229)
+- white space for class concatenation by [@radames](https://github.com/radames) in [PR 1228](https://github.com/gradio-app/gradio/pull/1228)
+- Tweaks by [@pngwn](https://github.com/pngwn) in [PR 1230](https://github.com/gradio-app/gradio/pull/1230)
+- css tweaks by [@pngwn](https://github.com/pngwn) in [PR 1235](https://github.com/gradio-app/gradio/pull/1235)
+- ensure defaults height match for media inputs by [@pngwn](https://github.com/pngwn) in [PR 1236](https://github.com/gradio-app/gradio/pull/1236)
+- Default Label label value by [@radames](https://github.com/radames) in [PR 1239](https://github.com/gradio-app/gradio/pull/1239)
+- update-shortcut-syntax by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1234](https://github.com/gradio-app/gradio/pull/1234)
+- Update version.txt by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1244](https://github.com/gradio-app/gradio/pull/1244)
+- Layout bugs by [@pngwn](https://github.com/pngwn) in [PR 1246](https://github.com/gradio-app/gradio/pull/1246)
+- Update demo by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1253](https://github.com/gradio-app/gradio/pull/1253)
+- Button default name by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1243](https://github.com/gradio-app/gradio/pull/1243)
+- Labels spacing by [@gary149](https://github.com/gary149) in [PR 1254](https://github.com/gradio-app/gradio/pull/1254)
+- add global loader for gradio app by [@pngwn](https://github.com/pngwn) in [PR 1251](https://github.com/gradio-app/gradio/pull/1251)
+- ui apis for dalle-mini by [@pngwn](https://github.com/pngwn) in [PR 1258](https://github.com/gradio-app/gradio/pull/1258)
+- Add precision to Number, backend only by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1125](https://github.com/gradio-app/gradio/pull/1125)
+- Website Design Changes by [@abidlabs](https://github.com/abidlabs) in [PR 1015](https://github.com/gradio-app/gradio/pull/1015)
+- Small fixes for multiple demos compatible with 3.0 by [@radames](https://github.com/radames) in [PR 1257](https://github.com/gradio-app/gradio/pull/1257)
+- Issue #1160: Model 3D component not destroyed correctly by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1219](https://github.com/gradio-app/gradio/pull/1219)
+- Fixes to components by [@abidlabs](https://github.com/abidlabs) in [PR 1260](https://github.com/gradio-app/gradio/pull/1260)
+- layout docs by [@abidlabs](https://github.com/abidlabs) in [PR 1263](https://github.com/gradio-app/gradio/pull/1263)
+- Static forms by [@pngwn](https://github.com/pngwn) in [PR 1264](https://github.com/gradio-app/gradio/pull/1264)
+- Cdn assets by [@pngwn](https://github.com/pngwn) in [PR 1265](https://github.com/gradio-app/gradio/pull/1265)
+- update logo by [@gary149](https://github.com/gary149) in [PR 1266](https://github.com/gradio-app/gradio/pull/1266)
+- fix slider by [@aliabid94](https://github.com/aliabid94) in [PR 1268](https://github.com/gradio-app/gradio/pull/1268)
+- maybe fix auth in iframes by [@pngwn](https://github.com/pngwn) in [PR 1261](https://github.com/gradio-app/gradio/pull/1261)
+- Improves "Getting Started" guide by [@abidlabs](https://github.com/abidlabs) in [PR 1269](https://github.com/gradio-app/gradio/pull/1269)
+- Add embedded demos to website by [@aliabid94](https://github.com/aliabid94) in [PR 1270](https://github.com/gradio-app/gradio/pull/1270)
+- Label hotfixes by [@abidlabs](https://github.com/abidlabs) in [PR 1281](https://github.com/gradio-app/gradio/pull/1281)
+- General tweaks by [@pngwn](https://github.com/pngwn) in [PR 1276](https://github.com/gradio-app/gradio/pull/1276)
+- only affect links within the document by [@pngwn](https://github.com/pngwn) in [PR 1282](https://github.com/gradio-app/gradio/pull/1282)
+- release 3.0b9 by [@abidlabs](https://github.com/abidlabs) in [PR 1283](https://github.com/gradio-app/gradio/pull/1283)
+- Dm by [@pngwn](https://github.com/pngwn) in [PR 1284](https://github.com/gradio-app/gradio/pull/1284)
+- Website fixes by [@aliabd](https://github.com/aliabd) in [PR 1286](https://github.com/gradio-app/gradio/pull/1286)
+- Create Streamables by [@aliabid94](https://github.com/aliabid94) in [PR 1279](https://github.com/gradio-app/gradio/pull/1279)
+- ensure table works on mobile by [@pngwn](https://github.com/pngwn) in [PR 1277](https://github.com/gradio-app/gradio/pull/1277)
+- changes by [@aliabid94](https://github.com/aliabid94) in [PR 1287](https://github.com/gradio-app/gradio/pull/1287)
+- demo alignment on landing page by [@aliabd](https://github.com/aliabd) in [PR 1288](https://github.com/gradio-app/gradio/pull/1288)
+- New meta img by [@aliabd](https://github.com/aliabd) in [PR 1289](https://github.com/gradio-app/gradio/pull/1289)
+- updated PyPi version to 3.0 by [@abidlabs](https://github.com/abidlabs) in [PR 1290](https://github.com/gradio-app/gradio/pull/1290)
+- Fix site by [@aliabid94](https://github.com/aliabid94) in [PR 1291](https://github.com/gradio-app/gradio/pull/1291)
+- Mobile responsive guides by [@aliabd](https://github.com/aliabd) in [PR 1293](https://github.com/gradio-app/gradio/pull/1293)
+- Update readme by [@abidlabs](https://github.com/abidlabs) in [PR 1292](https://github.com/gradio-app/gradio/pull/1292)
+- gif by [@abidlabs](https://github.com/abidlabs) in [PR 1296](https://github.com/gradio-app/gradio/pull/1296)
+- Allow decoding headerless b64 string [@1lint](https://github.com/1lint) in [PR 4031](https://github.com/gradio-app/gradio/pull/4031)
+
+### Contributors Shoutout:
+
+- [@JefferyChiang](https://github.com/JefferyChiang) made their first contribution in [PR 1004](https://github.com/gradio-app/gradio/pull/1004)
+- [@NimaBoscarino](https://github.com/NimaBoscarino) made their first contribution in [PR 1000](https://github.com/gradio-app/gradio/pull/1000)
+- [@ronvoluted](https://github.com/ronvoluted) made their first contribution in [PR 1050](https://github.com/gradio-app/gradio/pull/1050)
+- [@radames](https://github.com/radames) made their first contribution in [PR 1074](https://github.com/gradio-app/gradio/pull/1074)
+- [@freddyaboulton](https://github.com/freddyaboulton) made their first contribution in [PR 1085](https://github.com/gradio-app/gradio/pull/1085)
+- [@liteli1987gmail](https://github.com/liteli1987gmail) & [@chenglu](https://github.com/chenglu) made their first contribution in [PR 4767](https://github.com/gradio-app/gradio/pull/4767)
\ No newline at end of file
diff --git a/CITATION.cff b/CITATION.cff
new file mode 100644
index 0000000..0768f8c
--- /dev/null
+++ b/CITATION.cff
@@ -0,0 +1,45 @@
+cff-version: 1.2.0
+message: Please cite this project using these metadata.
+title: "Gradio: Hassle-free sharing and testing of ML models in the wild"
+abstract: >-
+ Accessibility is a major challenge of machine learning (ML).
+ Typical ML models are built by specialists and require
+ specialized hardware/software as well as ML experience to
+ validate. This makes it challenging for non-technical
+ collaborators and endpoint users (e.g. physicians) to easily
+ provide feedback on model development and to gain trust in
+ ML. The accessibility challenge also makes collaboration
+ more difficult and limits the ML researcher's exposure to
+ realistic data and scenarios that occur in the wild. To
+ improve accessibility and facilitate collaboration, we
+ developed an open-source Python package, Gradio, which
+ allows researchers to rapidly generate a visual interface
+ for their ML models. Gradio makes accessing any ML model as
+ easy as sharing a URL. Our development of Gradio is informed
+ by interviews with a number of machine learning researchers
+ who participate in interdisciplinary collaborations. Their
+ feedback identified that Gradio should support a variety of
+ interfaces and frameworks, allow for easy sharing of the
+ interface, allow for input manipulation and interactive
+ inference by the domain expert, as well as allow embedding
+ the interface in iPython notebooks. We developed these
+ features and carried out a case study to understand Gradio's
+ usefulness and usability in the setting of a machine
+ learning collaboration between a researcher and a
+ cardiologist.
+authors:
+ - family-names: Abid
+ given-names: Abubakar
+ - family-names: Abdalla
+ given-names: Ali
+ - family-names: Abid
+ given-names: Ali
+ - family-names: Khan
+ given-names: Dawood
+ - family-names: Alfozan
+ given-names: Abdulrahman
+ - family-names: Zou
+ given-names: James
+doi: 10.48550/arXiv.1906.02569
+date-released: 2019-06-06
+url: https://arxiv.org/abs/1906.02569
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..ae3a11f
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,134 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+ community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email address,
+ without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official email address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+[Discord], or at our [Email].
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
+
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
+[Discord]: https://discord.com/invite/feTf9x3ZSB
+[Email]: gradio-team@huggingface.co
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..b47d424
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,526 @@
+# Contributing to Gradio
+
+
+
+
+More than 300 awesome developers have contributed to the `gradio` library, and the best way to help us today is by **opening a detailed issue** rather than a pull request.
+
+## 📝 The best way to contribute: open a great issue
+
+We have **paused accepting pull requests from outside the Gradio maintainer team.** As AI coding agents have become widespread, the volume of community PRs has grown to the point where reviewing them carefully takes more maintainer time than the alternative: pointing our own coding agents at a well-described issue and solving it ourselves.
+
+So the single most valuable thing you can do is **file a clear, detailed, reproducible issue.** A great issue lets us (and our agents) understand and fix the problem quickly, and it helps us prioritize what to work on next for Gradio.
+
+> If you are a member of the Gradio maintainer team, the local setup, testing, and PR sections further down still apply to you.
+
+### 🐛 Reporting a bug
+
+Please [open an issue](https://github.com/gradio-app/gradio/issues/new/choose) and include:
+
+1. **A clear, descriptive title** that summarizes the problem.
+2. **What you expected to happen** vs. **what actually happened.**
+3. **Step-by-step instructions to reproduce the bug**, numbered and as specific as possible (which component, which event, what input, etc.).
+4. **A minimal reproduction.** This is the most important part. Please include the smallest self-contained Gradio app that triggers the bug, for example:
+ - a short code snippet (ideally runnable as-is), and/or
+ - a link to a minimal hosted demo on [Hugging Face Spaces](https://huggingface.co/spaces) or a small repo that reproduces the issue.
+5. **Your environment**: Gradio version (`gradio --version` or `gr.__version__`), Python version, operating system, and browser (for frontend issues).
+6. **Screenshots, screen recordings, or error tracebacks** where relevant.
+
+The smaller and more self-contained the reproduction, the faster we can fix it.
+
+[#10568](https://github.com/gradio-app/gradio/issues/10568) is a good example of a bug report to follow.
+
+### ✨ Requesting a feature
+
+Please [open an issue](https://github.com/gradio-app/gradio/issues/new/choose) and include:
+
+1. **A clear, descriptive title.**
+2. **The problem or use case** you're trying to solve — what are you trying to build, and where does Gradio fall short today?
+3. **A detailed description of the feature** you'd like, including a proposed API or example of how you imagine it would be used in code.
+4. **A demo or mockup** where possible — a link to a small repro/demo, a sketch, or a screenshot of a similar feature elsewhere helps us understand the request quickly.
+5. **Alternatives you've considered**, including any workarounds you're currently using.
+
+Detailed feature requests directly shape our roadmap and help us prioritize what to build next.
+
+[#12519](https://github.com/gradio-app/gradio/issues/12519) is a good example of a feature request issue to follow.
+
+## 🏡 Setup Gradio locally
+
+> The following sections (local setup, structure, testing, and submitting PRs) are primarily for the Gradio maintainer team. If you're an outside contributor, the most helpful thing you can do is [open a detailed issue](#-the-best-way-to-contribute-open-a-great-issue) — but you're also welcome to run Gradio locally to build a clean reproduction for your issue.
+
+There are a few ways to install and run Gradio.
+
+### 🛠️ Install Gradio from `main`
+
+- Clone this repo
+- Navigate to the repo directory and run:
+
+
+
+- Run the frontend (only required if you are making changes to the frontend and would like to preview them)
+
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```bash
+bash scripts/install_gradio.sh
+```
+
+
+
+```bash
+scripts\install_gradio.bat
+```
+
+
+
+
+- Install test requirements (only required if you want to run tests locally)
+
+(Note that it is highly recommended to use a virtual environment running **Python 3.10** since the versions of Gradio's dependencies are pinned)
+
+
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```bash
+bash scripts/run_frontend.sh
+```
+
+
+
+```bash
+scripts\run_frontend.bat
+```
+
+
+
+
+If you have a different Python version and conflicting packages during the installation, please first run:
+
+
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```bash
+bash scripts/install_test_requirements.sh
+```
+
+
+
+```bash
+scripts\install_test_requirements.bat
+```
+
+
+
+
+
+### 📦 Using dev containers
+
+Instead of installing Gradio locally, you can alternatively use dev containers. This is supported on all platforms (macOS/Windows/Linux), as well as on GitHub Codespaces.
+
+Prerequisites:
+
+- An editor which supports dev containers, like VS Code
+- Docker support on the host computer:
+ - macOS: [Docker Desktop 2.0+](https://www.docker.com/products/docker-desktop/)
+ - Windows: [Docker Desktop 2.0+](https://www.docker.com/products/docker-desktop/)
+ - Linux: [Docker CE/EE 18.06+](https://docs.docker.com/get-docker/) and [Docker Compose 1.21+](https://docs.docker.com/compose/install/)
+- If using VS Code, the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension
+
+Steps:
+
+- Clone repository
+- Open it in your editor
+- For VS Code, execute `Dev Containers: Reopen in container` command
+
+For detailed instructions, please see the [Dev Containers tutorial](https://code.visualstudio.com/docs/devcontainers/tutorial).
+
+## 🧱 Structure of the Repository
+
+If you're a newcomer to Gradio, we recommend getting familiar with the overall structure of the repository so that you can focus on the part of the source code you'd like to contribute to.
+
+- `/gradio`: contains the Python source code for the library
+ - `/gradio/interface.py`: contains the Python source code for the core `Interface` class
+ - `/gradio/blocks.py`: contains the Python source code for the core `Blocks` class
+ - `/gradio/components/`: the directory that contains the Python source code for all of the Gradio components.
+- `/test`: contains Python unit tests for the library
+- `/js`: contains the HTML/JS/CSS source code for the library, including the fronted code for each component in a separate directory
+ - `/js/_website`: contains the code for the Gradio website (www.gradio.app). See the README in the `/js/_website` folder for more details
+- `/guides`: the written guides and tutorials that are found on Gradio's website.
+
+## 🚀 Run a Gradio app
+
+You can get started by creating an `app.py` file in the root:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ gr.Button()
+
+if __name__ == "__main__":
+ demo.launch()
+```
+
+then run:
+
+```
+gradio app.py
+```
+
+This will start the backend server in reload mode, which will watch for changes in the `gradio` folder and reload the app if changes are made. By default, Gradio will launch on port 7860. You can also just use `python app.py`, but this won't automatically trigger updates.
+
+Note: if you have `gradio` installed elsewhere in your system, you may need to uninstall it or at least make sure your `PYTHONPATH` includes the directory where the Gradio repository is cloned, e.g.,
+`export PYTHONPATH="./"`
+
+
+If you're making frontend changes, start the frontend server:
+
+
+
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```bash
+bash scripts/create_test_requirements.sh
+```
+
+
+
+```bash
+scripts\create_test_requirements.bat
+```
+
+
+
+
+This will open a separate browser tab. By default, Gradio will launch this on port 9876. Any changes to the frontend will also reload automatically in the browser. For more information about developing in the frontend, you can refer to [js/README.md](js/README.md).
+
+We also have demos of all our components in the `/gradio/demo` directory. To get our simple gradio Chatbot running locally:
+
+```
+gradio demo/chatbot_simple/run.py
+```
+
+
+## 🧪 Testing
+
+We use Pytest, Playwright and Vitest to test our code.
+
+- The Python tests are located in `/test`. To run these tests:
+
+
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```bash
+bash scripts/run_frontend.sh
+```
+
+
+
+```bash
+scripts\run_frontend.bat
+```
+
+
+
+
+- The frontend unit tests are any defined with the filename `*.test.ts`. To run them:
+
+```
+pnpm test
+```
+
+- Browser tests are located in `js/spa/test` and are defined as `*spec.ts` files.
+
+To install browser test dependencies:
+
+```
+pnpm exec playwright install chromium firefox
+pnpm exec playwright install-deps chromium firefox
+pnpm --filter @gradio/utils --filter @gradio/theme package
+```
+
+To run browser tests:
+
+```
+pnpm test:browser
+```
+
+To build the frontend code before running browser tests:
+
+```
+pnpm test:browser:full
+```
+
+You can also run browser tests in the UI mode by adding the `--ui` flag:
+
+```
+pnpm test:browser --ui
+```
+
+If you have made any significant visual changes to a component, we encourage you to add a new Storybook story or amend an existing one to reflect them. You can create a new story with a `*.stories.svelte` file. You can run the storybook locally:
+
+```
+pnpm storybook
+```
+
+## ✍️ Gradio Website & Docs
+
+We also welcome any contributions to our [website](https://www.gradio.app) and [docs](https://www.gradio.app/docs).
+
+### Building The Website
+
+All of the website code lives in the `js/_website/` directory.
+
+To start the website on dev mode simply cd into this directory and run:
+
+```
+pnpm i
+pnpm dev
+```
+
+This will serve the website on `http://localhost:5173/` (or the next available port).
+
+When you're done with changes and want to build the website you can run:
+
+```
+pnpm build && pnpm preview
+```
+
+This will serve the website on `http://localhost:4173/` (or the next available port).
+
+### Documentation
+#### API Reference
+
+Gradio's [API reference](https://www.gradio.app/docs/gradio/interface) is built from templates written in [mdsvex](https://mdsvex.pngwn.io/). You can find all the templates in this directory:
+
+```
+js/_website/src/lib/templates/gradio
+```
+
+The templates directory is structured as follows:
+
+```
+├── gradio/
+│ ├── 01_building-demos/
+│ │ ├── 01_interface.svx
+│ │ ├── 02_chatinterface.svx
+│ │ ├── 03_tabbedinterface.svx
+│ │ ├── 04_blocks.svx
+│ ├── 02_blocks-layout/
+│ ├── 03_components/
+│ ├── 04_helpers/
+│ ├── 05_modals/
+│ ├── 06_routes/
+│ ├── other/
+```
+
+This structure defines the pages' ordering. You can use a numeral prefix (XX_) before a name to dictate where a page is listed, but it's otherwise ignored in the url route. Note that the folder names (01_building-demos, etc) are only used for the navbar and are not in the url.
+
+The mdsvex files use a combination of markdown and svelte. They also pull documentation directly from the source code. Adding a `@document()` wrapper around any class or function in the source code will make its docstrings available in the templates.
+
+Here's an example: the template for [Image docs](https://www.gradio.app/docs/gradio/image) is [here](https://github.com/gradio-app/gradio/blob/main/js/_website/src/lib/templates/gradio/03_components/image.svx). You can see the initialization section references `obj.parameters`. So to edit the description of a parameter you'll have to edit the docstring in the [source code](https://github.com/gradio-app/gradio/blob/main/gradio/components/image.py). But the page also includes a section titled 'GIF and SVG Image Formats' which is written in plain markdown and can be edited directly on the template.
+
+If you are making changes to docstrings and want to see them on the website you have to make sure you're on an editable install of the gradio library. Just run this command from root:
+
+```
+pip install -e .
+```
+
+And then from the website directory:
+
+```
+pnpm dev
+```
+
+#### Guides
+
+Guides like [Quickstart](https://www.gradio.app/guides/quickstart) are built from this directory: `/guides`. The directory follows the same structure as the API reference templates, with nested folders and numerical prefixes for ordering, but the files are standard markdown files. After adding a new guide, or editing an existing one, to see the changes on the website make sure you are on an editable install of the gradio library. Run this command from root:
+
+```
+pip install -e .
+```
+
+and then from the website directory:
+
+```
+pnpm dev
+```
+
+#### Main vs. Released
+
+The website supports documentation for both the latest released version on pypi as well as the main build on github. You can switch between them on the website by using the toggle on any page or by prefixing '/main' before the route in the url. For example: https://www.gradio.app/main/guides/quickstart
+
+If you're making changes to documentation and are wondering why they're not showing up, make sure you're looking at the 'main' version of the page. Since they haven't been included in a release yet, they will only be visible there.
+
+## 📮 Submitting PRs
+
+All PRs should be submitted against `main`, and ideally should address an open issue, unless the change is small. Direct commits to main are blocked, and PRs require an approving review to merge into main. By convention, the Gradio maintainers will review PRs when:
+
+- An initial review has been requested
+- A clear, descriptive title has been assigned to the PR
+- A maintainer (@abidlabs, @aliabid94, @aliabd, @AK391, @dawoodkhan82, @pngwn, @freddyaboulton, @hannahblair, @hysts, @whitphx) is tagged in the PR comments and asked to complete a review
+
+ 🧹 We ask that you make sure initial CI checks are passing before requesting a review. One of the Gradio maintainers will merge the PR when all the checks are passing. You can safely ignore the Vercel and Spaces checks, which only run under maintainers' pull requests.
+
+Don't forget to format your code before pushing:
+
+
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```
+bash scripts/run_backend_tests.sh
+```
+
+
+
+```bash
+scripts\run_backend_tests.bat
+```
+
+
+
+
+And if you made changes to the frontend:
+
+
+
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```
+bash scripts/format_backend.sh
+```
+
+
+
+```bash
+scripts\format_backend.bat
+```
+
+
+
+
+Thank you for taking the time to contribute to Gradio!
+
+
+## ❓ Need help or have questions?
+
+- [Open a detailed issue](https://github.com/gradio-app/gradio/issues/new/choose) for a bug or feature request — see the [guidelines above](#-the-best-way-to-contribute-open-a-great-issue).
+- Ask the Gradio community in our [Discord](https://discord.com/invite/feTf9x3ZSB).
+- Browse existing [issues](https://github.com/gradio-app/gradio/issues) to see if your problem has already been reported (and add a 👍 or extra reproduction details if so).
+
+## 🚧 Troubleshooting
+`ERROR: Error loading ASGI app. Could not import module "
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```
+bash scripts/format_frontend.sh
+```
+
+
+
+```bash
+scripts\format_frontend.bat
+```
+
+
+
+---
+
+```FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory``` when running `scripts/build_frontend.sh`.
+
+Run `scripts/build_frontend.sh` with the environment variable `NODE_OPTIONS=--max_old_space_size=8192` to increase the heap size.
+
+---
+
+In the case of:
+- Unexpected exceptions being thrown, or
+- The following warning:
+`IMPORTANT: You are using gradio version
+
+ MacOS / Linux
+ Windows
+
+
+
+
+```
+bash scripts/install_gradio.sh
+bash scripts/build_frontend.sh
+```
+
+
+
+```bash
+scripts\install_gradio.bat
+scripts\build_frontend.bat
+```
+
+
+
+
+
+
+
+[](https://github.com/gradio-app/gradio/actions/workflows/test-python.yml)
+[](https://github.com/gradio-app/gradio/actions/workflows/tests-js.yml)
+[](https://pypi.org/project/gradio/)
+[](https://pypi.org/project/gradio/)
+
+[](https://twitter.com/gradio)
+
+[Website](https://gradio.app)
+| [Documentation](https://gradio.app/docs/)
+| [Guides](https://gradio.app/guides/)
+| [Getting Started](https://gradio.app/getting_started/)
+| [Examples](demo/)
+
+
+
+It just takes a few lines of Python to create your own demo, so let's get started 💫
+
+
+### Installation
+
+**Prerequisite**: Gradio requires [Python 3.10 or higher](https://www.python.org/downloads/).
+
+
+We recommend installing Gradio using `pip`, which is included by default in Python. Run this in your terminal or command prompt:
+
+```bash
+pip install --upgrade gradio
+```
+
+
+> [!TIP]
+ > It is best to install Gradio in a virtual environment. Detailed installation instructions for all common operating systems are provided here.
+
+### Building Your First Demo
+
+You can run Gradio in your favorite code editor, Jupyter notebook, Google Colab, or anywhere else you write Python. Let's write your first Gradio app:
+
+
+```python
+import gradio as gr
+
+def greet(name, intensity):
+ return "Hello, " + name + "!" * int(intensity)
+
+demo = gr.Interface(
+ fn=greet,
+ inputs=["text", "slider"],
+ outputs=["text"],
+ api_name="predict"
+)
+
+demo.launch()
+```
+
+
+
+> [!TIP]
+ > We shorten the imported name from gradio to gr. This is a widely adopted convention for better readability of code.
+
+Now, run your code. If you've written the Python code in a file named `app.py`, then you would run `python app.py` from the terminal.
+
+The demo below will open in a browser on [http://localhost:7860](http://localhost:7860) if running from a file. If you are running within a notebook, the demo will appear embedded within the notebook.
+
+
+
+Type your name in the textbox on the left, drag the slider, and then press the Submit button. You should see a friendly greeting on the right.
+
+> [!TIP]
+ > When developing locally, you can run your Gradio app in hot reload mode, which automatically reloads the Gradio app whenever you make changes to the file. To do this, simply type in gradio before the name of the file instead of python. In the example above, you would type: `gradio app.py` in your terminal. You can also enable vibe mode by using the --vibe flag, e.g. gradio --vibe app.py, which provides an in-browser chat that can be used to write or edit your Gradio app using natural language. Learn more in the Hot Reloading Guide.
+
+
+**Understanding the `Interface` Class**
+
+You'll notice that in order to make your first demo, you created an instance of the `gr.Interface` class. The `Interface` class is designed to create demos for machine learning models which accept one or more inputs, and return one or more outputs.
+
+The `Interface` class has three core arguments:
+
+- `fn`: the function to wrap a user interface (UI) around
+- `inputs`: the Gradio component(s) to use for the input. The number of components should match the number of arguments in your function.
+- `outputs`: the Gradio component(s) to use for the output. The number of components should match the number of return values from your function.
+
+The `fn` argument is very flexible -- you can pass *any* Python function that you want to wrap with a UI. In the example above, we saw a relatively simple function, but the function could be anything from a music generator to a tax calculator to the prediction function of a pretrained machine learning model.
+
+The `inputs` and `outputs` arguments take one or more Gradio components. As we'll see, Gradio includes more than [30 built-in components](https://www.gradio.app/docs/gradio/introduction) (such as the `gr.Textbox()`, `gr.Image()`, and `gr.HTML()` components) that are designed for machine learning applications.
+
+> [!TIP]
+ > For the `inputs` and `outputs` arguments, you can pass in the name of these components as a string (`"textbox"`) or an instance of the class (`gr.Textbox()`).
+
+If your function accepts more than one argument, as is the case above, pass a list of input components to `inputs`, with each input component corresponding to one of the arguments of the function, in order. The same holds true if your function returns more than one value: simply pass in a list of components to `outputs`. This flexibility makes the `Interface` class a very powerful way to create demos.
+
+We'll dive deeper into the `gr.Interface` on our series on [building Interfaces](https://www.gradio.app/main/guides/the-interface-class).
+
+### Sharing Your Demo
+
+What good is a beautiful demo if you can't share it? Gradio lets you easily share a machine learning demo without having to worry about the hassle of hosting on a web server. Simply set `share=True` in `launch()`, and a publicly accessible URL will be created for your demo. Let's revisit our example demo, but change the last line as follows:
+
+```python
+import gradio as gr
+
+def greet(name):
+ return "Hello " + name + "!"
+
+demo = gr.Interface(fn=greet, inputs="textbox", outputs="textbox")
+
+demo.launch(share=True) # Share your demo with just 1 extra parameter 🚀
+```
+
+When you run this code, a public URL will be generated for your demo in a matter of seconds, something like:
+
+👉 `https://a23dsf231adb.gradio.live`
+
+Now, anyone around the world can try your Gradio demo from their browser, while the machine learning model and all computation continues to run locally on your computer.
+
+To learn more about sharing your demo, read our dedicated guide on [sharing your Gradio application](https://www.gradio.app/guides/sharing-your-app).
+
+
+### An Overview of Gradio
+
+So far, we've been discussing the `Interface` class, which is a high-level class that lets you build demos quickly with Gradio. But what else does Gradio include?
+
+#### Custom Demos with `gr.Blocks`
+
+Gradio offers a low-level approach for designing web apps with more customizable layouts and data flows with the `gr.Blocks` class. Blocks supports things like controlling where components appear on the page, handling multiple data flows and more complex interactions (e.g. outputs can serve as inputs to other functions), and updating properties/visibility of components based on user interaction — still all in Python.
+
+You can build very custom and complex applications using `gr.Blocks()`. For example, the popular image generation [Automatic1111 Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) is built using Gradio Blocks. We dive deeper into the `gr.Blocks` on our series on [building with Blocks](https://www.gradio.app/guides/blocks-and-event-listeners).
+
+#### Chatbots with `gr.ChatInterface`
+
+Gradio includes another high-level class, `gr.ChatInterface`, which is specifically designed to create Chatbot UIs. Similar to `Interface`, you supply a function and Gradio creates a fully working Chatbot UI. If you're interested in creating a chatbot, you can jump straight to [our dedicated guide on `gr.ChatInterface`](https://www.gradio.app/guides/creating-a-chatbot-fast).
+
+#### The Gradio Python & JavaScript Ecosystem
+
+That's the gist of the core `gradio` Python library, but Gradio is actually so much more! It's an entire ecosystem of Python and JavaScript libraries that let you build machine learning applications, or query them programmatically, in Python or JavaScript. Here are other related parts of the Gradio ecosystem:
+
+* [Gradio Python Client](https://www.gradio.app/guides/getting-started-with-the-python-client) (`gradio_client`): query any Gradio app programmatically in Python.
+* [Gradio JavaScript Client](https://www.gradio.app/guides/getting-started-with-the-js-client) (`@gradio/client`): query any Gradio app programmatically in JavaScript.
+* [Hugging Face Spaces](https://huggingface.co/spaces): the most popular place to host Gradio applications — for free!
+* [Server mode](https://www.gradio.app/guides/server-mode) (`gradio.Server`): build a custom frontend with Gradio's backend — queue, streaming, MCP, ZeroGPU, and Spaces hosting included.
+
+### What's Next?
+
+Keep learning about Gradio sequentially using the Gradio Guides, which include explanations as well as example code and embedded interactive demos. Next up: [let's dive deeper into the Interface class](https://www.gradio.app/guides/the-interface-class).
+
+Or, if you already know the basics and are looking for something specific, you can search the more [technical API documentation](https://www.gradio.app/docs/).
+
+
+### AI Coding Skills
+
+Gradio provides a "skill" that enriches AI coding assistants (like Cursor, Claude Code, Codex, etc.) with Gradio-specific knowledge, so that they can build Gradio apps more effectively. This is especially useful when creating custom Gradio components or styling. Install the Gradio skill for your coding assistant with a single command:
+
+```bash
+gradio skills add --cursor # or --claude, --codex, --opencode
+```
+
+Use `--global` to install at the user level (applies to all projects). Your skill will be automatically available for the particular coding agent.
+
+You can also install a skill for a **specific Gradio Space**, which generates API usage docs (Python, JS, cURL) on the fly:
+
+```bash
+gradio skills add abidlabs/en2fr --cursor
+```
+
+## Questions?
+
+If you'd like to report a bug or have a feature request, please create an [issue on GitHub](https://github.com/gradio-app/gradio/issues/new/choose). For general questions about usage, we are available on [our Discord server](https://discord.com/invite/feTf9x3ZSB) and happy to help.
+
+If you like Gradio, please leave us a ⭐ on GitHub!
+
+## Open Source Stack
+
+Gradio is built on top of many wonderful open-source libraries!
+
+[](https://huggingface.co)
+[
](https://www.python.org)
+[
](https://fastapi.tiangolo.com)
+[
](https://www.encode.io)
+[
](https://svelte.dev)
+[
](https://vitejs.dev)
+[
](https://pnpm.io)
+[
](https://tailwindcss.com)
+[
](https://storybook.js.org/)
+[
](https://www.chromatic.com/)
+
+## License
+
+Gradio is licensed under the Apache License 2.0 found in the [LICENSE](LICENSE) file in the root directory of this repository.
+
+## Citation
+
+Also check out the paper _[Gradio: Hassle-Free Sharing and Testing of ML Models in the Wild](https://arxiv.org/abs/1906.02569), ICML HILL 2019_, and please cite it if you use Gradio in your work.
+
+```
+@article{abid2019gradio,
+ title = {Gradio: Hassle-Free Sharing and Testing of ML Models in the Wild},
+ author = {Abid, Abubakar and Abdalla, Ali and Abid, Ali and Khan, Dawood and Alfozan, Abdulrahman and Zou, James},
+ journal = {arXiv preprint arXiv:1906.02569},
+ year = {2019},
+}
+```
diff --git a/README.wehub.md b/README.wehub.md
new file mode 100644
index 0000000..8eccd67
--- /dev/null
+++ b/README.wehub.md
@@ -0,0 +1,7 @@
+# WeHub 来源说明
+
+- 原始项目:`gradio-app/gradio`
+- 原始仓库:https://github.com/gradio-app/gradio
+- 导入方式:上游默认分支的最新快照
+- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
+- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..3e1e99f
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,5 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+If you discover a security vulnerability, we would be very grateful if you could email us at gradio-team@huggingface.co. This is the preferred approach instead of opening a public issue. We take all vulnerability reports seriously, and will work to patch the vulnerability immediately. Whenever possible, we will credit the person or people who report the security vulnerabilities after it has been patched.
diff --git a/_worker.bundle b/_worker.bundle
new file mode 100644
index 0000000..8f12d3f
--- /dev/null
+++ b/_worker.bundle
@@ -0,0 +1,517 @@
+------formdata-undici-075560669371
+Content-Disposition: form-data; name="metadata"
+
+{"main_module":"functionsWorker-0.6571259265887377.js"}
+------formdata-undici-075560669371
+Content-Disposition: form-data; name="functionsWorker-0.6571259265887377.js"; filename="functionsWorker-0.6571259265887377.js"
+Content-Type: application/javascript+module
+
+// _shared.ts
+var AI_UA_PATTERNS = [
+ /\bgptbot\b/i,
+ /\bchatgpt-user\b/i,
+ /\bclaudebot\b/i,
+ /\bclaude-web\b/i,
+ /\bclaude-user\b/i,
+ /\banthropic\b/i,
+ /\bperplexitybot\b/i,
+ /\bmeta-external(fetcher|agent)\b/i,
+ /\bfacebookbot\b/i,
+ /\bamazonbot\b/i,
+ /\bapplebot\b/i,
+ /\bbytespider\b/i,
+ /\bccbot\b/i,
+ /\bcohere\b/i,
+ /\bgoogle-extended\b/i
+];
+function isLLMRequest(request) {
+ const ua = request.headers.get("user-agent") || "";
+ const accept = request.headers.get("accept") || "";
+ if (accept.includes("text/markdown"))
+ return true;
+ return AI_UA_PATTERNS.some((re) => re.test(ua));
+}
+async function serveDocMarkdown(context) {
+ const { request, params, next } = context;
+ if (!isLLMRequest(request))
+ return next();
+ const url = new URL(request.url);
+ return Response.redirect(`${url.origin}/api/markdown/${params.doc}`, 302);
+}
+async function serveGuideMarkdown(context) {
+ const { request, params, next } = context;
+ if (!isLLMRequest(request))
+ return next();
+ const url = new URL(request.url);
+ return Response.redirect(
+ `${url.origin}/api/markdown/guide/${params.guide}`,
+ 302
+ );
+}
+
+// main/docs/gradio/[doc].ts
+var onRequestGet = serveDocMarkdown;
+
+// docs/gradio/[doc].ts
+var onRequestGet2 = serveDocMarkdown;
+
+// main/guides/[guide].ts
+var onRequestGet3 = serveGuideMarkdown;
+
+// guides/[guide].ts
+var onRequestGet4 = serveGuideMarkdown;
+
+// ../../../.wrangler/tmp/pages-CpiPgT/functionsRoutes-0.19632173451245094.mjs
+var routes = [
+ {
+ routePath: "/main/docs/gradio/:doc",
+ mountPath: "/main/docs/gradio",
+ method: "GET",
+ middlewares: [],
+ modules: [onRequestGet]
+ },
+ {
+ routePath: "/docs/gradio/:doc",
+ mountPath: "/docs/gradio",
+ method: "GET",
+ middlewares: [],
+ modules: [onRequestGet2]
+ },
+ {
+ routePath: "/main/guides/:guide",
+ mountPath: "/main/guides",
+ method: "GET",
+ middlewares: [],
+ modules: [onRequestGet3]
+ },
+ {
+ routePath: "/guides/:guide",
+ mountPath: "/guides",
+ method: "GET",
+ middlewares: [],
+ modules: [onRequestGet4]
+ }
+];
+
+// ../../../../../../../Library/pnpm/global/5/.pnpm/path-to-regexp@6.2.2/node_modules/path-to-regexp/dist.es2015/index.js
+function lexer(str) {
+ var tokens = [];
+ var i = 0;
+ while (i < str.length) {
+ var char = str[i];
+ if (char === "*" || char === "+" || char === "?") {
+ tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
+ continue;
+ }
+ if (char === "\\") {
+ tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
+ continue;
+ }
+ if (char === "{") {
+ tokens.push({ type: "OPEN", index: i, value: str[i++] });
+ continue;
+ }
+ if (char === "}") {
+ tokens.push({ type: "CLOSE", index: i, value: str[i++] });
+ continue;
+ }
+ if (char === ":") {
+ var name = "";
+ var j = i + 1;
+ while (j < str.length) {
+ var code = str.charCodeAt(j);
+ if (
+ // `0-9`
+ code >= 48 && code <= 57 || // `A-Z`
+ code >= 65 && code <= 90 || // `a-z`
+ code >= 97 && code <= 122 || // `_`
+ code === 95
+ ) {
+ name += str[j++];
+ continue;
+ }
+ break;
+ }
+ if (!name)
+ throw new TypeError("Missing parameter name at ".concat(i));
+ tokens.push({ type: "NAME", index: i, value: name });
+ i = j;
+ continue;
+ }
+ if (char === "(") {
+ var count = 1;
+ var pattern = "";
+ var j = i + 1;
+ if (str[j] === "?") {
+ throw new TypeError('Pattern cannot start with "?" at '.concat(j));
+ }
+ while (j < str.length) {
+ if (str[j] === "\\") {
+ pattern += str[j++] + str[j++];
+ continue;
+ }
+ if (str[j] === ")") {
+ count--;
+ if (count === 0) {
+ j++;
+ break;
+ }
+ } else if (str[j] === "(") {
+ count++;
+ if (str[j + 1] !== "?") {
+ throw new TypeError("Capturing groups are not allowed at ".concat(j));
+ }
+ }
+ pattern += str[j++];
+ }
+ if (count)
+ throw new TypeError("Unbalanced pattern at ".concat(i));
+ if (!pattern)
+ throw new TypeError("Missing pattern at ".concat(i));
+ tokens.push({ type: "PATTERN", index: i, value: pattern });
+ i = j;
+ continue;
+ }
+ tokens.push({ type: "CHAR", index: i, value: str[i++] });
+ }
+ tokens.push({ type: "END", index: i, value: "" });
+ return tokens;
+}
+function parse(str, options) {
+ if (options === void 0) {
+ options = {};
+ }
+ var tokens = lexer(str);
+ var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a;
+ var defaultPattern = "[^".concat(escapeString(options.delimiter || "/#?"), "]+?");
+ var result = [];
+ var key = 0;
+ var i = 0;
+ var path = "";
+ var tryConsume = function(type) {
+ if (i < tokens.length && tokens[i].type === type)
+ return tokens[i++].value;
+ };
+ var mustConsume = function(type) {
+ var value2 = tryConsume(type);
+ if (value2 !== void 0)
+ return value2;
+ var _a2 = tokens[i], nextType = _a2.type, index = _a2.index;
+ throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type));
+ };
+ var consumeText = function() {
+ var result2 = "";
+ var value2;
+ while (value2 = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR")) {
+ result2 += value2;
+ }
+ return result2;
+ };
+ while (i < tokens.length) {
+ var char = tryConsume("CHAR");
+ var name = tryConsume("NAME");
+ var pattern = tryConsume("PATTERN");
+ if (name || pattern) {
+ var prefix = char || "";
+ if (prefixes.indexOf(prefix) === -1) {
+ path += prefix;
+ prefix = "";
+ }
+ if (path) {
+ result.push(path);
+ path = "";
+ }
+ result.push({
+ name: name || key++,
+ prefix,
+ suffix: "",
+ pattern: pattern || defaultPattern,
+ modifier: tryConsume("MODIFIER") || ""
+ });
+ continue;
+ }
+ var value = char || tryConsume("ESCAPED_CHAR");
+ if (value) {
+ path += value;
+ continue;
+ }
+ if (path) {
+ result.push(path);
+ path = "";
+ }
+ var open = tryConsume("OPEN");
+ if (open) {
+ var prefix = consumeText();
+ var name_1 = tryConsume("NAME") || "";
+ var pattern_1 = tryConsume("PATTERN") || "";
+ var suffix = consumeText();
+ mustConsume("CLOSE");
+ result.push({
+ name: name_1 || (pattern_1 ? key++ : ""),
+ pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1,
+ prefix,
+ suffix,
+ modifier: tryConsume("MODIFIER") || ""
+ });
+ continue;
+ }
+ mustConsume("END");
+ }
+ return result;
+}
+function match(str, options) {
+ var keys = [];
+ var re = pathToRegexp(str, keys, options);
+ return regexpToFunction(re, keys, options);
+}
+function regexpToFunction(re, keys, options) {
+ if (options === void 0) {
+ options = {};
+ }
+ var _a = options.decode, decode = _a === void 0 ? function(x) {
+ return x;
+ } : _a;
+ return function(pathname) {
+ var m = re.exec(pathname);
+ if (!m)
+ return false;
+ var path = m[0], index = m.index;
+ var params = /* @__PURE__ */ Object.create(null);
+ var _loop_1 = function(i2) {
+ if (m[i2] === void 0)
+ return "continue";
+ var key = keys[i2 - 1];
+ if (key.modifier === "*" || key.modifier === "+") {
+ params[key.name] = m[i2].split(key.prefix + key.suffix).map(function(value) {
+ return decode(value, key);
+ });
+ } else {
+ params[key.name] = decode(m[i2], key);
+ }
+ };
+ for (var i = 1; i < m.length; i++) {
+ _loop_1(i);
+ }
+ return { path, index, params };
+ };
+}
+function escapeString(str) {
+ return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
+}
+function flags(options) {
+ return options && options.sensitive ? "" : "i";
+}
+function regexpToRegexp(path, keys) {
+ if (!keys)
+ return path;
+ var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
+ var index = 0;
+ var execResult = groupsRegex.exec(path.source);
+ while (execResult) {
+ keys.push({
+ // Use parenthesized substring match if available, index otherwise
+ name: execResult[1] || index++,
+ prefix: "",
+ suffix: "",
+ modifier: "",
+ pattern: ""
+ });
+ execResult = groupsRegex.exec(path.source);
+ }
+ return path;
+}
+function arrayToRegexp(paths, keys, options) {
+ var parts = paths.map(function(path) {
+ return pathToRegexp(path, keys, options).source;
+ });
+ return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options));
+}
+function stringToRegexp(path, keys, options) {
+ return tokensToRegexp(parse(path, options), keys, options);
+}
+function tokensToRegexp(tokens, keys, options) {
+ if (options === void 0) {
+ options = {};
+ }
+ var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function(x) {
+ return x;
+ } : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f;
+ var endsWithRe = "[".concat(escapeString(endsWith), "]|$");
+ var delimiterRe = "[".concat(escapeString(delimiter), "]");
+ var route = start ? "^" : "";
+ for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
+ var token = tokens_1[_i];
+ if (typeof token === "string") {
+ route += escapeString(encode(token));
+ } else {
+ var prefix = escapeString(encode(token.prefix));
+ var suffix = escapeString(encode(token.suffix));
+ if (token.pattern) {
+ if (keys)
+ keys.push(token);
+ if (prefix || suffix) {
+ if (token.modifier === "+" || token.modifier === "*") {
+ var mod = token.modifier === "*" ? "?" : "";
+ route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod);
+ } else {
+ route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier);
+ }
+ } else {
+ if (token.modifier === "+" || token.modifier === "*") {
+ route += "((?:".concat(token.pattern, ")").concat(token.modifier, ")");
+ } else {
+ route += "(".concat(token.pattern, ")").concat(token.modifier);
+ }
+ }
+ } else {
+ route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier);
+ }
+ }
+ }
+ if (end) {
+ if (!strict)
+ route += "".concat(delimiterRe, "?");
+ route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")");
+ } else {
+ var endToken = tokens[tokens.length - 1];
+ var isEndDelimited = typeof endToken === "string" ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1 : endToken === void 0;
+ if (!strict) {
+ route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?");
+ }
+ if (!isEndDelimited) {
+ route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")");
+ }
+ }
+ return new RegExp(route, flags(options));
+}
+function pathToRegexp(path, keys, options) {
+ if (path instanceof RegExp)
+ return regexpToRegexp(path, keys);
+ if (Array.isArray(path))
+ return arrayToRegexp(path, keys, options);
+ return stringToRegexp(path, keys, options);
+}
+
+// ../../../../../../../Library/pnpm/global/5/.pnpm/wrangler@3.65.1/node_modules/wrangler/templates/pages-template-worker.ts
+var escapeRegex = /[.+?^${}()|[\]\\]/g;
+function* executeRequest(request) {
+ const requestPath = new URL(request.url).pathname;
+ for (const route of [...routes].reverse()) {
+ if (route.method && route.method !== request.method) {
+ continue;
+ }
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
+ end: false
+ });
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
+ end: false
+ });
+ const matchResult = routeMatcher(requestPath);
+ const mountMatchResult = mountMatcher(requestPath);
+ if (matchResult && mountMatchResult) {
+ for (const handler of route.middlewares.flat()) {
+ yield {
+ handler,
+ params: matchResult.params,
+ path: mountMatchResult.path
+ };
+ }
+ }
+ }
+ for (const route of routes) {
+ if (route.method && route.method !== request.method) {
+ continue;
+ }
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
+ end: true
+ });
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
+ end: false
+ });
+ const matchResult = routeMatcher(requestPath);
+ const mountMatchResult = mountMatcher(requestPath);
+ if (matchResult && mountMatchResult && route.modules.length) {
+ for (const handler of route.modules.flat()) {
+ yield {
+ handler,
+ params: matchResult.params,
+ path: matchResult.path
+ };
+ }
+ break;
+ }
+ }
+}
+var pages_template_worker_default = {
+ async fetch(originalRequest, env, workerContext) {
+ let request = originalRequest;
+ const handlerIterator = executeRequest(request);
+ let data = {};
+ let isFailOpen = false;
+ const next = async (input, init) => {
+ if (input !== void 0) {
+ let url = input;
+ if (typeof input === "string") {
+ url = new URL(input, request.url).toString();
+ }
+ request = new Request(url, init);
+ }
+ const result = handlerIterator.next();
+ if (result.done === false) {
+ const { handler, params, path } = result.value;
+ const context = {
+ request: new Request(request.clone()),
+ functionPath: path,
+ next,
+ params,
+ get data() {
+ return data;
+ },
+ set data(value) {
+ if (typeof value !== "object" || value === null) {
+ throw new Error("context.data must be an object");
+ }
+ data = value;
+ },
+ env,
+ waitUntil: workerContext.waitUntil.bind(workerContext),
+ passThroughOnException: () => {
+ isFailOpen = true;
+ }
+ };
+ const response = await handler(context);
+ if (!(response instanceof Response)) {
+ throw new Error("Your Pages function should return a Response");
+ }
+ return cloneResponse(response);
+ } else if ("ASSETS") {
+ const response = await env["ASSETS"].fetch(request);
+ return cloneResponse(response);
+ } else {
+ const response = await fetch(request);
+ return cloneResponse(response);
+ }
+ };
+ try {
+ return await next();
+ } catch (error) {
+ if (isFailOpen) {
+ const response = await env["ASSETS"].fetch(request);
+ return cloneResponse(response);
+ }
+ throw error;
+ }
+ }
+};
+var cloneResponse = (response) => (
+ // https://fetch.spec.whatwg.org/#null-body-status
+ new Response(
+ [101, 204, 205, 304].includes(response.status) ? null : response.body,
+ response
+ )
+);
+export {
+ pages_template_worker_default as default
+};
+
+------formdata-undici-075560669371--
\ No newline at end of file
diff --git a/build_pypi.sh b/build_pypi.sh
new file mode 100755
index 0000000..11e7e1a
--- /dev/null
+++ b/build_pypi.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+set -e
+
+cd "$(dirname ${0})"
+
+# You should update the version in package.json before running this script
+FILE="gradio/package.json"
+new_version=$(python -c "import json; f = open('$FILE', 'r'); data = json.load(f); print(data['version']); f.close();")
+GRADIO_VERSION=$new_version
+
+rm -rf gradio/templates/frontend
+pnpm i --frozen-lockfile --ignore-scripts
+GRADIO_VERSION=$new_version pnpm build
+python3 scripts/download_offline_assets.py
+aws s3 cp gradio/templates/frontend "s3://gradio/${new_version}/" --recursive --region us-west-2
+
+rm -rf dist/*
+rm -rf build/*
+python3 -m build
diff --git a/client/js/CHANGELOG.md b/client/js/CHANGELOG.md
new file mode 100644
index 0000000..f0a4166
--- /dev/null
+++ b/client/js/CHANGELOG.md
@@ -0,0 +1,1049 @@
+# @gradio/client
+
+## 2.3.1
+
+### Fixes
+
+- [#13588](https://github.com/gradio-app/gradio/pull/13588) [`2e80558`](https://github.com/gradio-app/gradio/commit/2e805588c8dc84a4a584898fd43b267c72209f78) - Fire `state.change()` for streaming (`.stream()`) events. Thanks @hysts!
+- [#13581](https://github.com/gradio-app/gradio/pull/13581) [`461d82d`](https://github.com/gradio-app/gradio/commit/461d82df2689ec0c53e84b8722eaeae58fd2a6ae) - Use same-origin credentials in the JS client so cross-origin embeds work again. Thanks @hysts!
+- [#13579](https://github.com/gradio-app/gradio/pull/13579) [`3d26f41`](https://github.com/gradio-app/gradio/commit/3d26f41c4a3bfa31ea01c4ae0d082a9fc9ed0bc7) - Make `Client.predict()` reject its returned promise on unknown endpoints so the error is catchable. Thanks @abidlabs!
+
+## 2.3.0
+
+### Features
+
+- [#13526](https://github.com/gradio-app/gradio/pull/13526) [`53cb4ca`](https://github.com/gradio-app/gradio/commit/53cb4cae1ec3521e9170d12867253516413ba37a) - Run `pnpm lint` and `pnpm ts:check` on CI. Thanks @abidlabs!
+
+## 2.2.2
+
+### Fixes
+
+- [#13500](https://github.com/gradio-app/gradio/pull/13500) [`751a397`](https://github.com/gradio-app/gradio/commit/751a397ce519732526863ee84e311f7f64195f9d) - Close iterator on terminal error in JS client. Thanks @hysts!
+
+## 2.2.1
+
+### Fixes
+
+- [#13403](https://github.com/gradio-app/gradio/pull/13403) [`df56862`](https://github.com/gradio-app/gradio/commit/df56862a1531fc372a3248716dc4bd07e6db1c52) - [js-client] close submit iterator on next/close race. Thanks @hysts!
+
+## 2.2.0
+
+### Features
+
+- [#13176](https://github.com/gradio-app/gradio/pull/13176) [`45c4ecd`](https://github.com/gradio-app/gradio/commit/45c4ecd25fc83c7ee0210d216df1bc4bd509e622) - Add `@gr.cache()` decorator for caching deterministic functions, as as well as a lower-level `gr.Cache` that uses dependency injection. Thanks @abidlabs!
+
+### Fixes
+
+- [#13210](https://github.com/gradio-app/gradio/pull/13210) [`4005b93`](https://github.com/gradio-app/gradio/commit/4005b93dd59d7e0d144619986f01e900659b3d0b) - Fix ZeroGPU handling for `gr.Server`. Thanks @abidlabs!
+
+## 2.1.0
+
+### Features
+
+- [#12929](https://github.com/gradio-app/gradio/pull/12929) [`978bc6e`](https://github.com/gradio-app/gradio/commit/978bc6ea5094aa11e10994cdd662c4c663a86a83) - Add server functions support to gr.HTML. Thanks @aliabid94!
+- [#12907](https://github.com/gradio-app/gradio/pull/12907) [`3e625a0`](https://github.com/gradio-app/gradio/commit/3e625a0ecfab6e74b7561b68adbe55341ecbc47a) - Better error handling when connection to server is lost. Thanks @abidlabs!
+
+## 2.0.4
+
+### Fixes
+
+- [#12828](https://github.com/gradio-app/gradio/pull/12828) [`151cbd1`](https://github.com/gradio-app/gradio/commit/151cbd1aac0da3aeb5f0b7b33585223d2bc47138) - Fix private spaces. Thanks @freddyaboulton!
+- [#12835](https://github.com/gradio-app/gradio/pull/12835) [`5ecf6d2`](https://github.com/gradio-app/gradio/commit/5ecf6d27c50a20e2329c1aca0634924479ceb6cd) - Fix CSS root in spaces. Thanks @freddyaboulton!
+- [#12817](https://github.com/gradio-app/gradio/pull/12817) [`05acc66`](https://github.com/gradio-app/gradio/commit/05acc6627de866db6a742e0540c2733041d76a86) - Fix Login. Thanks @freddyaboulton!
+
+## 2.0.3
+
+### Fixes
+
+- [#12773](https://github.com/gradio-app/gradio/pull/12773) [`6edcea5`](https://github.com/gradio-app/gradio/commit/6edcea5d74be661d37c70ccca08900be2ceb8e1e) - Fix dev mode. Thanks @pngwn!
+- [#12472](https://github.com/gradio-app/gradio/pull/12472) [`9a2bc0d`](https://github.com/gradio-app/gradio/commit/9a2bc0dacdd2b3f670fae815093c61ad08eee7e3) - Re-enable SSR mode. Thanks @pngwn!
+
+## 2.0.2
+
+### Fixes
+
+- [#12608](https://github.com/gradio-app/gradio/pull/12608) [`ab20c59`](https://github.com/gradio-app/gradio/commit/ab20c592e1fab33e96ab2698bbbb942d0972501b) - feat(client): add generic type parameter to predict() method. Thanks @majiayu000!
+
+## 2.0.1
+
+### Fixes
+
+- [#12600](https://github.com/gradio-app/gradio/pull/12600) [`1fafaba`](https://github.com/gradio-app/gradio/commit/1fafabaace315b1c699855cadb69eb17488de957) - Add x-gradio-user-header. Thanks @freddyaboulton!
+
+## 2.0.0
+
+### Features
+
+- [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - [No Merge] Gradio 6.0
+- [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Pass component props as input
+- [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Rename show_api
+- [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Fix error toast type definition for exception handling
+- [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Fix queue false render issue
+- [#11908](https://github.com/gradio-app/gradio/pull/11908) [`029034f`](https://github.com/gradio-app/gradio/commit/029034f7853ea018d110efe9b7e2ef7d1407091c) - Fix some e2e tests
+- [#12438](https://github.com/gradio-app/gradio/pull/12438) [`25ffc03`](https://github.com/gradio-app/gradio/commit/25ffc0398f8feb43d817c02b2ab970c16de6d797) - Svelte5 migration and bugfix
+
+## 2.0.0-dev.2
+
+### Features
+
+- [#12363](https://github.com/gradio-app/gradio/pull/12363) [`4845bfd`](https://github.com/gradio-app/gradio/commit/4845bfdab53096a609d80485e87d5cc70f3f0521) - Fix queue false render issue. Thanks @freddyaboulton!
+- [#12378](https://github.com/gradio-app/gradio/pull/12378) [`f5e5344`](https://github.com/gradio-app/gradio/commit/f5e5344db61eba28253719928577dc2c495958e1) - Fix some e2e tests. Thanks @freddyaboulton!
+
+## 2.0.0-dev.1
+
+### Features
+
+- [#12069](https://github.com/gradio-app/gradio/pull/12069) [`9de88ca`](https://github.com/gradio-app/gradio/commit/9de88ca470ce529366d259f0deaa955f658000b9) - Rename show_api. Thanks @freddyaboulton!
+
+## 2.0.0-dev.0
+
+### Features
+
+- [#11908](https://github.com/gradio-app/gradio/pull/11908) [`53d9098`](https://github.com/gradio-app/gradio/commit/53d9098cca378f6ebff9dec15d2faa8a3d2fb510) - [No Merge] Gradio 6.0. Thanks @freddyaboulton!
+
+## 1.19.1
+
+### Fixes
+
+- [#11955](https://github.com/gradio-app/gradio/pull/11955) [`64046cc`](https://github.com/gradio-app/gradio/commit/64046ccd76a41f06ef9eed0e3aa1ec66348f55bd) - Fix Multi-page demo in Dev Mode. Thanks @ftoh!
+
+## 1.19.0
+
+### Features
+
+- [#11858](https://github.com/gradio-app/gradio/pull/11858) [`3f8ea13`](https://github.com/gradio-app/gradio/commit/3f8ea13a8ca92abf0ad34392e403a449fda3c6c2) - remove lite. Thanks @pngwn!
+- [#11902](https://github.com/gradio-app/gradio/pull/11902) [`6d39644`](https://github.com/gradio-app/gradio/commit/6d39644ee1000b04728eb4909cee405e7ee3b5b2) - Add navbar visibility controls and customization options. Thanks @abidlabs!
+
+### Fixes
+
+- [#11784](https://github.com/gradio-app/gradio/pull/11784) [`d9dd3f5`](https://github.com/gradio-app/gradio/commit/d9dd3f54b7fb34cf7118e549d39fc63937ca3489) - Add "hidden" option to component's `visible` kwarg to render but visually hide the component. Thanks @pngwn!
+
+## 1.18.0
+
+### Features
+
+- [#11814](https://github.com/gradio-app/gradio/pull/11814) [`013784a`](https://github.com/gradio-app/gradio/commit/013784a7086047651e8e661a38bde7d5c7f10db7) - add validation support. Thanks @pngwn!
+
+## 1.17.1
+
+### Fixes
+
+- [#11709](https://github.com/gradio-app/gradio/pull/11709) [`48f1dcf`](https://github.com/gradio-app/gradio/commit/48f1dcf58cd6437daa03a8cf1c9942aae2cec421) - Fix output paths for the @gradio/client browser build. Thanks @pngwn!
+
+## 1.17.0
+
+### Features
+
+- [#11691](https://github.com/gradio-app/gradio/pull/11691) [`2605a99`](https://github.com/gradio-app/gradio/commit/2605a99bf29bebbbb0a97cc8e0015b5bf8d8e79b) - Add .failure() event listener for error handling. Thanks @elanehan!
+
+## 1.16.0
+
+### Features
+
+- [#11662](https://github.com/gradio-app/gradio/pull/11662) [`a78f5fa`](https://github.com/gradio-app/gradio/commit/a78f5fa466a4b11ffaaafc5099a64df49afb6e41) - Gradio vibe editor. Thanks @aliabid94!
+
+## 1.15.7
+
+### Fixes
+
+- [#11604](https://github.com/gradio-app/gradio/pull/11604) [`83e14bc`](https://github.com/gradio-app/gradio/commit/83e14bcfa653e2ec477f0d09b497adf605734e46) - Fix event cancelling bug. Thanks @freddyaboulton!
+
+## 1.15.6
+
+### Features
+
+- [#11543](https://github.com/gradio-app/gradio/pull/11543) [`ac95ac0`](https://github.com/gradio-app/gradio/commit/ac95ac0d8c2e65d1632376e632fb7d16131334b6) - Connection handling messaging. Thanks @aliabid94!
+
+## 1.15.5
+
+### Fixes
+
+- [#11430](https://github.com/gradio-app/gradio/pull/11430) [`4d4cd4b`](https://github.com/gradio-app/gradio/commit/4d4cd4b08d6daafc66e0189a2b274916da00d480) - fix exports + add browser build. Thanks @pngwn!
+- [#11435](https://github.com/gradio-app/gradio/pull/11435) [`50d43a8`](https://github.com/gradio-app/gradio/commit/50d43a8e8bc2a4862668a5d46a5e6482b4f75fe3) - SSR Auth Fix. Thanks @dawoodkhan82!
+
+## 1.15.4
+
+### Fixes
+
+- [#11432](https://github.com/gradio-app/gradio/pull/11432) [`dd1eee5`](https://github.com/gradio-app/gradio/commit/dd1eee5f9cd3d70773912fd6444d093bdcea321a) - Fix bug where cancelling an event would close the event stream. Thanks @freddyaboulton!
+- [#11421](https://github.com/gradio-app/gradio/pull/11421) [`c2acf6e`](https://github.com/gradio-app/gradio/commit/c2acf6e33025fe7bbfe0660c182006651cc95090) - Preserve value in reload mode. Thanks @aliabid94!
+
+## 1.15.3
+
+### Fixes
+
+- [#11387](https://github.com/gradio-app/gradio/pull/11387) [`8245afc`](https://github.com/gradio-app/gradio/commit/8245afc669501e1e5f0d619f452455f68a3b7667) - Define root URL in frontend. Thanks @aliabid94!
+
+## 1.15.2
+
+### Fixes
+
+- [#11325](https://github.com/gradio-app/gradio/pull/11325) [`2b571e1`](https://github.com/gradio-app/gradio/commit/2b571e13afdc8031ce9c1291abf0fc7062340064) - Fix image streaming - wait for ws to open. Thanks @freddyaboulton!
+
+## 1.15.1
+
+### Fixes
+
+- [#11243](https://github.com/gradio-app/gradio/pull/11243) [`35afa21`](https://github.com/gradio-app/gradio/commit/35afa21f0d6647e4fbda711f3f22a2fd54eedaf9) - Only show parameters warning when valid `endpoint_info` exists. Thanks @hannahblair!
+
+## 1.15.0
+
+### Features
+
+- [#11155](https://github.com/gradio-app/gradio/pull/11155) [`30a1d9e`](https://github.com/gradio-app/gradio/commit/30a1d9e2ac3013d9c844b236410010bce97ffaf5) - Improvements to MCP page. Thanks @abidlabs!
+- [#11047](https://github.com/gradio-app/gradio/pull/11047) [`6d4b8a7`](https://github.com/gradio-app/gradio/commit/6d4b8a7f10daefc9c79aa224635da23fbaeebb76) - Implement custom i18n. Thanks @hannahblair!
+
+## 1.14.2
+
+### Fixes
+
+- [#11017](https://github.com/gradio-app/gradio/pull/11017) [`734b309`](https://github.com/gradio-app/gradio/commit/734b3099d79647695e635d87726666d4b28d1bcf) - Include HF token in stream requests. Thanks @nostalgebraist!
+
+## 1.14.1
+
+### Features
+
+- [#10890](https://github.com/gradio-app/gradio/pull/10890) [`01b88c7`](https://github.com/gradio-app/gradio/commit/01b88c7fdedb413ba92ef6191967a8aed25e185f) - Improve API error handling in JS Client. Thanks @l2dy!
+
+## 1.14.0
+
+### Features
+
+- [#10834](https://github.com/gradio-app/gradio/pull/10834) [`c05610c`](https://github.com/gradio-app/gradio/commit/c05610c87dd7f9e9fe5d0aed2fe93e40fdd32648) - Add Deep Links. Thanks @freddyaboulton!
+
+## 1.13.1
+
+### Features
+
+- [#10694](https://github.com/gradio-app/gradio/pull/10694) [`16244f3`](https://github.com/gradio-app/gradio/commit/16244f3c1cb1a65ac1f719142f8fab67512fbb25) - Event Listeners in gradio sketch. Thanks @aliabid94!
+
+### Fixes
+
+- [#10719](https://github.com/gradio-app/gradio/pull/10719) [`b710d7c`](https://github.com/gradio-app/gradio/commit/b710d7cf13c1277fd18c7809cad0f707b880ef70) - Fix error display. Thanks @aliabid94!
+
+## 1.13.0
+
+### Features
+
+- [#10500](https://github.com/gradio-app/gradio/pull/10500) [`16d419b`](https://github.com/gradio-app/gradio/commit/16d419b9f1f18ae4507d18a4739eb83ac4f3fae9) - Allow functions that solely update component properties to run in the frontend by setting `js=True`. Thanks @abidlabs!
+
+## 1.12.0
+
+### Features
+
+- [#10492](https://github.com/gradio-app/gradio/pull/10492) [`29880d5`](https://github.com/gradio-app/gradio/commit/29880d51fbe7fbd222b0765a83c95134dc7d0e90) - Allow showing progress updates on arbitrary components. Thanks @abidlabs!
+
+### Fixes
+
+- [#10547](https://github.com/gradio-app/gradio/pull/10547) [`083d68b`](https://github.com/gradio-app/gradio/commit/083d68b223be82a65f18c553df9ae690a8118a49) - quick_fix_client. Thanks @aliabid94!
+
+## 1.11.0
+
+### Features
+
+- [#10433](https://github.com/gradio-app/gradio/pull/10433) [`2e8dc74`](https://github.com/gradio-app/gradio/commit/2e8dc74f751be02f7217f78d241806b42fcdca04) - Allow building multipage Gradio apps. Thanks @aliabid94!
+
+## 1.10.0
+
+### Features
+
+- [#10270](https://github.com/gradio-app/gradio/pull/10270) [`bb11a2a`](https://github.com/gradio-app/gradio/commit/bb11a2a702ca04fde245e7d54d155cbcbde7791e) - [ZeroGPU] Handshake-based postMessage. Thanks @cbensimon!
+
+### Fixes
+
+- [#10332](https://github.com/gradio-app/gradio/pull/10332) [`e742dcc`](https://github.com/gradio-app/gradio/commit/e742dcccb376692c9ddd5a6c251080e7c5936574) - Allow users to add a custom API route. Thanks @aliabid94!
+
+## 1.9.0
+
+### Features
+
+- [#10262](https://github.com/gradio-app/gradio/pull/10262) [`f3bedd4`](https://github.com/gradio-app/gradio/commit/f3bedd4011bdfdecc952eb1275a9dd96af3e8d71) - add gr.Success and update windows contributing. Thanks @not-lain!
+- [#10254](https://github.com/gradio-app/gradio/pull/10254) [`da07707`](https://github.com/gradio-app/gradio/commit/da0770748db9ea40194a43c9138ee2c6536b1247) - Add a `settings` link to the footer with i18n options & pwa instructions. Thanks @abidlabs!
+
+## 1.8.0
+
+### Features
+
+- [#9930](https://github.com/gradio-app/gradio/pull/9930) [`eae345e`](https://github.com/gradio-app/gradio/commit/eae345e5fde39aea220b57c6a954cd7d72ff32d5) - Allow settings custom headers in js client. Thanks @elgiano!
+- [#9950](https://github.com/gradio-app/gradio/pull/9950) [`fc06fe4`](https://github.com/gradio-app/gradio/commit/fc06fe41f015678a0545f4e5c99f6ae2704f0031) - Add ability to read and write from LocalStorage. Thanks @abidlabs!
+
+## 1.7.1
+
+### Fixes
+
+- [#9814](https://github.com/gradio-app/gradio/pull/9814) [`6505d42`](https://github.com/gradio-app/gradio/commit/6505d4289a3e3d27d9133b1c8af41697fdc1476d) - support gradio apps on spaces served on subpaths. Thanks @pngwn!
+
+## 1.7.0
+
+### Features
+
+- [#9681](https://github.com/gradio-app/gradio/pull/9681) [`2ed2361`](https://github.com/gradio-app/gradio/commit/2ed236187a9aab18e17fc4a8079eddef7dd195a5) - Allow setting title in gr.Info/Warning/Error. Thanks @ABucket!
+
+## 1.6.0
+
+### Features
+
+- [#8843](https://github.com/gradio-app/gradio/pull/8843) [`6f95286`](https://github.com/gradio-app/gradio/commit/6f95286337459efbccb95c9cfac63355669df9ee) - Disable liking user message in chatbot by default but make it configurable
+- [#8843](https://github.com/gradio-app/gradio/pull/8843) [`6f95286`](https://github.com/gradio-app/gradio/commit/6f95286337459efbccb95c9cfac63355669df9ee) - Open audio/image input stream only when queue is ready
+- [#8843](https://github.com/gradio-app/gradio/pull/8843) [`6f95286`](https://github.com/gradio-app/gradio/commit/6f95286337459efbccb95c9cfac63355669df9ee) - Send Streaming data over Websocket if possible. Also support base64 output format for images.
+- [#8843](https://github.com/gradio-app/gradio/pull/8843) [`6f95286`](https://github.com/gradio-app/gradio/commit/6f95286337459efbccb95c9cfac63355669df9ee) - Streaming inputs for 5.0
+- [#8843](https://github.com/gradio-app/gradio/pull/8843) [`6f95286`](https://github.com/gradio-app/gradio/commit/6f95286337459efbccb95c9cfac63355669df9ee) - fix SSR apps on spaces
+- [#8843](https://github.com/gradio-app/gradio/pull/8843) [`6f95286`](https://github.com/gradio-app/gradio/commit/6f95286337459efbccb95c9cfac63355669df9ee) - Ssr part 2
+- [#8843](https://github.com/gradio-app/gradio/pull/8843) [`6f95286`](https://github.com/gradio-app/gradio/commit/6f95286337459efbccb95c9cfac63355669df9ee) - prefix api routes
+
+### Fixes
+
+
+- [#8843](https://github.com/gradio-app/gradio/pull/8843) [`6f95286`](https://github.com/gradio-app/gradio/commit/6f95286337459efbccb95c9cfac63355669df9ee) - Trigger state change event on iterators
+
+## 1.6.0-beta.4
+
+### Features
+
+- [#9483](https://github.com/gradio-app/gradio/pull/9483) [`8dc7c12`](https://github.com/gradio-app/gradio/commit/8dc7c12389311b60efcde1b9d3e3668a34d2dc00) - Send Streaming data over Websocket if possible. Also support base64 output format for images. Thanks @freddyaboulton!
+
+## 1.6.0-beta.3
+
+### Features
+
+- [#9412](https://github.com/gradio-app/gradio/pull/9412) [`c2c2fd9`](https://github.com/gradio-app/gradio/commit/c2c2fd989348f826566773c07c0e0bda200199ff) - fix SSR apps on spaces. Thanks @pngwn!
+
+## 1.6.0-beta.2
+
+### Features
+
+- [#9323](https://github.com/gradio-app/gradio/pull/9323) [`06babda`](https://github.com/gradio-app/gradio/commit/06babda0395fd3fbd323c1c3cb33704ecfd6deb0) - Disable liking user message in chatbot by default but make it configurable. Thanks @freddyaboulton!
+- [#9339](https://github.com/gradio-app/gradio/pull/9339) [`4c8c6f2`](https://github.com/gradio-app/gradio/commit/4c8c6f2fe603081941c5fdc43f48a0632b9f31ad) - Ssr part 2. Thanks @pngwn!
+
+### Fixes
+
+- [#9299](https://github.com/gradio-app/gradio/pull/9299) [`aa35b07`](https://github.com/gradio-app/gradio/commit/aa35b0788e613fdd45446d267513e6f94fa208ea) - Trigger state change event on iterators. Thanks @freddyaboulton!
+
+## 1.6.0-beta.1
+
+### Features
+
+- [#9200](https://github.com/gradio-app/gradio/pull/9200) [`2e179d3`](https://github.com/gradio-app/gradio/commit/2e179d35be6ed60a5a6bfc7303178d63e41781ad) - prefix api routes. Thanks @pngwn!
+
+## 1.6.0-beta.0
+
+### Features
+
+- [#9149](https://github.com/gradio-app/gradio/pull/9149) [`3d7a9b8`](https://github.com/gradio-app/gradio/commit/3d7a9b81f6fef06187eca832471dc1692eb493a0) - Open audio/image input stream only when queue is ready. Thanks @freddyaboulton!
+- [#8941](https://github.com/gradio-app/gradio/pull/8941) [`97a7bf6`](https://github.com/gradio-app/gradio/commit/97a7bf66a79179d1b91a3199d68e5c11216ca500) - Streaming inputs for 5.0. Thanks @freddyaboulton!
+
+## 1.5.2
+
+### Fixes
+
+- [#9163](https://github.com/gradio-app/gradio/pull/9163) [`2b6cbf2`](https://github.com/gradio-app/gradio/commit/2b6cbf25908e42cf027324e54ef2cc0baad11a91) - fix exports and generate types. Thanks @pngwn!
+
+## 1.5.1
+
+### Features
+
+- [#9118](https://github.com/gradio-app/gradio/pull/9118) [`e1c404d`](https://github.com/gradio-app/gradio/commit/e1c404da1143fb52b659d03e028bdba1badf443d) - setup npm-previews of all packages. Thanks @pngwn!
+
+## 1.5.0
+
+### Features
+
+- [#8965](https://github.com/gradio-app/gradio/pull/8965) [`d30432e`](https://github.com/gradio-app/gradio/commit/d30432e9c6d4cc1e5cfd989a1a3ae4aba7e21290) - harden CI. Thanks @pngwn!
+
+### Fixes
+
+- [#8847](https://github.com/gradio-app/gradio/pull/8847) [`4d8a473`](https://github.com/gradio-app/gradio/commit/4d8a473632e388a312aee5c705b3c1f79853441b) - fix: wrong named param check for js client. Thanks @freddyaboulton!
+
+## 1.4.0
+
+### Features
+
+- [#8816](https://github.com/gradio-app/gradio/pull/8816) [`9ee6839`](https://github.com/gradio-app/gradio/commit/9ee6839f94d23d685a800ed3a275206e0b0e48f6) - Change optionality of the `data` param in `submit` + `predict`. Thanks @hannahblair!
+
+### Fixes
+
+- [#8820](https://github.com/gradio-app/gradio/pull/8820) [`5050b36`](https://github.com/gradio-app/gradio/commit/5050b36221e75a18d8a5d4f74a725e70768a4c4a) - fix: wrong named param check for js client. Thanks @JacobLinCool!
+
+## 1.3.0
+
+### Fixes
+
+- [#8699](https://github.com/gradio-app/gradio/pull/8699) [`012da05`](https://github.com/gradio-app/gradio/commit/012da05287846d94beb0ecdc28d7fbc48c4248ff) - Ensure JS client `status_callback` functionality works and improve status messages. Thanks @hannahblair!
+- [#8505](https://github.com/gradio-app/gradio/pull/8505) [`2943d6d`](https://github.com/gradio-app/gradio/commit/2943d6d68847314885dc6c5c0247083116017ca0) - Add Timer component. Thanks @aliabid94!
+- [#8715](https://github.com/gradio-app/gradio/pull/8715) [`a6b3c6c`](https://github.com/gradio-app/gradio/commit/a6b3c6ce4e1d06253860c72740024a9138e3a93a) - Ensure `@gradio/client`'s `submit` iterator releases as expected. Thanks @pngwn!
+- [#8716](https://github.com/gradio-app/gradio/pull/8716) [`e834d30`](https://github.com/gradio-app/gradio/commit/e834d302e44f7a54565129bf2c11acf4e882a59b) - ensure `@gradio/client` always returns the correct data. Thanks @pngwn!
+- [#8714](https://github.com/gradio-app/gradio/pull/8714) [`1b5b5b0`](https://github.com/gradio-app/gradio/commit/1b5b5b0b43e69ee84f3baad2aae59ffc9c4d995a) - Bind `fetch` and `stream` in JS client. Thanks @hannahblair!
+- [#8720](https://github.com/gradio-app/gradio/pull/8720) [`936c713`](https://github.com/gradio-app/gradio/commit/936c7137a99ef59efdf75bae5dd27eea2ac1f577) - Documents auth in the guides, in the view API page, and also types the Blocks.config object. Thanks @abidlabs!
+
+## 1.2.1
+
+### Features
+
+- [#8649](https://github.com/gradio-app/gradio/pull/8649) [`4b6c8b1`](https://github.com/gradio-app/gradio/commit/4b6c8b1c004cee67345a7f103ba2dc8e90b82e6c) - ensure `File` objects are handled in JS client `handle_file`. Thanks @hannahblair!
+
+## 1.2.0
+
+### Features
+
+- [#8489](https://github.com/gradio-app/gradio/pull/8489) [`c2a0d05`](https://github.com/gradio-app/gradio/commit/c2a0d056d679d90631d9ccd944dadd67e7e03b7f) - Control Display of Error, Info, Warning. Thanks @freddyaboulton!
+- [#8571](https://github.com/gradio-app/gradio/pull/8571) [`a77877f`](https://github.com/gradio-app/gradio/commit/a77877f62df7c610fcfac7b3b00e186a087c8ec6) - First time loading performance optimization. Thanks @baojianting!
+- [#8600](https://github.com/gradio-app/gradio/pull/8600) [`7289c4b`](https://github.com/gradio-app/gradio/commit/7289c4b036d8a78c48f8c9e66ba998e6730e80d2) - Add credentials: include and Cookie header to prevent 401 error. Thanks @yinkiu602!
+- [#8522](https://github.com/gradio-app/gradio/pull/8522) [`bdaa678`](https://github.com/gradio-app/gradio/commit/bdaa678d0c0a22250b41104f32e9121f98dc7437) - add handle_file docs. Thanks @pngwn!
+
+### Fixes
+
+- [#8521](https://github.com/gradio-app/gradio/pull/8521) [`900cf25`](https://github.com/gradio-app/gradio/commit/900cf25256a5b0563860097d69aac28b6afbfd8b) - Ensure frontend functions work when they don't return a value. Thanks @pngwn!
+- [#8548](https://github.com/gradio-app/gradio/pull/8548) [`7fc0f51`](https://github.com/gradio-app/gradio/commit/7fc0f5149bb8d31f3d01b4151b478070499751ee) - Fix reload mode by implementing `close` on the client. Thanks @freddyaboulton!
+
+## 1.1.1
+
+### Features
+
+- [#8499](https://github.com/gradio-app/gradio/pull/8499) [`c5f6e77`](https://github.com/gradio-app/gradio/commit/c5f6e7722a197d4706419ade14276ddecf3196f8) - Cache break themes on change. Thanks @aliabid94!
+
+## 1.1.0
+
+### Features
+
+- [#8483](https://github.com/gradio-app/gradio/pull/8483) [`e2271e2`](https://github.com/gradio-app/gradio/commit/e2271e207d98074bf39b02ae3c5443b2f097627d) - documentation for @gradio/client. Thanks @pngwn!
+- [#8485](https://github.com/gradio-app/gradio/pull/8485) [`f8ebace`](https://github.com/gradio-app/gradio/commit/f8ebaceccef60a112603d290d10072ef4e938a6a) - Ensure all status are reported internally when calling `predict`. Thanks @pngwn!
+
+## 1.0.0
+
+### Highlights
+
+#### Clients 1.0 Launch! ([#8468](https://github.com/gradio-app/gradio/pull/8468) [`7cc0a0c`](https://github.com/gradio-app/gradio/commit/7cc0a0c1abea585c3f50ffb1ff78d2b08ddbdd92))
+
+We're excited to unveil the first major release of the Gradio clients.
+We've made it even easier to turn any Gradio application into a production endpoint thanks to the clients' **ergonomic**, **transparent**, and **portable** design.
+
+#### Ergonomic API 💆
+
+**Stream From a Gradio app in 5 lines**
+
+Use the `submit` method to get a job you can iterate over:
+
+```python
+from gradio_client import Client
+
+client = Client("gradio/llm_stream")
+
+for result in client.submit("What's the best UI framework in Python?"):
+ print(result)
+```
+
+```ts
+import { Client } from "@gradio/client";
+
+const client = await Client.connect("gradio/llm_stream")
+const job = client.submit("/predict", {"text": "What's the best UI framework in Python?"})
+
+for await (const msg of job) console.log(msg.data)
+```
+
+**Use the same keyword arguments as the app**
+
+
+```python
+from gradio_client import Client
+
+client = Client("http://127.0.0.1:7860/")
+result = client.predict(
+ message="Hello!!",
+ system_prompt="You are helpful AI.",
+ tokens=10,
+ api_name="/chat"
+)
+print(result)
+```
+
+```ts
+import { Client } from "@gradio/client";
+
+const client = await Client.connect("http://127.0.0.1:7860/");
+const result = await client.predict("/chat", {
+ message: "Hello!!",
+ system_prompt: "Hello!!",
+ tokens: 10,
+});
+
+console.log(result.data);
+```
+
+**Better Error Messages**
+
+If something goes wrong in the upstream app, the client will raise the same exception as the app provided that `show_error=True` in the original app's `launch()` function, or it's a `gr.Error` exception.
+
+#### Transparent Design 🪟
+
+Anything you can do in the UI, you can do with the client:
+* 🔒 Authentication
+* 🛑 Job Cancelling
+* ℹ️ Access Queue Position and API
+* 📕 View the API information
+
+Here's an example showing how to display the queue position of a pending job:
+
+```python
+from gradio_client import Client
+
+client = Client("gradio/diffusion_model")
+
+job = client.submit("A cute cat")
+while not job.done():
+ status = job.status()
+ print(f"Current in position {status.rank} out of {status.queue_size}")
+```
+
+#### Portable Design ⛺️
+
+The client can run from pretty much any python and javascript environment (node, deno, the browser, Service Workers).
+
+Here's an example using the client from a Flask server using gevent:
+
+```python
+from gevent import monkey
+monkey.patch_all()
+
+from gradio_client import Client
+from flask import Flask, send_file
+import time
+
+app = Flask(__name__)
+
+imageclient = Client("gradio/diffusion_model")
+
+@app.route("/gen")
+def gen():
+ result = imageclient.predict(
+ "A cute cat",
+ api_name="/predict"
+ )
+ return send_file(result)
+
+if __name__ == "__main__":
+ app.run(host="0.0.0.0", port=5000)
+```
+
+#### 1.0 Migration Guide and Breaking Changes
+
+**Python**
+- The `serialize` argument of the `Client` class was removed. Has no effect.
+- The `upload_files` argument of the `Client` was removed.
+- All filepaths must be wrapped in the `handle_file` method. Example:
+```python
+from gradio_client import Client, handle_file
+
+client = Client("gradio/image_captioner")
+client.predict(handle_file("cute_cat.jpg"))
+```
+- The `output_dir` argument was removed. It is not specified in the `download_files` argument.
+
+
+**Javascript**
+The client has been redesigned entirely. It was refactored from a function into a class. An instance can now be constructed by awaiting the `connect` method.
+
+```js
+const app = await Client.connect("gradio/whisper")
+```
+The app variable has the same methods as the python class (`submit`, `predict`, `view_api`, `duplicate`).
+
+
+
+#### Additional Changes
+
+- [#8243](https://github.com/gradio-app/gradio/pull/8243) - Set orig_name in python client file uploads.
+- [#8264](https://github.com/gradio-app/gradio/pull/8264) - Make exceptions in the Client more specific.
+- [#8247](https://github.com/gradio-app/gradio/pull/8247) - Fix api recorder.
+- [#8276](https://github.com/gradio-app/gradio/pull/8276) - Fix bug where client could not connect to apps that had self signed certificates.
+- [#8245](https://github.com/gradio-app/gradio/pull/8245) - Cancel server progress from the python client.
+- [#8200](https://github.com/gradio-app/gradio/pull/8200) - Support custom components in gr.load
+- [#8182](https://github.com/gradio-app/gradio/pull/8182) - Convert sse calls in client from async to sync.
+- [#7732](https://github.com/gradio-app/gradio/pull/7732) - Adds support for kwargs and default arguments in the python client, and improves how parameter information is displayed in the "view API" page.
+- [#7888](https://github.com/gradio-app/gradio/pull/7888) - Cache view_api info in server and python client.
+- [#7575](https://github.com/gradio-app/gradio/pull/7575) - Files should now be supplied as `file(...)` in the Client, and some fixes to `gr.load()` as well.
+- [#8401](https://github.com/gradio-app/gradio/pull/8401) - Add CDN installation to JS docs.
+- [#8299](https://github.com/gradio-app/gradio/pull/8299) - Allow JS Client to work with authenticated spaces 🍪.
+- [#8408](https://github.com/gradio-app/gradio/pull/8408) - Connect heartbeat if state created in render. Also fix config cleanup bug #8407.
+- [#8258](https://github.com/gradio-app/gradio/pull/8258) - Improve URL handling in JS Client.
+- [#8322](https://github.com/gradio-app/gradio/pull/8322) - ensure the client correctly handles all binary data.
+- [#8296](https://github.com/gradio-app/gradio/pull/8296) - always create a jwt when connecting to a space if a hf_token is present.
+- [#8285](https://github.com/gradio-app/gradio/pull/8285) - use the correct query param to pass the jwt to the heartbeat event.
+- [#8272](https://github.com/gradio-app/gradio/pull/8272) - ensure client works for private spaces.
+- [#8197](https://github.com/gradio-app/gradio/pull/8197) - Add support for passing keyword args to `data` in JS client.
+- [#8252](https://github.com/gradio-app/gradio/pull/8252) - Client node fix.
+- [#8209](https://github.com/gradio-app/gradio/pull/8209) - Rename `eventSource_Factory` and `fetch_implementation`.
+- [#8109](https://github.com/gradio-app/gradio/pull/8109) - Implement JS Client tests.
+- [#8211](https://github.com/gradio-app/gradio/pull/8211) - remove redundant event source logic.
+- [#8179](https://github.com/gradio-app/gradio/pull/8179) - rework upload to be a class method + pass client into each component.
+- [#8181](https://github.com/gradio-app/gradio/pull/8181) - Ensure connectivity to private HF spaces with SSE protocol.
+- [#8169](https://github.com/gradio-app/gradio/pull/8169) - Only connect to heartbeat if needed.
+- [#8118](https://github.com/gradio-app/gradio/pull/8118) - Add eventsource polyfill for Node.js and browser environments.
+- [#7646](https://github.com/gradio-app/gradio/pull/7646) - Refactor JS Client.
+- [#7974](https://github.com/gradio-app/gradio/pull/7974) - Fix heartbeat in the js client to be Lite compatible.
+- [#7926](https://github.com/gradio-app/gradio/pull/7926) - Fixes streaming event race condition.
+
+ Thanks @freddyaboulton!
+
+### Features
+
+- [#8370](https://github.com/gradio-app/gradio/pull/8370) [`48eeea4`](https://github.com/gradio-app/gradio/commit/48eeea4eaab7e24168688e3c3fbafb30e4e78d51) - Refactor Cancelling Logic To Use /cancel. Thanks @freddyaboulton!
+
+### Fixes
+
+- [#8477](https://github.com/gradio-app/gradio/pull/8477) [`d5a9604`](https://github.com/gradio-app/gradio/commit/d5a960493017a4890685af61d78ce7d3b3b12e6b) - Fix js client bundle. Thanks @pngwn!
+- [#8451](https://github.com/gradio-app/gradio/pull/8451) [`9d2d605`](https://github.com/gradio-app/gradio/commit/9d2d6051caed5c8749a26a6fa7480a5ae6e6c4f3) - Change client submit API to be an AsyncIterable and support more platforms. Thanks @pngwn!
+- [#8462](https://github.com/gradio-app/gradio/pull/8462) [`6447dfa`](https://github.com/gradio-app/gradio/commit/6447dface4d46db1c69460e8325a1928d0476a46) - Improve file handling in JS Client. Thanks @hannahblair!
+- [#8439](https://github.com/gradio-app/gradio/pull/8439) [`63d36fb`](https://github.com/gradio-app/gradio/commit/63d36fbbf4bf6dc909be9a0ffc7b6bf6621d83e8) - Handle gradio apps using `state` in the JS Client. Thanks @hannahblair!
+
+## 0.20.1
+
+### Features
+
+- [#8415](https://github.com/gradio-app/gradio/pull/8415) [`227de35`](https://github.com/gradio-app/gradio/commit/227de352982b3dcdf9384eaa28b7e9cf09afb6e8) - Fix spaces load error. Thanks @aliabid94!
+
+## 0.20.0
+
+### Features
+
+- [#8401](https://github.com/gradio-app/gradio/pull/8401) [`d078621`](https://github.com/gradio-app/gradio/commit/d078621928136c09ca902d2f37594ed887c67d2e) - Add CDN installation to JS docs. Thanks @hannahblair!
+- [#8243](https://github.com/gradio-app/gradio/pull/8243) [`55f664f`](https://github.com/gradio-app/gradio/commit/55f664f2979a49acc29a73cde16c6ebdfcc91db2) - Add event listener support to render blocks. Thanks @aliabid94!
+- [#8398](https://github.com/gradio-app/gradio/pull/8398) [`945ac83`](https://github.com/gradio-app/gradio/commit/945ac837e779b120790814ea6f6f81bd2712f5f8) - Improve rendering. Thanks @aliabid94!
+- [#8299](https://github.com/gradio-app/gradio/pull/8299) [`ab65360`](https://github.com/gradio-app/gradio/commit/ab653608045ff9462db7ad9fe63e1c60bf20e773) - Allow JS Client to work with authenticated spaces 🍪. Thanks @hannahblair!
+
+### Fixes
+
+- [#8408](https://github.com/gradio-app/gradio/pull/8408) [`e86dd01`](https://github.com/gradio-app/gradio/commit/e86dd01b6e8f7bab3d3c25b84f2ad33129138af4) - Connect heartbeat if state created in render. Also fix config cleanup bug #8407. Thanks @freddyaboulton!
+- [#8258](https://github.com/gradio-app/gradio/pull/8258) [`1f8e5c4`](https://github.com/gradio-app/gradio/commit/1f8e5c44e054b943052d8f24d044696ddfd01a54) - Improve URL handling in JS Client. Thanks @hannahblair!
+
+## 0.19.4
+
+### Fixes
+
+- [#8322](https://github.com/gradio-app/gradio/pull/8322) [`47012a0`](https://github.com/gradio-app/gradio/commit/47012a0c4e3e8a80fcae620aaf08b16ceb343cde) - ensure the client correctly handles all binary data. Thanks @Saghen!
+
+## 0.19.3
+
+### Features
+
+- [#8229](https://github.com/gradio-app/gradio/pull/8229) [`7c81897`](https://github.com/gradio-app/gradio/commit/7c81897076ddcd0bb05e0e4ffec35bb9a986d330) - chore(deps): update dependency esbuild to ^0.21.0. Thanks @renovate!
+
+### Fixes
+
+- [#8296](https://github.com/gradio-app/gradio/pull/8296) [`929d216`](https://github.com/gradio-app/gradio/commit/929d216d49aa05614bc83f0761cf7b1cd803d8fe) - always create a jwt when connecting to a space if a hf_token is present. Thanks @pngwn!
+
+## 0.19.2
+
+### Fixes
+
+- [#8285](https://github.com/gradio-app/gradio/pull/8285) [`7d9d8ea`](https://github.com/gradio-app/gradio/commit/7d9d8eab50d36cbecbb84c6a0f3cc1bca7215604) - use the correct query param to pass the jwt to the heartbeat event. Thanks @pngwn!
+
+## 0.19.1
+
+### Fixes
+
+- [#8272](https://github.com/gradio-app/gradio/pull/8272) [`fbf4edd`](https://github.com/gradio-app/gradio/commit/fbf4edde7c896cdf4c903463e44c31ed96111b3c) - ensure client works for private spaces. Thanks @pngwn!
+
+## 0.19.0
+
+### Features
+
+- [#8110](https://github.com/gradio-app/gradio/pull/8110) [`5436031`](https://github.com/gradio-app/gradio/commit/5436031f92c1596282eb64e1e74d555f279e9697) - Render decorator 2. Thanks @aliabid94!
+- [#8197](https://github.com/gradio-app/gradio/pull/8197) [`e09b4e8`](https://github.com/gradio-app/gradio/commit/e09b4e8216b970bc1b142a0f08e7d190b954eb35) - Add support for passing keyword args to `data` in JS client. Thanks @hannahblair!
+
+### Fixes
+
+- [#8252](https://github.com/gradio-app/gradio/pull/8252) [`22df61a`](https://github.com/gradio-app/gradio/commit/22df61a26adf8023f6dd49c051979990e8d3879a) - Client node fix. Thanks @pngwn!
+
+## 0.18.0
+
+### Features
+
+- [#8121](https://github.com/gradio-app/gradio/pull/8121) [`f5b710c`](https://github.com/gradio-app/gradio/commit/f5b710c919b0ce604ea955f0d5f4faa91095ca4a) - chore(deps): update dependency eslint to v9. Thanks @renovate!
+- [#8209](https://github.com/gradio-app/gradio/pull/8209) [`b9afe93`](https://github.com/gradio-app/gradio/commit/b9afe93915401df5bd6737c89395c2477acfa585) - Rename `eventSource_Factory` and `fetch_implementation`. Thanks @hannahblair!
+- [#8109](https://github.com/gradio-app/gradio/pull/8109) [`bed2f82`](https://github.com/gradio-app/gradio/commit/bed2f82e2297b50f7b59423a3de05af0b9910724) - Implement JS Client tests. Thanks @hannahblair!
+- [#8211](https://github.com/gradio-app/gradio/pull/8211) [`91b5cd6`](https://github.com/gradio-app/gradio/commit/91b5cd6132fb8903c92f70fce0800324836a1fc3) - remove redundant event source logic. Thanks @hannahblair!
+
+### Fixes
+
+- [#8179](https://github.com/gradio-app/gradio/pull/8179) [`6a218b4`](https://github.com/gradio-app/gradio/commit/6a218b4148095aaa0c58d8c20973ba01c8764fc2) - rework upload to be a class method + pass client into each component. Thanks @pngwn!
+- [#8181](https://github.com/gradio-app/gradio/pull/8181) [`cf52ca6`](https://github.com/gradio-app/gradio/commit/cf52ca6a51320ece97f009a177792840b5fbc785) - Ensure connectivity to private HF spaces with SSE protocol. Thanks @hannahblair!
+- [#8169](https://github.com/gradio-app/gradio/pull/8169) [`3a6f1a5`](https://github.com/gradio-app/gradio/commit/3a6f1a50b263e0a733f609a08019fc4d05480e1a) - Only connect to heartbeat if needed. Thanks @freddyaboulton!
+- [#8118](https://github.com/gradio-app/gradio/pull/8118) [`7aca673`](https://github.com/gradio-app/gradio/commit/7aca673b38a087533524b2fd8dd3a03e0e4bacfe) - Add eventsource polyfill for Node.js and browser environments. Thanks @hannahblair!
+
+## 0.17.0
+
+### Highlights
+
+#### Setting File Upload Limits ([#7909](https://github.com/gradio-app/gradio/pull/7909) [`2afca65`](https://github.com/gradio-app/gradio/commit/2afca6541912b37dc84f447c7ad4af21607d7c72))
+
+We have added a `max_file_size` size parameter to `launch()` that limits to size of files uploaded to the server. This limit applies to each individual file. This parameter can be specified as a string or an integer (corresponding to the size in bytes).
+
+The following code snippet sets a max file size of 5 megabytes.
+
+```python
+import gradio as gr
+
+demo = gr.Interface(lambda x: x, "image", "image")
+
+demo.launch(max_file_size="5mb")
+# or
+demo.launch(max_file_size=5 * gr.FileSize.MB)
+```
+
+
+
+
+#### Error states can now be cleared
+
+When a component encounters an error, the error state shown in the UI can now be cleared by clicking on the `x` icon in the top right of the component. This applies to all types of errors, whether it's raised in the UI or the server.
+
+
+
+ Thanks @freddyaboulton!
+
+### Features
+
+- [#8056](https://github.com/gradio-app/gradio/pull/8056) [`2e469a5`](https://github.com/gradio-app/gradio/commit/2e469a5f99e52a5011a010f46e47dde7bb0c7140) - Using keys to preserve values between reloads. Thanks @aliabid94!
+- [#7646](https://github.com/gradio-app/gradio/pull/7646) [`450b8cc`](https://github.com/gradio-app/gradio/commit/450b8cc898f130f15caa3742f65c17b9f7a8f398) - Refactor JS Client. Thanks @hannahblair!
+- [#8061](https://github.com/gradio-app/gradio/pull/8061) [`17e83c9`](https://github.com/gradio-app/gradio/commit/17e83c958ebb35b3e122ca486067d1bd5ce33a22) - Docs Reorg and Intro Page. Thanks @aliabd!
+
+### Fixes
+
+- [#8066](https://github.com/gradio-app/gradio/pull/8066) [`624f9b9`](https://github.com/gradio-app/gradio/commit/624f9b9477f74a581a6c14119234f9efdfcda398) - make gradio dev tools a local dependency rather than bundling. Thanks @pngwn!
+
+## 0.16.0
+
+### Features
+
+- [#7845](https://github.com/gradio-app/gradio/pull/7845) [`dbb7373`](https://github.com/gradio-app/gradio/commit/dbb7373dde69d4ed2741942b5a1898f8620cec24) - ensure `ImageEditor` events work as expected. Thanks @pngwn!
+
+### Fixes
+
+- [#7974](https://github.com/gradio-app/gradio/pull/7974) [`79e0aa8`](https://github.com/gradio-app/gradio/commit/79e0aa81c94e755faa6e85d76ac5d5a666313e6a) - Fix heartbeat in the js client to be Lite compatible. Thanks @whitphx!
+
+## 0.15.1
+
+### Fixes
+
+- [#7926](https://github.com/gradio-app/gradio/pull/7926) [`9666854`](https://github.com/gradio-app/gradio/commit/966685479078f59430b3bced7e6068eb8157c003) - Fixes streaming event race condition. Thanks @aliabid94!
+
+## 0.15.0
+
+### Highlights
+
+#### Automatically delete state after user has disconnected from the webpage ([#7829](https://github.com/gradio-app/gradio/pull/7829) [`6a4bf7a`](https://github.com/gradio-app/gradio/commit/6a4bf7abe29059dbdc6a342e0366fdaa2e4120ee))
+
+Gradio now automatically deletes `gr.State` variables stored in the server's RAM when users close their browser tab.
+The deletion will happen 60 minutes after the server detected a disconnect from the user's browser.
+If the user connects again in that timeframe, their state will not be deleted.
+
+Additionally, Gradio now includes a `Blocks.unload()` event, allowing you to run arbitrary cleanup functions when users disconnect (this does not have a 60 minute delay).
+You can think of the `unload` event as the opposite of the `load` event.
+
+
+```python
+with gr.Blocks() as demo:
+ gr.Markdown(
+"""# State Cleanup Demo
+🖼️ Images are saved in a user-specific directory and deleted when the users closes the page via demo.unload.
+""")
+ with gr.Row():
+ with gr.Column(scale=1):
+ with gr.Row():
+ img = gr.Image(label="Generated Image", height=300, width=300)
+ with gr.Row():
+ gen = gr.Button(value="Generate")
+ with gr.Row():
+ history = gr.Gallery(label="Previous Generations", height=500, columns=10)
+ state = gr.State(value=[], delete_callback=lambda v: print("STATE DELETED"))
+
+ demo.load(generate_random_img, [state], [img, state, history])
+ gen.click(generate_random_img, [state], [img, state, history])
+ demo.unload(delete_directory)
+
+
+demo.launch(auth=lambda user,pwd: True,
+ auth_message="Enter any username and password to continue")
+```
+
+ Thanks @freddyaboulton!
+
+## 0.14.0
+
+### Features
+
+- [#7691](https://github.com/gradio-app/gradio/pull/7691) [`84f81fe`](https://github.com/gradio-app/gradio/commit/84f81fec9287b041203a141bbf2852720f7d199c) - Closing stream from the backend. Thanks @aliabid94!
+
+### Fixes
+
+- [#7564](https://github.com/gradio-app/gradio/pull/7564) [`5d1e8da`](https://github.com/gradio-app/gradio/commit/5d1e8dae5ac23f605c3b5f41dbe18751dff380a0) - batch UI updates on a per frame basis. Thanks @pngwn!
+
+## 0.13.0
+
+### Fixes
+
+- [#7575](https://github.com/gradio-app/gradio/pull/7575) [`d0688b3`](https://github.com/gradio-app/gradio/commit/d0688b3c25feabb4fc7dfa0ab86086b3af7eb337) - Files should now be supplied as `file(...)` in the Client, and some fixes to `gr.load()` as well. Thanks @abidlabs!
+
+## 0.12.2
+
+### Features
+
+- [#7528](https://github.com/gradio-app/gradio/pull/7528) [`eda33b3`](https://github.com/gradio-app/gradio/commit/eda33b3763897a542acf298e523fa493dc655aee) - Refactors `get_fetchable_url_or_file()` to remove it from the frontend. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#7340](https://github.com/gradio-app/gradio/pull/7340) [`4b0d589`](https://github.com/gradio-app/gradio/commit/4b0d58933057432758a54169a360eb352903d6b4) - chore(deps): update all non-major dependencies. Thanks [@renovate](https://github.com/apps/renovate)!
+
+## 0.12.1
+
+### Fixes
+
+- [#7411](https://github.com/gradio-app/gradio/pull/7411) [`32b317f`](https://github.com/gradio-app/gradio/commit/32b317f24e3d43f26684bb9f3964f31efd0ea556) - Set `root` correctly for Gradio apps that are deployed behind reverse proxies. Thanks [@abidlabs](https://github.com/abidlabs)!
+
+## 0.12.0
+
+### Features
+
+- [#7183](https://github.com/gradio-app/gradio/pull/7183) [`49d9c48`](https://github.com/gradio-app/gradio/commit/49d9c48537aa706bf72628e3640389470138bdc6) - [WIP] Refactor file normalization to be in the backend and remove it from the frontend of each component. Thanks [@abidlabs](https://github.com/abidlabs)!
+
+## 0.11.0
+
+### Features
+
+- [#7102](https://github.com/gradio-app/gradio/pull/7102) [`68a54a7`](https://github.com/gradio-app/gradio/commit/68a54a7a310d8d7072fdae930bf1cfdf12c45a7f) - Improve chatbot streaming performance with diffs. Thanks [@aliabid94](https://github.com/aliabid94)!/n Note that this PR changes the API format for generator functions, which would be a breaking change for any clients reading the EventStream directly
+
+## 0.10.1
+
+### Fixes
+
+- [#7055](https://github.com/gradio-app/gradio/pull/7055) [`3c3cf86`](https://github.com/gradio-app/gradio/commit/3c3cf8618a8cad1ef66a7f96664923d2c9f5e0e2) - Fix UI freeze on rapid generators. Thanks [@aliabid94](https://github.com/aliabid94)!
+
+## 0.10.0
+
+### Features
+
+- [#6931](https://github.com/gradio-app/gradio/pull/6931) [`6c863af`](https://github.com/gradio-app/gradio/commit/6c863af92fa9ceb5c638857eb22cc5ddb718d549) - Fix functional tests. Thanks [@aliabid94](https://github.com/aliabid94)!
+- [#6820](https://github.com/gradio-app/gradio/pull/6820) [`649cd4d`](https://github.com/gradio-app/gradio/commit/649cd4d68041d11fcbe31f8efa455345ac49fc74) - Use `EventSource_factory` in `open_stream()` for Wasm. Thanks [@whitphx](https://github.com/whitphx)!
+
+## 0.9.4
+
+### Fixes
+
+- [#6863](https://github.com/gradio-app/gradio/pull/6863) [`d406855`](https://github.com/gradio-app/gradio/commit/d4068557953746662235d595ec435c42ceb24414) - Fix JS Client when app is running behind a proxy. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+## 0.9.3
+
+### Features
+
+- [#6814](https://github.com/gradio-app/gradio/pull/6814) [`828fb9e`](https://github.com/gradio-app/gradio/commit/828fb9e6ce15b6ea08318675a2361117596a1b5d) - Refactor queue so that there are separate queues for each concurrency id. Thanks [@aliabid94](https://github.com/aliabid94)!
+
+## 0.9.2
+
+### Features
+
+- [#6798](https://github.com/gradio-app/gradio/pull/6798) [`245d58e`](https://github.com/gradio-app/gradio/commit/245d58eff788e8d44a59d37a2d9b26d0f08a62b4) - Improve how server/js client handle unexpected errors. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+## 0.9.1
+
+### Fixes
+
+- [#6693](https://github.com/gradio-app/gradio/pull/6693) [`34f9431`](https://github.com/gradio-app/gradio/commit/34f943101bf7dd6b8a8974a6131c1ed7c4a0dac0) - Python client properly handles hearbeat and log messages. Also handles responses longer than 65k. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+## 0.9.0
+
+### Features
+
+- [#6398](https://github.com/gradio-app/gradio/pull/6398) [`67ddd40`](https://github.com/gradio-app/gradio/commit/67ddd40b4b70d3a37cb1637c33620f8d197dbee0) - Lite v4. Thanks [@whitphx](https://github.com/whitphx)!
+
+### Fixes
+
+- [#6556](https://github.com/gradio-app/gradio/pull/6556) [`d76bcaa`](https://github.com/gradio-app/gradio/commit/d76bcaaaf0734aaf49a680f94ea9d4d22a602e70) - Fix api event drops. Thanks [@aliabid94](https://github.com/aliabid94)!
+
+## 0.8.2
+
+### Features
+
+- [#6511](https://github.com/gradio-app/gradio/pull/6511) [`71f1a1f99`](https://github.com/gradio-app/gradio/commit/71f1a1f9931489d465c2c1302a5c8d768a3cd23a) - Mark `FileData.orig_name` optional on the frontend aligning the type definition on the Python side. Thanks [@whitphx](https://github.com/whitphx)!
+
+## 0.8.1
+
+### Fixes
+
+- [#6383](https://github.com/gradio-app/gradio/pull/6383) [`324867f63`](https://github.com/gradio-app/gradio/commit/324867f63c920113d89a565892aa596cf8b1e486) - Fix event target. Thanks [@aliabid94](https://github.com/aliabid94)!
+
+## 0.8.0
+
+### Features
+
+- [#6307](https://github.com/gradio-app/gradio/pull/6307) [`f1409f95e`](https://github.com/gradio-app/gradio/commit/f1409f95ed39c5565bed6a601e41f94e30196a57) - Provide status updates on file uploads. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+## 0.7.2
+
+### Fixes
+
+- [#6327](https://github.com/gradio-app/gradio/pull/6327) [`bca6c2c80`](https://github.com/gradio-app/gradio/commit/bca6c2c80f7e5062427019de45c282238388af95) - Restore query parameters in request. Thanks [@aliabid94](https://github.com/aliabid94)!
+
+## 0.7.1
+
+### Features
+
+- [#6137](https://github.com/gradio-app/gradio/pull/6137) [`2ba14b284`](https://github.com/gradio-app/gradio/commit/2ba14b284f908aa13859f4337167a157075a68eb) - JS Param. Thanks [@dawoodkhan82](https://github.com/dawoodkhan82)!
+
+## 0.7.0
+
+### Features
+
+- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`287fe6782`](https://github.com/gradio-app/gradio/commit/287fe6782825479513e79a5cf0ba0fbfe51443d7) - fix circular dependency with client + upload. Thanks [@pngwn](https://github.com/pngwn)!
+- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`287fe6782`](https://github.com/gradio-app/gradio/commit/287fe6782825479513e79a5cf0ba0fbfe51443d7) - Image v4. Thanks [@pngwn](https://github.com/pngwn)!
+- [#5498](https://github.com/gradio-app/gradio/pull/5498) [`287fe6782`](https://github.com/gradio-app/gradio/commit/287fe6782825479513e79a5cf0ba0fbfe51443d7) - Swap websockets for SSE. Thanks [@pngwn](https://github.com/pngwn)!
+
+## 0.7.0-beta.1
+
+### Features
+
+- [#6143](https://github.com/gradio-app/gradio/pull/6143) [`e4f7b4b40`](https://github.com/gradio-app/gradio/commit/e4f7b4b409323b01aa01b39e15ce6139e29aa073) - fix circular dependency with client + upload. Thanks [@pngwn](https://github.com/pngwn)!
+- [#6094](https://github.com/gradio-app/gradio/pull/6094) [`c476bd5a5`](https://github.com/gradio-app/gradio/commit/c476bd5a5b70836163b9c69bf4bfe068b17fbe13) - Image v4. Thanks [@pngwn](https://github.com/pngwn)!
+- [#6069](https://github.com/gradio-app/gradio/pull/6069) [`bf127e124`](https://github.com/gradio-app/gradio/commit/bf127e1241a41401e144874ea468dff8474eb505) - Swap websockets for SSE. Thanks [@aliabid94](https://github.com/aliabid94)!
+
+## 0.7.0-beta.0
+
+### Features
+
+- [#6016](https://github.com/gradio-app/gradio/pull/6016) [`83e947676`](https://github.com/gradio-app/gradio/commit/83e947676d327ca2ab6ae2a2d710c78961c771a0) - Format js in v4 branch. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+### Fixes
+
+- [#6046](https://github.com/gradio-app/gradio/pull/6046) [`dbb7de5e0`](https://github.com/gradio-app/gradio/commit/dbb7de5e02c53fee05889d696d764d212cb96c74) - fix tests. Thanks [@pngwn](https://github.com/pngwn)!
+
+## 0.6.0
+
+### Features
+
+- [#5972](https://github.com/gradio-app/gradio/pull/5972) [`11a300791`](https://github.com/gradio-app/gradio/commit/11a3007916071f0791844b0a37f0fb4cec69cea3) - Lite: Support opening the entrypoint HTML page directly in browser via the `file:` protocol. Thanks [@whitphx](https://github.com/whitphx)!
+
+## 0.5.2
+
+### Fixes
+
+- [#5840](https://github.com/gradio-app/gradio/pull/5840) [`4e62b8493`](https://github.com/gradio-app/gradio/commit/4e62b8493dfce50bafafe49f1a5deb929d822103) - Ensure websocket polyfill doesn't load if there is already a `global.Webocket` property set. Thanks [@Jay2theWhy](https://github.com/Jay2theWhy)!
+
+## 0.5.1
+
+### Fixes
+
+- [#5816](https://github.com/gradio-app/gradio/pull/5816) [`796145e2c`](https://github.com/gradio-app/gradio/commit/796145e2c48c4087bec17f8ec0be4ceee47170cb) - Fix calls to the component server so that `gr.FileExplorer` works on Spaces. Thanks [@abidlabs](https://github.com/abidlabs)!
+
+## 0.5.0
+
+### Highlights
+
+#### new `FileExplorer` component ([#5672](https://github.com/gradio-app/gradio/pull/5672) [`e4a307ed6`](https://github.com/gradio-app/gradio/commit/e4a307ed6cde3bbdf4ff2f17655739addeec941e))
+
+Thanks to a new capability that allows components to communicate directly with the server _without_ passing data via the value, we have created a new `FileExplorer` component.
+
+This component allows you to populate the explorer by passing a glob, but only provides the selected file(s) in your prediction function.
+
+Users can then navigate the virtual filesystem and select files which will be accessible in your predict function. This component will allow developers to build more complex spaces, with more flexible input options.
+
+
+
+For more information check the [`FileExplorer` documentation](https://gradio.app/docs/fileexplorer).
+
+ Thanks [@aliabid94](https://github.com/aliabid94)!
+
+### Features
+
+- [#5787](https://github.com/gradio-app/gradio/pull/5787) [`caeee8bf7`](https://github.com/gradio-app/gradio/commit/caeee8bf7821fd5fe2f936ed82483bed00f613ec) - ensure the client does not depend on `window` when running in a node environment. Thanks [@gibiee](https://github.com/gibiee)!
+
+### Fixes
+
+- [#5776](https://github.com/gradio-app/gradio/pull/5776) [`c0fef4454`](https://github.com/gradio-app/gradio/commit/c0fef44541bfa61568bdcfcdfc7d7d79869ab1df) - Revert replica proxy logic and instead implement using the `root` variable. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+## 0.4.2
+
+### Features
+
+- [#5124](https://github.com/gradio-app/gradio/pull/5124) [`6e56a0d9b`](https://github.com/gradio-app/gradio/commit/6e56a0d9b0c863e76c69e1183d9d40196922b4cd) - Lite: Websocket queueing. Thanks [@whitphx](https://github.com/whitphx)!
+
+## 0.4.1
+
+### Fixes
+
+- [#5705](https://github.com/gradio-app/gradio/pull/5705) [`78e7cf516`](https://github.com/gradio-app/gradio/commit/78e7cf5163e8d205e8999428fce4c02dbdece25f) - ensure internal data has updated before dispatching `success` or `then` events. Thanks [@pngwn](https://github.com/pngwn)!
+
+## 0.4.0
+
+### Features
+
+- [#5682](https://github.com/gradio-app/gradio/pull/5682) [`c57f1b75e`](https://github.com/gradio-app/gradio/commit/c57f1b75e272c76b0af4d6bd0c7f44743ff34f26) - Fix functional tests. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5681](https://github.com/gradio-app/gradio/pull/5681) [`40de3d217`](https://github.com/gradio-app/gradio/commit/40de3d2178b61ebe424b6f6228f94c0c6f679bea) - add query parameters to the `gr.Request` object through the `query_params` attribute. Thanks [@DarhkVoyd](https://github.com/DarhkVoyd)!
+- [#5653](https://github.com/gradio-app/gradio/pull/5653) [`ea0e00b20`](https://github.com/gradio-app/gradio/commit/ea0e00b207b4b90a10e9d054c4202d4e705a29ba) - Prevent Clients from accessing API endpoints that set `api_name=False`. Thanks [@abidlabs](https://github.com/abidlabs)!
+
+## 0.3.1
+
+### Fixes
+
+- [#5412](https://github.com/gradio-app/gradio/pull/5412) [`26fef8c7`](https://github.com/gradio-app/gradio/commit/26fef8c7f85a006c7e25cdbed1792df19c512d02) - Skip view_api request in js client when auth enabled. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+## 0.3.0
+
+### Features
+
+- [#5267](https://github.com/gradio-app/gradio/pull/5267) [`119c8343`](https://github.com/gradio-app/gradio/commit/119c834331bfae60d4742c8f20e9cdecdd67e8c2) - Faster reload mode. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+## 0.2.1
+
+### Features
+
+- [#5173](https://github.com/gradio-app/gradio/pull/5173) [`730f0c1d`](https://github.com/gradio-app/gradio/commit/730f0c1d54792eb11359e40c9f2326e8a6e39203) - Ensure gradio client works as expected for functions that return nothing. Thanks [@raymondtri](https://github.com/raymondtri)!
+
+## 0.2.0
+
+### Features
+
+- [#5133](https://github.com/gradio-app/gradio/pull/5133) [`61129052`](https://github.com/gradio-app/gradio/commit/61129052ed1391a75c825c891d57fa0ad6c09fc8) - Update dependency esbuild to ^0.19.0. Thanks [@renovate](https://github.com/apps/renovate)!
+- [#5035](https://github.com/gradio-app/gradio/pull/5035) [`8b4eb8ca`](https://github.com/gradio-app/gradio/commit/8b4eb8cac9ea07bde31b44e2006ca2b7b5f4de36) - JS Client: Fixes cannot read properties of null (reading 'is_file'). Thanks [@raymondtri](https://github.com/raymondtri)!
+
+### Fixes
+
+- [#5075](https://github.com/gradio-app/gradio/pull/5075) [`67265a58`](https://github.com/gradio-app/gradio/commit/67265a58027ef1f9e4c0eb849a532f72eaebde48) - Allow supporting >1000 files in `gr.File()` and `gr.UploadButton()`. Thanks [@abidlabs](https://github.com/abidlabs)!
+
+## 0.1.4
+
+### Patch Changes
+
+- [#4717](https://github.com/gradio-app/gradio/pull/4717) [`ab5d1ea0`](https://github.com/gradio-app/gradio/commit/ab5d1ea0de87ed888779b66fd2a705583bd29e02) Thanks [@whitphx](https://github.com/whitphx)! - Fix the package description
+
+## 0.1.3
+
+### Patch Changes
+
+- [#4357](https://github.com/gradio-app/gradio/pull/4357) [`0dbd8f7f`](https://github.com/gradio-app/gradio/commit/0dbd8f7fee4b4877f783fa7bc493f98bbfc3d01d) Thanks [@pngwn](https://github.com/pngwn)! - Various internal refactors and cleanups.
+
+## 0.1.2
+
+### Patch Changes
+
+- [#4273](https://github.com/gradio-app/gradio/pull/4273) [`1d0f0a9d`](https://github.com/gradio-app/gradio/commit/1d0f0a9db096552e67eb2197c932342587e9e61e) Thanks [@pngwn](https://github.com/pngwn)! - Ensure websocket error messages are correctly handled.
+
+- [#4315](https://github.com/gradio-app/gradio/pull/4315) [`b525b122`](https://github.com/gradio-app/gradio/commit/b525b122dd8569bbaf7e06db5b90d622d2e9073d) Thanks [@whitphx](https://github.com/whitphx)! - Refacor types.
+
+- [#4271](https://github.com/gradio-app/gradio/pull/4271) [`1151c525`](https://github.com/gradio-app/gradio/commit/1151c5253554cb87ebd4a44a8a470ac215ff782b) Thanks [@pngwn](https://github.com/pngwn)! - Ensure the full root path is always respected when making requests to a gradio app server.
+
+## 0.1.1
+
+### Patch Changes
+
+- [#4201](https://github.com/gradio-app/gradio/pull/4201) [`da5b4ee1`](https://github.com/gradio-app/gradio/commit/da5b4ee11721175858ded96e5710225369097f74) Thanks [@pngwn](https://github.com/pngwn)! - Ensure semiver is bundled so CDN links work correctly.
+
+- [#4202](https://github.com/gradio-app/gradio/pull/4202) [`a26e9afd`](https://github.com/gradio-app/gradio/commit/a26e9afde319382993e6ddc77cc4e56337a31248) Thanks [@pngwn](https://github.com/pngwn)! - Ensure all URLs returned by the client are complete URLs with the correct host instead of an absolute path relative to a server.
+
+## 0.1.0
+
+### Minor Changes
+
+- [#4185](https://github.com/gradio-app/gradio/pull/4185) [`67239ca9`](https://github.com/gradio-app/gradio/commit/67239ca9b2fe3796853fbf7bf865c9e4b383200d) Thanks [@pngwn](https://github.com/pngwn)! - Update client for initial release
+
+### Patch Changes
+
+- [#3692](https://github.com/gradio-app/gradio/pull/3692) [`48e8b113`](https://github.com/gradio-app/gradio/commit/48e8b113f4b55e461d9da4f153bf72aeb4adf0f1) Thanks [@pngwn](https://github.com/pngwn)! - Ensure client works in node, create ESM bundle and generate typescript declaration files.
+
+- [#3605](https://github.com/gradio-app/gradio/pull/3605) [`ae4277a9`](https://github.com/gradio-app/gradio/commit/ae4277a9a83d49bdadfe523b0739ba988128e73b) Thanks [@pngwn](https://github.com/pngwn)! - Update readme.
\ No newline at end of file
diff --git a/client/js/README.md b/client/js/README.md
new file mode 100644
index 0000000..4f79bf0
--- /dev/null
+++ b/client/js/README.md
@@ -0,0 +1,448 @@
+## JavaScript Client Library
+
+Interact with Gradio APIs using our JavaScript (and TypeScript) client.
+
+
+## Installation
+
+The Gradio JavaScript Client is available on npm as `@gradio/client`. You can install it as below:
+
+```shell
+npm i @gradio/client
+```
+
+Or, you can include it directly in your HTML via the jsDelivr CDN:
+
+```shell
+
+```
+
+## Usage
+
+The JavaScript Gradio Client exposes the Client class, `Client`, along with various other utility functions. `Client` is used to initialise and establish a connection to, or duplicate, a Gradio app.
+
+### `Client`
+
+The Client function connects to the API of a hosted Gradio space and returns an object that allows you to make calls to that API.
+
+The simplest example looks like this:
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const result = await app.predict("/predict");
+```
+
+This function accepts two arguments: `source` and `options`:
+
+#### `source`
+
+This is the url or name of the gradio app whose API you wish to connect to. This parameter is required and should always be a string. For example:
+
+```ts
+Client.connect("user/space-name");
+```
+
+#### `options`
+
+The options object can optionally be passed a second parameter. This object has two properties, `token` and `status_callback`.
+
+##### `token`
+
+This should be a Hugging Face personal access token and is required if you wish to make calls to a private gradio api. This option is optional and should be a string starting with `"hf_"`.
+
+Example:
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name", { token: "hf_..." });
+```
+
+##### `status_callback`
+
+This should be a function which will notify you of the status of a space if it is not running. If the gradio API you are connecting to is not awake and running or is not hosted on Hugging Face space then this function will do nothing.
+
+**Additional context**
+
+Applications hosted on Hugging Face spaces can be in a number of different states. As spaces are a GitOps tool and will rebuild when new changes are pushed to the repository, they have various building, running and error states. If a space is not 'running' then the function passed as the `status_callback` will notify you of the current state of the space and the status of the space as it changes. Spaces that are building or sleeping can take longer than usual to respond, so you can use this information to give users feedback about the progress of their action.
+
+```ts
+import { Client, type SpaceStatus } from "@gradio/client";
+
+const app = await Client.connect("user/space-name", {
+ // The space_status parameter does not need to be manually annotated, this is just for illustration.
+ space_status: (space_status: SpaceStatus) => console.log(space_status)
+});
+```
+
+```ts
+interface SpaceStatusNormal {
+ status: "sleeping" | "running" | "building" | "error" | "stopped";
+ detail:
+ | "SLEEPING"
+ | "RUNNING"
+ | "RUNNING_BUILDING"
+ | "BUILDING"
+ | "NOT_FOUND";
+ load_status: "pending" | "error" | "complete" | "generating";
+ message: string;
+}
+
+interface SpaceStatusError {
+ status: "space_error";
+ detail: "NO_APP_FILE" | "CONFIG_ERROR" | "BUILD_ERROR" | "RUNTIME_ERROR";
+ load_status: "error";
+ message: string;
+ discussions_enabled: boolean;
+
+type SpaceStatus = SpaceStatusNormal | SpaceStatusError;
+```
+
+The gradio client returns an object with a number of methods and properties:
+
+#### `predict`
+
+The `predict` method allows you to call an api endpoint and get a prediction result:
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const result = await app.predict("/predict");
+```
+
+`predict` accepts two parameters, `endpoint` and `payload`. It returns a promise that resolves to the prediction result.
+
+##### `endpoint`
+
+This is the endpoint for an api request and is required. The default endpoint for a `gradio.Interface` is `"/predict"`. Explicitly named endpoints have a custom name. The endpoint names can be found on the "View API" page of a space.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const result = await app.predict("/predict");
+```
+
+##### `payload`
+
+The `payload` argument is generally required but this depends on the API itself. If the API endpoint depends on values being passed in then the argument is required for the API request to succeed. The data that should be passed in is detailed on the "View API" page of a space, or accessible via the `view_api()` method of the client.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const result = await app.predict("/predict", {
+ input: 1,
+ word_1: "Hello",
+ word_2: "friends"
+});
+```
+
+#### `submit`
+
+The `submit` method provides a more flexible way to call an API endpoint, providing you with status updates about the current progress of the prediction as well as supporting more complex endpoint types.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const submission = app.submit("/predict", { name: "Chewbacca" });
+```
+
+The `submit` method accepts the same [`endpoint`](#endpoint) and [`payload`](#payload) arguments as `predict`.
+
+The `submit` method does not return a promise and should not be awaited, instead it returns an async iterator with a `cancel` method.
+
+##### Accessing values
+
+Iterating the submission allows you to access the events related to the submitted API request. There are two types of events that can be listened for: `"data"` updates and `"status"` updates. By default only the `"data"` event is reported, but you can listen for the `"status"` event by manually passing the events you care about when instantiating the client:
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name", {
+ events: ["data", "status"]
+});
+```
+
+`"data"` updates are issued when the API computes a value, the callback provided as the second argument will be called when such a value is sent to the client. The shape of the data depends on the way the API itself is constructed. This event may fire more than once if that endpoint supports emmitting new values over time.
+
+`"status` updates are issued when the status of a request changes. This information allows you to offer feedback to users when the queue position of the request changes, or when the request changes from queued to processing.
+
+The status payload look like this:
+
+```ts
+interface Status {
+ queue: boolean;
+ code?: string;
+ success?: boolean;
+ stage: "pending" | "error" | "complete" | "generating";
+ size?: number;
+ position?: number;
+ eta?: number;
+ message?: string;
+ progress_data?: Array<{
+ progress: number | null;
+ index: number | null;
+ length: number | null;
+ unit: string | null;
+ desc: string | null;
+ }>;
+ time?: Date;
+}
+```
+
+Usage looks like this:
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const submission = app
+ .submit("/predict", { name: "Chewbacca" })
+
+ for await (const msg of submission) {
+ if (msg.type === "data") {
+ console.log(msg.data);
+ }
+
+ if (msg.type === "status") {
+ console.log(msg);
+ }
+ }
+```
+
+
+##### `cancel`
+
+Certain types of gradio function can run repeatedly and in some cases indefinitely. the `cancel` method will stop such an endpoints and prevent the API from issuing additional updates.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const submission = app
+ .submit("/predict", { name: "Chewbacca" })
+
+
+// later
+
+submission.cancel();
+```
+
+#### `view_api`
+
+The `view_api` method provides details about the API you are connected to. It returns a JavaScript object of all named endpoints, unnamed endpoints and what values they accept and return. This method does not accept arguments.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const api_info = await app.view_api();
+
+console.log(api_info);
+```
+
+#### `config`
+
+The `config` property contains the configuration for the gradio application you are connected to. This object may contain useful meta information about the application.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+console.log(app.config);
+```
+
+### `duplicate`
+
+The duplicate function will attempt to duplicate the space that is referenced and return an instance of `client` connected to that space. If the space has already been duplicated then it will not create a new duplicate and will instead connect to the existing duplicated space. The huggingface token that is passed in will dictate the user under which the space is created.
+
+`duplicate` accepts the same arguments as `client` with the addition of a `private` options property dictating whether the duplicated space should be private or public. A huggingface token is required for duplication to work.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.duplicate("user/space-name", {
+ token: "hf_..."
+});
+```
+
+This function accepts two arguments: `source` and `options`:
+
+#### `source`
+
+The space to duplicate and connect to. [See `client`'s `source` parameter](#source).
+
+#### `options`
+
+Accepts all options that `client` accepts, except `token` is required. [See `client`'s `options` parameter](#source).
+
+`duplicate` also accepts one additional `options` property.
+
+##### `private`
+
+This is an optional property specific to `duplicate`'s options object and will determine whether the space should be public or private. Spaces duplicated via the `duplicate` method are public by default.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.duplicate("user/space-name", {
+ token: "hf_...",
+ private: true
+});
+```
+
+##### `timeout`
+
+This is an optional property specific to `duplicate`'s options object and will set the timeout in minutes before the duplicated space will go to sleep.
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.duplicate("user/space-name", {
+ token: "hf_...",
+ private: true,
+ timeout: 5
+});
+```
+
+##### `hardware`
+
+This is an optional property specific to `duplicate`'s options object and will set the hardware for the duplicated space. By default the hardware used will match that of the original space. If this cannot be obtained it will default to `"cpu-basic"`. For hardware upgrades (beyond the basic CPU tier), you may be required to provide [billing information on Hugging Face](https://huggingface.co/settings/billing).
+
+Possible hardware options are:
+
+- `"cpu-basic"`
+- `"cpu-upgrade"`
+- `"cpu-xl"`
+- `"t4-small"`
+- `"t4-medium"`
+- `"a10g-small"`
+- `"a10g-large"`
+- `"a10g-largex2"`
+- `"a10g-largex4"`
+- `"a100-large"`
+- `"zero-a10g"`
+- `"h100"`
+- `"h100x8"`
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.duplicate("user/space-name", {
+ token: "hf_...",
+ private: true,
+ hardware: "a10g-small"
+});
+```
+
+### `handle_file(file_or_url: File | string | Blob | Buffer)`
+
+This utility function is used to simplify the process of handling file inputs for the client.
+
+Gradio APIs expect a special file datastructure that references a location on the server. These files can be manually uploaded but figuring what to do with different file types can be difficult depending on your environment.
+
+This function will handle files regardless of whether or not they are local files (node only), URLs, Blobs, or Buffers. It will take in a reference and handle it accordingly,uploading the file where appropriate and generating the correct data structure for the client.
+
+The return value of this function can be used anywhere in the input data where a file is expected:
+
+```ts
+import { handle_file } from "@gradio/client";
+
+const app = await Client.connect("user/space-name");
+const result = await app.predict("/predict", {
+ single: handle_file(file),
+ flat: [handle_file(url), handle_file(buffer)],
+ nested: {
+ image: handle_file(url),
+ layers: [handle_file(buffer)]
+ },
+ deeply_nested: {
+ image: handle_file(url),
+ layers: [{
+ layer1: handle_file(buffer),
+ layer2: handle_file(buffer)
+ }]
+ }
+});
+```
+
+#### filepaths
+
+`handle_file` can be passed a local filepath which it will upload to the client server and return a reference that the client can understand.
+
+This only works in a node environment.
+
+Filepaths are resolved relative to the current working directory, not the location of the file that calls `handle_file`.
+
+```ts
+import { handle_file } from "@gradio/client";
+
+// not uploaded yet
+const file_ref = handle_file("path/to/file");
+
+const app = await Client.connect("user/space-name");
+
+// upload happens here
+const result = await app.predict("/predict", {
+ file: file_ref,
+});
+```
+
+#### URLs
+
+`handle_file` can be passed a URL which it will convert into a reference that the client can understand.
+
+```ts
+import { handle_file } from "@gradio/client";
+
+const url_ref = handle_file("https://example.com/file.png");
+
+const app = await Client.connect("user/space-name");
+const result = await app.predict("/predict", {
+ url: url_ref,
+});
+```
+
+#### Blobs
+
+`handle_file` can be passed a Blob which it will upload to the client server and return a reference that the client can understand.
+
+The upload is not initiated until predict or submit are called.
+
+```ts
+import { handle_file } from "@gradio/client";
+
+// not uploaded yet
+const blob_ref = handle_file(new Blob(["Hello, world!"]));
+
+const app = await Client.connect("user/space-name");
+
+// upload happens here
+const result = await app.predict("/predict", {
+ blob: blob_ref,
+});
+```
+
+#### Buffers
+
+`handle_file` can be passed a Buffer which it will upload to the client server and return a reference that the client can understand.
+
+```ts
+import { handle_file } from "@gradio/client";
+import { readFileSync } from "fs";
+
+// not uploaded yet
+const buffer_ref = handle_file(readFileSync("file.png"));
+
+const app = await Client.connect("user/space-name");
+
+// upload happens here
+const result = await app.predict("/predict", {
+ buffer: buffer_ref,
+});
+```
\ No newline at end of file
diff --git a/client/js/index.html b/client/js/index.html
new file mode 100644
index 0000000..1afd437
--- /dev/null
+++ b/client/js/index.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
I am red
', + # 'I am blue
', + # ] + # ) + # ), + gr.Gallery( + value=lambda: images + ), + gr.Model3D(value=gr.get_model3d), + # gr.Plot(value=random_plot), + gr.Markdown(value=lambda: f"### {random.choice(['Hello', 'Hi', 'Goodbye!'])}"), +] + +def evaluate_values(*args): + are_false = [] + for a in args: + if isinstance(a, (pd.DataFrame, np.ndarray)): + are_false.append(not a.any().any()) # type: ignore + elif isinstance(a, str) and a.startswith("#"): + are_false.append(a == "#000000") + else: + are_false.append(not a) + return all(are_false) + +with gr.Blocks() as demo: + for i, component in enumerate(components): + component.label = f"component_{str(i).zfill(2)}" + component.render() + clear = gr.ClearButton(value="Clear", components=components) + result = gr.Textbox(label="Are all cleared?") + hide = gr.Button(value="Hide") + reveal = gr.Button(value="Reveal") + clear_button_and_components = components + [clear] + hide.click( + lambda: [c.__class__(visible=False) for c in clear_button_and_components], + inputs=[], + outputs=clear_button_and_components + ) + reveal.click( + lambda: [c.__class__(visible=True) for c in clear_button_and_components], + inputs=[], + outputs=clear_button_and_components + ) + get_value = gr.Button(value="Get Values") + get_value.click(evaluate_values, components, result) + +if __name__ == "__main__": + demo.launch(allowed_paths=[str(MEDIA_ROOT)]) \ No newline at end of file diff --git a/demo/clearbutton_component/run.py b/demo/clearbutton_component/run.py new file mode 100644 index 0000000..fafc554 --- /dev/null +++ b/demo/clearbutton_component/run.py @@ -0,0 +1,8 @@ +import gradio as gr + +with gr.Blocks() as demo: + textbox = gr.Textbox(value="This is some text") + gr.ClearButton(textbox) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/code/file.css b/demo/code/file.css new file mode 100644 index 0000000..abc61b6 --- /dev/null +++ b/demo/code/file.css @@ -0,0 +1,11 @@ +.class { + color: blue; +} + +#id { + color: pink; +} + +div { + color: purple; +} \ No newline at end of file diff --git a/demo/code/run.py b/demo/code/run.py new file mode 100644 index 0000000..9d6ed77 --- /dev/null +++ b/demo/code/run.py @@ -0,0 +1,39 @@ +import gradio as gr +import os +from time import sleep + +css_file = os.path.join(os.path.dirname(__file__), "file.css") + +def set_lang(language): + print(language) + return gr.Code(language=language) + +def set_lang_from_path(): + sleep(1) + return gr.Code(open(css_file).read(), language="css") + +def code(language, code): + return gr.Code(code, language=language) + +io = gr.Interface(lambda x: x, "code", "code", api_name="predict") + +with gr.Blocks() as demo: + lang = gr.Dropdown(value="python", choices=gr.Code.languages) # type: ignore + with gr.Row(): + code_in = gr.Code( + language="python", + label="Input", + value='def all_odd_elements(sequence):\n """Returns every odd element of the sequence."""', + show_line_numbers = False + ) + code_out = gr.Code(label="Output", show_line_numbers = True) + btn = gr.Button("Run") + btn_two = gr.Button("Load File") + + lang.change(set_lang, inputs=lang, outputs=code_in) + btn.click(code, inputs=[lang, code_in], outputs=code_out) + btn_two.click(set_lang_from_path, inputs=None, outputs=code_out) + io.render() + +if __name__ == "__main__": + demo.launch() diff --git a/demo/code_component/run.py b/demo/code_component/run.py new file mode 100644 index 0000000..3b3738a --- /dev/null +++ b/demo/code_component/run.py @@ -0,0 +1,13 @@ +import gradio as gr + +demo = gr.Interface( + lambda x: x, + gr.Code(language="python"), + gr.Code(language="python"), + examples=[[("/Users/freddy/sources/gradio/demo/code_component/run.py",)], + ["print('Hello, World!')"], + [("/Users/freddy/sources/gradio/demo/code/run.py", )]] +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/color_generator/requirements.txt b/demo/color_generator/requirements.txt new file mode 100644 index 0000000..6f1e523 --- /dev/null +++ b/demo/color_generator/requirements.txt @@ -0,0 +1,2 @@ +opencv-python +numpy \ No newline at end of file diff --git a/demo/color_generator/run.py b/demo/color_generator/run.py new file mode 100644 index 0000000..418c4fd --- /dev/null +++ b/demo/color_generator/run.py @@ -0,0 +1,57 @@ +import gradio as gr +import cv2 # type: ignore +import numpy as np +import random + +# Convert decimal color to hexadecimal color +def RGB_to_Hex(rgb): + color = "#" + for i in rgb: + num = int(i) + color += str(hex(num))[-2:].replace("x", "0").upper() + return color + +# Randomly generate light or dark colors +def random_color(is_light=True): + return ( + random.randint(0, 127) + int(is_light) * 128, + random.randint(0, 127) + int(is_light) * 128, + random.randint(0, 127) + int(is_light) * 128, + ) + +def switch_color(color_style): + is_light = color_style == "light" + back_color_ = random_color(is_light) # Randomly generate colors + back_color = RGB_to_Hex(back_color_) # Convert to hexadecimal + + # Draw color pictures. + w, h = 50, 50 + img = np.zeros((h, w, 3), np.uint8) + cv2.rectangle(img, (0, 0), (w, h), back_color_, thickness=-1) + + return back_color, back_color, img + +inputs = [gr.Radio(["light", "dark"], value="light")] + +outputs = [ + gr.ColorPicker(label="color"), + gr.Textbox(label="hexadecimal color"), + gr.Image(type="numpy", label="color picture"), +] + +title = "Color Generator" +description = ( + "Click the Submit button, and a dark or light color will be randomly generated." +) + +demo = gr.Interface( + fn=switch_color, + inputs=inputs, + outputs=outputs, + title=title, + description=description, + api_name="predict", +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/color_picker/rabbit.png b/demo/color_picker/rabbit.png new file mode 100644 index 0000000..0ccd761 Binary files /dev/null and b/demo/color_picker/rabbit.png differ diff --git a/demo/color_picker/requirements.txt b/demo/color_picker/requirements.txt new file mode 100644 index 0000000..5873a22 --- /dev/null +++ b/demo/color_picker/requirements.txt @@ -0,0 +1 @@ +Pillow \ No newline at end of file diff --git a/demo/color_picker/run.py b/demo/color_picker/run.py new file mode 100644 index 0000000..ef78dd6 --- /dev/null +++ b/demo/color_picker/run.py @@ -0,0 +1,38 @@ +import gradio as gr +import numpy as np +from PIL import Image, ImageColor + +def change_color(icon, color): + + """ + Function that given an icon in .png format changes its color + Args: + icon: Icon whose color needs to be changed. + color: Chosen color with which to edit the input icon. + Returns: + edited_image: Edited icon. + """ + img = icon.convert("LA") + img = img.convert("RGBA") + image_np = np.array(icon) + _, _, _, alpha = image_np.T + mask = alpha > 0 + image_np[..., :-1][mask.T] = ImageColor.getcolor(color, "RGB") + edited_image = Image.fromarray(image_np) + return edited_image + +inputs = [ + gr.Image(label="icon", type="pil", image_mode="RGBA"), + gr.ColorPicker(label="color"), +] +outputs = gr.Image(label="colored icon") + +demo = gr.Interface( + fn=change_color, + inputs=inputs, + outputs=outputs, + api_name="predict" +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/color_picker/screenshot.gif b/demo/color_picker/screenshot.gif new file mode 100644 index 0000000..9ce8cdd Binary files /dev/null and b/demo/color_picker/screenshot.gif differ diff --git a/demo/color_picker/screenshot.png b/demo/color_picker/screenshot.png new file mode 100644 index 0000000..99b747f Binary files /dev/null and b/demo/color_picker/screenshot.png differ diff --git a/demo/colorpicker_component/run.py b/demo/colorpicker_component/run.py new file mode 100644 index 0000000..eafd1d8 --- /dev/null +++ b/demo/colorpicker_component/run.py @@ -0,0 +1,6 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.ColorPicker() + +demo.launch() diff --git a/demo/component_props/cheetah.jpg b/demo/component_props/cheetah.jpg new file mode 100644 index 0000000..ca8a7aa Binary files /dev/null and b/demo/component_props/cheetah.jpg differ diff --git a/demo/component_props/run.py b/demo/component_props/run.py new file mode 100644 index 0000000..1c56e37 --- /dev/null +++ b/demo/component_props/run.py @@ -0,0 +1,79 @@ +import gradio as gr +from gradio.media import get_image + +with gr.Blocks() as demo: + a = gr.Number(value=5, minimum=0, maximum=10, label="Input A", info="Enter a number between 0 and 10") + output_a = gr.JSON(label="Output", elem_id="output") + with gr.Row(): + show_value_btn = gr.Button("Show Value") + double_btn = gr.Button("Double Value and Maximum") + reset_btn = gr.Button("Reset") + + def process_with_props(x: gr.Number): + return { + "value": x.value, + "maximum": x.maximum, + "minimum": x.minimum, + } + show_value_btn.click(process_with_props, a, output_a) + + def double_value_and_max(x: gr.Number): + x.maximum *= 2 # type: ignore + x.value = (x.value or 0) * 2 + x.info = f"Enter a number between 0 and {x.maximum}" + return x + + double_btn.click(double_value_and_max, a, a).then( + process_with_props, a, output_a + ) + + def reset(x: gr.Number): + x.maximum = 10 + x.value = 5 + x.info = "Enter a number between 0 and 10" + return x + + reset_btn.click(reset, a, a).then( + process_with_props, a, output_a + ) + + # Image component demo + gr.Markdown("## Image Component Props") + b = gr.Image(value=get_image("cheetah.jpg"), label="Input Image", width=300, height=300, type="filepath") + output_b = gr.JSON(label="Image Props Output", elem_id="image-output") + with gr.Row(): + show_image_props_btn = gr.Button("Show Image Props") + change_image_size_btn = gr.Button("Change Image Size") + reset_image_btn = gr.Button("Reset Image") + + def show_image_props(x: gr.Image): + return { + "value": x.value if x.value is None else str(x.value), + "width": x.width, + "height": x.height, + "type": x.type, + } + show_image_props_btn.click(show_image_props, b, output_b) + + def change_image_size(x: gr.Image): + x.width = 400 + x.height = 400 + return x + + change_image_size_btn.click(change_image_size, b, b).then( + show_image_props, b, output_b + ) + + def reset_image(x: gr.Image): + x.width = 300 + x.height = 300 + x.value = get_image("cheetah.jpg") + return x + + reset_image_btn.click(reset_image, b, b).then( + show_image_props, b, output_b + ) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/concurrency_with_queue/run.py b/demo/concurrency_with_queue/run.py new file mode 100644 index 0000000..8e3d5e1 --- /dev/null +++ b/demo/concurrency_with_queue/run.py @@ -0,0 +1,14 @@ +import gradio as gr +import time + +def say_hello(name): + time.sleep(5) + return f"Hello {name}!" + +with gr.Blocks() as demo: + inp = gr.Textbox() + outp = gr.Textbox() + button = gr.Button() + button.click(say_hello, inp, outp) + + demo.launch() diff --git a/demo/concurrency_without_queue/run.py b/demo/concurrency_without_queue/run.py new file mode 100644 index 0000000..816a73e --- /dev/null +++ b/demo/concurrency_without_queue/run.py @@ -0,0 +1,14 @@ +import gradio as gr +import time + +def say_hello(name): + time.sleep(5) + return f"Hello {name}!" + +with gr.Blocks() as demo: + inp = gr.Textbox() + outp = gr.Textbox() + button = gr.Button() + button.click(say_hello, inp, outp) + + demo.launch(max_threads=41) diff --git a/demo/copy_events/run.py b/demo/copy_events/run.py new file mode 100644 index 0000000..778c524 --- /dev/null +++ b/demo/copy_events/run.py @@ -0,0 +1,22 @@ +import gradio as gr + +md = "This is **bold** text." + +def copy_callback(copy_data: gr.CopyData): + return copy_data.value + +with gr.Blocks() as demo: + textbox = gr.Textbox(label="Copied text") + with gr.Row(): + markdown = gr.Markdown(value=md, header_links=True, height=400, buttons=["copy"]) + chatbot = gr.Chatbot([("Hello", "World"), ("Goodbye", "World")], buttons=["copy"]) # type: ignore + textbox2 = gr.Textbox("Write something here", interactive=True, buttons=["copy"]) + + gr.on( + [markdown.copy, chatbot.copy, textbox2.copy], + copy_callback, + outputs=textbox + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/count_generator/run.py b/demo/count_generator/run.py new file mode 100644 index 0000000..d9fc3b0 --- /dev/null +++ b/demo/count_generator/run.py @@ -0,0 +1,25 @@ +import gradio as gr +import time + +def count(n): + for i in range(int(n)): + time.sleep(0.5) + yield i + +def show(n): + return str(list(range(int(n)))) + +with gr.Blocks() as demo: + with gr.Column(): + num = gr.Number(value=10) + with gr.Row(): + count_btn = gr.Button("Count") + list_btn = gr.Button("List") + with gr.Column(): + out = gr.Textbox() + + count_btn.click(count, num, out) + list_btn.click(show, num, out) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/custom_css/custom_css.css b/demo/custom_css/custom_css.css new file mode 100644 index 0000000..f65ffa1 --- /dev/null +++ b/demo/custom_css/custom_css.css @@ -0,0 +1,64 @@ +/* CSSKeyframesRule for animation */ +@keyframes animation { + from {background-color: red;} + to {background-color: blue;} +} + +.cool-col { + background-color: red; + animation-name: animation; + animation-duration: 4s; + animation-delay: 2s; + animation-iteration-count: infinite; + border-radius: 10px; + padding: 20px; +} + +/* CSSStyleRule */ +.markdown { + background-color: lightblue; + padding: 20px; +} + +.markdown p { + color: royalblue; +} + +/* CSSMediaRule */ +@media screen and (max-width: 600px) { + .markdown { + background: blue; + } + .markdown p { + color: lightblue; + } +} + +.dark .markdown { + background: pink; +} + +.darktest h3 { + color: black; +} + +.dark .darktest h3 { + color: yellow; +} + +/* CSSFontFaceRule */ +@font-face { + font-family: "test-font"; + src: url("https://mdn.github.io/css-examples/web-fonts/VeraSeBd.ttf") format("truetype"); +} + +.cool-col { + font-family: "test-font"; +} + +/* CSSImportRule */ +@import url("https://fonts.googleapis.com/css2?family=Protest+Riot&display=swap"); + +.markdown { + font-family: "Protest Riot", sans-serif; +} \ No newline at end of file diff --git a/demo/custom_css/run.py b/demo/custom_css/run.py new file mode 100644 index 0000000..ecd96dd --- /dev/null +++ b/demo/custom_css/run.py @@ -0,0 +1,12 @@ +import gradio as gr + +with gr.Blocks() as demo: + with gr.Column(elem_classes="cool-col"): + gr.Markdown("### Gradio Demo with Custom CSS", elem_classes="darktest") + gr.Markdown( + elem_classes="markdown", + value="Resize the browser window to see the CSS media query in action.", + ) + +if __name__ == "__main__": + demo.launch(css_paths=["demo/custom_css/custom_css.css"]) diff --git a/demo/custom_path/run.py b/demo/custom_path/run.py new file mode 100644 index 0000000..06722d1 --- /dev/null +++ b/demo/custom_path/run.py @@ -0,0 +1,59 @@ +""" +Test script for https://github.com/gradio-app/gradio/issues/11848 +Gradio does not show media when FastAPI is behind a reverse proxy (root_path). + +Simulates a reverse proxy by wrapping the FastAPI app in an outer +Starlette app mounted at /myapp. Gradio is mounted at /gradio inside. + +Open http://localhost:8000/myapp/gradio/ to test. +""" + +import os +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from starlette.applications import Starlette +from starlette.routing import Mount +import gradio as gr + +CUSTOM_PATH = "/gradio" +PROXY_PREFIX = "/myapp" + +app = FastAPI() + + +@app.get("/") +def read_main(): + return {"message": "This is your main app"} + + +def identity(image): + return image + + +demo = gr.Interface( + fn=identity, + inputs=gr.Image(type="filepath"), + outputs=gr.Image(label="Output Image"), +) + +app = gr.mount_gradio_app(app, demo, path=CUSTOM_PATH, root_path=f"{PROXY_PREFIX}{CUSTOM_PATH}") + + +@asynccontextmanager +async def lifespan(outer_app): + async with app.router.lifespan_context(app): + yield + + +outer_app = Starlette( + routes=[Mount(PROXY_PREFIX, app=app)], + lifespan=lifespan, +) + +if __name__ == "__main__": + import uvicorn + + port = int(os.environ.get("GRADIO_SERVER_PORT", 8000)) + print(f"\nOpen http://localhost:{port}{PROXY_PREFIX}{CUSTOM_PATH}/\n") + uvicorn.run(outer_app, host="0.0.0.0", port=port) diff --git a/demo/dashboard/DESCRIPTION.md b/demo/dashboard/DESCRIPTION.md new file mode 100644 index 0000000..6bd25ee --- /dev/null +++ b/demo/dashboard/DESCRIPTION.md @@ -0,0 +1 @@ +This demo shows how you can build an interactive dashboard with gradio. Click on a python library on the left hand side and then on the right hand side click on the metric you'd like to see plot over time. Data is pulled from HuggingFace Hub datasets. \ No newline at end of file diff --git a/demo/dashboard/helpers.py b/demo/dashboard/helpers.py new file mode 100644 index 0000000..01505f6 --- /dev/null +++ b/demo/dashboard/helpers.py @@ -0,0 +1,161 @@ +# type: ignore +import collections +from datetime import datetime + +from datasets import DatasetDict, load_dataset +import numpy as np + +datasets = { + "stars": load_dataset("open-source-metrics/stars").sort('dates'), + "issues": load_dataset("open-source-metrics/issues").sort('dates'), + "pip": load_dataset("open-source-metrics/pip").sort('day') +} + +val = 0 + +def _range(e): + global val + e['range'] = val + val += 1 + + current_date = datetime.strptime(e['dates'], "%Y-%m-%dT%H:%M:%SZ") + first_date = datetime.fromtimestamp(1) + week = abs(current_date - first_date).days // 7 + e['week'] = week + + return e + +def _ignore_org_members(e): + global val + e['range_non_org'] = val + + if e['type']['authorAssociation'] != 'MEMBER': + val += 1 + + return e + +stars = {} +for k, v in datasets['stars'].items(): + stars[k] = v.map(_range) + val = 0 + +issues = {} +for k, v in datasets['issues'].items(): + issues[k] = v.map(_range) + val = 0 + issues[k] = issues[k].map(_ignore_org_members) + val = 0 + +datasets['stars'] = DatasetDict(**stars) +datasets['issues'] = DatasetDict(**issues) + +def link_values(library_names, returned_values): + previous_values = {library_name: None for library_name in library_names} + for library_name in library_names: + for i in returned_values.keys(): + if library_name not in returned_values[i]: + returned_values[i][library_name] = previous_values[library_name] + else: + previous_values[library_name] = returned_values[i][library_name] + + return returned_values + +def running_mean(x, N, total_length=-1): + cumsum = np.cumsum(np.insert(x, 0, 0)) + to_pad = max(total_length - len(cumsum), 0) + return np.pad(cumsum[N:] - cumsum[:-N], (to_pad, 0)) / float(N) + +def retrieve_pip_installs(library_names, cumulated): + + if cumulated: + returned_values = {} + for library_name in library_names: + for i in datasets['pip'][library_name]: + if i['day'] in returned_values: + returned_values[i['day']]['Cumulated'] += i['num_downloads'] + else: + returned_values[i['day']] = {'Cumulated': i['num_downloads']} + + library_names = ['Cumulated'] + + else: + returned_values = {} + for library_name in library_names: + for i in datasets['pip'][library_name]: + if i['day'] in returned_values: + returned_values[i['day']][library_name] = i['num_downloads'] + else: + returned_values[i['day']] = {library_name: i['num_downloads']} + + for library_name in library_names: + for i in returned_values.keys(): + if library_name not in returned_values[i]: + returned_values[i][library_name] = None + + returned_values = collections.OrderedDict(sorted(returned_values.items())) + output = {l: [k[l] for k in returned_values.values()] for l in library_names} + output['day'] = list(returned_values.keys()) + return output + +def retrieve_stars(libraries, week_over_week): + returned_values = {} + dataset_dict = datasets['stars'] + + for library_name in libraries: + dataset = dataset_dict[library_name] + + last_value = 0 + last_week = dataset[0]['week'] + for i in dataset: + if week_over_week and last_week == i['week']: + continue + if i['dates'] in returned_values: + returned_values[i['dates']][library_name] = i['range'] - last_value + else: + returned_values[i['dates']] = {library_name: i['range'] - last_value} + + last_value = i['range'] if week_over_week else 0 + last_week = i['week'] + + returned_values = collections.OrderedDict(sorted(returned_values.items())) + returned_values = link_values(libraries, returned_values) + output = {l: [k[l] for k in returned_values.values()][::-1] for l in libraries} + output['day'] = list(returned_values.keys())[::-1] + + # Trim down to a smaller number of points. + output = {k: [v for i, v in enumerate(value) if i % int(len(value) / 100) == 0] for k, value in output.items()} + return output + +def retrieve_issues(libraries, exclude_org_members, week_over_week): + + returned_values = {} + dataset_dict = datasets['issues'] + range_id = 'range' if not exclude_org_members else 'range_non_org' + + for library_name in libraries: + dataset = dataset_dict[library_name] + + last_value = 0 + last_week = dataset[0]['week'] + for i in dataset: + if week_over_week and last_week == i['week']: + continue + + if i['dates'] in returned_values: + returned_values[i['dates']][library_name] = i[range_id] - last_value + else: + returned_values[i['dates']] = {library_name: i[range_id] - last_value} + + last_value = i[range_id] if week_over_week else 0 + last_week = i['week'] + + returned_values = collections.OrderedDict(sorted(returned_values.items())) + returned_values = link_values(libraries, returned_values) + output = {l: [k[l] for k in returned_values.values()][::-1] for l in libraries} + output['day'] = list(returned_values.keys())[::-1] + + # Trim down to a smaller number of points. + output = { + k: [v for i, v in enumerate(value) if i % int(len(value) / 100) == 0] for k, value in output.items() + } + return output diff --git a/demo/dashboard/requirements.txt b/demo/dashboard/requirements.txt new file mode 100644 index 0000000..b68957b --- /dev/null +++ b/demo/dashboard/requirements.txt @@ -0,0 +1,2 @@ +plotly +pandas diff --git a/demo/dashboard/run.py b/demo/dashboard/run.py new file mode 100644 index 0000000..81d79a0 --- /dev/null +++ b/demo/dashboard/run.py @@ -0,0 +1,65 @@ +import gradio as gr +import pandas as pd +import plotly.express as px # type: ignore +from helpers import retrieve_pip_installs, retrieve_stars, retrieve_issues # type: ignore + +LIBRARIES = ["accelerate", "datasets", "diffusers", "evaluate", "gradio", "hub_docs", + "huggingface_hub", "optimum", "pytorch_image_models", "tokenizers", "transformers"] + +def create_pip_plot(libraries, pip_choices): + if "Pip" not in pip_choices: + return gr.Plot(visible=False) + output = retrieve_pip_installs(libraries, "Cumulated" in pip_choices) + df = pd.DataFrame(output).melt(id_vars="day") + plot = px.line(df, x="day", y="value", color="variable", + title="Pip installs") + plot.update_layout(legend=dict(x=0.5, y=0.99), title_x=0.5, legend_title_text="") + return gr.Plot(value=plot, visible=True) + +def create_star_plot(libraries, star_choices): + if "Stars" not in star_choices: + return gr.Plot(visible=False) + output = retrieve_stars(libraries, "Week over Week" in star_choices) + df = pd.DataFrame(output).melt(id_vars="day") + plot = px.line(df, x="day", y="value", color="variable", + title="Number of stargazers") + plot.update_layout(legend=dict(x=0.5, y=0.99), title_x=0.5, legend_title_text="") + return gr.Plot(value=plot, visible=True) + +def create_issue_plot(libraries, issue_choices): + if "Issue" not in issue_choices: + return gr.Plot(visible=False) + output = retrieve_issues(libraries, + exclude_org_members="Exclude org members" in issue_choices, + week_over_week="Week over Week" in issue_choices) + df = pd.DataFrame(output).melt(id_vars="day") + plot = px.line(df, x="day", y="value", color="variable", + title="Cumulated number of issues, PRs, and comments", + ) + plot.update_layout(legend=dict(x=0.5, y=0.99), title_x=0.5, legend_title_text="") + return gr.Plot(value=plot, visible=True) + +with gr.Blocks() as demo: + with gr.Row(): + with gr.Column(): + gr.Markdown("## Select libraries to display") + libraries = gr.CheckboxGroup(choices=LIBRARIES, show_label=False) + with gr.Column(): + gr.Markdown("## Select graphs to display") + pip = gr.CheckboxGroup(choices=["Pip", "Cumulated"], show_label=False) + stars = gr.CheckboxGroup(choices=["Stars", "Week over Week"], show_label=False) + issues = gr.CheckboxGroup(choices=["Issue", "Exclude org members", "week over week"], show_label=False) + with gr.Row(): + fetch = gr.Button(value="Fetch") + with gr.Row(): + with gr.Column(): + pip_plot = gr.Plot(visible=False) + star_plot = gr.Plot(visible=False) + issue_plot = gr.Plot(visible=False) + + fetch.click(create_pip_plot, inputs=[libraries, pip], outputs=pip_plot) + fetch.click(create_star_plot, inputs=[libraries, stars], outputs=star_plot) + fetch.click(create_issue_plot, inputs=[libraries, issues], outputs=issue_plot) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/dataframe_block-ui-test/run.py b/demo/dataframe_block-ui-test/run.py new file mode 100644 index 0000000..e5cfe43 --- /dev/null +++ b/demo/dataframe_block-ui-test/run.py @@ -0,0 +1,15 @@ +import gradio as gr + +with gr.Blocks() as demo: + count = gr.Slider(minimum=1, maximum=10, step=1, label="count") + data = gr.DataFrame( + headers=["A", "B"], col_count=(2, "fixed"), type="array", interactive=True # type: ignore + ) + btn = gr.Button(value="click") + btn.click( + fn=lambda cnt: [[str(2 * i), str(2 * i + 1)] for i in range(int(cnt))], + inputs=[count], + outputs=[data], + ) + +demo.launch() diff --git a/demo/dataframe_colorful/requirements.txt b/demo/dataframe_colorful/requirements.txt new file mode 100644 index 0000000..fb6c7ed --- /dev/null +++ b/demo/dataframe_colorful/requirements.txt @@ -0,0 +1 @@ +pandas diff --git a/demo/dataframe_colorful/run.py b/demo/dataframe_colorful/run.py new file mode 100644 index 0000000..38f146c --- /dev/null +++ b/demo/dataframe_colorful/run.py @@ -0,0 +1,20 @@ +import pandas as pd +import gradio as gr + +df = pd.DataFrame( + { + "A": [14, 4, 5, 4, 1], + "B": [5, 2, 54, 3, 2], + "C": [20, 20, 7, 3, 8], + "D": [14, 3, 6, 2, 6], + "E": [23, 45, 64, 32, 23], + } +) + +t = df.style.highlight_max(color="lightgreen", axis=0) + +with gr.Blocks() as demo: + gr.Dataframe(t) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/dataframe_component/run.py b/demo/dataframe_component/run.py new file mode 100644 index 0000000..64fd22e --- /dev/null +++ b/demo/dataframe_component/run.py @@ -0,0 +1,6 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.Dataframe(interactive=True) + +demo.launch() diff --git a/demo/dataframe_custom_styling/run.py b/demo/dataframe_custom_styling/run.py new file mode 100644 index 0000000..a543b13 --- /dev/null +++ b/demo/dataframe_custom_styling/run.py @@ -0,0 +1,43 @@ +import gradio as gr + +data = [ + ["DeepSeek Coder", 79.3], + ["Llama 3.3", 68.9], + ["Qwen 2.5", 61.9], + ["Gemma 2", 59.5], + ["GPT 2", 18.3], +] + +headers = ["Model", "% Correct (LeetCode Hard)"] + +def get_styling(values): + return [["", f"background: linear-gradient(90deg, rgba(220, 242, 220) {row[1]}%, transparent {row[1]}%)"] for row in values] + +def get_display_value(values): + display_values = [] + medals = ["🥇", "🥈", "🥉"] + for i, row in enumerate(values): + if i < 3: + display_values.append([f"{medals[i]} {row[0]}", row[1]]) + else: + display_values.append([row[0], row[1]]) + return display_values + +styling = get_styling(data) +display_value = get_display_value(data) + + +value = { + "data": data, + "headers": headers, + "metadata": { + "styling": styling, + "display_value": display_value, + }, +} + +with gr.Blocks() as demo: + gr.Dataframe(value, show_search="search") + +if __name__ == "__main__": + demo.launch() diff --git a/demo/dataframe_datatype/requirements.txt b/demo/dataframe_datatype/requirements.txt new file mode 100644 index 0000000..5da331c --- /dev/null +++ b/demo/dataframe_datatype/requirements.txt @@ -0,0 +1,2 @@ +numpy +pandas diff --git a/demo/dataframe_datatype/run.py b/demo/dataframe_datatype/run.py new file mode 100644 index 0000000..bea0505 --- /dev/null +++ b/demo/dataframe_datatype/run.py @@ -0,0 +1,19 @@ +import gradio as gr +import pandas as pd +import numpy as np + +def make_dataframe(n_periods): + rng = np.random.default_rng() + return pd.DataFrame({"date_1": pd.date_range("2021-01-01", periods=n_periods), + "date_2": pd.date_range("2022-02-15", periods=n_periods).strftime('%B %d, %Y, %r'), + "number": rng.random(n_periods).astype(np.float64), + "number_2": rng.integers(0, 100, n_periods).astype(np.int32), + "bool": [True] * n_periods, + "markdown": ["# Hello"] * n_periods}) + +demo = gr.Interface(make_dataframe, + gr.Number(precision=0), + gr.Dataframe(datatype=["date", "date", "number", "number", "bool", "markdown"])) # type: ignore + +if __name__ == "__main__": + demo.launch() diff --git a/demo/dataframe_datetime_long/run.py b/demo/dataframe_datetime_long/run.py new file mode 100644 index 0000000..849e4ff --- /dev/null +++ b/demo/dataframe_datetime_long/run.py @@ -0,0 +1,78 @@ +import random + +import gradio as gr + +ROWS = 5000 +rng = random.Random(42) + +headers = [ + "date", + "str_short", + "str_long", + "num", + "bool", + "markdown", + "html", +] +datatype = ["date", "str", "str", "number", "bool", "markdown", "html"] + +WORDS = [ + "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", + "iota", "kappa", "lambda", "mu", "nu", "xi", "omicron", "pi", +] + +MD_WRAPPERS = [ + lambda s: f"**{s}**", + lambda s: f"*{s}*", + lambda s: f"`{s}`", + lambda s: f"[{s}](https://example.com)", + lambda s: f"# {s}", + lambda s: s, +] + +HTML_WRAPPERS = [ + lambda s: f"{s}", + lambda s: f"{s}", + lambda s: f'{s}', + lambda s: f'{s}', + lambda s: f"{s}",
+ lambda s: s,
+]
+
+
+def random_text(min_words: int, max_words: int) -> str:
+ n = rng.randint(min_words, max_words)
+ return " ".join(rng.choice(WORDS) for _ in range(n))
+
+
+def random_md() -> str:
+ return rng.choice(MD_WRAPPERS)(random_text(1, 8))
+
+
+def random_html() -> str:
+ return rng.choice(HTML_WRAPPERS)(random_text(1, 8))
+
+
+data = [
+ [
+ f"2026-01-{(i % 28) + 1:02d}",
+ rng.choice(WORDS),
+ random_text(2, 6),
+ round(rng.random() * 1000, 2),
+ rng.random() > 0.5,
+ random_md(),
+ random_html(),
+ ]
+ for i in range(ROWS)
+]
+
+with gr.Blocks() as demo:
+ gr.Markdown(
+ f"### Reproduction for #13279: {ROWS} rows × mixed dtypes (markdown/html/date/number/bool/str)"
+ )
+ gr.Dataframe(
+ value=data, headers=headers, datatype=datatype, interactive=False # type: ignore
+ )
+
+if __name__ == "__main__":
+ demo.launch()
diff --git a/demo/dataframe_events/run.py b/demo/dataframe_events/run.py
new file mode 100644
index 0000000..51320d4
--- /dev/null
+++ b/demo/dataframe_events/run.py
@@ -0,0 +1,224 @@
+import gradio as gr
+import pandas as pd
+import numpy as np
+
+
+def update_dataframe():
+ regular_df = pd.DataFrame(
+ np.random.randint(1, 10, size=(5, 5)),
+ columns=pd.Index([str(i) for i in range(5)]),
+ )
+ wide_df = pd.DataFrame(
+ [
+ [5, 22, 91, 17, 73, 38, 84, 46, 65, 10, 155, 122, 11, 144, 133],
+ [81, 42, 13, 97, 33, 77, 59, 100, 29, 61, 213, 195, 142, 118, 127],
+ [37, 71, 63, 102, 28, 94, 19, 55, 88, 44, 116, 139, 122, 150, 147],
+ [104, 52, 49, 26, 83, 67, 31, 92, 79, 18, 241, 115, 159, 123, 137],
+ [16, 95, 74, 68, 43, 101, 27, 85, 39, 57, 129, 148, 132, 111, 156],
+ ],
+ columns=pd.Index([f"col_{i}" for i in range(15)]),
+ )
+ return regular_df, wide_df
+
+
+def clear_dataframes():
+ regular_empty_df = pd.DataFrame([], columns=pd.Index([str(i) for i in range(5)]))
+ wide_empty_df = pd.DataFrame([], columns=pd.Index([f"col_{i}" for i in range(15)]))
+ return regular_empty_df, wide_empty_df
+
+
+def increment_select_counter(evt: gr.SelectData, count):
+ count_val = 1 if count is None else count + 1
+ return count_val, evt.index, evt.value
+
+
+def edit_dataframe(evt: gr.EditData, count):
+ event_data = ", ".join(
+ [
+ f"index: {evt.index}",
+ f"value: {evt.value}",
+ f"previous_value: {evt.previous_value}",
+ ]
+ )
+ return event_data, count + 1
+
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column(scale=1):
+ initial_regular_df = pd.DataFrame(
+ np.zeros((5, 5), dtype=int),
+ columns=pd.Index([str(i) for i in range(5)]),
+ )
+
+ df = gr.Dataframe(
+ value=initial_regular_df,
+ interactive=True,
+ label="Interactive Dataframe",
+ show_label=True,
+ elem_id="dataframe",
+ show_search="filter",
+ buttons=["copy"],
+ show_row_numbers=True,
+ static_columns=[4],
+ )
+
+ with gr.Column(scale=1):
+ initial_wide_df = pd.DataFrame(
+ np.zeros((5, 15), dtype=int),
+ columns=pd.Index([f"col_{i}" for i in range(15)]),
+ )
+
+ df_view = gr.Dataframe(
+ value=initial_wide_df,
+ interactive=False,
+ label="Non-Interactive View (Scroll Horizontally)",
+ show_label=True,
+ show_search="search",
+ elem_id="non-interactive-dataframe",
+ buttons=["copy", "fullscreen"],
+ show_row_numbers=True,
+ )
+
+ tall_df_value = [
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ["DeepSeek Coder", 79.3, True],
+ ["Llama 3.3", 68.9, True],
+ ["Qwen 2.5", 61.9, True],
+ ["Gemma 2", 59.5, False],
+ ["GPT 2", 18.3, False],
+ ]
+
+ def get_display_value(values):
+ display_values = []
+ medals = ["🥇", "🥈", "🥉"]
+ for i, row in enumerate(values):
+ if i < 3:
+ display_values.append([f"{medals[i]} {row[0]}", row[1]])
+ else:
+ display_values.append([row[0], row[1]])
+ return display_values
+
+ display_value = get_display_value(tall_df_value)
+
+ tall_df_value = {
+ "data": tall_df_value,
+ "headers": ["Model", "% Correct (LeetCode Hard)", "Is Open Source"],
+ "metadata": {"display_value": display_value},
+ }
+
+ with gr.Row():
+ with gr.Column():
+ df_tall = gr.Dataframe(
+ value=tall_df_value,
+ interactive=False,
+ label="Tall Dataframe (Scroll Vertically)",
+ datatype=["str", "number", "bool"], # type: ignore
+ max_height=200,
+ show_label=True,
+ elem_id="dataframe_tall",
+ buttons=["copy"],
+ show_row_numbers=True,
+ show_search="search",
+ )
+
+ df_tall_selected_cell_index = gr.Textbox(
+ label="Tall dataframe selected cell index",
+ elem_id="tall_selected_cell_index",
+ )
+ df_tall_selected_cell_value = gr.Textbox(
+ label="Tall dataframe selected cell value",
+ elem_id="tall_selected_cell_value",
+ )
+
+ with gr.Row():
+ with gr.Column():
+ update_btn = gr.Button("Update dataframe", elem_id="update_btn")
+ clear_btn = gr.Button("Clear dataframe", elem_id="clear_btn")
+
+ with gr.Row():
+ change_events = gr.Number(
+ value=0, label="Change events", elem_id="change_events"
+ )
+ input_events = gr.Number(value=0, label="Input events", elem_id="input_events")
+ select_events = gr.Number(
+ value=0, label="Select events", elem_id="select_events"
+ )
+ edit_events = gr.Number(value=0, label="Edit events", elem_id="edit_events")
+
+ with gr.Row():
+ selected_cell_index = gr.Textbox(
+ label="Selected cell index", elem_id="selected_cell_index"
+ )
+ selected_cell_value = gr.Textbox(
+ label="Selected cell value", elem_id="selected_cell_value"
+ )
+ edit_data = gr.Textbox(label="Edit event data", elem_id="edit_data")
+
+ update_btn.click(fn=update_dataframe, outputs=[df, df_view])
+ clear_btn.click(fn=clear_dataframes, outputs=[df, df_view])
+ df.change(fn=lambda x: x + 1, inputs=[change_events], outputs=[change_events])
+ df.edit(edit_dataframe, inputs=[edit_events], outputs=[edit_data, edit_events])
+ df.input(fn=lambda x: x + 1, inputs=[input_events], outputs=[input_events])
+ df.select(
+ fn=increment_select_counter,
+ inputs=[select_events],
+ outputs=[select_events, selected_cell_index, selected_cell_value],
+ )
+
+ df_tall.select(
+ fn=increment_select_counter,
+ inputs=[select_events],
+ outputs=[
+ select_events,
+ df_tall_selected_cell_index,
+ df_tall_selected_cell_value,
+ ],
+ )
+
+if __name__ == "__main__":
+ demo.launch()
diff --git a/demo/dataframe_streaming/run.py b/demo/dataframe_streaming/run.py
new file mode 100644
index 0000000..d5108b4
--- /dev/null
+++ b/demo/dataframe_streaming/run.py
@@ -0,0 +1,34 @@
+import gradio as gr
+import pandas as pd
+import time
+
+def update_dataframe(df):
+ df.iloc[:, :] = 1
+ yield df, 1
+ time.sleep(0.1)
+ df.iloc[:, :] = 2
+ yield df, 2
+
+def sum_values(df):
+ return pd.to_numeric(df.values.flatten(), errors='coerce').sum() # type: ignore
+
+initial_df = pd.DataFrame(0, index=range(5), columns=range(5))
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ button = gr.Button("Update DataFrame")
+ number = gr.Number(value=0, label="Number")
+ dataframe = gr.Dataframe(value=initial_df, label="Dataframe")
+
+ button.click(fn=update_dataframe, inputs=dataframe, outputs=[dataframe, number])
+ with gr.Row():
+ change_events = gr.Number(label="Change events")
+ input_events = gr.Number(label="Input events")
+ sum_of_values = gr.Number(label="Sum of values")
+
+ dataframe.change(lambda x:x+1, inputs=change_events, outputs=change_events)
+ dataframe.input(lambda x:x+1, inputs=input_events, outputs=input_events)
+ dataframe.change(sum_values, inputs=dataframe, outputs=sum_of_values)
+
+if __name__ == "__main__":
+ demo.launch()
diff --git a/demo/dataframe_tab/run.py b/demo/dataframe_tab/run.py
new file mode 100644
index 0000000..6a3729d
--- /dev/null
+++ b/demo/dataframe_tab/run.py
@@ -0,0 +1,12 @@
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Tab():
+ gr.HTML("hi
") + with gr.Tab(): + gr.Dataframe( + value=[[i + 1] for i in range(10)], + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/dataframe_tab_select/run.py b/demo/dataframe_tab_select/run.py new file mode 100644 index 0000000..205e894 --- /dev/null +++ b/demo/dataframe_tab_select/run.py @@ -0,0 +1,20 @@ +import gradio as gr + + +def get_data(): + return [[i, f"Item {i}"] for i in range(10)] + + +with gr.Blocks() as demo: + gr.Markdown("Switching to 'Tab 2' populates the dataframe via its select event.") + + with gr.Tab("Tab 1"): + gr.Markdown("Click 'Tab 2'.") + + with gr.Tab("Tab 2") as tab2: + df = gr.Dataframe(headers=["ID", "Name"], elem_id="tab_df") + tab2.select(get_data, outputs=df) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/dataframe_widths/run.py b/demo/dataframe_widths/run.py new file mode 100644 index 0000000..cab5eb9 --- /dev/null +++ b/demo/dataframe_widths/run.py @@ -0,0 +1,262 @@ +import gradio as gr + +headers = ["Short", "Medium Column", "Long Description", "Also Long"] + +short_row = ["id-1", "alpha", "small cell", "small cell"] +long_row = [ + "id-2", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + ( + "This is a deliberately very long cell value intended to exceed any " + "reasonable column width so we can see truncation and wrapping behavior " + "kick in. It keeps going and going and going so that the column must " + "either truncate with an ellipsis or wrap across multiple lines." + ), + "The quick brown fox jumps over the lazy dog several times in a row.", +] +unbreakable_row = [ + "id-3", + "ok", + "Supercalifragilisticexpialidocious" * 4, + "word " * 30, +] + +data = [ + short_row, + long_row, + unbreakable_row, + short_row, + long_row, + unbreakable_row, + short_row, + long_row, + unbreakable_row, +] + +with gr.Blocks(title="Dataframe widths & wrapping") as demo: + gr.Markdown( + """ + # Dataframe column widths and wrapping + + Demonstrates how `wrap` and `column_widths` interact. In every case the + column's max width is capped at 100% of the dataframe's viewport so a + single cell can never be wider than the scroll area. + """ + ) + + with gr.Row(): + with gr.Tab("Defaults (no wrap, auto widths)"): + gr.Markdown( + "No `wrap`, no `column_widths`. Columns size to the longest cell; " + "anything longer than the viewport truncates with an ellipsis." + ) + gr.Dataframe(value=data, headers=headers) + + with gr.Tab("wrap=True, auto widths"): + gr.Markdown( + "`wrap=True` and no `column_widths`. Columns still size to the " + "longest cell, but content that would exceed the viewport wraps " + "onto multiple lines instead of truncating." + ) + gr.Dataframe(value=data, headers=headers, wrap=True) + + with gr.Tab("Fixed pixel widths"): + gr.Markdown( + "`column_widths=['80px', '160px', '240px', '200px']`. Pixel widths " + "are honored exactly; long content truncates." + ) + gr.Dataframe( + value=data, + headers=headers, + column_widths=["80px", "160px", "240px", "200px"], + ) + + with gr.Tab("Fixed pixel widths + wrap"): + gr.Markdown( + "Same fixed widths but `wrap=True`. Long content wraps within the " + "fixed column instead of truncating." + ) + gr.Dataframe( + value=data, + headers=headers, + column_widths=["80px", "160px", "240px", "200px"], + wrap=True, + ) + + with gr.Tab("Percentage widths"): + gr.Markdown( + "`column_widths=['10%', '25%', '40%', '25%']`. Percentages resolve " + "against the dataframe's viewport width, so they re-flow when you " + "resize the window." + ) + gr.Dataframe( + value=data, + headers=headers, + column_widths=["10%", "25%", "40%", "25%"], + ) + + with gr.Tab("Percentage widths + wrap"): + gr.Markdown("Same percentages, but with `wrap=True`.") + gr.Dataframe( + value=data, + headers=headers, + column_widths=["10%", "25%", "40%", "25%"], + wrap=True, + ) + + with gr.Tab("Mixed widths (px / % / int)"): + gr.Markdown( + "`column_widths=[120, '20%', 'auto', '30%']`. Integers are treated " + "as pixels; `'auto'` falls back to content-based sizing (capped at " + "the viewport)." + ) + gr.Dataframe( + value=data, + headers=headers, + column_widths=[120, "20%", "auto", "30%"], + ) + + with gr.Tab("max_chars with wide data"): + gr.Markdown( + "`max_chars=20` truncates cell text to 20 characters with an " + "ellipsis. The column is sized to the rendered (truncated) text " + "rather than the full underlying value, so there's no wasted " + "horizontal space." + ) + gr.Dataframe( + value=data, + headers=headers, + max_chars=20, + ) + + with gr.Tab("max_chars + fixed widths + wrap"): + gr.Markdown( + "`max_chars=15`, explicit pixel widths, and `wrap=True`. Text is " + "first truncated by character count, then wrapped to fit the " + "column." + ) + gr.Dataframe( + value=data, + headers=headers, + max_chars=15, + column_widths=["80px", "160px", "200px", "200px"], + wrap=True, + ) + + with gr.Row(): + with gr.Tab("Defaults (no wrap, auto widths)"): + gr.Markdown( + "No `wrap`, no `column_widths`. Columns size to the longest cell; " + "anything longer than the viewport truncates with an ellipsis." + ) + gr.Dataframe( + value=data, + headers=headers, + interactive=True, + ) + + with gr.Tab("wrap=True, auto widths"): + gr.Markdown( + "`wrap=True` and no `column_widths`. Columns still size to the " + "longest cell, but content that would exceed the viewport wraps " + "onto multiple lines instead of truncating." + ) + gr.Dataframe( + value=data, + headers=headers, + wrap=True, + interactive=True, + ) + + with gr.Tab("Fixed pixel widths"): + gr.Markdown( + "`column_widths=['80px', '160px', '240px', '200px']`. Pixel widths " + "are honored exactly; long content truncates." + ) + gr.Dataframe( + interactive=True, + value=data, + headers=headers, + column_widths=["80px", "160px", "240px", "200px"], + ) + + with gr.Tab("Fixed pixel widths + wrap"): + gr.Markdown( + "Same fixed widths but `wrap=True`. Long content wraps within the " + "fixed column instead of truncating." + ) + gr.Dataframe( + interactive=True, + value=data, + headers=headers, + column_widths=["80px", "160px", "240px", "200px"], + wrap=True, + ) + + with gr.Tab("Percentage widths"): + gr.Markdown( + "`column_widths=['10%', '25%', '40%', '25%']`. Percentages resolve " + "against the dataframe's viewport width, so they re-flow when you " + "resize the window." + ) + gr.Dataframe( + interactive=True, + value=data, + headers=headers, + column_widths=["10%", "25%", "40%", "25%"], + ) + + with gr.Tab("Percentage widths + wrap"): + gr.Markdown("Same percentages, but with `wrap=True`.") + gr.Dataframe( + value=data, + interactive=True, + headers=headers, + column_widths=["10%", "25%", "40%", "25%"], + wrap=True, + ) + + with gr.Tab("Mixed widths (px / % / int)"): + gr.Markdown( + "`column_widths=[120, '20%', 'auto', '30%']`. Integers are treated " + "as pixels; `'auto'` falls back to content-based sizing (capped at " + "the viewport)." + ) + gr.Dataframe( + value=data, + interactive=True, + headers=headers, + column_widths=[120, "20%", "auto", "30%"], + ) + + with gr.Tab("max_chars with wide data"): + gr.Markdown( + "`max_chars=20` truncates cell text to 20 characters with an " + "ellipsis. The column is sized to the rendered (truncated) text " + "rather than the full underlying value, so there's no wasted " + "horizontal space." + ) + gr.Dataframe( + interactive=True, + value=data, + headers=headers, + max_chars=20, + ) + + with gr.Tab("max_chars + fixed widths + wrap"): + gr.Markdown( + "`max_chars=15`, explicit pixel widths, and `wrap=True`. Text is " + "first truncated by character count, then wrapped to fit the " + "column." + ) + gr.Dataframe( + interactive=True, + value=data, + headers=headers, + max_chars=15, + column_widths=["80px", "160px", "200px", "200px"], + wrap=True, + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/dataset/requirements.txt b/demo/dataset/requirements.txt new file mode 100644 index 0000000..24ce15a --- /dev/null +++ b/demo/dataset/requirements.txt @@ -0,0 +1 @@ +numpy diff --git a/demo/dataset/run.py b/demo/dataset/run.py new file mode 100755 index 0000000..d8ceef0 --- /dev/null +++ b/demo/dataset/run.py @@ -0,0 +1,173 @@ +import gradio as gr +import numpy as np +# get_image(), get_video(), get_audio(), get_file(), get_model3d() return file paths to sample media included with Gradio +from gradio.media import get_image, get_video, get_audio, get_file, get_model3d + +txt = "the quick brown fox" +num = 10 + +img = get_image("cheetah1.jpg") +vid = get_video("world.mp4") +audio = get_audio("cantina.wav") +csv = get_file("time.csv") +model = get_model3d("Bunny.obj") + +dataframe = [[1, 2, 3, 4], [4, 5, 6, 7], [8, 9, 1, 2], [3, 4, 5, 6]] + +with gr.Blocks() as demo: + gr.Markdown("# Dataset previews") + a = gr.Audio(visible=False) + gr.Dataset( + components=[a], + label="Audio", + samples=[ + [audio], + [audio], + [audio], + [audio], + [audio], + [audio], + ], + ) + c = gr.Checkbox(visible=False) + gr.Dataset( + label="Checkbox", + components=[c], + samples=[[True], [True], [False], [True], [False], [False]], + ) + + c_2 = gr.CheckboxGroup(visible=False, choices=['a', 'b', 'c']) + gr.Dataset( + label="CheckboxGroup", + components=[c_2], + samples=[ + [["a"]], + [["a", "b"]], + [["a", "b", "c"]], + [["b"]], + [["c"]], + [["a", "c"]], + ], + ) + c_3 = gr.ColorPicker(visible=False) + gr.Dataset( + label="ColorPicker", + components=[c_3], + samples=[ + ["#FFFFFF"], + ["#000000"], + ["#FFFFFF"], + ["#000000"], + ["#FFFFFF"], + ["#000000"], + ], + ) + d = gr.DataFrame(visible=False) + gr.Dataset( + components=[d], + label="Dataframe", + samples=[ + [np.zeros((3, 3)).tolist()], + [np.ones((2, 2)).tolist()], + [np.random.randint(0, 10, (3, 10)).tolist()], + [np.random.randint(0, 10, (10, 3)).tolist()], + [np.random.randint(0, 10, (10, 10)).tolist()], + ], + ) + d_2 = gr.Dropdown(visible=False, choices=["one", "two", "three"]) + gr.Dataset( + components=[d_2], + label="Dropdown", + samples=[["one"], ["two"], ["three"], ["one"], ["two"], ["three"]], + ) + f = gr.File(visible=False) + gr.Dataset( + components=[f], + label="File", + samples=[ + [csv], + [csv], + [csv], + [csv], + [csv], + [csv], + ], + ) + h = gr.HTML(visible=False) + gr.Dataset( + components=[h], + label="HTML", + samples=[ + ["
Dia Dialogue Generation Model
+ This is paragraph {i+1}.
" + time.sleep(0.2) + yield val + +with gr.Blocks() as demo: + h = gr.HTML(value="This is a paragraph 0.
", max_height=200, autoscroll=True) + demo.load(longer, h, h) + +demo.launch() diff --git a/demo/html_children/run.py b/demo/html_children/run.py new file mode 100644 index 0000000..fe3ac0d --- /dev/null +++ b/demo/html_children/run.py @@ -0,0 +1,37 @@ +import gradio as gr + +with gr.Blocks() as demo: + with gr.HTML(html_template=''' + +This example was written in HTML
") + +demo.launch() diff --git a/demo/html_custom_event/run.py b/demo/html_custom_event/run.py new file mode 100644 index 0000000..68d9e7c --- /dev/null +++ b/demo/html_custom_event/run.py @@ -0,0 +1,22 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.Markdown("# Custom Event Demo\nPress any key to see it displayed below.") + + keyboard = gr.HTML( + js_on_load=""" + document.addEventListener('keydown', (e) => { + trigger('keypress', {key: e.key}); + }); + """, + ) + + textbox = gr.Textbox(label="Key pressed") + + def get_key(evt_data: gr.EventData): + return evt_data.key + + keyboard.keypress(get_key, None, textbox) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/html_head_script_async/run.py b/demo/html_head_script_async/run.py new file mode 100644 index 0000000..72267bb --- /dev/null +++ b/demo/html_head_script_async/run.py @@ -0,0 +1,17 @@ +import gradio as gr + +# Fixture for the "explicit async is honored" test: both scripts are `async`, +# so download-completion order applies (delayed first runs after fast second). + +with gr.Blocks() as demo: + gr.HTML( + html_template="Directory: ${value}
+ + +I am red
', + 'I am blue
', + ] + ) + ), + gr.Gallery( + value=lambda: [get_image() for _ in range(3)] + ), + gr.Chatbot( + value=lambda: random.choice([[("hello", "hi!")], [("bye", "goodbye!")]]) + ), + gr.Model3D(value=lambda: get_model3d()), + gr.Plot(value=random_plot), + gr.Markdown(value=lambda: f"### {random.choice(['Hello', 'Hi', 'Goodbye!'])}"), + ], + outputs=[ + gr.State(value=lambda: random.choice(string.ascii_lowercase)) + ], + api_name="predict", +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/label_component/run.py b/demo/label_component/run.py new file mode 100644 index 0000000..939732f --- /dev/null +++ b/demo/label_component/run.py @@ -0,0 +1,6 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.Label(value={"First Label": 0.7, "Second Label": 0.2, "Third Label": 0.1}) + +demo.launch() diff --git a/demo/latex/run.py b/demo/latex/run.py new file mode 100644 index 0000000..abf27dc --- /dev/null +++ b/demo/latex/run.py @@ -0,0 +1,26 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.Markdown( + r""" + # Hello World! $\frac{\sqrt{x + y}}{4}$ is today's lesson + + ## the $\sqrt{x + y}$ is first + + Start with $\frac{\frac{x+1}{x+2}}{x+3}$ then we get $ 2+x $ and $3$. + + There are three formulas to know: + + the first is $\gamma^2 + \theta^2 = \omega^2$ + + $\sqrt{x^2+1}$ is next + + Integral $\int_{a}^{b} x^2 \,dx$ is last + + Start typing below to see the output. + + I spent $5 at the grocery store. Then I bought a $2.50 ice cream cone. + """) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/letter_counter/run.py b/demo/letter_counter/run.py new file mode 100644 index 0000000..ba94b47 --- /dev/null +++ b/demo/letter_counter/run.py @@ -0,0 +1,29 @@ +import gradio as gr + +def letter_counter(word, letter): + """ + Count the number of occurrences of a letter in a word or text. + + Args: + word (str): The input text to search through + letter (str): The letter to search for + + Returns: + str: A message indicating how many times the letter appears + """ + word = word.lower() + letter = letter.lower() + count = word.count(letter) + return count + +demo = gr.Interface( + fn=letter_counter, + inputs=[gr.Textbox("strawberry"), gr.Textbox("r")], + outputs=[gr.Number()], + title="Letter Counter", + description="Enter text and a letter to count how many times the letter appears in the text.", + api_name="predict" +) + +if __name__ == "__main__": + demo.launch(mcp_server=True) diff --git a/demo/line_plot/requirements.txt b/demo/line_plot/requirements.txt new file mode 100644 index 0000000..7e0e60e --- /dev/null +++ b/demo/line_plot/requirements.txt @@ -0,0 +1,2 @@ +vega_datasets +pandas \ No newline at end of file diff --git a/demo/line_plot/run.py b/demo/line_plot/run.py new file mode 100644 index 0000000..17f5b11 --- /dev/null +++ b/demo/line_plot/run.py @@ -0,0 +1,95 @@ +import gradio as gr +from vega_datasets import data + +stocks = data.stocks() +gapminder = data.gapminder() +gapminder = gapminder.loc[ + gapminder.country.isin(["Argentina", "Australia", "Afghanistan"]) +] +climate = data.climate() +seattle_weather = data.seattle_weather() + +## Or generate your own fake data, here's an example for stocks: +# +# import pandas as pd +# import random +# +# stocks = pd.DataFrame( +# { +# "symbol": [ +# random.choice( +# [ +# "MSFT", +# "AAPL", +# "AMZN", +# "IBM", +# "GOOG", +# ] +# ) +# for _ in range(120) +# ], +# "date": [ +# pd.Timestamp(year=2000 + i, month=j, day=1) +# for i in range(10) +# for j in range(1, 13) +# ], +# "price": [random.randint(10, 200) for _ in range(120)], +# } +# ) + +def line_plot_fn(dataset): + if dataset == "stocks": + return gr.LinePlot( + stocks, + x="date", + y="price", + color="symbol", + title="Stock Prices", + tooltip=["date", "price", "symbol"], + height=300, + ) + elif dataset == "climate": + return gr.LinePlot( + climate, + x="DATE", + y="HLY-TEMP-NORMAL", + y_lim=[250, 500], + title="Climate", + tooltip=["DATE", "HLY-TEMP-NORMAL"], + height=300, + ) + elif dataset == "seattle_weather": + return gr.LinePlot( + seattle_weather, + x="date", + y="temp_min", + tooltip=["weather", "date"], + title="Seattle Weather", + height=300, + ) + elif dataset == "gapminder": + return gr.LinePlot( + gapminder, + x="year", + y="life_expect", + color="country", + title="Life expectancy for countries", + x_lim=[1950, 2010], + tooltip=["country", "life_expect"], + height=300, + ) + +with gr.Blocks() as line_plot: + with gr.Row(): + with gr.Column(): + dataset = gr.Dropdown( + choices=["stocks", "climate", "seattle_weather", "gapminder"], + value="stocks", + ) + with gr.Column(): + plot = gr.LinePlot() + dataset.change(line_plot_fn, inputs=dataset, outputs=plot) + line_plot.load(fn=line_plot_fn, inputs=dataset, outputs=plot) + +if __name__ == "__main__": + line_plot.launch() diff --git a/demo/line_plot_demo/requirements.txt b/demo/line_plot_demo/requirements.txt new file mode 100644 index 0000000..fb6c7ed --- /dev/null +++ b/demo/line_plot_demo/requirements.txt @@ -0,0 +1 @@ +pandas diff --git a/demo/line_plot_demo/run.py b/demo/line_plot_demo/run.py new file mode 100644 index 0000000..f227c53 --- /dev/null +++ b/demo/line_plot_demo/run.py @@ -0,0 +1,86 @@ +import pandas as pd +from random import randint, random +import gradio as gr + + +temp_sensor_data = pd.DataFrame( + { + "time": pd.date_range("2021-01-01", end="2021-01-05", periods=200), + "temperature": [randint(50 + 10 * (i % 2), 65 + 15 * (i % 2)) for i in range(200)], + "humidity": [randint(50 + 10 * (i % 2), 65 + 15 * (i % 2)) for i in range(200)], + "location": ["indoor", "outdoor"] * 100, + } +) + +food_rating_data = pd.DataFrame( + { + "cuisine": [["Italian", "Mexican", "Chinese"][i % 3] for i in range(100)], + "rating": [random() * 4 + 0.5 * (i % 3) for i in range(100)], + "price": [randint(10, 50) + 4 * (i % 3) for i in range(100)], + "wait": [random() for i in range(100)], + } +) + +with gr.Blocks() as line_plots: + with gr.Row(): + start = gr.DateTime("2021-01-01 00:00:00", label="Start") + end = gr.DateTime("2021-01-05 00:00:00", label="End") + apply_btn = gr.Button("Apply", scale=0) + with gr.Row(): + group_by = gr.Radio(["None", "30m", "1h", "4h", "1d"], value="None", label="Group by") + aggregate = gr.Radio(["sum", "mean", "median", "min", "max"], value="sum", label="Aggregation") + + temp_by_time = gr.LinePlot( + temp_sensor_data, + x="time", + y="temperature", + ) + temp_by_time_location = gr.LinePlot( + temp_sensor_data, + x="time", + y="temperature", + color="location", + ) + + time_graphs = [temp_by_time, temp_by_time_location] + group_by.change( + lambda group: [gr.LinePlot(x_bin=None if group == "None" else group)] * len(time_graphs), + group_by, + time_graphs + ) + aggregate.change( + lambda aggregate: [gr.LinePlot(y_aggregate=aggregate)] * len(time_graphs), + aggregate, + time_graphs + ) + + def rescale(select: gr.SelectData): + return select.index + rescale_evt = gr.on([plot.select for plot in time_graphs], rescale, None, [start, end]) + + for trigger in [apply_btn.click, rescale_evt.then]: + trigger( + lambda start, end: [gr.LinePlot(x_lim=[start, end])] * len(time_graphs), [start, end], time_graphs + ) + + price_by_cuisine = gr.LinePlot( + food_rating_data, + x="cuisine", + y="price", + ) + with gr.Row(): + price_by_rating = gr.LinePlot( + food_rating_data, + x="rating", + y="price", + ) + price_by_rating_color = gr.LinePlot( + food_rating_data, + x="rating", + y="price", + color="cuisine", + color_map={"Italian": "red", "Mexican": "green", "Chinese": "blue"}, + ) + +if __name__ == "__main__": + line_plots.launch() diff --git a/demo/lineplot_component/requirements.txt b/demo/lineplot_component/requirements.txt new file mode 100644 index 0000000..d1c8a7a --- /dev/null +++ b/demo/lineplot_component/requirements.txt @@ -0,0 +1 @@ +vega_datasets \ No newline at end of file diff --git a/demo/lineplot_component/run.py b/demo/lineplot_component/run.py new file mode 100644 index 0000000..1fbc6f2 --- /dev/null +++ b/demo/lineplot_component/run.py @@ -0,0 +1,17 @@ +import gradio as gr +from vega_datasets import data + +with gr.Blocks() as demo: + gr.LinePlot( + data.stocks(), + x="date", + y="price", + color="symbol", + title="Stock Prices", + tooltip=["date", "price", "symbol"], + height=300, + container=False, + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/live_dashboard/DESCRIPTION.md b/demo/live_dashboard/DESCRIPTION.md new file mode 100644 index 0000000..4d2bfeb --- /dev/null +++ b/demo/live_dashboard/DESCRIPTION.md @@ -0,0 +1,3 @@ +This demo shows how you can build a live interactive dashboard with gradio. +The current time is refreshed every second and the plot every half second by using the 'every' keyword in the event handler. +Changing the value of the slider will control the period of the sine curve (the distance between peaks). \ No newline at end of file diff --git a/demo/live_dashboard/requirements.txt b/demo/live_dashboard/requirements.txt new file mode 100644 index 0000000..38c5515 --- /dev/null +++ b/demo/live_dashboard/requirements.txt @@ -0,0 +1,3 @@ +numpy +pandas +plotly diff --git a/demo/live_dashboard/run.py b/demo/live_dashboard/run.py new file mode 100644 index 0000000..16052c7 --- /dev/null +++ b/demo/live_dashboard/run.py @@ -0,0 +1,51 @@ +import math + +import pandas as pd + +import gradio as gr +import datetime +import numpy as np + +def get_time(): + return datetime.datetime.now() + +plot_end = 2 * math.pi + +def get_plot(period=1): + global plot_end + x = np.arange(plot_end - 2 * math.pi, plot_end, 0.02) + y = np.sin(2 * math.pi * period * x) + update = gr.LinePlot( + value=pd.DataFrame({"x": x, "y": y}), + x="x", + y="y", + title="Plot (updates every second)", + height=350, + ) + plot_end += 0.1 + return update + +with gr.Blocks() as demo: + with gr.Row(): + with gr.Column(): + c_time2 = gr.Textbox(label="Current Time refreshed every second") + period = gr.Slider( + label="Period of plot", value=1, minimum=0, maximum=10 + ) + plot = gr.LinePlot(show_label=False) + with gr.Column(): + start_time = gr.Textbox(label="Start Time") + end_time = gr.Textbox(label="End Time") + + timer = gr.Timer(1) + + timer.tick(lambda: datetime.datetime.now(), None, c_time2) + timer.tick(get_plot, period, plot) + + def select(selection_range: gr.SelectData): + return gr.LinePlot(x_lim=selection_range.index), selection_range.index[0], selection_range.index[1] + plot.select(select, None, [plot, start_time, end_time]) + plot.double_click(lambda: gr.LinePlot(x_lim=None), None, plot) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/live_with_vars/run.py b/demo/live_with_vars/run.py new file mode 100644 index 0000000..736b165 --- /dev/null +++ b/demo/live_with_vars/run.py @@ -0,0 +1,9 @@ +import gradio as gr + +demo = gr.Interface( + lambda x, y: (x + y if y is not None else x, x + y if y is not None else x), + ["textbox", "state"], + ["textbox", "state"], live=True, api_name="predict") + +if __name__ == "__main__": + demo.launch() diff --git a/demo/llm_claude/requirements.txt b/demo/llm_claude/requirements.txt new file mode 100644 index 0000000..3ceaffc --- /dev/null +++ b/demo/llm_claude/requirements.txt @@ -0,0 +1 @@ +openai>=1.0.0 \ No newline at end of file diff --git a/demo/llm_claude/run.py b/demo/llm_claude/run.py new file mode 100644 index 0000000..a18f510 --- /dev/null +++ b/demo/llm_claude/run.py @@ -0,0 +1,41 @@ +# This is a simple 20 questions-style game built on top of the Anthropic API. +# Before running this, make sure you have exported your Anthropic API key as an environment variable: +# export ANTHROPIC_API_KEY="your-anthropic-api-key" + +import anthropic # type: ignore +import gradio as gr + +client = anthropic.Anthropic() + +def predict(message, history): + keys_to_keep = ["role", "content"] + history = [{k: d[k] for k in keys_to_keep if k in d} for d in history] + history.append({"role": "user", "content": message}) + if len(history) > 20: + history.append({"role": "user", "content": "DONE"}) + output = client.messages.create( + messages=history, # type: ignore + model="claude-3-5-sonnet-20241022", + max_tokens=1000, + system="You are guessing an object that the user is thinking of. You can ask 10 yes/no questions. Keep asking questions until the user says DONE" + ) + return { + "role": "assistant", + "content": output.content[0].text, # type: ignore + "options": [{"value": "Yes"}, {"value": "No"}] + } + +placeholder = """ +Powered by Parler-TTS + """ + ) + with gr.Group(): + with gr.Row(): + audio_out = gr.Audio( + label="Spoken Answer", streaming=True, autoplay=True, loop=False + ) + answer = gr.Textbox(label="Answer") + state = gr.State() + with gr.Row(): + gr.Markdown( + "Example questions: 'Should I get a dog?', 'What is the meaning of life?'" + ) + audio_in = gr.Audio( + label="Speak you question", sources="microphone", type="filepath" + ) + with gr.Row(): + gr.HTML( + """
" + "🎙️ Learn more about UniSpeech-SAT | " + "📚 UniSpeech-SAT paper | " + "📚 X-Vector paper" + "
" +) +examples = [ + ["samples/cate_blanch.mp3", "samples/cate_blanch_2.mp3"], + ["samples/cate_blanch.mp3", "samples/heath_ledger.mp3"], +] + +interface = gr.Interface( + fn=similarity_fn, + inputs=inputs, + outputs=output, + layout="horizontal", + flagging_mode="never", + live=False, + examples=examples, + cache_examples=False +) +interface.launch() diff --git a/demo/save_file_no_output/run.py b/demo/save_file_no_output/run.py new file mode 100644 index 0000000..64b1ac1 --- /dev/null +++ b/demo/save_file_no_output/run.py @@ -0,0 +1,17 @@ +import random +import string +import gradio as gr + +def save_image_random_name(image): + random_string = ''.join(random.choices(string.ascii_letters, k=20)) + '.png' + image.save(random_string) + print(f"Saved image to {random_string}!") + +demo = gr.Interface( + fn=save_image_random_name, + inputs=gr.Image(type="pil"), + outputs=None, + api_name="predict", +) +if __name__ == "__main__": + demo.launch() diff --git a/demo/scatter_plot/requirements.txt b/demo/scatter_plot/requirements.txt new file mode 100644 index 0000000..7e0e60e --- /dev/null +++ b/demo/scatter_plot/requirements.txt @@ -0,0 +1,2 @@ +vega_datasets +pandas \ No newline at end of file diff --git a/demo/scatter_plot/run.py b/demo/scatter_plot/run.py new file mode 100644 index 0000000..1adb917 --- /dev/null +++ b/demo/scatter_plot/run.py @@ -0,0 +1,65 @@ +import gradio as gr +from vega_datasets import data + +cars = data.cars() +iris = data.iris() + +# # Or generate your own fake data + +# import pandas as pd +# import random + +# cars_data = { +# "Name": ["car name " + f" {int(i/10)}" for i in range(400)], +# "Miles_per_Gallon": [random.randint(10, 30) for _ in range(400)], +# "Origin": [random.choice(["USA", "Europe", "Japan"]) for _ in range(400)], +# "Horsepower": [random.randint(50, 250) for _ in range(400)], +# } + +# iris_data = { +# "petalWidth": [round(random.uniform(0, 2.5), 2) for _ in range(150)], +# "petalLength": [round(random.uniform(0, 7), 2) for _ in range(150)], +# "species": [ +# random.choice(["setosa", "versicolor", "virginica"]) for _ in range(150) +# ], +# } + +# cars = pd.DataFrame(cars_data) +# iris = pd.DataFrame(iris_data) + +def scatter_plot_fn(dataset): + if dataset == "iris": + return gr.ScatterPlot( + value=iris, + x="petalWidth", + y="petalLength", + color="species", + title="Iris Dataset", + x_title="Petal Width", + y_title="Petal Length", + tooltip=["petalWidth", "petalLength", "species"], + caption="", + ) + else: + return gr.ScatterPlot( + value=cars, + x="Horsepower", + y="Miles_per_Gallon", + color="Origin", + tooltip=["Name"], + title="Car Data", + y_title="Miles per Gallon", + caption="MPG vs Horsepower of various cars", + ) + +with gr.Blocks() as scatter_plot: + with gr.Row(): + with gr.Column(): + dataset = gr.Dropdown(choices=["cars", "iris"], value="cars") + with gr.Column(): + plot = gr.ScatterPlot() + dataset.change(scatter_plot_fn, inputs=dataset, outputs=plot) + scatter_plot.load(fn=scatter_plot_fn, inputs=dataset, outputs=plot) + +if __name__ == "__main__": + scatter_plot.launch() diff --git a/demo/scatter_plot_demo/requirements.txt b/demo/scatter_plot_demo/requirements.txt new file mode 100644 index 0000000..fb6c7ed --- /dev/null +++ b/demo/scatter_plot_demo/requirements.txt @@ -0,0 +1 @@ +pandas diff --git a/demo/scatter_plot_demo/run.py b/demo/scatter_plot_demo/run.py new file mode 100644 index 0000000..0274b08 --- /dev/null +++ b/demo/scatter_plot_demo/run.py @@ -0,0 +1,78 @@ +import pandas as pd +from random import randint, random +import gradio as gr + + +temp_sensor_data = pd.DataFrame( + { + "time": pd.date_range("2021-01-01", end="2021-01-05", periods=200), + "temperature": [randint(50 + 10 * (i % 2), 65 + 15 * (i % 2)) for i in range(200)], + "humidity": [randint(50 + 10 * (i % 2), 65 + 15 * (i % 2)) for i in range(200)], + "location": ["indoor", "outdoor"] * 100, + } +) + +food_rating_data = pd.DataFrame( + { + "cuisine": [["Italian", "Mexican", "Chinese"][i % 3] for i in range(100)], + "rating": [random() * 4 + 0.5 * (i % 3) for i in range(100)], + "price": [randint(10, 50) + 4 * (i % 3) for i in range(100)], + "wait": [random() for i in range(100)], + } +) + +with gr.Blocks() as scatter_plots: + with gr.Row(): + start = gr.DateTime("2021-01-01 00:00:00", label="Start") + end = gr.DateTime("2021-01-05 00:00:00", label="End") + apply_btn = gr.Button("Apply", scale=0) + with gr.Row(): + group_by = gr.Radio(["None", "30m", "1h", "4h", "1d"], value="None", label="Group by") + aggregate = gr.Radio(["sum", "mean", "median", "min", "max"], value="sum", label="Aggregation") + + temp_by_time = gr.ScatterPlot( + temp_sensor_data, + x="time", + y="temperature", + ) + temp_by_time_location = gr.ScatterPlot( + temp_sensor_data, + x="time", + y="temperature", + color="location", + ) + + time_graphs = [temp_by_time, temp_by_time_location] + group_by.change( + lambda group: [gr.ScatterPlot(x_bin=None if group == "None" else group)] * len(time_graphs), + group_by, + time_graphs + ) + aggregate.change( + lambda aggregate: [gr.ScatterPlot(y_aggregate=aggregate)] * len(time_graphs), + aggregate, + time_graphs + ) + + price_by_cuisine = gr.ScatterPlot( + food_rating_data, + x="cuisine", + y="price", + ) + with gr.Row(): + price_by_rating = gr.ScatterPlot( + food_rating_data, + x="rating", + y="price", + color="wait", + buttons=["actions"], # type: ignore + ) + price_by_rating_color = gr.ScatterPlot( + food_rating_data, + x="rating", + y="price", + color="cuisine", + ) + +if __name__ == "__main__": + scatter_plots.launch() diff --git a/demo/scatterplot_component/requirements.txt b/demo/scatterplot_component/requirements.txt new file mode 100644 index 0000000..d1c8a7a --- /dev/null +++ b/demo/scatterplot_component/requirements.txt @@ -0,0 +1 @@ +vega_datasets \ No newline at end of file diff --git a/demo/scatterplot_component/run.py b/demo/scatterplot_component/run.py new file mode 100644 index 0000000..8fcf715 --- /dev/null +++ b/demo/scatterplot_component/run.py @@ -0,0 +1,19 @@ +import gradio as gr +from vega_datasets import data + +cars = data.cars() + +with gr.Blocks() as demo: + gr.ScatterPlot( + value=cars, + x="Horsepower", + y="Miles_per_Gallon", + color="Origin", + tooltip=["Name"], + title="Car Data", + y_title="Miles per Gallon", + container=False, + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/score_tracker/run.py b/demo/score_tracker/run.py new file mode 100644 index 0000000..c5570b4 --- /dev/null +++ b/demo/score_tracker/run.py @@ -0,0 +1,17 @@ +import gradio as gr + +scores = [] + +def track_score(score): + scores.append(score) + top_scores = sorted(scores, reverse=True)[:3] + return top_scores + +demo = gr.Interface( + track_score, + gr.Number(label="Score"), + gr.JSON(label="Top Scores"), + api_name="predict" +) +if __name__ == "__main__": + demo.launch() diff --git a/demo/sentence_builder/run.py b/demo/sentence_builder/run.py new file mode 100644 index 0000000..dcb0f25 --- /dev/null +++ b/demo/sentence_builder/run.py @@ -0,0 +1,31 @@ +import gradio as gr + +def sentence_builder(quantity, animal, countries, place, activity_list, morning): + return f"""The {quantity} {animal}s from {" and ".join(countries)} went to the {place} where they {" and ".join(activity_list)} until the {"morning" if morning else "night"}""" + +demo = gr.Interface( + sentence_builder, + [ + gr.Slider(2, 20, value=4, label="Count", info="Choose between 2 and 20"), + gr.Dropdown( + ["cat", "dog", "bird"], label="Animal", info="Will add more animals later!" + ), + gr.CheckboxGroup(["USA", "Japan", "Pakistan"], label="Countries", info="Where are they from?"), + gr.Radio(["park", "zoo", "road"], label="Location", info="Where did they go?"), + gr.Dropdown( + ["ran", "swam", "ate", "slept"], value=["swam", "slept"], multiselect=True, label="Activity", info="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed auctor, nisl eget ultricies aliquam, nunc nisl aliquet nunc, eget aliquam nisl nunc vel nisl." + ), + gr.Checkbox(label="Morning", info="Did they do it in the morning?"), + ], + "text", + api_name="predict", + examples=[ + [2, "cat", ["Japan", "Pakistan"], "park", ["ate", "swam"], True], + [4, "dog", ["Japan"], "zoo", ["ate", "swam"], False], + [10, "bird", ["USA", "Pakistan"], "road", ["ran"], False], + [8, "cat", ["Pakistan"], "zoo", ["ate"], True], + ] +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/sentence_builder/screenshot.png b/demo/sentence_builder/screenshot.png new file mode 100644 index 0000000..8cf53e4 Binary files /dev/null and b/demo/sentence_builder/screenshot.png differ diff --git a/demo/sentiment_analysis/DESCRIPTION.md b/demo/sentiment_analysis/DESCRIPTION.md new file mode 100644 index 0000000..affa1e8 --- /dev/null +++ b/demo/sentiment_analysis/DESCRIPTION.md @@ -0,0 +1 @@ +This sentiment analaysis demo takes in input text and returns its classification for either positive, negative or neutral using Gradio's Label output. \ No newline at end of file diff --git a/demo/sentiment_analysis/requirements.txt b/demo/sentiment_analysis/requirements.txt new file mode 100644 index 0000000..6fa2de4 --- /dev/null +++ b/demo/sentiment_analysis/requirements.txt @@ -0,0 +1 @@ +nltk \ No newline at end of file diff --git a/demo/sentiment_analysis/run.py b/demo/sentiment_analysis/run.py new file mode 100644 index 0000000..92e1693 --- /dev/null +++ b/demo/sentiment_analysis/run.py @@ -0,0 +1,20 @@ +import gradio as gr +import nltk # type: ignore +from nltk.sentiment.vader import SentimentIntensityAnalyzer # type: ignore + +nltk.download("vader_lexicon") +sid = SentimentIntensityAnalyzer() + +def sentiment_analysis(text): + scores = sid.polarity_scores(text) + del scores["compound"] + return scores + +demo = gr.Interface( + fn=sentiment_analysis, + inputs=gr.Textbox(placeholder="Enter a positive or negative sentence here..."), + outputs="label", + examples=[["This is wonderful!"]], + api_name="predict") + +demo.launch() diff --git a/demo/sepia_filter/requirements.txt b/demo/sepia_filter/requirements.txt new file mode 100644 index 0000000..24ce15a --- /dev/null +++ b/demo/sepia_filter/requirements.txt @@ -0,0 +1 @@ +numpy diff --git a/demo/sepia_filter/run.py b/demo/sepia_filter/run.py new file mode 100644 index 0000000..da90875 --- /dev/null +++ b/demo/sepia_filter/run.py @@ -0,0 +1,16 @@ +import numpy as np +import gradio as gr + +def sepia(input_img): + sepia_filter = np.array([ + [0.393, 0.769, 0.189], + [0.349, 0.686, 0.168], + [0.272, 0.534, 0.131] + ]) + sepia_img = input_img.dot(sepia_filter.T) + sepia_img /= sepia_img.max() + return sepia_img + +demo = gr.Interface(sepia, gr.Image(), "image", api_name="predict") +if __name__ == "__main__": + demo.launch() diff --git a/demo/sepia_filter/screenshot.gif b/demo/sepia_filter/screenshot.gif new file mode 100644 index 0000000..076bdc2 Binary files /dev/null and b/demo/sepia_filter/screenshot.gif differ diff --git a/demo/server_app/run.py b/demo/server_app/run.py new file mode 100644 index 0000000..6a9cf21 --- /dev/null +++ b/demo/server_app/run.py @@ -0,0 +1,55 @@ +from gradio import Server +from fastapi.responses import HTMLResponse + +app = Server() + +@app.mcp.tool(name="add") +@app.api(name="add") +def add(a: int, b: int) -> int: + """Add two numbers together.""" + return a + b + +@app.mcp.tool(name="multiply") +@app.api(name="multiply") +def multiply(a: int, b: int) -> int: + """Multiply two numbers together.""" + return a * b + +@app.get("/", response_class=HTMLResponse) +async def homepage(): + return """ + + +${value.length} letters
+ + + """, + js_on_load=""" + const input = element.querySelector('input'); + const submit_button = element.querySelector('button.submit'); + const clear_button = element.querySelector('button.clear'); + input.addEventListener('input', () => { + props.valid = input.value.length > 5; + props.value = input.value; + }); + submit_button.addEventListener('click', () => { + trigger('submit'); + }); + clear_button.addEventListener('click', () => { + props.value = ""; + props.valid = false; + trigger('clear'); + }); + """, + valid=False, + elem_id="form", + ) + output_box = gr.Textbox(label="Output Box") + form.submit(lambda x: x, form, outputs=output_box) + output_box.submit(lambda x: x, output_box, outputs=form) + + gr.Markdown(""" + # Watch API + Use `watch` inside `js_on_load` to run a callback after the template re-renders whenever specific props are changed from the backend (Python event listener). The callback is NOT triggered by frontend (JavaScript) changes to props. The callback takes no arguments — read current values from `props` directly. + """) + watch_html = gr.HTML( + value=0, + html_template=""" +No file uploaded yet.
+Directory: ${value}
+ + +Press any key...
", + js_on_load=""" + document.addEventListener('keydown', (e) => { + trigger('keypress', {key: e.key}); + }); + """, + elem_id="custom_event", + ) + + key_output = gr.Textbox(label="Key Pressed", elem_id="key_output") + + def get_key(evt_data: gr.EventData): + return evt_data.key + + keyboard.keypress(get_key, None, key_output) + + gr.Markdown(""" + # Head / Third-Party Scripts + The `head` parameter lets you load third-party JS/CSS libraries directly on the component. + Scripts are deduplicated by `src`, so multiple components sharing the same library only load it once. + """) + + head_html = gr.HTML( + value=[30, 70, 45, 90, 60], + html_template=""" + + """, + js_on_load=""" + const canvas = element.querySelector('#head-chart'); + new Chart(canvas, { + type: 'bar', + data: { + labels: props.value.map((_, i) => 'Item ' + (i + 1)), + datasets: [{ + label: 'Values', + data: props.value, + backgroundColor: 'rgba(99, 132, 255, 0.5)', + }] + }, + options: { responsive: false } + }); + """, + head='', + elem_id="head_demo", + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/tab_render_children/run.py b/demo/tab_render_children/run.py new file mode 100644 index 0000000..cad746a --- /dev/null +++ b/demo/tab_render_children/run.py @@ -0,0 +1,16 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.Markdown("## Tab render_children parameter") + with gr.Tabs(): + with gr.Tab("Tab 1") as tab1: + gr.Markdown("This tab is visible by default") + with gr.Tab("Tab 2", render_children=True) as tab2: + tb = gr.Textbox(label="Will be rendered but hidden", elem_id="invisible-but-rendered") + tb2 = gr.Textbox(label="Will not be rendered", elem_id="invisible-and-not-rendered", visible=False) + tb3 = gr.Textbox(label="Will be rendered but hidden with visible='hidden'", elem_id="visibility-hidden", visible="hidden") + btn = gr.Button("Make textbox interactive", variant="primary") + btn.click(lambda: gr.update(interactive=True), None, tb) + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/demo/tabbed_interface_lite/run.py b/demo/tabbed_interface_lite/run.py new file mode 100644 index 0000000..dedcf5a --- /dev/null +++ b/demo/tabbed_interface_lite/run.py @@ -0,0 +1,10 @@ +import gradio as gr + +hello_world = gr.Interface(lambda name: "Hello " + name, "text", "text", api_name="predict") +bye_world = gr.Interface(lambda name: "Bye " + name, "text", "text", api_name="predict") +chat = gr.ChatInterface(lambda *args: "Hello " + args[0], api_name="chat") + +demo = gr.TabbedInterface([hello_world, bye_world, chat], ["Hello World", "Bye World", "Chat"]) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/tabs/run.py b/demo/tabs/run.py new file mode 100644 index 0000000..4b22953 --- /dev/null +++ b/demo/tabs/run.py @@ -0,0 +1,71 @@ +import gradio as gr + +with gr.Blocks() as demo: + with gr.Tabs() as outer_tabs: + with gr.Tab("Set 1"): + with gr.Tabs(selected="a3") as tabs_1: + tabset_1 = [] + textset_1 = [] + for i in range(10): + with gr.Tab(f"Tab {i+1}", id=f"a{i+1}") as tab: + gr.Markdown(f"Text {i+1}!") + textbox = gr.Textbox(label=f"Input {i+1}") + tabset_1.append(tab) + textset_1.append(textbox) + with gr.Tab("Set 2"): + tabset_2 = [] + textset_2 = [] + for i in range(10): + with gr.Tab(f"Tab {i+11}") as tab: + gr.Markdown(f"Text {i+11}!") + textbox = gr.Textbox(label=f"Input {i+11}") + tabset_2.append(tab) + textset_2.append(textbox) + + for text1, text2 in zip(textset_1, textset_2): + text1.submit(lambda x: x, text1, text2) + + with gr.Tab("Locked Tab", id="locked", interactive=False) as locked_tab: + gr.Markdown("This tab was unlocked!") + + selected = gr.Textbox(label="Selected Tab") + outer_selected = gr.Textbox(label="Outer Container Tab") + select_count = gr.Number(label="Tab Select Count", value=0) + with gr.Row(): + hide_odd_btn = gr.Button("Hide Odd Tabs") + show_all_btn = gr.Button("Show All Tabs") + make_even_uninteractive_btn = gr.Button("Make Even Tabs Uninteractive") + make_all_interactive_btn = gr.Button("Make All Tabs Interactive") + unlock_btn = gr.Button("Unlock Tab") + + select_tab_num = gr.Number(label="Select Tab #", value=1) + + hide_odd_btn.click(lambda: [gr.Tab(visible=i % 2 == 1) for i, _ in enumerate(tabset_1 + tabset_2)], outputs=(tabset_1 + tabset_2)) + show_all_btn.click(lambda: [gr.Tab(visible=True) for tab in tabset_1 + tabset_2], outputs=(tabset_1 + tabset_2)) + make_even_uninteractive_btn.click(lambda: [gr.Tab(interactive=i % 2 == 0) for i, _ in enumerate(tabset_1 + tabset_2)], outputs=(tabset_1 + tabset_2)) + make_all_interactive_btn.click(lambda: [gr.Tab(interactive=True) for tab in tabset_1 + tabset_2], outputs=(tabset_1 + tabset_2)) + unlock_btn.click(lambda: gr.Tab(interactive=True), outputs=locked_tab) + select_tab_num.submit(lambda x: gr.Tabs(selected=f"a{x}"), inputs=select_tab_num, outputs=tabs_1) + + def get_selected_index(evt: gr.SelectData): + return evt.value + gr.on([tab.select for tab in tabset_1 + tabset_2], get_selected_index, outputs=selected) + + # Count how many times the per-tab `select` fires. Uses a server-side + # accumulator (not a read-modify-write of the component) so concurrent + # dispatches under `trigger_mode="multiple"` can't clobber each other, and + # `trigger_mode="multiple"` so a double-dispatch regression isn't masked by + # the default "once" mode. + tab_select_count = {"n": 0} + def count_tab_select(): + tab_select_count["n"] += 1 + return tab_select_count["n"] + gr.on([tab.select for tab in tabset_1 + tabset_2], count_tab_select, outputs=select_count, trigger_mode="multiple") + + # The `select` event on a Tabs container should fire when its active tab changes. + def get_outer_selected(evt: gr.SelectData): + return evt.value + outer_tabs.select(get_outer_selected, None, outer_selected) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/tabs_visibility/run.py b/demo/tabs_visibility/run.py new file mode 100644 index 0000000..4b6c026 --- /dev/null +++ b/demo/tabs_visibility/run.py @@ -0,0 +1,36 @@ +import gradio as gr + +with gr.Blocks() as demo: + with gr.Tab("abc"): + gr.Textbox(label="abc") + with gr.Tab("def", visible=False) as t: + gr.Textbox(label="def") + with gr.Tab("ghi"): + gr.Textbox(label="ghi") + with gr.Tab("jkl", visible=False) as t2: + gr.Textbox(label="jkl") + with gr.Tab("mno"): + gr.Textbox(label="mno") + with gr.Tab("pqr", visible=False) as t3: + gr.Textbox(label="pqr") + with gr.Tab("stu"): + gr.Textbox(label="stu") + with gr.Tab("vwx", visible=False) as t4: + gr.Textbox(label="vwx") + with gr.Tab("yz"): + gr.Textbox(label="yz") + b = gr.Button("Make visible") + + b.click( + lambda: [ + gr.Tab(visible=True), + gr.Tab(visible=True), + gr.Tab(visible=True), + gr.Tab(visible=True), + ], + inputs=None, + outputs=[t, t2, t3, t4], + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/tax_calculator/DESCRIPTION.md b/demo/tax_calculator/DESCRIPTION.md new file mode 100644 index 0000000..9ed8163 --- /dev/null +++ b/demo/tax_calculator/DESCRIPTION.md @@ -0,0 +1 @@ +Calculate taxes using Textbox, Radio, and Dataframe components \ No newline at end of file diff --git a/demo/tax_calculator/run.py b/demo/tax_calculator/run.py new file mode 100644 index 0000000..0add680 --- /dev/null +++ b/demo/tax_calculator/run.py @@ -0,0 +1,40 @@ +import gradio as gr + +def tax_calculator(income, marital_status, assets): + tax_brackets = [(10, 0), (25, 8), (60, 12), (120, 20), (250, 30)] + total_deductible = sum(cost for cost, deductible in zip(assets["Cost"], assets["Deductible"]) if deductible) + taxable_income = income - total_deductible + + total_tax = 0 + for bracket, rate in tax_brackets: + if taxable_income > bracket: + total_tax += (taxable_income - bracket) * rate / 100 + + if marital_status == "Married": + total_tax *= 0.75 + elif marital_status == "Divorced": + total_tax *= 0.8 + + return round(total_tax) + +demo = gr.Interface( + tax_calculator, + [ + "number", + gr.Radio(["Single", "Married", "Divorced"]), + gr.Dataframe( + headers=["Item", "Cost", "Deductible"], + datatype=["str", "number", "bool"], # type: ignore + label="Assets Purchased this Year", + ), + ], + gr.Number(label="Tax due"), + examples=[ + [10000, "Married", [["Suit", 5000, True], ["Laptop (for work)", 800, False], ["Car", 1800, True]]], + [80000, "Single", [["Suit", 800, True], ["Watch", 1800, True], ["Food", 800, True]]], + ], + live=True, + api_name="predict" +) + +demo.launch() diff --git a/demo/test_chatinterface_examples/eager_caching_examples_testcase.py b/demo/test_chatinterface_examples/eager_caching_examples_testcase.py new file mode 100644 index 0000000..57f3b77 --- /dev/null +++ b/demo/test_chatinterface_examples/eager_caching_examples_testcase.py @@ -0,0 +1,26 @@ +import gradio as gr + +def generate( + message: str, + chat_history: list[dict], +): + + output = "" + for character in message: + output += character + yield output + + +demo = gr.ChatInterface( + fn=generate, + examples=[ + ["Hey"], + ["Can you explain briefly to me what is the Python programming language?"], + ], + cache_examples=True, + cache_mode="eager", +) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/test_chatinterface_examples/lazy_caching_examples_testcase.py b/demo/test_chatinterface_examples/lazy_caching_examples_testcase.py new file mode 100644 index 0000000..fc2b8c1 --- /dev/null +++ b/demo/test_chatinterface_examples/lazy_caching_examples_testcase.py @@ -0,0 +1,26 @@ +import gradio as gr + +def generate( + message: str, + chat_history: list[dict], +): + + output = "" + for character in message: + output += character + yield output + + +demo = gr.ChatInterface( + fn=generate, + examples=[ + ["Hey"], + ["Can you explain briefly to me what is the Python programming language?"], + ], + cache_examples=True, + cache_mode="lazy", +) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/test_chatinterface_examples/multimodal_messages_examples_testcase.py b/demo/test_chatinterface_examples/multimodal_messages_examples_testcase.py new file mode 100644 index 0000000..6f82345 --- /dev/null +++ b/demo/test_chatinterface_examples/multimodal_messages_examples_testcase.py @@ -0,0 +1,26 @@ +import gradio as gr + +def generate( + message: dict, + chat_history: list[dict], +): + + output = "" + for character in message['text']: + output += character + yield output + + +demo = gr.ChatInterface( + fn=generate, + examples=[ + [{"text": "Hey"}], + [{"text": "Can you explain briefly to me what is the Python programming language?"}], + ], + cache_examples=False, + multimodal=True, +) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/test_chatinterface_examples/run.py b/demo/test_chatinterface_examples/run.py new file mode 100644 index 0000000..d6c25af --- /dev/null +++ b/demo/test_chatinterface_examples/run.py @@ -0,0 +1,25 @@ +import gradio as gr + +def generate( + message: str, + chat_history: list[dict], +): + + output = "" + for character in message: + output += character + yield output + + +demo = gr.ChatInterface( + fn=generate, + examples=[ + ["Hey"], + ["Can you explain briefly to me what is the Python programming language?"], + ], + cache_examples=False, +) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/test_chatinterface_multimodal_examples/cached_testcase.py b/demo/test_chatinterface_multimodal_examples/cached_testcase.py new file mode 100644 index 0000000..509c590 --- /dev/null +++ b/demo/test_chatinterface_multimodal_examples/cached_testcase.py @@ -0,0 +1,18 @@ +import gradio as gr + +image = gr.get_image("avatar.png") +audio = gr.get_audio("cantina.wav") + +def echo(message, history): + return f"You wrote: {message['text']} and uploaded {len(message['files'])} files." + +demo = gr.ChatInterface( + fn=echo, + examples=[{"text": "hello"}, {"text": "hola", "files": [image]}, {"text": "merhaba", "files": [image, audio]}], # type: ignore + title="Echo Bot", + multimodal=True, + api_name="chat", +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/test_chatinterface_multimodal_examples/run.py b/demo/test_chatinterface_multimodal_examples/run.py new file mode 100644 index 0000000..509c590 --- /dev/null +++ b/demo/test_chatinterface_multimodal_examples/run.py @@ -0,0 +1,18 @@ +import gradio as gr + +image = gr.get_image("avatar.png") +audio = gr.get_audio("cantina.wav") + +def echo(message, history): + return f"You wrote: {message['text']} and uploaded {len(message['files'])} files." + +demo = gr.ChatInterface( + fn=echo, + examples=[{"text": "hello"}, {"text": "hola", "files": [image]}, {"text": "merhaba", "files": [image, audio]}], # type: ignore + title="Echo Bot", + multimodal=True, + api_name="chat", +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/test_chatinterface_streaming_echo/multimodal_group_messages_testcase.py b/demo/test_chatinterface_streaming_echo/multimodal_group_messages_testcase.py new file mode 100644 index 0000000..9a56962 --- /dev/null +++ b/demo/test_chatinterface_streaming_echo/multimodal_group_messages_testcase.py @@ -0,0 +1,28 @@ +import gradio as gr + +runs = 0 + +def reset_runs(): + global runs + runs = 0 + +def slow_echo(message, history): + global runs # i didn't want to add state or anything to this demo + runs = runs + 1 + for i in range(len(message['text'])): + yield f"Run {runs} - You typed: " + message['text'][: i + 1] + if "files" in message and message["files"]: + yield f"Run {runs} - You typed: " + message['text'] + f" And you sent {len(message['files'])} files" + +chat = gr.ChatInterface(slow_echo, multimodal=True) + +with gr.Blocks() as demo: + chat.render() + # We reset the global variable to minimize flakes + # this works because CI runs only one test at at time + # need to use gr.State if we want to parallelize this test + # currently chatinterface does not support that + demo.unload(reset_runs) + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/demo/test_chatinterface_streaming_echo/multimodal_messages_testcase.py b/demo/test_chatinterface_streaming_echo/multimodal_messages_testcase.py new file mode 100644 index 0000000..4dd7bd2 --- /dev/null +++ b/demo/test_chatinterface_streaming_echo/multimodal_messages_testcase.py @@ -0,0 +1,26 @@ +import gradio as gr + +runs = 0 + +def reset_runs(): + global runs + runs = 0 + +def slow_echo(message, history): + global runs # i didn't want to add state or anything to this demo + runs = runs + 1 + for i in range(len(message['text'])): + yield f"Run {runs} - You typed: " + message['text'][: i + 1] + +chat = gr.ChatInterface(slow_echo, multimodal=True, api_name="chat") + +with gr.Blocks() as demo: + chat.render() + # We reset the global variable to minimize flakes + # this works because CI runs only one test at at time + # need to use gr.State if we want to parallelize this test + # currently chatinterface does not support that + demo.unload(reset_runs) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/test_chatinterface_streaming_echo/multimodal_non_stream_testcase.py b/demo/test_chatinterface_streaming_echo/multimodal_non_stream_testcase.py new file mode 100644 index 0000000..8b93341 --- /dev/null +++ b/demo/test_chatinterface_streaming_echo/multimodal_non_stream_testcase.py @@ -0,0 +1,25 @@ +import gradio as gr + +runs = 0 + +def reset_runs(): + global runs + runs = 0 + +def slow_echo(message, history): + global runs # i didn't want to add state or anything to this demo + runs = runs + 1 + return f"Run {runs} - You typed: " + message['text'] + +chat = gr.ChatInterface(slow_echo, multimodal=True, api_name="chat") + +with gr.Blocks() as demo: + chat.render() + # We reset the global variable to minimize flakes + # this works because CI runs only one test at at time + # need to use gr.State if we want to parallelize this test + # currently chatinterface does not support that + demo.unload(reset_runs) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/test_chatinterface_streaming_echo/run.py b/demo/test_chatinterface_streaming_echo/run.py new file mode 100644 index 0000000..a69d35e --- /dev/null +++ b/demo/test_chatinterface_streaming_echo/run.py @@ -0,0 +1,28 @@ +import gradio as gr +import time + +runs = 0 + +def reset_runs(): + global runs + runs = 0 + +def slow_echo(message, history): + global runs # i didn't want to add state or anything to this demo + runs = runs + 1 + for i in range(len(message)): + time.sleep(0.02) + yield f"Run {runs} - You typed: " + message[: i + 1] + +chat = gr.ChatInterface(slow_echo, fill_height=True, editable=True, api_name="chat") + +with gr.Blocks() as demo: + chat.render() + # We reset the global variable to minimize flakes + # this works because CI runs only one test at at time + # need to use gr.State if we want to parallelize this test + # currently chatinterface does not support that + demo.unload(reset_runs) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/text_analysis/DESCRIPTION.md b/demo/text_analysis/DESCRIPTION.md new file mode 100644 index 0000000..dddeb43 --- /dev/null +++ b/demo/text_analysis/DESCRIPTION.md @@ -0,0 +1 @@ +This simple demo takes advantage of Gradio's HighlightedText, JSON and HTML outputs to create a clear NER segmentation. \ No newline at end of file diff --git a/demo/text_analysis/requirements.txt b/demo/text_analysis/requirements.txt new file mode 100644 index 0000000..33ea94c --- /dev/null +++ b/demo/text_analysis/requirements.txt @@ -0,0 +1 @@ +spacy \ No newline at end of file diff --git a/demo/text_analysis/run.py b/demo/text_analysis/run.py new file mode 100644 index 0000000..2cf76d3 --- /dev/null +++ b/demo/text_analysis/run.py @@ -0,0 +1,39 @@ +import gradio as gr +import os +os.system('python -m spacy download en_core_web_sm') +import spacy # type: ignore +from spacy import displacy # type: ignore + +nlp = spacy.load("en_core_web_sm") + +def text_analysis(text): + doc = nlp(text) + html = displacy.render(doc, style="dep", page=True) + html = ( + "" + "🎙️ Learn more about UniSpeech-SAT | " + "📚 UniSpeech-SAT paper | " + "📚 X-Vector paper" + "
" +) +examples = [ + ["samples/cate_blanch.mp3", "samples/cate_blanch_2.mp3"], + ["samples/cate_blanch.mp3", "samples/cate_blanch_3.mp3"], + ["samples/cate_blanch_2.mp3", "samples/cate_blanch_3.mp3"], + ["samples/heath_ledger.mp3", "samples/heath_ledger_2.mp3"], + ["samples/cate_blanch.mp3", "samples/kirsten_dunst.wav"], +] + +demo = gr.Interface( + fn=similarity_fn, + inputs=inputs, + outputs=output, + title="Voice Authentication with UniSpeech-SAT + X-Vectors", + description=description, + article=article, + flagging_mode="never", + live=False, + examples=examples, + api_name="predict", +) + +if __name__ == "__main__": + demo.launch() + diff --git a/demo/unispeech-speaker-verification/samples/cate_blanch_3.mp3 b/demo/unispeech-speaker-verification/samples/cate_blanch_3.mp3 new file mode 100644 index 0000000..fa0a9f7 Binary files /dev/null and b/demo/unispeech-speaker-verification/samples/cate_blanch_3.mp3 differ diff --git a/demo/unispeech-speaker-verification/samples/heath_ledger_2.mp3 b/demo/unispeech-speaker-verification/samples/heath_ledger_2.mp3 new file mode 100644 index 0000000..d74f1ec Binary files /dev/null and b/demo/unispeech-speaker-verification/samples/heath_ledger_2.mp3 differ diff --git a/demo/unispeech-speaker-verification/samples/kirsten_dunst.wav b/demo/unispeech-speaker-verification/samples/kirsten_dunst.wav new file mode 100644 index 0000000..7cf10c9 Binary files /dev/null and b/demo/unispeech-speaker-verification/samples/kirsten_dunst.wav differ diff --git a/demo/unload_event_test/run.py b/demo/unload_event_test/run.py new file mode 100644 index 0000000..16565bb --- /dev/null +++ b/demo/unload_event_test/run.py @@ -0,0 +1,32 @@ +"""This demo is only meant to test the unload event. +It will write to a file when the unload event is triggered. +May not work as expected if multiple people are using it. +""" +import gradio as gr +from pathlib import Path + +log_file = (Path(__file__).parent / "output_log.txt").resolve() + +def test_fn(x): + with open(log_file, "a") as f: + f.write(f"incremented {x}\n") + return x + 1, x + 1 + +def delete_fn(v): + with log_file.open("a") as f: + f.write(f"deleted {v}\n") + +def unload_fn(): + with log_file.open("a") as f: + f.write("unloading\n") + +with gr.Blocks() as demo: + n1 = gr.Number(value=0, label="Number") + state = gr.State(value=0, delete_callback=delete_fn) + button = gr.Button("Increment") + button.click(test_fn, [state], [n1, state], api_name="increment") + demo.unload(unload_fn) + demo.load(lambda: log_file.write_text("")) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/upload_and_download/run.py b/demo/upload_and_download/run.py new file mode 100644 index 0000000..48725bb --- /dev/null +++ b/demo/upload_and_download/run.py @@ -0,0 +1,21 @@ +from pathlib import Path +import gradio as gr + +def upload_file(filepath): + name = Path(filepath).name + return [gr.UploadButton(visible=False), gr.DownloadButton(label=f"Download {name}", value=filepath, visible=True)] + +def download_file(): + return [gr.UploadButton(visible=True), gr.DownloadButton(visible=False)] + +with gr.Blocks() as demo: + gr.Markdown("First upload a file and and then you'll be able download it (but only once!)") + with gr.Row(): + u = gr.UploadButton("Upload a file", file_count="single") + d = gr.DownloadButton("Download the file", visible=False) + + u.upload(upload_file, u, [u, d]) + d.click(download_file, None, [u, d]) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/upload_button/DESCRIPTION.md b/demo/upload_button/DESCRIPTION.md new file mode 100644 index 0000000..a070ed1 --- /dev/null +++ b/demo/upload_button/DESCRIPTION.md @@ -0,0 +1 @@ +A simple demo showcasing the upload button used with its `upload` event trigger. \ No newline at end of file diff --git a/demo/upload_button/run.py b/demo/upload_button/run.py new file mode 100644 index 0000000..6e40d2a --- /dev/null +++ b/demo/upload_button/run.py @@ -0,0 +1,12 @@ +import gradio as gr + +def upload_file(files): + file_paths = [file.name for file in files] + return file_paths + +with gr.Blocks() as demo: + file_output = gr.File() + upload_button = gr.UploadButton("Click to Upload a File", file_types=["image", "video"], file_count="multiple") + upload_button.upload(upload_file, upload_button, file_output) + +demo.launch() diff --git a/demo/upload_button_component_events/run.py b/demo/upload_button_component_events/run.py new file mode 100644 index 0000000..8c87bc8 --- /dev/null +++ b/demo/upload_button_component_events/run.py @@ -0,0 +1,25 @@ +import gradio as gr + +with gr.Blocks() as demo: + + with gr.Row(): + with gr.Column(): + upload_btn = gr.UploadButton(label="Upload Single File", file_count="single") + with gr.Column(): + output_file_1 = gr.File(label="Upload Single File Output", file_count="single") + num_load_btn_1 = gr.Number(label="# Load Upload Single File", value=0) + output_click_1 = gr.Number(label="# Click Upload Single File Output", value=0) + upload_btn.upload(lambda s,n: (s, n + 1), [upload_btn, num_load_btn_1], [output_file_1, num_load_btn_1]) + upload_btn.click(lambda n: (n + 1), output_click_1, [output_click_1]) + with gr.Row(): + with gr.Column(): + upload_btn_multiple = gr.UploadButton(label="Upload Multiple Files", file_count="multiple") + with gr.Column(): + output_file_2 = gr.File(label="Upload Multiple Files Output", file_count="multiple") + num_load_btn_2 = gr.Number(label="# Load Upload Multiple Files", value=0) + output_click_2 = gr.Number(label="# Click Upload Multiple Files Output", value=0) + upload_btn_multiple.upload(lambda s,n: (s, n + 1), [upload_btn_multiple, num_load_btn_2], [output_file_2, num_load_btn_2]) + upload_btn_multiple.click(lambda n: (n + 1), output_click_2, [output_click_2]) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/upload_file_limit_test/run.py b/demo/upload_file_limit_test/run.py new file mode 100644 index 0000000..9c88fa6 --- /dev/null +++ b/demo/upload_file_limit_test/run.py @@ -0,0 +1,25 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.Markdown(""" + # ⬆️📁 max_file_size test + The demo has a max file size of 15kb. The error modal should pop up when a file larger than that is uploaded. + """) + with gr.Row(): + with gr.Column(): + gr.Image(label="Image", interactive=True) + gr.Gallery(label="Gallery", interactive=True) + gr.File(label="Single File", interactive=True, file_count="single") + with gr.Column(): + gr.Model3D(label="Model 3D", interactive=True,) + gr.MultimodalTextbox(label="Multimodal Textbox", interactive=True) + gr.UploadButton(label="Upload Button", interactive=True) + with gr.Column(): + gr.Video(label="Video", interactive=True) + gr.Audio(label="Audio", interactive=True) + gr.File(label="Multiple Files", interactive=True, file_count="multiple") + +if __name__ == "__main__": + # The upload limit is set in playwright_setup.js + # since the e2e tests use mount_gradio_app + demo.launch(max_file_size="15kb") diff --git a/demo/uploadbutton_component/run.py b/demo/uploadbutton_component/run.py new file mode 100644 index 0000000..c668bc0 --- /dev/null +++ b/demo/uploadbutton_component/run.py @@ -0,0 +1,12 @@ +import gradio as gr + +def upload_file(files): + file_paths = [file.name for file in files] + return file_paths + +with gr.Blocks() as demo: + file_output = gr.File() + upload_button = gr.UploadButton("Click to Upload an Image or Video File", file_types=["image", "video"], file_count="multiple") + upload_button.upload(upload_file, upload_button, file_output) + +demo.launch() diff --git a/demo/validator_chatinterface/run.py b/demo/validator_chatinterface/run.py new file mode 100644 index 0000000..78c7bd8 --- /dev/null +++ b/demo/validator_chatinterface/run.py @@ -0,0 +1,20 @@ +import gradio as gr +import time + + +def validate_input(x): + print("VALIDATE", x) + return gr.validate(x != "error", "Can't be error") + + +def do_chat(message, history): + for i in range(len(message)): + time.sleep(0.05) + yield "You typed: " + message[: i + 1] + + +demo = gr.ChatInterface(fn=do_chat, validator=validate_input, show_progress="full") + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/validator_interface/run.py b/demo/validator_interface/run.py new file mode 100644 index 0000000..29f6e55 --- /dev/null +++ b/demo/validator_interface/run.py @@ -0,0 +1,90 @@ +import gradio as gr + + +def validate_input(age, location): + is_age_valid = True + is_location_valid = True + if not age or age < 3: + is_age_valid = False + if "london" in location.lower(): + is_location_valid = False + + return [ + gr.validate(is_age_valid, "Age must be at least 3"), + gr.validate(is_location_valid, "Location must not be in London"), + ] + + +def process_text(age, location): + result = f"Processed: {age} -- {location.upper()}" + return result + + +def validate_image(image): + # we don't want to error when a user is clearing the image + if not image: + return None + is_portrait = image.width < image.height + + return gr.validate(is_portrait, "Image must be in portrait mode") + + +def process_image(image): + if not image: + return "No image uploaded" + return "HELLO IMAGE!!!" + + +def raise_error(): + raise ValueError("test error") + + +with gr.Blocks() as demo: + with gr.Tab("Text"): + gr.Markdown("# Validator Parameter Test Demo") + + with gr.Row(): + with gr.Column(): + age = gr.Number( + label="Enter age", + placeholder="Enter age", + ) + location = gr.Textbox( + max_lines=3, + label="Enter location", + placeholder="Enter location", + ) + + validate_btn = gr.Button("Process with Validation", variant="primary") + + output_with_validation = gr.Textbox( + label="Output (with validation)", interactive=False + ) + + validate_btn.click( + fn=process_text, + validator=validate_input, + inputs=[age, location], + outputs=output_with_validation, + ) + with gr.Tab("Image"): + im = gr.Image(label="Enter image", placeholder="Enter image", type="pil") + t = gr.Textbox(label="Enter text", placeholder="Enter text") + im.change( + fn=process_image, + validator=validate_image, + inputs=im, + outputs=t, + ) + with gr.Tab("Validation Error"): + error_btn = gr.Button("Raise Validation Error", variant="primary") + error_btn.click( + validator=raise_error, + fn=raise_error, + inputs=[], + outputs=[], + ) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/validator_simple/run.py b/demo/validator_simple/run.py new file mode 100644 index 0000000..628b262 --- /dev/null +++ b/demo/validator_simple/run.py @@ -0,0 +1,45 @@ +import gradio as gr + + +def validate_input(age, location): + return [ + gr.validate(not age or age > 3, "Age must be at least 3"), + gr.validate("london" not in location.lower(), "Location must not be in London"), + ] + + +def process_text(age, location): + return f"Processed: {age} -- {location.upper()}" + + +with gr.Blocks() as demo: + gr.Markdown("# Validator Parameter Test Demo") + + with gr.Row(): + with gr.Column(): + age = gr.Number( + label="Enter age", + placeholder="Enter age", + ) + location = gr.Textbox( + max_lines=3, + label="Enter location", + placeholder="Enter location", + ) + + validate_btn = gr.Button("Process with Validation", variant="primary") + + output_with_validation = gr.Textbox( + label="Output (with validation)", interactive=False + ) + + validate_btn.click( + fn=process_text, + validator=validate_input, + inputs=[age, location], + outputs=output_with_validation, + ) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/validator_test/run.py b/demo/validator_test/run.py new file mode 100644 index 0000000..29f6e55 --- /dev/null +++ b/demo/validator_test/run.py @@ -0,0 +1,90 @@ +import gradio as gr + + +def validate_input(age, location): + is_age_valid = True + is_location_valid = True + if not age or age < 3: + is_age_valid = False + if "london" in location.lower(): + is_location_valid = False + + return [ + gr.validate(is_age_valid, "Age must be at least 3"), + gr.validate(is_location_valid, "Location must not be in London"), + ] + + +def process_text(age, location): + result = f"Processed: {age} -- {location.upper()}" + return result + + +def validate_image(image): + # we don't want to error when a user is clearing the image + if not image: + return None + is_portrait = image.width < image.height + + return gr.validate(is_portrait, "Image must be in portrait mode") + + +def process_image(image): + if not image: + return "No image uploaded" + return "HELLO IMAGE!!!" + + +def raise_error(): + raise ValueError("test error") + + +with gr.Blocks() as demo: + with gr.Tab("Text"): + gr.Markdown("# Validator Parameter Test Demo") + + with gr.Row(): + with gr.Column(): + age = gr.Number( + label="Enter age", + placeholder="Enter age", + ) + location = gr.Textbox( + max_lines=3, + label="Enter location", + placeholder="Enter location", + ) + + validate_btn = gr.Button("Process with Validation", variant="primary") + + output_with_validation = gr.Textbox( + label="Output (with validation)", interactive=False + ) + + validate_btn.click( + fn=process_text, + validator=validate_input, + inputs=[age, location], + outputs=output_with_validation, + ) + with gr.Tab("Image"): + im = gr.Image(label="Enter image", placeholder="Enter image", type="pil") + t = gr.Textbox(label="Enter text", placeholder="Enter text") + im.change( + fn=process_image, + validator=validate_image, + inputs=im, + outputs=t, + ) + with gr.Tab("Validation Error"): + error_btn = gr.Button("Raise Validation Error", variant="primary") + error_btn.click( + validator=raise_error, + fn=raise_error, + inputs=[], + outputs=[], + ) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/variable_outputs/run.py b/demo/variable_outputs/run.py new file mode 100644 index 0000000..c893faf --- /dev/null +++ b/demo/variable_outputs/run.py @@ -0,0 +1,19 @@ +import gradio as gr + +max_textboxes = 10 + +def variable_outputs(k): + k = int(k) + return [gr.Textbox(visible=True)]*k + [gr.Textbox(visible=False)]*(max_textboxes-k) + +with gr.Blocks() as demo: + s = gr.Slider(1, max_textboxes, value=max_textboxes, step=1, label="How many textboxes to show:") + textboxes = [] + for i in range(max_textboxes): + t = gr.Textbox(f"Textbox {i}") + textboxes.append(t) + + s.change(variable_outputs, s, textboxes) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/video_component/run.py b/demo/video_component/run.py new file mode 100644 index 0000000..984f671 --- /dev/null +++ b/demo/video_component/run.py @@ -0,0 +1,19 @@ +import gradio as gr +# get_video() returns the file path to sample videos included with Gradio +from gradio.media import get_video, MEDIA_PATHS + +demo = gr.Interface( + fn=lambda x: x, + inputs=gr.Video(), + outputs=gr.Video(), + examples=[ + [get_video("world.mp4")], + [get_video("a.mp4")], + [get_video("b.mp4")], + ], + api_name="predict", + cache_examples=True +) + +if __name__ == "__main__": + demo.launch(allowed_paths=[str(p) for p in MEDIA_PATHS]) diff --git a/demo/video_component_events/run.py b/demo/video_component_events/run.py new file mode 100644 index 0000000..24f8bfb --- /dev/null +++ b/demo/video_component_events/run.py @@ -0,0 +1,22 @@ +import gradio as gr + +with gr.Blocks() as demo: + with gr.Row(): + with gr.Column(): + input_video = gr.Video(label="Input Video") + with gr.Column(): + output_video = gr.Video(label="Output Video") + with gr.Column(): + num_change = gr.Number(label="# Change Events", value=0) + num_input = gr.Number(label="# Input Events", value=0) + num_load = gr.Number(label="# Upload Events", value=0) + num_play = gr.Number(label="# Play Events", value=0) + num_pause = gr.Number(label="# Pause Events", value=0) + input_video.upload(lambda s, n: (s, n + 1), [input_video, num_load], [output_video, num_load]) + input_video.input(lambda n: n + 1, num_input, num_input) + input_video.play(lambda n: n + 1, num_play, num_play) + input_video.pause(lambda n: n + 1, num_pause, num_pause) + input_video.change(lambda n: n + 1, num_change, num_change) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/video_identity/run.py b/demo/video_identity/run.py new file mode 100644 index 0000000..247dd88 --- /dev/null +++ b/demo/video_identity/run.py @@ -0,0 +1,18 @@ +import gradio as gr +from gradio.media import get_video + +def video_identity(video): + return video + +# get_video() returns file paths to sample media included with Gradio +demo = gr.Interface(video_identity, + gr.Video(), + "playable_video", + examples=[ + get_video("world.mp4") + ], + cache_examples=True, + api_name="predict",) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/video_identity/screenshot.png b/demo/video_identity/screenshot.png new file mode 100644 index 0000000..443948a Binary files /dev/null and b/demo/video_identity/screenshot.png differ diff --git a/demo/video_identity_2/run.py b/demo/video_identity_2/run.py new file mode 100644 index 0000000..37c3c03 --- /dev/null +++ b/demo/video_identity_2/run.py @@ -0,0 +1,13 @@ +import gradio as gr + +def video_identity(video): + return video + +demo = gr.Interface(video_identity, + gr.Video(), + "playable_video", + api_name="predict" + ) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/video_subtitle/run.py b/demo/video_subtitle/run.py new file mode 100644 index 0000000..ae2c4d2 --- /dev/null +++ b/demo/video_subtitle/run.py @@ -0,0 +1,25 @@ +import gradio as gr +from gradio.media import get_video, get_file + +def video_demo(video, subtitle=None): + if subtitle is None: + return video + return gr.Video(label="Out", value=video, subtitles=subtitle.name) + +demo = gr.Interface( + fn=video_demo, + inputs=[ + gr.Video(label="In", interactive=True), + gr.File(label="Subtitle", file_types=[".srt", ".vtt", ".json"]), + ], + outputs=gr.Video(label="Out"), + examples=[ + [get_video("a.mp4"), get_file("s1.srt")], + [get_video("b.mp4"), get_file("s2.vtt")], + [get_video("a.mp4"), get_file("s3.json")], + ], + api_name="predict", +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/video_watermark/files/w2.png b/demo/video_watermark/files/w2.png new file mode 100644 index 0000000..855b404 Binary files /dev/null and b/demo/video_watermark/files/w2.png differ diff --git a/demo/video_watermark/run.py b/demo/video_watermark/run.py new file mode 100644 index 0000000..2509d59 --- /dev/null +++ b/demo/video_watermark/run.py @@ -0,0 +1,17 @@ +import gradio as gr + + +a = gr.get_video("a.mp4") +b = gr.get_video("b.mp4") +w1 = gr.get_image("logo.png") +w2 = gr.get_image("bus.png") + +def generate_video(original_video, watermark, position): + return gr.Video(original_video, watermark=gr.WatermarkOptions(watermark=watermark, position=position)) + + +demo = gr.Interface(generate_video, [gr.Video(), gr.File(), gr.Dropdown(["top-left", "top-right", "bottom-left", "bottom-right"], label="Position")], gr.Video(), + examples=[[a, w1, "top-left"], [b, w2, "bottom-right"]], api_name="predict") + +if __name__ == "__main__": + demo.launch() diff --git a/demo/visibility_test/run.py b/demo/visibility_test/run.py new file mode 100644 index 0000000..db25f89 --- /dev/null +++ b/demo/visibility_test/run.py @@ -0,0 +1,111 @@ +import gradio as gr + + +def toggle_visibility(choice, input_text): + """Toggle visibility based on choice and return current input value.""" + updates = {} + + if choice == "Visible": + updates["textbox"] = gr.update(visible=True) + updates["button"] = gr.update(visible=True) + elif choice == "Hidden (in DOM)": + updates["textbox"] = gr.update(visible="hidden") + updates["button"] = gr.update(visible="hidden") + else: # "Not Visible (removed)" + updates["textbox"] = gr.update(visible=False) + updates["button"] = gr.update(visible=False) + + return updates["textbox"], updates["button"], f"Current value: {input_text}" + + +def get_value(input_text): + """Get the current value from the textbox.""" + return f"Retrieved value: {input_text}" + + +def increment_counter(counter): + """Increment counter to test event handling.""" + return counter + 1 + + +def reveal_hidden_column(): + """Generator that toggles two columns over multiple yields (issue #13494): + the column that starts hidden must end up visible.""" + yield gr.update(visible=False), gr.update(visible=False) + yield gr.update(visible=False), gr.update(visible=True) + + +def hide_revealed_column(): + yield gr.update(visible=True), gr.update(visible=True) + yield gr.update(visible=True), gr.update(visible=False) + + +with gr.Blocks() as demo: + gr.Markdown("# Visibility Test Demo") + gr.Markdown( + "Test the three visibility states: visible=True, visible='hidden', visible=False" + ) + + with gr.Row(): + visibility_radio = gr.Radio( + ["Visible", "Hidden (in DOM)", "Not Visible (removed)"], + label="Choose visibility state", + value="Visible", + elem_id="visibility-radio", + ) + + with gr.Row(): + with gr.Column(): + textbox = gr.Textbox( + label="Test Input", + value="Initial text", + elem_id="test-textbox", + visible=True, + ) + button = gr.Button("Get Value", elem_id="test-button", visible=True) + + # Hidden counter for testing events on hidden elements + counter = gr.Number(value=0, visible="hidden", elem_id="counter") + + increment_btn = gr.Button( + "Increment Counter", + elem_id="increment-button", + ) + counter_result = gr.Textbox( + label="Counter Result", elem_id="counter-result" + ) + + with gr.Column(): + status = gr.Textbox(label="Status", elem_id="status-output") + output = gr.Textbox(label="Output", elem_id="output-textbox") + + # Wire up the events + visibility_radio.change( + toggle_visibility, + inputs=[visibility_radio, textbox], + outputs=[textbox, button, status], + ) + + button.click(get_value, inputs=textbox, outputs=output) + counter.change( + lambda x: f"Counter Result: {x}", inputs=counter, outputs=counter_result + ) + increment_btn.click(increment_counter, inputs=counter, outputs=counter) + + # Regression coverage for #13494: a multi-yield generator that updates two + # columns at once (one hidden, one revealed) must leave the revealed column + # visible. The reveal/hide buttons sit outside both columns so they persist. + with gr.Row(): + reveal_btn = gr.Button("Reveal Hidden Column", elem_id="yield-reveal") + hide_btn = gr.Button("Hide Revealed Column", elem_id="yield-hide") + with gr.Column(elem_id="yield-col-x") as yield_col_x: + gr.Markdown("Column X") + with gr.Column(visible=False, elem_id="yield-col-y") as yield_col_y: + gr.Textbox(label="Revealed", elem_id="yield-target") + + reveal_btn.click(reveal_hidden_column, outputs=[yield_col_x, yield_col_y]) + hide_btn.click(hide_revealed_column, outputs=[yield_col_x, yield_col_y]) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/walkthrough/run.py b/demo/walkthrough/run.py new file mode 100644 index 0000000..445a337 --- /dev/null +++ b/demo/walkthrough/run.py @@ -0,0 +1,18 @@ +import gradio as gr + +with gr.Blocks() as demo: + with gr.Walkthrough(selected=0) as walkthrough: + with gr.Step("Image", id=0): + image = gr.Image() + btn = gr.Button("go to prompt") + btn.click(lambda: gr.Walkthrough(selected=1), outputs=walkthrough) + with gr.Step("Prompt", id=1): + prompt = gr.Textbox() + btn = gr.Button("generate") + btn.click(lambda: gr.Walkthrough(selected=2), outputs=walkthrough) + with gr.Step("Result", id=2): + gr.Image(label="result", interactive=False) + + +if __name__ == "__main__": + demo.launch() diff --git a/demo/walkthrough_many/run.py b/demo/walkthrough_many/run.py new file mode 100644 index 0000000..f4cb662 --- /dev/null +++ b/demo/walkthrough_many/run.py @@ -0,0 +1,60 @@ +import gradio as gr + + +def process_step(step_num): + return f"You are on step {step_num}" + + +with gr.Blocks() as demo: + gr.Markdown("# Stepper - Many Steps") + + with gr.Walkthrough(selected=0) as walkthrough: + with gr.Step("Introduction", id=0): + gr.Markdown("This is the introduction step.") + output1 = gr.Textbox(label="Step 1 Output") + next1 = gr.Button("Next Step") + next1.click(lambda: gr.Walkthrough(selected=1), outputs=walkthrough) + + with gr.Step("Basic Information", id=1): + gr.Markdown("Enter your basic information.") + output2 = gr.Textbox(label="Step 2 Output") + next2 = gr.Button("Next Step") + next2.click(lambda: gr.Walkthrough(selected=2), outputs=walkthrough) + + with gr.Step("Preferences", id=2): + gr.Markdown("Set your preferences.") + output3 = gr.Textbox(label="Step 3 Output") + next3 = gr.Button("Next Step") + next3.click(lambda: gr.Walkthrough(selected=3), outputs=walkthrough) + + with gr.Step("Advanced Settings", id=3): + gr.Markdown("Configure advanced settings.") + output4 = gr.Textbox(label="Step 4 Output") + next4 = gr.Button("Next Step") + next4.click(lambda: gr.Walkthrough(selected=4), outputs=walkthrough) + + with gr.Step("Review", id=4): + gr.Markdown("Review your choices.") + output5 = gr.Textbox(label="Step 5 Output") + next5 = gr.Button("Next Step") + next5.click(lambda: gr.Walkthrough(selected=5), outputs=walkthrough) + + with gr.Step("Confirmation", id=5): + gr.Markdown("Confirm and submit.") + output6 = gr.Textbox(label="Step 6 Output") + next6 = gr.Button("Next Step") + next6.click(lambda: gr.Walkthrough(selected=6), outputs=walkthrough) + + with gr.Step("Additional Options", id=6): + gr.Markdown("Additional options if needed.") + output7 = gr.Textbox(label="Step 7 Output") + next7 = gr.Button("Next Step") + next7.click(lambda: gr.Walkthrough(selected=7), outputs=walkthrough) + + with gr.Step("Final Step", id=7): + gr.Markdown("This is the final step!") + output8 = gr.Textbox(label="Step 8 Output") + gr.Button("Complete") + +if __name__ == "__main__": + demo.launch() diff --git a/demo/web_component_embed/run.py b/demo/web_component_embed/run.py new file mode 100644 index 0000000..ca30a40 --- /dev/null +++ b/demo/web_component_embed/run.py @@ -0,0 +1,11 @@ +import gradio as gr + +with gr.Blocks() as demo: + gr.Markdown("# Web component embed target") + name = gr.Textbox(label="Name") + greet = gr.Button("Greet") + out = gr.Textbox(label="Greeting") + greet.click(lambda n: f"Hello, {n or 'world'}!", name, out) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/webcam/run.py b/demo/webcam/run.py new file mode 100644 index 0000000..5ff45f5 --- /dev/null +++ b/demo/webcam/run.py @@ -0,0 +1,15 @@ + +import gradio as gr + +def snap(image, video): + return [image, video] + +demo = gr.Interface( + snap, + [gr.Image(sources=["webcam"]), gr.Video(sources=["webcam"])], + ["image", "video"], + api_name="predict" +) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/webcam/screenshot.png b/demo/webcam/screenshot.png new file mode 100644 index 0000000..33d74a9 Binary files /dev/null and b/demo/webcam/screenshot.png differ diff --git a/demo/webcam_constraints/requirements.txt b/demo/webcam_constraints/requirements.txt new file mode 100644 index 0000000..1db7aea --- /dev/null +++ b/demo/webcam_constraints/requirements.txt @@ -0,0 +1 @@ +opencv-python \ No newline at end of file diff --git a/demo/webcam_constraints/run.py b/demo/webcam_constraints/run.py new file mode 100644 index 0000000..f0989da --- /dev/null +++ b/demo/webcam_constraints/run.py @@ -0,0 +1,43 @@ +import gradio as gr +import cv2 # type: ignore + +def get_video_shape(video): + cap = cv2.VideoCapture(video) + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + cap.release() + return {"width": width, "height": height} + +def image_mod(image): + width, height = image.size + return {"width": width, "height": height} + + +video = gr.Interface( + fn=get_video_shape, + inputs=gr.Video(webcam_options=gr.WebcamOptions(constraints={"video": {"width": 800, "height": 600}}), sources="webcam"), + outputs=gr.JSON(), + api_name="predict" +) + +image = gr.Interface( + image_mod, + gr.Image(type="pil", webcam_options=gr.WebcamOptions(constraints={"video": {"width": 800, "height": 600}}), sources="webcam"), + gr.Json(), + api_name="predict") + +with gr.Blocks() as demo: + gr.Markdown("""# Webcam Constraints + The webcam constraints are set to 800x600 with the following syntax: + ```python + gr.Video(webcam_options=gr.WebcamOptions(constraints={"video": {"width": 800, "height": 600}}), sources="webcam") + ``` + """) + with gr.Tabs(): + with gr.Tab("Video"): + video.render() + with gr.Tab("Image"): + image.render() + +if __name__ == "__main__": + demo.launch() \ No newline at end of file diff --git a/demo/white_noise_vid_not_playable/requirements.txt b/demo/white_noise_vid_not_playable/requirements.txt new file mode 100644 index 0000000..1db7aea --- /dev/null +++ b/demo/white_noise_vid_not_playable/requirements.txt @@ -0,0 +1 @@ +opencv-python \ No newline at end of file diff --git a/demo/white_noise_vid_not_playable/run.py b/demo/white_noise_vid_not_playable/run.py new file mode 100644 index 0000000..7e7484a --- /dev/null +++ b/demo/white_noise_vid_not_playable/run.py @@ -0,0 +1,20 @@ +import cv2 # type: ignore +import gradio as gr +import numpy as np + +def gif_maker(): + img_array = [] + height, width = 50, 50 + for _ in range(30): + img_array.append(np.random.randint(0, 255, size=(height, width, 3)).astype(np.uint8)) + output_file = "test.mp4" + out = cv2.VideoWriter(output_file, cv2.VideoWriter_fourcc(*'mp4v'), 15, (height, width)) # type: ignore + for i in range(len(img_array)): + out.write(img_array[i]) + out.release() + return output_file, output_file + +demo = gr.Interface(gif_maker, inputs=None, outputs=[gr.Video(buttons=["download"]), gr.File()], api_name="predict") + +if __name__ == "__main__": + demo.launch() diff --git a/demo/workflow/run.py b/demo/workflow/run.py new file mode 100644 index 0000000..96b2ae8 --- /dev/null +++ b/demo/workflow/run.py @@ -0,0 +1,55 @@ +import os +import random + +import gradio as gr + + +def describe_product(image) -> str: + if image is None: + return "a premium product" + img_path = image.get("path") if isinstance(image, dict) else str(image) + img_url = image.get("url", "") if isinstance(image, dict) else str(image) + try: + from gradio_client import Client, handle_file + source = img_path if (img_path and os.path.exists(img_path)) else img_url + if not source: + return "a premium product" + client = Client("vikhyatk/moondream2", verbose=False) + result = client.predict( + handle_file(source), + "Describe this product briefly and concisely. What is it?", + api_name="/answer_question", + ) + return str(result).strip() if result else "a premium product" + except Exception: + return "a premium product" + + +def craft_marketing_prompt(caption: str) -> str: + if not caption: + caption = "a premium product" + caption = caption.strip().rstrip(".") + styles = [ + ( + f"Professional product advertisement photograph of {caption}, " + "studio lighting, clean white background, commercial photography, " + "ultra-sharp, 8K quality" + ), + ( + f"Cinematic product shot of {caption}, dramatic lighting, " + "aspirational lifestyle context, premium brand aesthetic, " + "shot on Hasselblad, magazine cover quality" + ), + ( + f"Bold marketing campaign visual of {caption}, vibrant colors, " + "dynamic composition, modern editorial style, " + "award-winning commercial photography" + ), + ] + return random.choice(styles) + + +demo = gr.Workflow(bind=[describe_product, craft_marketing_prompt]) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/workflow/workflow.json b/demo/workflow/workflow.json new file mode 100644 index 0000000..d989787 --- /dev/null +++ b/demo/workflow/workflow.json @@ -0,0 +1 @@ +{"schema_version":"2","name":"Marketing Image Creator","runtime":{"default":"client"},"references":[{"label":"Text","inputs":[{"id":"in","label":"Text","type":"text"}],"outputs":[{"id":"out","label":"Text","type":"text"}],"width":220,"height":163,"asset_type":"text","role":"reference","id":"1539a1e9-4906-4008-b345-e6d05bb69b22","x":740,"y":80,"data":{"in":null}},{"label":"Image","inputs":[{"id":"in","label":"Image","type":"image"}],"outputs":[{"id":"out","label":"Image","type":"image"}],"width":220,"height":124,"asset_type":"image","role":"reference","id":"13e92b22-7173-416c-81c6-1ca097a2a299","x":80,"y":80,"data":{}}],"operators":[{"id":"n_flux","label":"Generate Image","inputs":[{"id":"in","label":"Prompt","type":"text","required":true}],"outputs":[{"id":"out","label":"Image","type":"image"}],"data":{"out":97500097},"x":1040,"y":80,"width":220,"height":124,"role":"operator","kind":"space","source":"hf://spaces/multimodalart/FLUX.1-merged","space_id":"multimodalart/FLUX.1-merged","runtime":"client","endpoints":[{"name":"/infer","inputs":[{"id":"in_0","label":"Prompt","type":"text","required":true},{"id":"in_1","label":"Seed","type":"number","required":false},{"id":"in_2","label":"Randomize seed","type":"boolean","required":false},{"id":"in_3","label":"Width","type":"number","required":false},{"id":"in_4","label":"Height","type":"number","required":false},{"id":"in_5","label":"Guidance Scale","type":"number","required":false},{"id":"in_6","label":"Number of inference steps","type":"number","required":false}],"outputs":[{"id":"out_0","label":"Result","type":"image","output_index":0},{"id":"out_1","label":"Seed","type":"number","output_index":1}]}]},{"label":"Image To Prompt","inputs":[{"id":"in_0","label":"Input Image","type":"image","required":true}],"outputs":[{"id":"out_0","label":"Output Prompt","type":"text","output_index":0}],"width":280,"height":124,"kind":"space","space_id":"ovi054/image-to-prompt","endpoint":"/predict","endpoints":[{"name":"/predict","inputs":[{"id":"in_0","label":"Input Image","type":"image","required":true}],"outputs":[{"id":"out_0","label":"Output Prompt","type":"text","output_index":0}]}],"pipeline_tag":"Image Captioning","role":"operator","id":"897592dc-17b5-473b-9e89-4ebdae4073d6","x":380,"y":80,"data":{}}],"subjects":[{"id":"n_output","label":"Marketing Image","inputs":[{"id":"in","label":"Image","type":"image"}],"outputs":[{"id":"out","label":"Image","type":"image"}],"data":{"in":null},"x":1340,"y":80,"width":220,"height":107,"role":"subject","asset_type":"image"}],"edges":[{"id":"e4","from_node_id":"n_flux","from_port_id":"out","to_node_id":"n_output","to_port_id":"in","type":"image"},{"from_node_id":"1539a1e9-4906-4008-b345-e6d05bb69b22","from_port_id":"out","to_node_id":"n_flux","to_port_id":"in","type":"text","id":"b44e83fe-23b1-4dcc-83d3-55b3481721bc"},{"from_node_id":"13e92b22-7173-416c-81c6-1ca097a2a299","from_port_id":"out","to_node_id":"897592dc-17b5-473b-9e89-4ebdae4073d6","to_port_id":"in_0","type":"image","id":"22796709-ff7d-4250-92c0-2d541f9e6a99"},{"from_node_id":"897592dc-17b5-473b-9e89-4ebdae4073d6","from_port_id":"out_0","to_node_id":"1539a1e9-4906-4008-b345-e6d05bb69b22","to_port_id":"in","type":"text","id":"96dab823-0f2c-4356-98c4-aa82feca5e0a"}],"view":{"default":"canvas"}} \ No newline at end of file diff --git a/demo/workflow_api/run.py b/demo/workflow_api/run.py new file mode 100644 index 0000000..e588613 --- /dev/null +++ b/demo/workflow_api/run.py @@ -0,0 +1,19 @@ +import gradio as gr + + +def shout(text: str) -> str: + return (text or "").upper() + + +def reverse(text: str) -> str: + return (text or "")[::-1] + + +# Two outputs ("Loud" and "Reversed") fed by a shared "Text" input. Because they +# form a single connected subgraph, they're exposed as ONE API endpoint that +# returns both outputs as a tuple — see the "View API" panel in the app, or +# connect with the gradio_client. +demo = gr.Workflow(graph="workflow.json", bind={"shout": shout, "reverse": reverse}) + +if __name__ == "__main__": + demo.launch() diff --git a/demo/workflow_api/workflow.json b/demo/workflow_api/workflow.json new file mode 100644 index 0000000..cc95a67 --- /dev/null +++ b/demo/workflow_api/workflow.json @@ -0,0 +1,48 @@ +{ + "schema_version": "2", + "name": "Text Tools", + "runtime": { "default": "client" }, + "references": [ + { + "id": "ref_text", "label": "Text", "role": "reference", "asset_type": "text", + "inputs": [{ "id": "in", "label": "Text", "type": "text" }], + "outputs": [{ "id": "out", "label": "Text", "type": "text" }], + "x": 80, "y": 120, "width": 220, "height": 120, "data": {} + } + ], + "operators": [ + { + "id": "op_loud", "label": "shout", "role": "operator", "kind": "fn", "fn": "shout", + "inputs": [{ "id": "in_text", "label": "text", "type": "text", "required": true }], + "outputs": [{ "id": "out_0", "label": "output", "type": "text", "output_index": 0 }], + "x": 380, "y": 40, "width": 220, "height": 120, "data": {} + }, + { + "id": "op_rev", "label": "reverse", "role": "operator", "kind": "fn", "fn": "reverse", + "inputs": [{ "id": "in_text", "label": "text", "type": "text", "required": true }], + "outputs": [{ "id": "out_0", "label": "output", "type": "text", "output_index": 0 }], + "x": 380, "y": 240, "width": 220, "height": 120, "data": {} + } + ], + "subjects": [ + { + "id": "sub_loud", "label": "Loud", "role": "subject", "asset_type": "text", + "inputs": [{ "id": "in", "label": "Text", "type": "text" }], + "outputs": [{ "id": "out", "label": "Text", "type": "text" }], + "x": 700, "y": 40, "width": 220, "height": 107, "data": {} + }, + { + "id": "sub_rev", "label": "Reversed", "role": "subject", "asset_type": "text", + "inputs": [{ "id": "in", "label": "Text", "type": "text" }], + "outputs": [{ "id": "out", "label": "Text", "type": "text" }], + "x": 700, "y": 240, "width": 220, "height": 107, "data": {} + } + ], + "edges": [ + { "id": "e1", "from_node_id": "ref_text", "from_port_id": "out", "to_node_id": "op_loud", "to_port_id": "in_text", "type": "text" }, + { "id": "e2", "from_node_id": "ref_text", "from_port_id": "out", "to_node_id": "op_rev", "to_port_id": "in_text", "type": "text" }, + { "id": "e3", "from_node_id": "op_loud", "from_port_id": "out_0", "to_node_id": "sub_loud", "to_port_id": "in", "type": "text" }, + { "id": "e4", "from_node_id": "op_rev", "from_port_id": "out_0", "to_node_id": "sub_rev", "to_port_id": "in", "type": "text" } + ], + "view": { "default": "canvas" } +} diff --git a/demo/xgboost-income-prediction-with-explainability/DESCRIPTION.md b/demo/xgboost-income-prediction-with-explainability/DESCRIPTION.md new file mode 100644 index 0000000..cd69ef8 --- /dev/null +++ b/demo/xgboost-income-prediction-with-explainability/DESCRIPTION.md @@ -0,0 +1 @@ +This demo takes in 12 inputs from the user in dropdowns and sliders and predicts income. It also has a separate button for explaining the prediction. \ No newline at end of file diff --git a/demo/xgboost-income-prediction-with-explainability/requirements.txt b/demo/xgboost-income-prediction-with-explainability/requirements.txt new file mode 100644 index 0000000..049ecfd --- /dev/null +++ b/demo/xgboost-income-prediction-with-explainability/requirements.txt @@ -0,0 +1,6 @@ +numpy==1.23.2 +matplotlib +shap +xgboost==1.7.6 +pandas +datasets \ No newline at end of file diff --git a/demo/xgboost-income-prediction-with-explainability/run.py b/demo/xgboost-income-prediction-with-explainability/run.py new file mode 100644 index 0000000..658b05b --- /dev/null +++ b/demo/xgboost-income-prediction-with-explainability/run.py @@ -0,0 +1,161 @@ +# type: ignore +import gradio as gr +import random +import matplotlib.pyplot as plt +import pandas as pd +import shap +import xgboost as xgb +from datasets import load_dataset + +dataset = load_dataset("scikit-learn/adult-census-income") +X_train = dataset["train"].to_pandas() +_ = X_train.pop("fnlwgt") +_ = X_train.pop("race") +y_train = X_train.pop("income") +y_train = (y_train == ">50K").astype(int) +categorical_columns = [ + "workclass", + "education", + "marital.status", + "occupation", + "relationship", + "sex", + "native.country", +] +X_train = X_train.astype({col: "category" for col in categorical_columns}) +data = xgb.DMatrix(X_train, label=y_train, enable_categorical=True) +model = xgb.train(params={"objective": "binary:logistic"}, dtrain=data) +explainer = shap.TreeExplainer(model) + +def predict(*args): + df = pd.DataFrame([args], columns=X_train.columns) + df = df.astype({col: "category" for col in categorical_columns}) + pos_pred = model.predict(xgb.DMatrix(df, enable_categorical=True)) + return {">50K": float(pos_pred[0]), "<=50K": 1 - float(pos_pred[0])} + +def interpret(*args): + df = pd.DataFrame([args], columns=X_train.columns) + df = df.astype({col: "category" for col in categorical_columns}) + shap_values = explainer.shap_values(xgb.DMatrix(df, enable_categorical=True)) + scores_desc = list(zip(shap_values[0], X_train.columns)) + scores_desc = sorted(scores_desc) + fig_m = plt.figure(tight_layout=True) + plt.barh([s[1] for s in scores_desc], [s[0] for s in scores_desc]) + plt.title("Feature Shap Values") + plt.ylabel("Shap Value") + plt.xlabel("Feature") + plt.tight_layout() + return fig_m + +unique_class = sorted(X_train["workclass"].unique()) +unique_education = sorted(X_train["education"].unique()) +unique_marital_status = sorted(X_train["marital.status"].unique()) +unique_relationship = sorted(X_train["relationship"].unique()) +unique_occupation = sorted(X_train["occupation"].unique()) +unique_sex = sorted(X_train["sex"].unique()) +unique_country = sorted(X_train["native.country"].unique()) + +with gr.Blocks() as demo: + gr.Markdown(""" + **Income Classification with XGBoost 💰**: This demo uses an XGBoost classifier predicts income based on demographic factors, along with Shapley value-based *explanations*. The [source code for this Gradio demo is here](https://huggingface.co/spaces/gradio/xgboost-income-prediction-with-explainability/blob/main/app.py). + """) + with gr.Row(): + with gr.Column(): + age = gr.Slider(label="Age", minimum=17, maximum=90, step=1, randomize=True) + work_class = gr.Dropdown( + label="Workclass", + choices=unique_class, + value=lambda: random.choice(unique_class), + ) + education = gr.Dropdown( + label="Education Level", + choices=unique_education, + value=lambda: random.choice(unique_education), + ) + years = gr.Slider( + label="Years of schooling", + minimum=1, + maximum=16, + step=1, + randomize=True, + ) + marital_status = gr.Dropdown( + label="Marital Status", + choices=unique_marital_status, + value=lambda: random.choice(unique_marital_status), + ) + occupation = gr.Dropdown( + label="Occupation", + choices=unique_occupation, + value=lambda: random.choice(unique_occupation), + ) + relationship = gr.Dropdown( + label="Relationship Status", + choices=unique_relationship, + value=lambda: random.choice(unique_relationship), + ) + sex = gr.Dropdown( + label="Sex", choices=unique_sex, value=lambda: random.choice(unique_sex) + ) + capital_gain = gr.Slider( + label="Capital Gain", + minimum=0, + maximum=100000, + step=500, + randomize=True, + ) + capital_loss = gr.Slider( + label="Capital Loss", minimum=0, maximum=10000, step=500, randomize=True + ) + hours_per_week = gr.Slider( + label="Hours Per Week Worked", minimum=1, maximum=99, step=1 + ) + country = gr.Dropdown( + label="Native Country", + choices=unique_country, + value=lambda: random.choice(unique_country), + ) + with gr.Column(): + label = gr.Label() + plot = gr.Plot() + with gr.Row(): + predict_btn = gr.Button(value="Predict") + interpret_btn = gr.Button(value="Explain") + predict_btn.click( + predict, + inputs=[ + age, + work_class, + education, + years, + marital_status, + occupation, + relationship, + sex, + capital_gain, + capital_loss, + hours_per_week, + country, + ], + outputs=[label], + ) + interpret_btn.click( + interpret, + inputs=[ + age, + work_class, + education, + years, + marital_status, + occupation, + relationship, + sex, + capital_gain, + capital_loss, + hours_per_week, + country, + ], + outputs=[plot], + ) + +demo.launch() diff --git a/demo/yolov10_webcam_stream/inference.py b/demo/yolov10_webcam_stream/inference.py new file mode 100644 index 0000000..70fbcfb --- /dev/null +++ b/demo/yolov10_webcam_stream/inference.py @@ -0,0 +1,148 @@ +import time +import cv2 # type: ignore +import numpy as np +import onnxruntime # type: ignore + +from utils import draw_detections # type: ignore + + +class YOLOv10: + def __init__(self, path): + # Initialize model + self.initialize_model(path) + + def __call__(self, image): + return self.detect_objects(image) + + def initialize_model(self, path): + self.session = onnxruntime.InferenceSession( + path, providers=onnxruntime.get_available_providers() + ) + # Get model info + self.get_input_details() + self.get_output_details() + + def detect_objects(self, image, conf_threshold=0.3): + input_tensor = self.prepare_input(image) + + # Perform inference on the image + new_image = self.inference(image, input_tensor, conf_threshold) + + return new_image + + def prepare_input(self, image): + self.img_height, self.img_width = image.shape[:2] + + input_img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) + + # Resize input image + input_img = cv2.resize(input_img, (self.input_width, self.input_height)) + + # Scale input pixel values to 0 to 1 + input_img = input_img / 255.0 + input_img = input_img.transpose(2, 0, 1) + input_tensor = input_img[np.newaxis, :, :, :].astype(np.float32) + + return input_tensor + + def inference(self, image, input_tensor, conf_threshold=0.3): + start = time.perf_counter() + outputs = self.session.run( + self.output_names, {self.input_names[0]: input_tensor} + ) + + print(f"Inference time: {(time.perf_counter() - start)*1000:.2f} ms") + ( + boxes, + scores, + class_ids, + ) = self.process_output(outputs, conf_threshold) + return self.draw_detections(image, boxes, scores, class_ids) + + def process_output(self, output, conf_threshold=0.3): + predictions = np.squeeze(output[0]) + + # Filter out object confidence scores below threshold + scores = predictions[:, 4] + predictions = predictions[scores > conf_threshold, :] + scores = scores[scores > conf_threshold] + + if len(scores) == 0: + return [], [], [] + + # Get the class with the highest confidence + class_ids = predictions[:, 5].astype(int) + + # Get bounding boxes for each object + boxes = self.extract_boxes(predictions) + + return boxes, scores, class_ids + + def extract_boxes(self, predictions): + # Extract boxes from predictions + boxes = predictions[:, :4] + + # Scale boxes to original image dimensions + boxes = self.rescale_boxes(boxes) + + # Convert boxes to xyxy format + # boxes = xywh2xyxy(boxes) + + return boxes + + def rescale_boxes(self, boxes): + # Rescale boxes to original image dimensions + input_shape = np.array( + [self.input_width, self.input_height, self.input_width, self.input_height] + ) + boxes = np.divide(boxes, input_shape, dtype=np.float32) + boxes *= np.array( + [self.img_width, self.img_height, self.img_width, self.img_height] + ) + return boxes + + def draw_detections( + self, image, boxes, scores, class_ids, draw_scores=True, mask_alpha=0.4 + ): + return draw_detections(image, boxes, scores, class_ids, mask_alpha) + + def get_input_details(self): + model_inputs = self.session.get_inputs() + self.input_names = [model_inputs[i].name for i in range(len(model_inputs))] + + self.input_shape = model_inputs[0].shape + self.input_height = self.input_shape[2] + self.input_width = self.input_shape[3] + + def get_output_details(self): + model_outputs = self.session.get_outputs() + self.output_names = [model_outputs[i].name for i in range(len(model_outputs))] + + +if __name__ == "__main__": + import requests + import tempfile + from huggingface_hub import hf_hub_download + + model_file = hf_hub_download( + repo_id="onnx-community/yolov10s", filename="onnx/model.onnx" + ) + + yolov8_detector = YOLOv10(model_file) + + with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as f: + f.write( + requests.get( + "https://live.staticflickr.com/13/19041780_d6fd803de0_3k.jpg" + ).content + ) + f.seek(0) + img = cv2.imread(f.name) + + # # Detect Objects + combined_image = yolov8_detector.detect_objects(img) + + # Draw detections + cv2.namedWindow("Output", cv2.WINDOW_NORMAL) + cv2.imshow("Output", combined_image) + cv2.waitKey(0) diff --git a/demo/yolov10_webcam_stream/requirements.txt b/demo/yolov10_webcam_stream/requirements.txt new file mode 100644 index 0000000..9766bb4 --- /dev/null +++ b/demo/yolov10_webcam_stream/requirements.txt @@ -0,0 +1,6 @@ +safetensors==0.4.3 +opencv-python +twilio +gradio>=5.0,<6.0 +gradio-webrtc==0.0.1 +onnxruntime-gpu \ No newline at end of file diff --git a/demo/yolov10_webcam_stream/run.py b/demo/yolov10_webcam_stream/run.py new file mode 100644 index 0000000..515986a --- /dev/null +++ b/demo/yolov10_webcam_stream/run.py @@ -0,0 +1,72 @@ +import gradio as gr +import cv2 # type: ignore +from huggingface_hub import hf_hub_download +from gradio_webrtc import WebRTC # type: ignore +from twilio.rest import Client # type: ignore +import os +from inference import YOLOv10 # type: ignore + +model_file = hf_hub_download( + repo_id="onnx-community/yolov10n", filename="onnx/model.onnx" +) + +model = YOLOv10(model_file) + +account_sid = os.environ.get("TWILIO_ACCOUNT_SID") +auth_token = os.environ.get("TWILIO_AUTH_TOKEN") + +if account_sid and auth_token: + client = Client(account_sid, auth_token) + + token = client.tokens.create() + + rtc_configuration = { + "iceServers": token.ice_servers, + "iceTransportPolicy": "relay", + } +else: + rtc_configuration = None + + +def detection(image, conf_threshold=0.3): + image = cv2.resize(image, (model.input_width, model.input_height)) + new_image = model.detect_objects(image, conf_threshold) + return cv2.resize(new_image, (500, 500)) + + +css = """.my-group {max-width: 600px !important; max-height: 600 !important;} + .my-column {display: flex !important; justify-content: center !important; align-items: center !important};""" + + +with gr.Blocks() as demo: + gr.HTML( + """ +` in Chatbot messages. Thanks [@jaywonchung](https://github.com/jaywonchung)!
+- [#5005](https://github.com/gradio-app/gradio/pull/5005) [`f5539c76`](https://github.com/gradio-app/gradio/commit/f5539c7618e31451420bd3228754774da14dc65f) - Enhancement: Add focus event to textbox and number component. Thanks [@JodyZ0203](https://github.com/JodyZ0203)!
+- [#5104](https://github.com/gradio-app/gradio/pull/5104) [`34f6b22e`](https://github.com/gradio-app/gradio/commit/34f6b22efbfedfa569d452f3f99ed2e6593e3c21) - Strip leading and trailing spaces from username in login route. Thanks [@sweep-ai](https://github.com/apps/sweep-ai)!
+- [#5149](https://github.com/gradio-app/gradio/pull/5149) [`144df459`](https://github.com/gradio-app/gradio/commit/144df459a3b7895e524defcfc4c03fbb8b083aca) - Add `show_edit_button` param to `gr.Audio`. Thanks [@hannahblair](https://github.com/hannahblair)!
+- [#5136](https://github.com/gradio-app/gradio/pull/5136) [`eaa1ce14`](https://github.com/gradio-app/gradio/commit/eaa1ce14ac41de1c23321e93f11f1b03a2f3c7f4) - Enhancing Tamil Translation: Language Refinement 🌟. Thanks [@sanjaiyan-dev](https://github.com/sanjaiyan-dev)!
+- [#5035](https://github.com/gradio-app/gradio/pull/5035) [`8b4eb8ca`](https://github.com/gradio-app/gradio/commit/8b4eb8cac9ea07bde31b44e2006ca2b7b5f4de36) - JS Client: Fixes cannot read properties of null (reading 'is_file'). Thanks [@raymondtri](https://github.com/raymondtri)!
+- [#5023](https://github.com/gradio-app/gradio/pull/5023) [`e6317d77`](https://github.com/gradio-app/gradio/commit/e6317d77f87d3dad638acca3dbc4a9228570e63c) - Update dependency extendable-media-recorder to v8. Thanks [@renovate](https://github.com/apps/renovate)!
+- [#5085](https://github.com/gradio-app/gradio/pull/5085) [`13e47835`](https://github.com/gradio-app/gradio/commit/13e478353532c4af18cfa50772f8b6fb3c6c9818) - chore(deps): update dependency extendable-media-recorder to v8. Thanks [@renovate](https://github.com/apps/renovate)!
+- [#5080](https://github.com/gradio-app/gradio/pull/5080) [`37caa2e0`](https://github.com/gradio-app/gradio/commit/37caa2e0fe95d6cab8beb174580fb557904f137f) - Add icon and link params to `gr.Button`. Thanks [@hannahblair](https://github.com/hannahblair)!
+
+### Fixes
+
+- [#5062](https://github.com/gradio-app/gradio/pull/5062) [`7d897165`](https://github.com/gradio-app/gradio/commit/7d89716519d0751072792c9bbda668ffeb597296) - `gr.Dropdown` now has correct behavior in static mode as well as when an option is selected. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5077](https://github.com/gradio-app/gradio/pull/5077) [`667875b2`](https://github.com/gradio-app/gradio/commit/667875b2441753e74d25bd9d3c8adedd8ede11cd) - Live audio streaming output
+- [#5118](https://github.com/gradio-app/gradio/pull/5118) [`1b017e68`](https://github.com/gradio-app/gradio/commit/1b017e68f6a9623cc2ec085bd20e056229552028) - Add `interactive` args to `gr.ColorPicker`. Thanks [@hannahblair](https://github.com/hannahblair)!
+- [#5114](https://github.com/gradio-app/gradio/pull/5114) [`56d2609d`](https://github.com/gradio-app/gradio/commit/56d2609de93387a75dc82b1c06c1240c5b28c0b8) - Reset textbox value to empty string when value is None. Thanks [@hannahblair](https://github.com/hannahblair)!
+- [#5075](https://github.com/gradio-app/gradio/pull/5075) [`67265a58`](https://github.com/gradio-app/gradio/commit/67265a58027ef1f9e4c0eb849a532f72eaebde48) - Allow supporting >1000 files in `gr.File()` and `gr.UploadButton()`. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5135](https://github.com/gradio-app/gradio/pull/5135) [`80727bbe`](https://github.com/gradio-app/gradio/commit/80727bbe2c6d631022054edf01515017691b3bdd) - Fix dataset features and dataset preview for HuggingFaceDatasetSaver. Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+- [#5039](https://github.com/gradio-app/gradio/pull/5039) [`620e4645`](https://github.com/gradio-app/gradio/commit/620e46452729d6d4877b3fab84a65daf2f2b7bc6) - `gr.Dropdown()` now supports values with arbitrary characters and doesn't clear value when re-focused. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5061](https://github.com/gradio-app/gradio/pull/5061) [`136adc9c`](https://github.com/gradio-app/gradio/commit/136adc9ccb23e5cb4d02d2e88f23f0b850041f98) - Ensure `gradio_client` is backwards compatible with `gradio==3.24.1`. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5129](https://github.com/gradio-app/gradio/pull/5129) [`97d804c7`](https://github.com/gradio-app/gradio/commit/97d804c748be9acfe27b8369dd2d64d61f43c2e7) - [Spaces] ZeroGPU Queue fix. Thanks [@cbensimon](https://github.com/cbensimon)!
+- [#5140](https://github.com/gradio-app/gradio/pull/5140) [`cd1353fa`](https://github.com/gradio-app/gradio/commit/cd1353fa3eb1b015f5860ca5d5a8e8d1aa4a831c) - Fixes the display of minutes in the video player. Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#5111](https://github.com/gradio-app/gradio/pull/5111) [`b84a35b7`](https://github.com/gradio-app/gradio/commit/b84a35b7b91eca947f787648ceb361b1d023427b) - Add icon and link to DuplicateButton. Thanks [@aliabd](https://github.com/aliabd)!
+- [#5030](https://github.com/gradio-app/gradio/pull/5030) [`f6c491b0`](https://github.com/gradio-app/gradio/commit/f6c491b079d335af633dd854c68eb26f9e61c552) - highlightedtext throws an error basing on model. Thanks [@rajeunoia](https://github.com/rajeunoia)!
+
+## 3.39.0
+
+### Highlights
+
+#### Create Discord Bots from Gradio Apps 🤖 ([#4960](https://github.com/gradio-app/gradio/pull/4960) [`46e4ef67`](https://github.com/gradio-app/gradio/commit/46e4ef67d287dd68a91473b73172b29cbad064bc))
+
+We're excited to announce that Gradio can now automatically create a discord bot from any `gr.ChatInterface` app.
+
+It's as easy as importing `gradio_client`, connecting to the app, and calling `deploy_discord`!
+
+_🦙 Turning Llama 2 70b into a discord bot 🦙_
+
+```python
+import gradio_client as grc
+grc.Client("ysharma/Explore_llamav2_with_TGI").deploy_discord(to_id="llama2-70b-discord-bot")
+```
+
+
+
+#### Getting started with template spaces
+
+To help get you started, we have created an organization on Hugging Face called [gradio-discord-bots](https://huggingface.co/gradio-discord-bots) with template spaces you can use to turn state of the art LLMs powered by Gradio to discord bots.
+
+Currently we have template spaces for:
+
+- [Llama-2-70b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/Llama-2-70b-chat-hf) powered by a FREE Hugging Face Inference Endpoint!
+- [Llama-2-13b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/Llama-2-13b-chat-hf) powered by Hugging Face Inference Endpoints.
+- [Llama-2-13b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/llama-2-13b-chat-transformers) powered by Hugging Face transformers.
+- [falcon-7b-instruct](https://huggingface.co/spaces/gradio-discord-bots/falcon-7b-instruct) powered by Hugging Face Inference Endpoints.
+- [gpt-3.5-turbo](https://huggingface.co/spaces/gradio-discord-bots/gpt-35-turbo), powered by openai. Requires an OpenAI key.
+
+But once again, you can deploy ANY `gr.ChatInterface` app exposed on the internet! So don't hesitate to try it on your own Chatbots.
+
+❗️ Additional Note ❗️: Technically, any gradio app that exposes an api route that takes in a single string and outputs a single string can be deployed to discord. But `gr.ChatInterface` apps naturally lend themselves to discord's chat functionality so we suggest you start with those.
+
+Thanks [@freddyaboulton](https://github.com/freddyaboulton)!
+
+### Features
+
+- [#4995](https://github.com/gradio-app/gradio/pull/4995) [`3f8c210b`](https://github.com/gradio-app/gradio/commit/3f8c210b01ef1ceaaf8ee73be4bf246b5b745bbf) - Implement left and right click in `Gallery` component and show implicit images in `Gallery` grid. Thanks [@hannahblair](https://github.com/hannahblair)!
+- [#4993](https://github.com/gradio-app/gradio/pull/4993) [`dc07a9f9`](https://github.com/gradio-app/gradio/commit/dc07a9f947de44b419d8384987a02dcf94977851) - Bringing back the "Add download button for audio" PR by [@leuryr](https://github.com/leuryr). Thanks [@abidlabs](https://github.com/abidlabs)!
+- [#4979](https://github.com/gradio-app/gradio/pull/4979) [`44ac8ad0`](https://github.com/gradio-app/gradio/commit/44ac8ad08d82ea12c503dde5c78f999eb0452de2) - Allow setting sketch color default. Thanks [@aliabid94](https://github.com/aliabid94)!
+- [#4985](https://github.com/gradio-app/gradio/pull/4985) [`b74f8453`](https://github.com/gradio-app/gradio/commit/b74f8453034328f0e42da8e41785f5eb039b45d7) - Adds `additional_inputs` to `gr.ChatInterface`. Thanks [@abidlabs](https://github.com/abidlabs)!
+
+### Fixes
+
+- [#4997](https://github.com/gradio-app/gradio/pull/4997) [`41c83070`](https://github.com/gradio-app/gradio/commit/41c83070b01632084e7d29123048a96c1e261407) - Add CSS resets and specifiers to play nice with HF blog. Thanks [@aliabid94](https://github.com/aliabid94)!
+
+## 3.38
+
+### New Features:
+
+- Provide a parameter `animate` (`False` by default) in `gr.make_waveform()` which animates the overlayed waveform by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4918](https://github.com/gradio-app/gradio/pull/4918)
+- Add `show_download_button` param to allow the download button in static Image components to be hidden by [@hannahblair](https://github.com/hannahblair) in [PR 4959](https://github.com/gradio-app/gradio/pull/4959)
+- Added autofocus argument to Textbox by [@aliabid94](https://github.com/aliabid94) in [PR 4978](https://github.com/gradio-app/gradio/pull/4978)
+- The `gr.ChatInterface` UI now converts the "Submit" button to a "Stop" button in ChatInterface while streaming, which can be used to pause generation. By [@abidlabs](https://github.com/abidlabs) in [PR 4971](https://github.com/gradio-app/gradio/pull/4971).
+- Add a `border_color_accent_subdued` theme variable to add a subdued border color to accented items. This is used by chatbot user messages. Set the value of this variable in `Default` theme to `*primary_200`. By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4989](https://github.com/gradio-app/gradio/pull/4989)
+- Add default sketch color argument `brush_color`. Also, masks drawn on images are now slightly translucent (and mask color can also be set via brush_color). By [@aliabid94](https://github.com/aliabid94) in [PR 4979](https://github.com/gradio-app/gradio/pull/4979)
+
+### Bug Fixes:
+
+- Fixes `cancels` for generators so that if a generator is canceled before it is complete, subsequent runs of the event do not continue from the previous iteration, but rather start from the beginning. By [@abidlabs](https://github.com/abidlabs) in [PR 4969](https://github.com/gradio-app/gradio/pull/4969).
+- Use `gr.State` in `gr.ChatInterface` to reduce latency by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4976](https://github.com/gradio-app/gradio/pull/4976)
+- Fix bug with `gr.Interface` where component labels inferred from handler parameters were including special args like `gr.Request` or `gr.EventData`. By [@cbensimon](https://github.com/cbensimon) in [PR 4956](https://github.com/gradio-app/gradio/pull/4956)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Other Changes:
+
+- Apply pyright to the `components` directory by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4948](https://github.com/gradio-app/gradio/pull/4948)
+- Improved look of ChatInterface by [@aliabid94](https://github.com/aliabid94) in [PR 4978](https://github.com/gradio-app/gradio/pull/4978)
+
+## 3.37
+
+### New Features:
+
+Introducing a new `gr.ChatInterface` abstraction, which allows Gradio users to build fully functioning Chat interfaces very easily. The only required parameter is a chat function `fn`, which accepts a (string) user input `message` and a (list of lists) chat `history` and returns a (string) response. Here's a toy example:
+
+```py
+import gradio as gr
+
+def echo(message, history):
+ return message
+
+demo = gr.ChatInterface(fn=echo, examples=["hello", "hola", "merhaba"], title="Echo Bot")
+demo.launch()
+```
+
+Which produces:
+
+
+
+And a corresponding easy-to-use API at `/chat`:
+
+
+
+The `gr.ChatInterface` abstraction works nicely with various LLM libraries, such as `langchain`. See the [dedicated guide](https://gradio.app/guides/creating-a-chatbot-fast) for more examples using `gr.ChatInterface`. Collective team effort in [PR 4869](https://github.com/gradio-app/gradio/pull/4869)
+
+- Chatbot messages now show hyperlinks to download files uploaded to `gr.Chatbot()` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4848](https://github.com/gradio-app/gradio/pull/4848)
+- Cached examples now work with generators and async generators by [@abidlabs](https://github.com/abidlabs) in [PR 4927](https://github.com/gradio-app/gradio/pull/4927)
+- Add RTL support to `gr.Markdown`, `gr.Chatbot`, `gr.Textbox` (via the `rtl` boolean parameter) and text-alignment to `gr.Textbox`(via the string `text_align` parameter) by [@abidlabs](https://github.com/abidlabs) in [PR 4933](https://github.com/gradio-app/gradio/pull/4933)
+
+Examples of usage:
+
+```py
+with gr.Blocks() as demo:
+ gr.Textbox(interactive=True, text_align="right")
+demo.launch()
+```
+
+```py
+with gr.Blocks() as demo:
+ gr.Markdown("سلام", rtl=True)
+demo.launch()
+```
+
+- The `get_api_info` method of `Blocks` now supports layout output components [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4871](https://github.com/gradio-app/gradio/pull/4871)
+
+- Added the support for the new command `gradio environment`to make it easier for people to file bug reports if we shipped an easy command to list the OS, gradio version, and versions of gradio/gradio-client dependencies. bu [@varshneydevansh](https://github.com/varshneydevansh) in [PR 4915](https://github.com/gradio-app/gradio/pull/4915).
+
+### Bug Fixes:
+
+- The `.change()` event is fixed in `Video` and `Image` so that it only fires once by [@abidlabs](https://github.com/abidlabs) in [PR 4793](https://github.com/gradio-app/gradio/pull/4793)
+- The `.change()` event is fixed in `Audio` so that fires when the component value is programmatically updated by [@abidlabs](https://github.com/abidlabs) in [PR 4793](https://github.com/gradio-app/gradio/pull/4793)
+
+* Add missing `display: flex` property to `Row` so that flex styling is applied to children by [@hannahblair] in [PR 4896](https://github.com/gradio-app/gradio/pull/4896)
+* Fixed bug where `gr.Video` could not preprocess urls by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4904](https://github.com/gradio-app/gradio/pull/4904)
+* Fixed copy button rendering in API page on Safari by [@aliabid94](https://github.com/aliabid94) in [PR 4924](https://github.com/gradio-app/gradio/pull/4924)
+* Fixed `gr.Group` and `container=False`. `container` parameter only available for `Textbox`, `Number`, and `Dropdown`, the only elements where it makes sense. By [@aliabid94](https://github.com/aliabid94) in [PR 4916](https://github.com/gradio-app/gradio/pull/4916)
+* Fixed broken image link in auto-generated `app.py` from `ThemeClass.push_to_hub` by [@deepkyu](https://github.com/deepkyu) in [PR 4944](https://github.com/gradio-app/gradio/pull/4944)
+
+### Other Changes:
+
+- Warning on mobile that if a user leaves the tab, websocket connection may break. On broken connection, tries to rejoin queue and displays error conveying connection broke. By [@aliabid94](https://github.com/aliabid94) in [PR 4742](https://github.com/gradio-app/gradio/pull/4742)
+- Remove blocking network calls made before the local URL gets printed - these slow down the display of the local URL, especially when no internet is available. [@aliabid94](https://github.com/aliabid94) in [PR 4905](https://github.com/gradio-app/gradio/pull/4905).
+- Pinned dependencies to major versions to reduce the likelihood of a broken `gradio` due to changes in downstream dependencies by [@abidlabs](https://github.com/abidlabs) in [PR 4885](https://github.com/gradio-app/gradio/pull/4885)
+- Queue `max_size` defaults to parent Blocks `max_thread` when running on Spaces with ZeroGPU hardware. By [@cbensimon](https://github.com/cbensimon) in [PR 4937](https://github.com/gradio-app/gradio/pull/4937)
+
+### Breaking Changes:
+
+Motivated by the release of `pydantic==2.0`, which included breaking changes that broke a large number of Gradio apps, we've pinned many gradio dependencies. Note that pinned dependencies can cause downstream conflicts, so this may be a breaking change. That being said, we've kept the pins pretty loose, and we're expecting change to be better for the long-term stability of Gradio apps.
+
+## 3.36.1
+
+### New Features:
+
+- Hotfix to support pydantic v1 and v2 by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4835](https://github.com/gradio-app/gradio/pull/4835)
+
+### Bug Fixes:
+
+- Fix bug where `gr.File` change event was not triggered when the value was changed by another event by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4811](https://github.com/gradio-app/gradio/pull/4811)
+
+### Other Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.36.0
+
+### New Features:
+
+- The `gr.Video`, `gr.Audio`, `gr.Image`, `gr.Chatbot`, and `gr.Gallery` components now include a share icon when deployed on Spaces. This behavior can be modified by setting the `show_share_button` parameter in the component classes. by [@aliabid94](https://github.com/aliabid94) in [PR 4651](https://github.com/gradio-app/gradio/pull/4651)
+- Allow the web component `space`, `src`, and `host` attributes to be updated dynamically by [@pngwn](https://github.com/pngwn) in [PR 4461](https://github.com/gradio-app/gradio/pull/4461)
+- Suggestion for Spaces Duplication built into Gradio, by [@aliabid94](https://github.com/aliabid94) in [PR 4458](https://github.com/gradio-app/gradio/pull/4458)
+- The `api_name` parameter now accepts `False` as a value, which means it does not show up in named or unnamed endpoints. By [@abidlabs](https://github.com/aliabid94) in [PR 4683](https://github.com/gradio-app/gradio/pull/4683)
+- Added support for `pathlib.Path` in `gr.Video`, `gr.Gallery`, and `gr.Chatbot` by [sunilkumardash9](https://github.com/sunilkumardash9) in [PR 4581](https://github.com/gradio-app/gradio/pull/4581).
+
+### Bug Fixes:
+
+- Updated components with `info` attribute to update when `update()` is called on them. by [@jebarpg](https://github.com/jebarpg) in [PR 4715](https://github.com/gradio-app/gradio/pull/4715).
+- Ensure the `Image` components undo button works mode is `mask` or `color-sketch` by [@amyorz](https://github.com/AmyOrz) in [PR 4692](https://github.com/gradio-app/gradio/pull/4692)
+- Load the iframe resizer external asset asynchronously, by [@akx](https://github.com/akx) in [PR 4336](https://github.com/gradio-app/gradio/pull/4336)
+- Restored missing imports in `gr.components` by [@abidlabs](https://github.com/abidlabs) in [PR 4566](https://github.com/gradio-app/gradio/pull/4566)
+- Fix bug where `select` event was not triggered in `gr.Gallery` if `height` was set to be large with `allow_preview=False` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4551](https://github.com/gradio-app/gradio/pull/4551)
+- Fix bug where setting `visible=False` in `gr.Group` event did not work by [@abidlabs](https://github.com/abidlabs) in [PR 4567](https://github.com/gradio-app/gradio/pull/4567)
+- Fix `make_waveform` to work with paths that contain spaces [@akx](https://github.com/akx) in [PR 4570](https://github.com/gradio-app/gradio/pull/4570) & [PR 4578](https://github.com/gradio-app/gradio/pull/4578)
+- Send captured data in `stop_recording` event for `gr.Audio` and `gr.Video` components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4554](https://github.com/gradio-app/gradio/pull/4554)
+- Fix bug in `gr.Gallery` where `height` and `object_fit` parameters where being ignored by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4576](https://github.com/gradio-app/gradio/pull/4576)
+- Fixes an HTML sanitization issue in DOMPurify where links in markdown were not opening in a new window by [@hannahblair] in [PR 4577](https://github.com/gradio-app/gradio/pull/4577)
+- Fixed Dropdown height rendering in Columns by [@aliabid94](https://github.com/aliabid94) in [PR 4584](https://github.com/gradio-app/gradio/pull/4584)
+- Fixed bug where `AnnotatedImage` css styling was causing the annotation masks to not be displayed correctly by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4628](https://github.com/gradio-app/gradio/pull/4628)
+- Ensure that Gradio does not silently fail when running on a port that is occupied by [@abidlabs](https://github.com/abidlabs) in [PR 4624](https://github.com/gradio-app/gradio/pull/4624).
+- Fix double upload bug that caused lag in file uploads by [@aliabid94](https://github.com/aliabid94) in [PR 4661](https://github.com/gradio-app/gradio/pull/4661)
+- `Progress` component now appears even when no `iterable` is specified in `tqdm` constructor by [@itrushkin](https://github.com/itrushkin) in [PR 4475](https://github.com/gradio-app/gradio/pull/4475)
+- Deprecation warnings now point at the user code using those deprecated features, instead of Gradio internals, by (https://github.com/akx) in [PR 4694](https://github.com/gradio-app/gradio/pull/4694)
+- Adapt column widths in gr.Examples based on content by [@pngwn](https://github.com/pngwn) & [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4700](https://github.com/gradio-app/gradio/pull/4700)
+- The `plot` parameter deprecation warnings should now only be emitted for `Image` components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4709](https://github.com/gradio-app/gradio/pull/4709)
+- Removed uncessessary `type` deprecation warning by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4709](https://github.com/gradio-app/gradio/pull/4709)
+- Ensure Audio autoplays works when `autoplay=True` and the video source is dynamically updated [@pngwn](https://github.com/pngwn) in [PR 4705](https://github.com/gradio-app/gradio/pull/4705)
+- When an error modal is shown in spaces, ensure we scroll to the top so it can be seen by [@pngwn](https://github.com/pngwn) in [PR 4712](https://github.com/gradio-app/gradio/pull/4712)
+- Update depedencies by [@pngwn](https://github.com/pngwn) in [PR 4675](https://github.com/gradio-app/gradio/pull/4675)
+- Fixes `gr.Dropdown` being cutoff at the bottom by [@abidlabs](https://github.com/abidlabs) in [PR 4691](https://github.com/gradio-app/gradio/pull/4691).
+- Scroll top when clicking "View API" in spaces by [@pngwn](https://github.com/pngwn) in [PR 4714](https://github.com/gradio-app/gradio/pull/4714)
+- Fix bug where `show_label` was hiding the entire component for `gr.Label` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4713](https://github.com/gradio-app/gradio/pull/4713)
+- Don't crash when uploaded image has broken EXIF data, by [@akx](https://github.com/akx) in [PR 4764](https://github.com/gradio-app/gradio/pull/4764)
+- Place toast messages at the top of the screen by [@pngwn](https://github.com/pngwn) in [PR 4796](https://github.com/gradio-app/gradio/pull/4796)
+- Fix regressed styling of Login page when auth is enabled by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4797](https://github.com/gradio-app/gradio/pull/4797)
+- Prevent broken scrolling to output on Spaces by [@aliabid94](https://github.com/aliabid94) in [PR 4822](https://github.com/gradio-app/gradio/pull/4822)
+
+### Other Changes:
+
+- Add `.git-blame-ignore-revs` by [@akx](https://github.com/akx) in [PR 4586](https://github.com/gradio-app/gradio/pull/4586)
+- Update frontend dependencies in [PR 4601](https://github.com/gradio-app/gradio/pull/4601)
+- Use `typing.Literal` where possible in gradio library and client by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4608](https://github.com/gradio-app/gradio/pull/4608)
+- Remove unnecessary mock json files for frontend E2E tests by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4625](https://github.com/gradio-app/gradio/pull/4625)
+- Update dependencies by [@pngwn](https://github.com/pngwn) in [PR 4643](https://github.com/gradio-app/gradio/pull/4643)
+- The theme builder now launches successfully, and the API docs are cleaned up. By [@abidlabs](https://github.com/aliabid94) in [PR 4683](https://github.com/gradio-app/gradio/pull/4683)
+- Remove `cleared_value` from some components as its no longer used internally by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4685](https://github.com/gradio-app/gradio/pull/4685)
+- Better errors when you define two Blocks and reference components in one Blocks from the events in the other Blocks [@abidlabs](https://github.com/abidlabs) in [PR 4738](https://github.com/gradio-app/gradio/pull/4738).
+- Better message when share link is not created by [@abidlabs](https://github.com/abidlabs) in [PR 4773](https://github.com/gradio-app/gradio/pull/4773).
+- Improve accessibility around selected images in gr.Gallery component by [@hannahblair](https://github.com/hannahblair) in [PR 4790](https://github.com/gradio-app/gradio/pull/4790)
+
+### Breaking Changes:
+
+[PR 4683](https://github.com/gradio-app/gradio/pull/4683) removes the explict named endpoint "load_examples" from gr.Interface that was introduced in [PR 4456](https://github.com/gradio-app/gradio/pull/4456).
+
+## 3.35.2
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fix chatbot streaming by [@aliabid94](https://github.com/aliabid94) in [PR 4537](https://github.com/gradio-app/gradio/pull/4537)
+- Fix chatbot height and scrolling by [@aliabid94](https://github.com/aliabid94) in [PR 4540](https://github.com/gradio-app/gradio/pull/4540)
+
+### Other Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.35.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fix chatbot streaming by [@aliabid94](https://github.com/aliabid94) in [PR 4537](https://github.com/gradio-app/gradio/pull/4537)
+- Fix error modal position and text size by [@pngwn](https://github.com/pngwn) in [PR 4538](https://github.com/gradio-app/gradio/pull/4538).
+
+### Other Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.35.0
+
+### New Features:
+
+- A `gr.ClearButton` which allows users to easily clear the values of components by [@abidlabs](https://github.com/abidlabs) in [PR 4456](https://github.com/gradio-app/gradio/pull/4456)
+
+Example usage:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ chatbot = gr.Chatbot([("Hello", "How are you?")])
+ with gr.Row():
+ textbox = gr.Textbox(scale=3, interactive=True)
+ gr.ClearButton([textbox, chatbot], scale=1)
+
+demo.launch()
+```
+
+- Min and max value for gr.Number by [@artegoser](https://github.com/artegoser) and [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3991](https://github.com/gradio-app/gradio/pull/3991)
+- Add `start_recording` and `stop_recording` events to `Video` and `Audio` components by [@pngwn](https://github.com/pngwn) in [PR 4422](https://github.com/gradio-app/gradio/pull/4422)
+- Allow any function to generate an error message and allow multiple messages to appear at a time. Other error modal improvements such as auto dismiss after a time limit and a new layout on mobile [@pngwn](https://github.com/pngwn) in [PR 4459](https://github.com/gradio-app/gradio/pull/4459).
+- Add `autoplay` kwarg to `Video` and `Audio` components by [@pngwn](https://github.com/pngwn) in [PR 4453](https://github.com/gradio-app/gradio/pull/4453)
+- Add `allow_preview` parameter to `Gallery` to control whether a detailed preview is displayed on click by
+ [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4470](https://github.com/gradio-app/gradio/pull/4470)
+- Add `latex_delimiters` parameter to `Chatbot` to control the delimiters used for LaTeX and to disable LaTeX in the `Chatbot` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4516](https://github.com/gradio-app/gradio/pull/4516)
+- Can now issue `gr.Warning` and `gr.Info` modals. Simply put the code `gr.Warning("Your warning message")` or `gr.Info("Your info message")` as a standalone line in your function. By [@aliabid94](https://github.com/aliabid94) in [PR 4518](https://github.com/gradio-app/gradio/pull/4518).
+
+Example:
+
+```python
+def start_process(name):
+ gr.Info("Starting process")
+ if name is None:
+ gr.Warning("Name is empty")
+ ...
+ if success == False:
+ raise gr.Error("Process failed")
+```
+
+### Bug Fixes:
+
+- Add support for PAUSED state in the JS client by [@abidlabs](https://github.com/abidlabs) in [PR 4438](https://github.com/gradio-app/gradio/pull/4438)
+- Ensure Tabs only occupy the space required by [@pngwn](https://github.com/pngwn) in [PR 4419](https://github.com/gradio-app/gradio/pull/4419)
+- Ensure components have the correct empty sizes to prevent empty containers from collapsing by [@pngwn](https://github.com/pngwn) in [PR 4447](https://github.com/gradio-app/gradio/pull/4447).
+- Frontend code no longer crashes when there is a relative URL in an `` element, by [@akx](https://github.com/akx) in [PR 4449](https://github.com/gradio-app/gradio/pull/4449).
+- Fix bug where setting `format='mp4'` on a video component would cause the function to error out if the uploaded video was not playable by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4467](https://github.com/gradio-app/gradio/pull/4467)
+- Fix `_js` parameter to work even without backend function, by [@aliabid94](https://github.com/aliabid94) in [PR 4486](https://github.com/gradio-app/gradio/pull/4486).
+- Fix new line issue with `gr.Chatbot()` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4491](https://github.com/gradio-app/gradio/pull/4491)
+- Fixes issue with Clear button not working for `Label` component by [@abidlabs](https://github.com/abidlabs) in [PR 4456](https://github.com/gradio-app/gradio/pull/4456)
+- Restores the ability to pass in a tuple (sample rate, audio array) to gr.Audio() by [@abidlabs](https://github.com/abidlabs) in [PR 4525](https://github.com/gradio-app/gradio/pull/4525)
+- Ensure code is correctly formatted and copy button is always present in Chatbot by [@pngwn](https://github.com/pngwn) in [PR 4527](https://github.com/gradio-app/gradio/pull/4527)
+- `show_label` will not automatically be set to `True` in `gr.BarPlot.update` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4531](https://github.com/gradio-app/gradio/pull/4531)
+- `gr.BarPlot` group text now respects darkmode by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4531](https://github.com/gradio-app/gradio/pull/4531)
+- Fix dispatched errors from within components [@aliabid94](https://github.com/aliabid94) in [PR 4786](https://github.com/gradio-app/gradio/pull/4786)
+
+### Other Changes:
+
+- Change styling of status and toast error components by [@hannahblair](https://github.com/hannahblair) in [PR 4454](https://github.com/gradio-app/gradio/pull/4454).
+- Clean up unnecessary `new Promise()`s by [@akx](https://github.com/akx) in [PR 4442](https://github.com/gradio-app/gradio/pull/4442).
+- Minor UI cleanup for Examples and Dataframe components [@aliabid94](https://github.com/aliabid94) in [PR 4455](https://github.com/gradio-app/gradio/pull/4455).
+- Minor UI cleanup for Examples and Dataframe components [@aliabid94](https://github.com/aliabid94) in [PR 4455](https://github.com/gradio-app/gradio/pull/4455).
+- Add Catalan translation [@jordimas](https://github.com/jordimas) in [PR 4483](https://github.com/gradio-app/gradio/pull/4483).
+- The API endpoint that loads examples upon click has been given an explicit name ("/load_examples") by [@abidlabs](https://github.com/abidlabs) in [PR 4456](https://github.com/gradio-app/gradio/pull/4456).
+- Allows configuration of FastAPI app when calling `mount_gradio_app`, by [@charlesfrye](https://github.com/charlesfrye) in [PR4519](https://github.com/gradio-app/gradio/pull/4519).
+
+### Breaking Changes:
+
+- The behavior of the `Clear` button has been changed for `Slider`, `CheckboxGroup`, `Radio`, `Dropdown` components by [@abidlabs](https://github.com/abidlabs) in [PR 4456](https://github.com/gradio-app/gradio/pull/4456). The Clear button now sets the value of these components to be empty as opposed to the original default set by the developer. This is to make them in line with the rest of the Gradio components.
+- Python 3.7 end of life is June 27 2023. Gradio will no longer support python 3.7 by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4484](https://github.com/gradio-app/gradio/pull/4484)
+- Removed `$` as a default LaTeX delimiter for the `Chatbot` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4516](https://github.com/gradio-app/gradio/pull/4516). The specific LaTeX delimeters can be set using the new `latex_delimiters` parameter in `Chatbot`.
+
+## 3.34.0
+
+### New Features:
+
+- The `gr.UploadButton` component now supports the `variant` and `interactive` parameters by [@abidlabs](https://github.com/abidlabs) in [PR 4436](https://github.com/gradio-app/gradio/pull/4436).
+
+### Bug Fixes:
+
+- Remove target="\_blank" override on anchor tags with internal targets by [@hannahblair](https://github.com/hannahblair) in [PR 4405](https://github.com/gradio-app/gradio/pull/4405)
+- Fixed bug where `gr.File(file_count='multiple')` could not be cached as output by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4421](https://github.com/gradio-app/gradio/pull/4421)
+- Restricts the domains that can be proxied via `/proxy` route by [@abidlabs](https://github.com/abidlabs) in [PR 4406](https://github.com/gradio-app/gradio/pull/4406).
+- Fixes issue where `gr.UploadButton` could not be used to upload the same file twice by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4437](https://github.com/gradio-app/gradio/pull/4437)
+- Fixes bug where `/proxy` route was being incorrectly constructed by the frontend by [@abidlabs](https://github.com/abidlabs) in [PR 4430](https://github.com/gradio-app/gradio/pull/4430).
+- Fix z-index of status component by [@hannahblair](https://github.com/hannahblair) in [PR 4429](https://github.com/gradio-app/gradio/pull/4429)
+- Fix video rendering in Safari by [@aliabid94](https://github.com/aliabid94) in [PR 4433](https://github.com/gradio-app/gradio/pull/4433).
+- The output directory for files downloaded when calling Blocks as a function is now set to a temporary directory by default (instead of the working directory in some cases) by [@abidlabs](https://github.com/abidlabs) in [PR 4501](https://github.com/gradio-app/gradio/pull/4501)
+
+### Other Changes:
+
+- When running on Spaces, handler functions will be transformed by the [PySpaces](https://pypi.org/project/spaces/) library in order to make them work with specific hardware. It will have no effect on standalone Gradio apps or regular Gradio Spaces and can be globally deactivated as follows : `import spaces; spaces.disable_gradio_auto_wrap()` by [@cbensimon](https://github.com/cbensimon) in [PR 4389](https://github.com/gradio-app/gradio/pull/4389).
+- Deprecated `.style` parameter and moved arguments to constructor. Added support for `.update()` to all arguments initially in style. Added `scale` and `min_width` support to every Component. By [@aliabid94](https://github.com/aliabid94) in [PR 4374](https://github.com/gradio-app/gradio/pull/4374)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.33.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Allow `every` to work with generators by [@dkjshk](https://github.com/dkjshk) in [PR 4434](https://github.com/gradio-app/gradio/pull/4434)
+- Fix z-index of status component by [@hannahblair](https://github.com/hannahblair) in [PR 4429](https://github.com/gradio-app/gradio/pull/4429)
+- Allow gradio to work offline, by [@aliabid94](https://github.com/aliabid94) in [PR 4398](https://github.com/gradio-app/gradio/pull/4398).
+- Fixed `validate_url` to check for 403 errors and use a GET request in place of a HEAD by [@alvindaiyan](https://github.com/alvindaiyan) in [PR 4388](https://github.com/gradio-app/gradio/pull/4388).
+
+### Other Changes:
+
+- More explicit error message when share link binary is blocked by antivirus by [@abidlabs](https://github.com/abidlabs) in [PR 4380](https://github.com/gradio-app/gradio/pull/4380).
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.33.0
+
+### New Features:
+
+- Introduced `gradio deploy` to launch a Gradio app to Spaces directly from your terminal. By [@aliabid94](https://github.com/aliabid94) in [PR 4033](https://github.com/gradio-app/gradio/pull/4033).
+- Introduce `show_progress='corner'` argument to event listeners, which will not cover the output components with the progress animation, but instead show it in the corner of the components. By [@aliabid94](https://github.com/aliabid94) in [PR 4396](https://github.com/gradio-app/gradio/pull/4396).
+
+### Bug Fixes:
+
+- Fix bug where Label change event was triggering itself by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4371](https://github.com/gradio-app/gradio/pull/4371)
+- Make `Blocks.load` behave like other event listeners (allows chaining `then` off of it) [@anentropic](https://github.com/anentropic/) in [PR 4304](https://github.com/gradio-app/gradio/pull/4304)
+- Respect `interactive=True` in output components of a `gr.Interface` by [@abidlabs](https://github.com/abidlabs) in [PR 4356](https://github.com/gradio-app/gradio/pull/4356).
+- Remove unused frontend code by [@akx](https://github.com/akx) in [PR 4275](https://github.com/gradio-app/gradio/pull/4275)
+- Fixes favicon path on Windows by [@abidlabs](https://github.com/abidlabs) in [PR 4369](https://github.com/gradio-app/gradio/pull/4369).
+- Prevent path traversal in `/file` routes by [@abidlabs](https://github.com/abidlabs) in [PR 4370](https://github.com/gradio-app/gradio/pull/4370).
+- Do not send HF token to other domains via `/proxy` route by [@abidlabs](https://github.com/abidlabs) in [PR 4368](https://github.com/gradio-app/gradio/pull/4368).
+- Replace default `markedjs` sanitize function with DOMPurify sanitizer for `gr.Chatbot()` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4360](https://github.com/gradio-app/gradio/pull/4360)
+- Prevent the creation of duplicate copy buttons in the chatbot and ensure copy buttons work in non-secure contexts by [@binary-husky](https://github.com/binary-husky) in [PR 4350](https://github.com/gradio-app/gradio/pull/4350).
+
+### Other Changes:
+
+- Remove flicker of loading bar by adding opacity transition, by [@aliabid94](https://github.com/aliabid94) in [PR 4349](https://github.com/gradio-app/gradio/pull/4349).
+- Performance optimization in the frontend's Blocks code by [@akx](https://github.com/akx) in [PR 4334](https://github.com/gradio-app/gradio/pull/4334)
+- Upgrade the pnpm lock file format version from v6.0 to v6.1 by [@whitphx](https://github.com/whitphx) in [PR 4393](https://github.com/gradio-app/gradio/pull/4393)
+
+### Breaking Changes:
+
+- The `/file=` route no longer allows accessing dotfiles or files in "dot directories" by [@akx](https://github.com/akx) in [PR 4303](https://github.com/gradio-app/gradio/pull/4303)
+
+## 3.32.0
+
+### New Features:
+
+- `Interface.launch()` and `Blocks.launch()` now accept an `app_kwargs` argument to allow customizing the configuration of the underlying FastAPI app, by [@akx](https://github.com/akx) in [PR 4282](https://github.com/gradio-app/gradio/pull/4282)
+
+### Bug Fixes:
+
+- Fixed Gallery/AnnotatedImage components not respecting GRADIO_DEFAULT_DIR variable by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4256](https://github.com/gradio-app/gradio/pull/4256)
+- Fixed Gallery/AnnotatedImage components resaving identical images by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4256](https://github.com/gradio-app/gradio/pull/4256)
+- Fixed Audio/Video/File components creating empty tempfiles on each run by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4256](https://github.com/gradio-app/gradio/pull/4256)
+- Fixed the behavior of the `run_on_click` parameter in `gr.Examples` by [@abidlabs](https://github.com/abidlabs) in [PR 4258](https://github.com/gradio-app/gradio/pull/4258).
+- Ensure error modal displays when the queue is enabled by [@pngwn](https://github.com/pngwn) in [PR 4273](https://github.com/gradio-app/gradio/pull/4273)
+- Ensure js client respcts the full root when making requests to the server by [@pngwn](https://github.com/pngwn) in [PR 4271](https://github.com/gradio-app/gradio/pull/4271)
+
+### Other Changes:
+
+- Refactor web component `initial_height` attribute by [@whitphx](https://github.com/whitphx) in [PR 4223](https://github.com/gradio-app/gradio/pull/4223)
+- Relocate `mount_css` fn to remove circular dependency [@whitphx](https://github.com/whitphx) in [PR 4222](https://github.com/gradio-app/gradio/pull/4222)
+- Upgrade Black to 23.3 by [@akx](https://github.com/akx) in [PR 4259](https://github.com/gradio-app/gradio/pull/4259)
+- Add frontend LaTeX support in `gr.Chatbot()` using `KaTeX` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4285](https://github.com/gradio-app/gradio/pull/4285).
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.31.0
+
+### New Features:
+
+- The reloader command (`gradio app.py`) can now accept command line arguments by [@micky2be](https://github.com/micky2be) in [PR 4119](https://github.com/gradio-app/gradio/pull/4119)
+- Added `format` argument to `Audio` component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4178](https://github.com/gradio-app/gradio/pull/4178)
+- Add JS client code snippets to use via api page by [@aliabd](https://github.com/aliabd) in [PR 3927](https://github.com/gradio-app/gradio/pull/3927).
+- Update to the JS client by [@pngwn](https://github.com/pngwn) in [PR 4202](https://github.com/gradio-app/gradio/pull/4202)
+
+### Bug Fixes:
+
+- Fix "TypeError: issubclass() arg 1 must be a class" When use Optional[Types] by [@lingfengchencn](https://github.com/lingfengchencn) in [PR 4200](https://github.com/gradio-app/gradio/pull/4200).
+- Gradio will no longer send any analytics or call home if analytics are disabled with the GRADIO_ANALYTICS_ENABLED environment variable. By [@akx](https://github.com/akx) in [PR 4194](https://github.com/gradio-app/gradio/pull/4194) and [PR 4236](https://github.com/gradio-app/gradio/pull/4236)
+- The deprecation warnings for kwargs now show the actual stack level for the invocation, by [@akx](https://github.com/akx) in [PR 4203](https://github.com/gradio-app/gradio/pull/4203).
+- Fix "TypeError: issubclass() arg 1 must be a class" When use Optional[Types] by [@lingfengchencn](https://github.com/lingfengchencn) in [PR 4200](https://github.com/gradio-app/gradio/pull/4200).
+- Ensure cancelling functions work correctly by [@pngwn](https://github.com/pngwn) in [PR 4225](https://github.com/gradio-app/gradio/pull/4225)
+- Fixes a bug with typing.get_type_hints() on Python 3.9 by [@abidlabs](https://github.com/abidlabs) in [PR 4228](https://github.com/gradio-app/gradio/pull/4228).
+- Fixes JSONDecodeError by [@davidai](https://github.com/davidai) in [PR 4241](https://github.com/gradio-app/gradio/pull/4241)
+- Fix `chatbot_dialogpt` demo by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4238](https://github.com/gradio-app/gradio/pull/4238).
+
+### Other Changes:
+
+- Change `gr.Chatbot()` markdown parsing to frontend using `marked` library and `prism` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4150](https://github.com/gradio-app/gradio/pull/4150)
+- Update the js client by [@pngwn](https://github.com/pngwn) in [PR 3899](https://github.com/gradio-app/gradio/pull/3899)
+- Fix documentation for the shape of the numpy array produced by the `Image` component by [@der3318](https://github.com/der3318) in [PR 4204](https://github.com/gradio-app/gradio/pull/4204).
+- Updates the timeout for websocket messaging from 1 second to 5 seconds by [@abidlabs](https://github.com/abidlabs) in [PR 4235](https://github.com/gradio-app/gradio/pull/4235)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.30.0
+
+### New Features:
+
+- Adds a `root_path` parameter to `launch()` that allows running Gradio applications on subpaths (e.g. www.example.com/app) behind a proxy, by [@abidlabs](https://github.com/abidlabs) in [PR 4133](https://github.com/gradio-app/gradio/pull/4133)
+- Fix dropdown change listener to trigger on change when updated as an output by [@aliabid94](https://github.com/aliabid94) in [PR 4128](https://github.com/gradio-app/gradio/pull/4128).
+- Add `.input` event listener, which is only triggered when a user changes the component value (as compared to `.change`, which is also triggered when a component updates as the result of a function trigger), by [@aliabid94](https://github.com/aliabid94) in [PR 4157](https://github.com/gradio-app/gradio/pull/4157).
+
+### Bug Fixes:
+
+- Records username when flagging by [@abidlabs](https://github.com/abidlabs) in [PR 4135](https://github.com/gradio-app/gradio/pull/4135)
+- Fix website build issue by [@aliabd](https://github.com/aliabd) in [PR 4142](https://github.com/gradio-app/gradio/pull/4142)
+- Fix lang agnostic type info for `gr.File(file_count='multiple')` output components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4153](https://github.com/gradio-app/gradio/pull/4153)
+
+### Other Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+## 3.29.0
+
+### New Features:
+
+- Returning language agnostic types in the `/info` route by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4039](https://github.com/gradio-app/gradio/pull/4039)
+
+### Bug Fixes:
+
+- Allow users to upload audio files in Audio component on iOS by by [@aliabid94](https://github.com/aliabid94) in [PR 4071](https://github.com/gradio-app/gradio/pull/4071).
+- Fixes the gradio theme builder error that appeared on launch by [@aliabid94](https://github.com/aliabid94) and [@abidlabs](https://github.com/abidlabs) in [PR 4080](https://github.com/gradio-app/gradio/pull/4080)
+- Keep Accordion content in DOM by [@aliabid94](https://github.com/aliabid94) in [PR 4070](https://github.com/gradio-app/gradio/pull/4073)
+- Fixed bug where type hints in functions caused the event handler to crash by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 4068](https://github.com/gradio-app/gradio/pull/4068)
+- Fix dropdown default value not appearing by [@aliabid94](https://github.com/aliabid94) in [PR 4072](https://github.com/gradio-app/gradio/pull/4072).
+- Soft theme label color fix by [@aliabid94](https://github.com/aliabid94) in [PR 4070](https://github.com/gradio-app/gradio/pull/4070)
+- Fix `gr.Slider` `release` event not triggering on mobile by [@space-nuko](https://github.com/space-nuko) in [PR 4098](https://github.com/gradio-app/gradio/pull/4098)
+- Removes extraneous `State` component info from the `/info` route by [@abidlabs](https://github.com/freddyaboulton) in [PR 4107](https://github.com/gradio-app/gradio/pull/4107)
+- Make .then() work even if first event fails by [@aliabid94](https://github.com/aliabid94) in [PR 4115](https://github.com/gradio-app/gradio/pull/4115).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Allow users to submit with enter in Interfaces with textbox / number inputs [@aliabid94](https://github.com/aliabid94) in [PR 4090](https://github.com/gradio-app/gradio/pull/4090).
+- Updates gradio's requirements.txt to requires uvicorn>=0.14.0 by [@abidlabs](https://github.com/abidlabs) in [PR 4086](https://github.com/gradio-app/gradio/pull/4086)
+- Updates some error messaging by [@abidlabs](https://github.com/abidlabs) in [PR 4086](https://github.com/gradio-app/gradio/pull/4086)
+- Renames simplified Chinese translation file from `zh-cn.json` to `zh-CN.json` by [@abidlabs](https://github.com/abidlabs) in [PR 4086](https://github.com/gradio-app/gradio/pull/4086)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.28.3
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fixes issue with indentation in `gr.Code()` component with streaming by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4043](https://github.com/gradio-app/gradio/pull/4043)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.28.2
+
+### Bug Fixes
+
+- Code component visual updates by [@pngwn](https://github.com/pngwn) in [PR 4051](https://github.com/gradio-app/gradio/pull/4051)
+
+### New Features:
+
+- Add support for `visual-question-answering`, `document-question-answering`, and `image-to-text` using `gr.Interface.load("models/...")` and `gr.Interface.from_pipeline` by [@osanseviero](https://github.com/osanseviero) in [PR 3887](https://github.com/gradio-app/gradio/pull/3887)
+- Add code block support in `gr.Chatbot()`, by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 4048](https://github.com/gradio-app/gradio/pull/4048)
+- Adds the ability to blocklist filepaths (and also improves the allowlist mechanism) by [@abidlabs](https://github.com/abidlabs) in [PR 4047](https://github.com/gradio-app/gradio/pull/4047).
+- Adds the ability to specify the upload directory via an environment variable by [@abidlabs](https://github.com/abidlabs) in [PR 4047](https://github.com/gradio-app/gradio/pull/4047).
+
+### Bug Fixes:
+
+- Fixes issue with `matplotlib` not rendering correctly if the backend was not set to `Agg` by [@abidlabs](https://github.com/abidlabs) in [PR 4029](https://github.com/gradio-app/gradio/pull/4029)
+- Fixes bug where rendering the same `gr.State` across different Interfaces/Blocks within larger Blocks would not work by [@abidlabs](https://github.com/abidlabs) in [PR 4030](https://github.com/gradio-app/gradio/pull/4030)
+- Code component visual updates by [@pngwn](https://github.com/pngwn) in [PR 4051](https://github.com/gradio-app/gradio/pull/4051)
+
+### Documentation Changes:
+
+- Adds a Guide on how to use the Python Client within a FastAPI app, by [@abidlabs](https://github.com/abidlabs) in [PR 3892](https://github.com/gradio-app/gradio/pull/3892)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+- `gr.HuggingFaceDatasetSaver` behavior changed internally. The `flagging/` folder is not a `.git/` folder anymore when using it. `organization` parameter is now ignored in favor of passing a full dataset id as `dataset_name` (e.g. `"username/my-dataset"`).
+- New lines (`\n`) are not automatically converted to `
` in `gr.Markdown()` or `gr.Chatbot()`. For multiple new lines, a developer must add multiple `
` tags.
+
+### Full Changelog:
+
+- Safer version of `gr.HuggingFaceDatasetSaver` using HTTP methods instead of git pull/push by [@Wauplin](https://github.com/Wauplin) in [PR 3973](https://github.com/gradio-app/gradio/pull/3973)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.28.1
+
+### New Features:
+
+- Add a "clear mask" button to `gr.Image` sketch modes, by [@space-nuko](https://github.com/space-nuko) in [PR 3615](https://github.com/gradio-app/gradio/pull/3615)
+
+### Bug Fixes:
+
+- Fix dropdown default value not appearing by [@aliabid94](https://github.com/aliabid94) in [PR 3996](https://github.com/gradio-app/gradio/pull/3996).
+- Fix faded coloring of output textboxes in iOS / Safari by [@aliabid94](https://github.com/aliabid94) in [PR 3993](https://github.com/gradio-app/gradio/pull/3993)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+- CI: Simplified Python CI workflow by [@akx](https://github.com/akx) in [PR 3982](https://github.com/gradio-app/gradio/pull/3982)
+- Upgrade pyright to 1.1.305 by [@akx](https://github.com/akx) in [PR 4042](https://github.com/gradio-app/gradio/pull/4042)
+- More Ruff rules are enabled and lint errors fixed by [@akx](https://github.com/akx) in [PR 4038](https://github.com/gradio-app/gradio/pull/4038)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.28.0
+
+### Bug Fixes:
+
+- Fix duplicate play commands in full-screen mode of 'video'. by [@tomchang25](https://github.com/tomchang25) in [PR 3968](https://github.com/gradio-app/gradio/pull/3968).
+- Fix the issue of the UI stuck caused by the 'selected' of DataFrame not being reset. by [@tomchang25](https://github.com/tomchang25) in [PR 3916](https://github.com/gradio-app/gradio/pull/3916).
+- Fix issue where `gr.Video()` would not work inside a `gr.Tab()` by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3891](https://github.com/gradio-app/gradio/pull/3891)
+- Fixed issue with old_value check in File. by [@tomchang25](https://github.com/tomchang25) in [PR 3859](https://github.com/gradio-app/gradio/pull/3859).
+- Fixed bug where all bokeh plots appeared in the same div by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3896](https://github.com/gradio-app/gradio/pull/3896)
+- Fixed image outputs to automatically take full output image height, unless explicitly set, by [@aliabid94](https://github.com/aliabid94) in [PR 3905](https://github.com/gradio-app/gradio/pull/3905)
+- Fix issue in `gr.Gallery()` where setting height causes aspect ratio of images to collapse by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3830](https://github.com/gradio-app/gradio/pull/3830)
+- Fix issue where requesting for a non-existing file would trigger a 500 error by [@micky2be](https://github.com/micky2be) in `[PR 3895](https://github.com/gradio-app/gradio/pull/3895)`.
+- Fix bugs with abspath about symlinks, and unresolvable path on Windows by [@micky2be](https://github.com/micky2be) in `[PR 3895](https://github.com/gradio-app/gradio/pull/3895)`.
+- Fixes type in client `Status` enum by [@10zinten](https://github.com/10zinten) in [PR 3931](https://github.com/gradio-app/gradio/pull/3931)
+- Fix `gr.ChatBot` to handle image url [tye-singwa](https://github.com/tye-signwa) in [PR 3953](https://github.com/gradio-app/gradio/pull/3953)
+- Move Google Tag Manager related initialization code to analytics-enabled block by [@akx](https://github.com/akx) in [PR 3956](https://github.com/gradio-app/gradio/pull/3956)
+- Fix bug where port was not reused if the demo was closed and then re-launched by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3896](https://github.com/gradio-app/gradio/pull/3959)
+- Fixes issue where dropdown does not position itself at selected element when opened [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3639](https://github.com/gradio-app/gradio/pull/3639)
+
+### Documentation Changes:
+
+- Make use of `gr` consistent across the docs by [@duerrsimon](https://github.com/duerrsimon) in [PR 3901](https://github.com/gradio-app/gradio/pull/3901)
+- Fixed typo in theming-guide.md by [@eltociear](https://github.com/eltociear) in [PR 3952](https://github.com/gradio-app/gradio/pull/3952)
+
+### Testing and Infrastructure Changes:
+
+- CI: Python backend lint is only run once, by [@akx](https://github.com/akx) in [PR 3960](https://github.com/gradio-app/gradio/pull/3960)
+- Format invocations and concatenations were replaced by f-strings where possible by [@akx](https://github.com/akx) in [PR 3984](https://github.com/gradio-app/gradio/pull/3984)
+- Linting rules were made more strict and issues fixed by [@akx](https://github.com/akx) in [PR 3979](https://github.com/gradio-app/gradio/pull/3979).
+
+### Breaking Changes:
+
+- Some re-exports in `gradio.themes` utilities (introduced in 3.24.0) have been eradicated.
+ By [@akx](https://github.com/akx) in [PR 3958](https://github.com/gradio-app/gradio/pull/3958)
+
+### Full Changelog:
+
+- Add DESCRIPTION.md to image_segmentation demo by [@aliabd](https://github.com/aliabd) in [PR 3866](https://github.com/gradio-app/gradio/pull/3866)
+- Fix error in running `gr.themes.builder()` by [@deepkyu](https://github.com/deepkyu) in [PR 3869](https://github.com/gradio-app/gradio/pull/3869)
+- Fixed a JavaScript TypeError when loading custom JS with `_js` and setting `outputs` to `None` in `gradio.Blocks()` by [@DavG25](https://github.com/DavG25) in [PR 3883](https://github.com/gradio-app/gradio/pull/3883)
+- Fixed bg_background_fill theme property to expand to whole background, block_radius to affect form elements as well, and added block_label_shadow theme property by [@aliabid94](https://github.com/aliabid94) in [PR 3590](https://github.com/gradio-app/gradio/pull/3590)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.27.0
+
+### New Features:
+
+###### AnnotatedImage Component
+
+New AnnotatedImage component allows users to highlight regions of an image, either by providing bounding boxes, or 0-1 pixel masks. This component is useful for tasks such as image segmentation, object detection, and image captioning.
+
+
+
+Example usage:
+
+```python
+with gr.Blocks() as demo:
+ img = gr.Image()
+ img_section = gr.AnnotatedImage()
+ def mask(img):
+ top_left_corner = [0, 0, 20, 20]
+ random_mask = np.random.randint(0, 2, img.shape[:2])
+ return (img, [(top_left_corner, "left corner"), (random_mask, "random")])
+ img.change(mask, img, img_section)
+```
+
+See the [image_segmentation demo](https://github.com/gradio-app/gradio/tree/main/demo/image_segmentation) for a full example. By [@aliabid94](https://github.com/aliabid94) in [PR 3836](https://github.com/gradio-app/gradio/pull/3836)
+
+### Bug Fixes:
+
+No changes to highlight.
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.26.0
+
+### New Features:
+
+###### `Video` component supports subtitles
+
+- Allow the video component to accept subtitles as input, by [@tomchang25](https://github.com/tomchang25) in [PR 3673](https://github.com/gradio-app/gradio/pull/3673). To provide subtitles, simply return a tuple consisting of `(path_to_video, path_to_subtitles)` from your function. Both `.srt` and `.vtt` formats are supported:
+
+```py
+with gr.Blocks() as demo:
+ gr.Video(("video.mp4", "captions.srt"))
+```
+
+### Bug Fixes:
+
+- Fix code markdown support in `gr.Chatbot()` component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3816](https://github.com/gradio-app/gradio/pull/3816)
+
+### Documentation Changes:
+
+- Updates the "view API" page in Gradio apps to use the `gradio_client` library by [@aliabd](https://github.com/aliabd) in [PR 3765](https://github.com/gradio-app/gradio/pull/3765)
+
+- Read more about how to use the `gradio_client` library here: https://gradio.app/getting-started-with-the-python-client/
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.25.0
+
+### New Features:
+
+- Improve error messages when number of inputs/outputs to event handlers mismatch, by [@space-nuko](https://github.com/space-nuko) in [PR 3519](https://github.com/gradio-app/gradio/pull/3519)
+
+- Add `select` listener to Images, allowing users to click on any part of an image and get the coordinates of the click by [@aliabid94](https://github.com/aliabid94) in [PR 3786](https://github.com/gradio-app/gradio/pull/3786).
+
+```python
+with gr.Blocks() as demo:
+ img = gr.Image()
+ textbox = gr.Textbox()
+
+ def select_handler(img, evt: gr.SelectData):
+ selected_pixel = img[evt.index[1], evt.index[0]]
+ return f"Selected pixel: {selected_pixel}"
+
+ img.select(select_handler, img, textbox)
+```
+
+
+
+### Bug Fixes:
+
+- Increase timeout for sending analytics data by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3647](https://github.com/gradio-app/gradio/pull/3647)
+- Fix bug where http token was not accessed over websocket connections by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3735](https://github.com/gradio-app/gradio/pull/3735)
+- Add ability to specify `rows`, `columns` and `object-fit` in `style()` for `gr.Gallery()` component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3586](https://github.com/gradio-app/gradio/pull/3586)
+- Fix bug where recording an audio file through the microphone resulted in a corrupted file name by [@abidlabs](https://github.com/abidlabs) in [PR 3770](https://github.com/gradio-app/gradio/pull/3770)
+- Added "ssl_verify" to blocks.launch method to allow for use of self-signed certs by [@garrettsutula](https://github.com/garrettsutula) in [PR 3873](https://github.com/gradio-app/gradio/pull/3873)
+- Fix bug where iterators where not being reset for processes that terminated early by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3777](https://github.com/gradio-app/gradio/pull/3777)
+- Fix bug where the upload button was not properly handling the `file_count='multiple'` case by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3782](https://github.com/gradio-app/gradio/pull/3782)
+- Fix bug where use Via API button was giving error by [@Devang-C](https://github.com/Devang-C) in [PR 3783](https://github.com/gradio-app/gradio/pull/3783)
+
+### Documentation Changes:
+
+- Fix invalid argument docstrings, by [@akx](https://github.com/akx) in [PR 3740](https://github.com/gradio-app/gradio/pull/3740)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fixed IPv6 listening to work with bracket [::1] notation, by [@dsully](https://github.com/dsully) in [PR 3695](https://github.com/gradio-app/gradio/pull/3695)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.24.1
+
+### New Features:
+
+- No changes to highlight.
+
+### Bug Fixes:
+
+- Fixes Chatbot issue where new lines were being created every time a message was sent back and forth by [@aliabid94](https://github.com/aliabid94) in [PR 3717](https://github.com/gradio-app/gradio/pull/3717).
+- Fixes data updating in DataFrame invoking a `select` event once the dataframe has been selected. By [@yiyuezhuo](https://github.com/yiyuezhuo) in [PR 3861](https://github.com/gradio-app/gradio/pull/3861)
+- Fixes false positive warning which is due to too strict type checking by [@yiyuezhuo](https://github.com/yiyuezhuo) in [PR 3837](https://github.com/gradio-app/gradio/pull/3837).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.24.0
+
+### New Features:
+
+- Trigger the release event when Slider number input is released or unfocused by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3589](https://github.com/gradio-app/gradio/pull/3589)
+- Created Theme Builder, which allows users to create themes without writing any code, by [@aliabid94](https://github.com/aliabid94) in [PR 3664](https://github.com/gradio-app/gradio/pull/3664). Launch by:
+
+ ```python
+ import gradio as gr
+ gr.themes.builder()
+ ```
+
+ 
+
+- The `Dropdown` component now has a `allow_custom_value` parameter that lets users type in custom values not in the original list of choices.
+- The `Colorpicker` component now has a `.blur()` event
+
+###### Added a download button for videos! 📥
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3581](https://github.com/gradio-app/gradio/pull/3581).
+
+- Trigger the release event when Slider number input is released or unfocused by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3589](https://github.com/gradio-app/gradio/pull/3589)
+
+### Bug Fixes:
+
+- Fixed bug where text for altair plots was not legible in dark mode by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3555](https://github.com/gradio-app/gradio/pull/3555)
+- Fixes `Chatbot` and `Image` components so that files passed during processing are added to a directory where they can be served from, by [@abidlabs](https://github.com/abidlabs) in [PR 3523](https://github.com/gradio-app/gradio/pull/3523)
+- Use Gradio API server to send telemetry using `huggingface_hub` [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3488](https://github.com/gradio-app/gradio/pull/3488)
+- Fixes an an issue where if the Blocks scope was not exited, then State could be shared across sessions, by [@abidlabs](https://github.com/abidlabs) in [PR 3600](https://github.com/gradio-app/gradio/pull/3600)
+- Ensures that `gr.load()` loads and applies the upstream theme, by [@abidlabs](https://github.com/abidlabs) in [PR 3641](https://github.com/gradio-app/gradio/pull/3641)
+- Fixed bug where "or" was not being localized in file upload text by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3599](https://github.com/gradio-app/gradio/pull/3599)
+- Fixed bug where chatbot does not autoscroll inside of a tab, row or column by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3637](https://github.com/gradio-app/gradio/pull/3637)
+- Fixed bug where textbox shrinks when `lines` set to larger than 20 by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3637](https://github.com/gradio-app/gradio/pull/3637)
+- Ensure CSS has fully loaded before rendering the application, by [@pngwn](https://github.com/pngwn) in [PR 3573](https://github.com/gradio-app/gradio/pull/3573)
+- Support using an empty list as `gr.Dataframe` value, by [@space-nuko](https://github.com/space-nuko) in [PR 3646](https://github.com/gradio-app/gradio/pull/3646)
+- Fixed `gr.Image` not filling the entire element size, by [@space-nuko](https://github.com/space-nuko) in [PR 3649](https://github.com/gradio-app/gradio/pull/3649)
+- Make `gr.Code` support the `lines` property, by [@space-nuko](https://github.com/space-nuko) in [PR 3651](https://github.com/gradio-app/gradio/pull/3651)
+- Fixes certain `_js` return values being double wrapped in an array, by [@space-nuko](https://github.com/space-nuko) in [PR 3594](https://github.com/gradio-app/gradio/pull/3594)
+- Correct the documentation of `gr.File` component to state that its preprocessing method converts the uploaded file to a temporary file, by @RussellLuo in [PR 3660](https://github.com/gradio-app/gradio/pull/3660)
+- Fixed bug in Serializer ValueError text by [@osanseviero](https://github.com/osanseviero) in [PR 3669](https://github.com/gradio-app/gradio/pull/3669)
+- Fix default parameter argument and `gr.Progress` used in same function, by [@space-nuko](https://github.com/space-nuko) in [PR 3671](https://github.com/gradio-app/gradio/pull/3671)
+- Hide `Remove All` button in `gr.Dropdown` single-select mode by [@space-nuko](https://github.com/space-nuko) in [PR 3678](https://github.com/gradio-app/gradio/pull/3678)
+- Fix broken spaces in docs by [@aliabd](https://github.com/aliabd) in [PR 3698](https://github.com/gradio-app/gradio/pull/3698)
+- Fix items in `gr.Dropdown` besides the selected item receiving a checkmark, by [@space-nuko](https://github.com/space-nuko) in [PR 3644](https://github.com/gradio-app/gradio/pull/3644)
+- Fix several `gr.Dropdown` issues and improve usability, by [@space-nuko](https://github.com/space-nuko) in [PR 3705](https://github.com/gradio-app/gradio/pull/3705)
+
+### Documentation Changes:
+
+- Makes some fixes to the Theme Guide related to naming of variables, by [@abidlabs](https://github.com/abidlabs) in [PR 3561](https://github.com/gradio-app/gradio/pull/3561)
+- Documented `HuggingFaceDatasetJSONSaver` by [@osanseviero](https://github.com/osanseviero) in [PR 3604](https://github.com/gradio-app/gradio/pull/3604)
+- Makes some additions to documentation of `Audio` and `State` components, and fixes the `pictionary` demo by [@abidlabs](https://github.com/abidlabs) in [PR 3611](https://github.com/gradio-app/gradio/pull/3611)
+- Fix outdated sharing your app guide by [@aliabd](https://github.com/aliabd) in [PR 3699](https://github.com/gradio-app/gradio/pull/3699)
+
+### Testing and Infrastructure Changes:
+
+- Removed heavily-mocked tests related to comet_ml, wandb, and mlflow as they added a significant amount of test dependencies that prevented installation of test dependencies on Windows environments. By [@abidlabs](https://github.com/abidlabs) in [PR 3608](https://github.com/gradio-app/gradio/pull/3608)
+- Added Windows continuous integration, by [@space-nuko](https://github.com/space-nuko) in [PR 3628](https://github.com/gradio-app/gradio/pull/3628)
+- Switched linting from flake8 + isort to `ruff`, by [@akx](https://github.com/akx) in [PR 3710](https://github.com/gradio-app/gradio/pull/3710)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Mobile responsive iframes in themes guide by [@aliabd](https://github.com/aliabd) in [PR 3562](https://github.com/gradio-app/gradio/pull/3562)
+- Remove extra $demo from theme guide by [@aliabd](https://github.com/aliabd) in [PR 3563](https://github.com/gradio-app/gradio/pull/3563)
+- Set the theme name to be the upstream repo name when loading from the hub by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3595](https://github.com/gradio-app/gradio/pull/3595)
+- Copy everything in website Dockerfile, fix build issues by [@aliabd](https://github.com/aliabd) in [PR 3659](https://github.com/gradio-app/gradio/pull/3659)
+- Raise error when an event is queued but the queue is not configured by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3640](https://github.com/gradio-app/gradio/pull/3640)
+- Allows users to apss in a string name for a built-in theme, by [@abidlabs](https://github.com/abidlabs) in [PR 3641](https://github.com/gradio-app/gradio/pull/3641)
+- Added `orig_name` to Video output in the backend so that the front end can set the right name for downloaded video files by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3700](https://github.com/gradio-app/gradio/pull/3700)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.23.0
+
+### New Features:
+
+###### Theme Sharing!
+
+Once you have created a theme, you can upload it to the HuggingFace Hub to let others view it, use it, and build off of it! You can also download, reuse, and remix other peoples' themes. See https://gradio.app/theming-guide/ for more details.
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3428](https://github.com/gradio-app/gradio/pull/3428)
+
+### Bug Fixes:
+
+- Removes leading spaces from all lines of code uniformly in the `gr.Code()` component. By [@abidlabs](https://github.com/abidlabs) in [PR 3556](https://github.com/gradio-app/gradio/pull/3556)
+- Fixed broken login page, by [@aliabid94](https://github.com/aliabid94) in [PR 3529](https://github.com/gradio-app/gradio/pull/3529)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fix rendering of dropdowns to take more space, and related bugs, by [@aliabid94](https://github.com/aliabid94) in [PR 3549](https://github.com/gradio-app/gradio/pull/3549)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.22.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Restore label bars by [@aliabid94](https://github.com/aliabid94) in [PR 3507](https://github.com/gradio-app/gradio/pull/3507)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.22.0
+
+### New Features:
+
+###### Official Theme release
+
+Gradio now supports a new theme system, which allows you to customize the look and feel of your app. You can now use the `theme=` kwarg to pass in a prebuilt theme, or customize your own! See https://gradio.app/theming-guide/ for more details. By [@aliabid94](https://github.com/aliabid94) in [PR 3470](https://github.com/gradio-app/gradio/pull/3470) and [PR 3497](https://github.com/gradio-app/gradio/pull/3497)
+
+###### `elem_classes`
+
+Add keyword argument `elem_classes` to Components to control class names of components, in the same manner as existing `elem_id`.
+By [@aliabid94](https://github.com/aliabid94) in [PR 3466](https://github.com/gradio-app/gradio/pull/3466)
+
+### Bug Fixes:
+
+- Fixes the File.upload() event trigger which broke as part of the change in how we uploaded files by [@abidlabs](https://github.com/abidlabs) in [PR 3462](https://github.com/gradio-app/gradio/pull/3462)
+- Fixed issue with `gr.Request` object failing to handle dictionaries when nested keys couldn't be converted to variable names [#3454](https://github.com/gradio-app/gradio/issues/3454) by [@radames](https://github.com/radames) in [PR 3459](https://github.com/gradio-app/gradio/pull/3459)
+- Fixed bug where css and client api was not working properly when mounted in a subpath by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3482](https://github.com/gradio-app/gradio/pull/3482)
+
+### Documentation Changes:
+
+- Document gr.Error in the docs by [@aliabd](https://github.com/aliabd) in [PR 3465](https://github.com/gradio-app/gradio/pull/3465)
+
+### Testing and Infrastructure Changes:
+
+- Pinned `pyright==1.1.298` for stability by [@abidlabs](https://github.com/abidlabs) in [PR 3475](https://github.com/gradio-app/gradio/pull/3475)
+- Removed `IOComponent.add_interactive_to_config()` by [@space-nuko](https://github.com/space-nuko) in [PR 3476](https://github.com/gradio-app/gradio/pull/3476)
+- Removed `IOComponent.generate_sample()` by [@space-nuko](https://github.com/space-nuko) in [PR 3475](https://github.com/gradio-app/gradio/pull/3483)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Revert primary button background color in dark mode by [@aliabid94](https://github.com/aliabid94) in [PR 3468](https://github.com/gradio-app/gradio/pull/3468)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.21.0
+
+### New Features:
+
+###### Theme Sharing 🎨 🤝
+
+You can now share your gradio themes with the world!
+
+After creating a theme, you can upload it to the HuggingFace Hub to let others view it, use it, and build off of it!
+
+###### Uploading
+
+There are two ways to upload a theme, via the theme class instance or the command line.
+
+1. Via the class instance
+
+```python
+my_theme.push_to_hub(repo_name="my_theme",
+ version="0.2.0",
+ hf_token="...")
+```
+
+2. Via the command line
+
+First save the theme to disk
+
+```python
+my_theme.dump(filename="my_theme.json")
+```
+
+Then use the `upload_theme` command:
+
+```bash
+upload_theme\
+"my_theme.json"\
+"my_theme"\
+"0.2.0"\
+""
+```
+
+The `version` must be a valid [semantic version](https://www.geeksforgeeks.org/introduction-semantic-versioning/) string.
+
+This creates a space on the huggingface hub to host the theme files and show potential users a preview of your theme.
+
+An example theme space is here: https://huggingface.co/spaces/freddyaboulton/dracula_revamped
+
+###### Downloading
+
+To use a theme from the hub, use the `from_hub` method on the `ThemeClass` and pass it to your app:
+
+```python
+my_theme = gr.Theme.from_hub("freddyaboulton/my_theme")
+
+with gr.Blocks(theme=my_theme) as demo:
+ ....
+```
+
+You can also pass the theme string directly to `Blocks` or `Interface` (`gr.Blocks(theme="freddyaboulton/my_theme")`)
+
+You can pin your app to an upstream theme version by using semantic versioning expressions.
+
+For example, the following would ensure the theme we load from the `my_theme` repo was between versions `0.1.0` and `0.2.0`:
+
+```python
+with gr.Blocks(theme="freddyaboulton/my_theme@>=0.1.0,<0.2.0") as demo:
+ ....
+```
+
+by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3428](https://github.com/gradio-app/gradio/pull/3428)
+
+###### Code component 🦾
+
+New code component allows you to enter, edit and display code with full syntax highlighting by [@pngwn](https://github.com/pngwn) in [PR 3421](https://github.com/gradio-app/gradio/pull/3421)
+
+###### The `Chatbot` component now supports audio, video, and images
+
+The `Chatbot` component now supports audio, video, and images with a simple syntax: simply
+pass in a tuple with the URL or filepath (the second optional element of the tuple is alt text), and the image/audio/video will be displayed:
+
+```python
+gr.Chatbot([
+ (("driving.mp4",), "cool video"),
+ (("cantina.wav",), "cool audio"),
+ (("lion.jpg", "A lion"), "cool pic"),
+]).style(height=800)
+```
+
+
+
+Note: images were previously supported via Markdown syntax and that is still supported for backwards compatibility. By [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3413](https://github.com/gradio-app/gradio/pull/3413)
+
+- Allow consecutive function triggers with `.then` and `.success` by [@aliabid94](https://github.com/aliabid94) in [PR 3430](https://github.com/gradio-app/gradio/pull/3430)
+
+- New code component allows you to enter, edit and display code with full syntax highlighting by [@pngwn](https://github.com/pngwn) in [PR 3421](https://github.com/gradio-app/gradio/pull/3421)
+
+
+
+- Added the `.select()` event listener, which also includes event data that can be passed as an argument to a function with type hint `gr.SelectData`. The following components support the `.select()` event listener: Chatbot, CheckboxGroup, Dataframe, Dropdown, File, Gallery, HighlightedText, Label, Radio, TabItem, Tab, Textbox. Example usage:
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ gallery = gr.Gallery(["images/1.jpg", "images/2.jpg", "images/3.jpg"])
+ selected_index = gr.Textbox()
+
+ def on_select(evt: gr.SelectData):
+ return evt.index
+
+ gallery.select(on_select, None, selected_index)
+```
+
+By [@aliabid94](https://github.com/aliabid94) in [PR 3399](https://github.com/gradio-app/gradio/pull/3399)
+
+- The `Textbox` component now includes a copy button by [@abidlabs](https://github.com/abidlabs) in [PR 3452](https://github.com/gradio-app/gradio/pull/3452)
+
+### Bug Fixes:
+
+- Use `huggingface_hub` to send telemetry on `interface` and `blocks`; eventually to replace segment by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3342](https://github.com/gradio-app/gradio/pull/3342)
+- Ensure load events created by components (randomize for slider, callable values) are never queued unless every is passed by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3391](https://github.com/gradio-app/gradio/pull/3391)
+- Prevent in-place updates of `generic_update` by shallow copying by [@gitgithan](https://github.com/gitgithan) in [PR 3405](https://github.com/gradio-app/gradio/pull/3405) to fix [#3282](https://github.com/gradio-app/gradio/issues/3282)
+- Fix bug caused by not importing `BlockContext` in `utils.py` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3424](https://github.com/gradio-app/gradio/pull/3424)
+- Ensure dropdown does not highlight partial matches by [@pngwn](https://github.com/pngwn) in [PR 3421](https://github.com/gradio-app/gradio/pull/3421)
+- Fix mic button display by [@aliabid94](https://github.com/aliabid94) in [PR 3456](https://github.com/gradio-app/gradio/pull/3456)
+
+### Documentation Changes:
+
+- Added a section on security and access when sharing Gradio apps by [@abidlabs](https://github.com/abidlabs) in [PR 3408](https://github.com/gradio-app/gradio/pull/3408)
+- Add Chinese README by [@uanu2002](https://github.com/uanu2002) in [PR 3394](https://github.com/gradio-app/gradio/pull/3394)
+- Adds documentation for web components by [@abidlabs](https://github.com/abidlabs) in [PR 3407](https://github.com/gradio-app/gradio/pull/3407)
+- Fixed link in Chinese readme by [@eltociear](https://github.com/eltociear) in [PR 3417](https://github.com/gradio-app/gradio/pull/3417)
+- Document Blocks methods by [@aliabd](https://github.com/aliabd) in [PR 3427](https://github.com/gradio-app/gradio/pull/3427)
+- Fixed bug where event handlers were not showing up in documentation by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3434](https://github.com/gradio-app/gradio/pull/3434)
+
+### Testing and Infrastructure Changes:
+
+- Fixes tests that were failing locally but passing on CI by [@abidlabs](https://github.com/abidlabs) in [PR 3411](https://github.com/gradio-app/gradio/pull/3411)
+- Remove codecov from the repo by [@aliabd](https://github.com/aliabd) in [PR 3415](https://github.com/gradio-app/gradio/pull/3415)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Prevent in-place updates of `generic_update` by shallow copying by [@gitgithan](https://github.com/gitgithan) in [PR 3405](https://github.com/gradio-app/gradio/pull/3405) to fix [#3282](https://github.com/gradio-app/gradio/issues/3282)
+- Persist file names of files uploaded through any Gradio component by [@abidlabs](https://github.com/abidlabs) in [PR 3412](https://github.com/gradio-app/gradio/pull/3412)
+- Fix markdown embedded component in docs by [@aliabd](https://github.com/aliabd) in [PR 3410](https://github.com/gradio-app/gradio/pull/3410)
+- Clean up event listeners code by [@aliabid94](https://github.com/aliabid94) in [PR 3420](https://github.com/gradio-app/gradio/pull/3420)
+- Fix css issue with spaces logo by [@aliabd](https://github.com/aliabd) in [PR 3422](https://github.com/gradio-app/gradio/pull/3422)
+- Makes a few fixes to the `JSON` component (show_label parameter, icons) in [@abidlabs](https://github.com/abidlabs) in [PR 3451](https://github.com/gradio-app/gradio/pull/3451)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.20.1
+
+### New Features:
+
+- Add `height` kwarg to style in `gr.Chatbot()` component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3369](https://github.com/gradio-app/gradio/pull/3369)
+
+```python
+chatbot = gr.Chatbot().style(height=500)
+```
+
+### Bug Fixes:
+
+- Ensure uploaded images are always shown in the sketch tool by [@pngwn](https://github.com/pngwn) in [PR 3386](https://github.com/gradio-app/gradio/pull/3386)
+- Fixes bug where when if fn is a non-static class member, then self should be ignored as the first param of the fn by [@or25](https://github.com/or25) in [PR #3227](https://github.com/gradio-app/gradio/pull/3227)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.20.0
+
+### New Features:
+
+###### Release event for Slider
+
+Now you can trigger your python function to run when the slider is released as opposed to every slider change value!
+
+Simply use the `release` method on the slider
+
+```python
+slider.release(function, inputs=[...], outputs=[...], api_name="predict")
+```
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3353](https://github.com/gradio-app/gradio/pull/3353)
+
+###### Dropdown Component Updates
+
+The standard dropdown component now supports searching for choices. Also when `multiselect` is `True`, you can specify `max_choices` to set the maximum number of choices you want the user to be able to select from the dropdown component.
+
+```python
+gr.Dropdown(label="Choose your favorite colors", choices=["red", "blue", "green", "yellow", "orange"], multiselect=True, max_choices=2)
+```
+
+by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3211](https://github.com/gradio-app/gradio/pull/3211)
+
+###### Download button for images 🖼️
+
+Output images will now automatically have a download button displayed to make it easier to save and share
+the results of Machine Learning art models.
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3297](https://github.com/gradio-app/gradio/pull/3297)
+
+- Updated image upload component to accept all image formats, including lossless formats like .webp by [@fienestar](https://github.com/fienestar) in [PR 3225](https://github.com/gradio-app/gradio/pull/3225)
+- Adds a disabled mode to the `gr.Button` component by setting `interactive=False` by [@abidlabs](https://github.com/abidlabs) in [PR 3266](https://github.com/gradio-app/gradio/pull/3266) and [PR 3288](https://github.com/gradio-app/gradio/pull/3288)
+- Adds visual feedback to the when the Flag button is clicked, by [@abidlabs](https://github.com/abidlabs) in [PR 3289](https://github.com/gradio-app/gradio/pull/3289)
+- Adds ability to set `flagging_options` display text and saved flag separately by [@abidlabs](https://github.com/abidlabs) in [PR 3289](https://github.com/gradio-app/gradio/pull/3289)
+- Allow the setting of `brush_radius` for the `Image` component both as a default and via `Image.update()` by [@pngwn](https://github.com/pngwn) in [PR 3277](https://github.com/gradio-app/gradio/pull/3277)
+- Added `info=` argument to form components to enable extra context provided to users, by [@aliabid94](https://github.com/aliabid94) in [PR 3291](https://github.com/gradio-app/gradio/pull/3291)
+- Allow developers to access the username of a logged-in user from the `gr.Request()` object using the `.username` attribute by [@abidlabs](https://github.com/abidlabs) in [PR 3296](https://github.com/gradio-app/gradio/pull/3296)
+- Add `preview` option to `Gallery.style` that launches the gallery in preview mode when first loaded by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3345](https://github.com/gradio-app/gradio/pull/3345)
+
+### Bug Fixes:
+
+- Ensure `mirror_webcam` is always respected by [@pngwn](https://github.com/pngwn) in [PR 3245](https://github.com/gradio-app/gradio/pull/3245)
+- Fix issue where updated markdown links were not being opened in a new tab by [@gante](https://github.com/gante) in [PR 3236](https://github.com/gradio-app/gradio/pull/3236)
+- API Docs Fixes by [@aliabd](https://github.com/aliabd) in [PR 3287](https://github.com/gradio-app/gradio/pull/3287)
+- Added a timeout to queue messages as some demos were experiencing infinitely growing queues from active jobs waiting forever for clients to respond by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3196](https://github.com/gradio-app/gradio/pull/3196)
+- Fixes the height of rendered LaTeX images so that they match the height of surrounding text by [@abidlabs](https://github.com/abidlabs) in [PR 3258](https://github.com/gradio-app/gradio/pull/3258) and in [PR 3276](https://github.com/gradio-app/gradio/pull/3276)
+- Fix bug where matplotlib images where always too small on the front end by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3274](https://github.com/gradio-app/gradio/pull/3274)
+- Remove embed's `initial_height` when loading is complete so the embed finds its natural height once it is loaded [@pngwn](https://github.com/pngwn) in [PR 3292](https://github.com/gradio-app/gradio/pull/3292)
+- Prevent Sketch from crashing when a default image is provided by [@pngwn](https://github.com/pngwn) in [PR 3277](https://github.com/gradio-app/gradio/pull/3277)
+- Respect the `shape` argument on the front end when creating Image Sketches by [@pngwn](https://github.com/pngwn) in [PR 3277](https://github.com/gradio-app/gradio/pull/3277)
+- Fix infinite loop caused by setting `Dropdown's` value to be `[]` and adding a change event on the dropdown by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3295](https://github.com/gradio-app/gradio/pull/3295)
+- Fix change event listed twice in image docs by [@aliabd](https://github.com/aliabd) in [PR 3318](https://github.com/gradio-app/gradio/pull/3318)
+- Fix bug that cause UI to be vertically centered at all times by [@pngwn](https://github.com/pngwn) in [PR 3336](https://github.com/gradio-app/gradio/pull/3336)
+- Fix bug where `height` set in `Gallery.style` was not respected by the front-end by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3343](https://github.com/gradio-app/gradio/pull/3343)
+- Ensure markdown lists are rendered correctly by [@pngwn](https://github.com/pngwn) in [PR 3341](https://github.com/gradio-app/gradio/pull/3341)
+- Ensure that the initial empty value for `gr.Dropdown(Multiselect=True)` is an empty list and the initial value for `gr.Dropdown(Multiselect=False)` is an empty string by [@pngwn](https://github.com/pngwn) in [PR 3338](https://github.com/gradio-app/gradio/pull/3338)
+- Ensure uploaded images respect the shape property when the canvas is also enabled by [@pngwn](https://github.com/pngwn) in [PR 3351](https://github.com/gradio-app/gradio/pull/3351)
+- Ensure that Google Analytics works correctly when gradio apps are created with `analytics_enabled=True` by [@abidlabs](https://github.com/abidlabs) in [PR 3349](https://github.com/gradio-app/gradio/pull/3349)
+- Fix bug where files were being re-uploaded after updates by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3375](https://github.com/gradio-app/gradio/pull/3375)
+- Fix error when using backen_fn and custom js at the same time by [@jialeicui](https://github.com/jialeicui) in [PR 3358](https://github.com/gradio-app/gradio/pull/3358)
+- Support new embeds for huggingface spaces subdomains by [@pngwn](https://github.com/pngwn) in [PR 3367](https://github.com/gradio-app/gradio/pull/3367)
+
+### Documentation Changes:
+
+- Added the `types` field to the dependency field in the config by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3315](https://github.com/gradio-app/gradio/pull/3315)
+- Gradio Status Page by [@aliabd](https://github.com/aliabd) in [PR 3331](https://github.com/gradio-app/gradio/pull/3331)
+- Adds a Guide on setting up a dashboard from Supabase data using the `gr.BarPlot`
+ component by [@abidlabs](https://github.com/abidlabs) in [PR 3275](https://github.com/gradio-app/gradio/pull/3275)
+
+### Testing and Infrastructure Changes:
+
+- Adds a script to benchmark the performance of the queue and adds some instructions on how to use it. By [@freddyaboulton](https://github.com/freddyaboulton) and [@abidlabs](https://github.com/abidlabs) in [PR 3272](https://github.com/gradio-app/gradio/pull/3272)
+- Flaky python tests no longer cancel non-flaky tests by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3344](https://github.com/gradio-app/gradio/pull/3344)
+
+### Breaking Changes:
+
+- Chatbot bubble colors can no longer be set by `chatbot.style(color_map=)` by [@aliabid94] in [PR 3370](https://github.com/gradio-app/gradio/pull/3370)
+
+### Full Changelog:
+
+- Fixed comment typo in components.py by [@eltociear](https://github.com/eltociear) in [PR 3235](https://github.com/gradio-app/gradio/pull/3235)
+- Cleaned up chatbot ui look and feel by [@aliabid94] in [PR 3370](https://github.com/gradio-app/gradio/pull/3370)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.19.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- UI fixes including footer and API docs by [@aliabid94](https://github.com/aliabid94) in [PR 3242](https://github.com/gradio-app/gradio/pull/3242)
+- Updated image upload component to accept all image formats, including lossless formats like .webp by [@fienestar](https://github.com/fienestar) in [PR 3225](https://github.com/gradio-app/gradio/pull/3225)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Added backend support for themes by [@aliabid94](https://github.com/aliabid94) in [PR 2931](https://github.com/gradio-app/gradio/pull/2931)
+- Added support for button sizes "lg" (default) and "sm".
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.19.0
+
+### New Features:
+
+###### Improved embedding experience
+
+When embedding a spaces-hosted gradio app as a web component, you now get an improved UI linking back to the original space, better error handling and more intelligent load performance. No changes are required to your code to benefit from this enhanced experience; simply upgrade your gradio SDK to the latest version.
+
+
+
+This behaviour is configurable. You can disable the info panel at the bottom by passing `info="false"`. You can disable the container entirely by passing `container="false"`.
+
+Error statuses are reported in the UI with an easy way for end-users to report problems to the original space author via the community tab of that Hugginface space:
+
+
+
+By default, gradio apps are lazy loaded, vastly improving performance when there are several demos on the page. Metadata is loaded ahead of time, but the space will only be loaded and rendered when it is in view.
+
+This behaviour is configurable. You can pass `eager="true"` to load and render the space regardless of whether or not it is currently on the screen.
+
+by [@pngwn](https://github.com/pngwn) in [PR 3205](https://github.com/gradio-app/gradio/pull/3205)
+
+###### New `gr.BarPlot` component! 📊
+
+Create interactive bar plots from a high-level interface with `gr.BarPlot`.
+No need to remember matplotlib syntax anymore!
+
+Example usage:
+
+```python
+import gradio as gr
+import pandas as pd
+
+simple = pd.DataFrame({
+ 'a': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'],
+ 'b': [28, 55, 43, 91, 81, 53, 19, 87, 52]
+})
+
+with gr.Blocks() as demo:
+ gr.BarPlot(
+ simple,
+ x="a",
+ y="b",
+ title="Simple Bar Plot with made up data",
+ tooltip=['a', 'b'],
+ )
+
+demo.launch()
+```
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3157](https://github.com/gradio-app/gradio/pull/3157)
+
+###### Bokeh plots are back! 🌠
+
+Fixed a bug that prevented bokeh plots from being displayed on the front end and extended support for both 2.x and 3.x versions of bokeh!
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3212](https://github.com/gradio-app/gradio/pull/3212)
+
+### Bug Fixes:
+
+- Adds ability to add a single message from the bot or user side. Ex: specify `None` as the second value in the tuple, to add a single message in the chatbot from the "bot" side.
+
+```python
+gr.Chatbot([("Hi, I'm DialoGPT. Try asking me a question.", None)])
+```
+
+By [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3165](https://github.com/gradio-app/gradio/pull/3165)
+
+- Fixes `gr.utils.delete_none` to only remove props whose values are `None` from the config by [@abidlabs](https://github.com/abidlabs) in [PR 3188](https://github.com/gradio-app/gradio/pull/3188)
+- Fix bug where embedded demos were not loading files properly by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3177](https://github.com/gradio-app/gradio/pull/3177)
+- The `change` event is now triggered when users click the 'Clear All' button of the multiselect DropDown component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3195](https://github.com/gradio-app/gradio/pull/3195)
+- Stops File component from freezing when a large file is uploaded by [@aliabid94](https://github.com/aliabid94) in [PR 3191](https://github.com/gradio-app/gradio/pull/3191)
+- Support Chinese pinyin in Dataframe by [@aliabid94](https://github.com/aliabid94) in [PR 3206](https://github.com/gradio-app/gradio/pull/3206)
+- The `clear` event is now triggered when images are cleared by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3218](https://github.com/gradio-app/gradio/pull/3218)
+- Fix bug where auth cookies where not sent when connecting to an app via http by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3223](https://github.com/gradio-app/gradio/pull/3223)
+- Ensure latext CSS is always applied in light and dark mode by [@pngwn](https://github.com/pngwn) in [PR 3233](https://github.com/gradio-app/gradio/pull/3233)
+
+### Documentation Changes:
+
+- Sort components in docs by alphabetic order by [@aliabd](https://github.com/aliabd) in [PR 3152](https://github.com/gradio-app/gradio/pull/3152)
+- Changes to W&B guide by [@scottire](https://github.com/scottire) in [PR 3153](https://github.com/gradio-app/gradio/pull/3153)
+- Keep pnginfo metadata for gallery by [@wfng92](https://github.com/wfng92) in [PR 3150](https://github.com/gradio-app/gradio/pull/3150)
+- Add a section on how to run a Gradio app locally [@osanseviero](https://github.com/osanseviero) in [PR 3170](https://github.com/gradio-app/gradio/pull/3170)
+- Fixed typos in gradio events function documentation by [@vidalmaxime](https://github.com/vidalmaxime) in [PR 3168](https://github.com/gradio-app/gradio/pull/3168)
+- Added an example using Gradio's batch mode with the diffusers library by [@abidlabs](https://github.com/abidlabs) in [PR 3224](https://github.com/gradio-app/gradio/pull/3224)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fix demos page css and add close demos button by [@aliabd](https://github.com/aliabd) in [PR 3151](https://github.com/gradio-app/gradio/pull/3151)
+- Caches temp files from base64 input data by giving them a deterministic path based on the contents of data by [@abidlabs](https://github.com/abidlabs) in [PR 3197](https://github.com/gradio-app/gradio/pull/3197)
+- Better warnings (when there is a mismatch between the number of output components and values returned by a function, or when the `File` component or `UploadButton` component includes a `file_types` parameter along with `file_count=="dir"`) by [@abidlabs](https://github.com/abidlabs) in [PR 3194](https://github.com/gradio-app/gradio/pull/3194)
+- Raises a `gr.Error` instead of a regular Python error when you use `gr.Interface.load()` to load a model and there's an error querying the HF API by [@abidlabs](https://github.com/abidlabs) in [PR 3194](https://github.com/gradio-app/gradio/pull/3194)
+- Fixed gradio share links so that they are persistent and do not reset if network
+ connection is disrupted by by [XciD](https://github.com/XciD), [Wauplin](https://github.com/Wauplin), and [@abidlabs](https://github.com/abidlabs) in [PR 3149](https://github.com/gradio-app/gradio/pull/3149) and a follow-up to allow it to work for users upgrading from a previous Gradio version in [PR 3221](https://github.com/gradio-app/gradio/pull/3221)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.18.0
+
+### New Features:
+
+###### Revamped Stop Button for Interfaces 🛑
+
+If your Interface function is a generator, there used to be a separate `Stop` button displayed next
+to the `Submit` button.
+
+We've revamed the `Submit` button so that it turns into a `Stop` button during the generation process.
+Clicking on the `Stop` button will cancel the generation and turn it back to a `Submit` button.
+The `Stop` button will automatically turn back to a `Submit` button at the end of the generation if you don't use it!
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3124](https://github.com/gradio-app/gradio/pull/3124)
+
+###### Queue now works with reload mode!
+
+You can now call `queue` on your `demo` outside of the `if __name__ == "__main__"` block and
+run the script in reload mode with the `gradio` command.
+
+Any changes to the `app.py` file will be reflected in the webpage automatically and the queue will work
+properly!
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3089](https://github.com/gradio-app/gradio/pull/3089)
+
+###### Allow serving files from additional directories
+
+```python
+demo = gr.Interface(...)
+demo.launch(
+ file_directories=["/var/lib/demo/path/to/resources"]
+)
+```
+
+By [@maxaudron](https://github.com/maxaudron) in [PR 3075](https://github.com/gradio-app/gradio/pull/3075)
+
+### Bug Fixes:
+
+- Fixes URL resolution on Windows by [@abidlabs](https://github.com/abidlabs) in [PR 3108](https://github.com/gradio-app/gradio/pull/3108)
+- Example caching now works with components without a label attribute (e.g. `Column`) by [@abidlabs](https://github.com/abidlabs) in [PR 3123](https://github.com/gradio-app/gradio/pull/3123)
+- Ensure the Video component correctly resets the UI state when a new video source is loaded and reduce choppiness of UI by [@pngwn](https://github.com/abidlabs) in [PR 3117](https://github.com/gradio-app/gradio/pull/3117)
+- Fixes loading private Spaces by [@abidlabs](https://github.com/abidlabs) in [PR 3068](https://github.com/gradio-app/gradio/pull/3068)
+- Added a warning when attempting to launch an `Interface` via the `%%blocks` jupyter notebook magic command by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3126](https://github.com/gradio-app/gradio/pull/3126)
+- Fixes bug where interactive output image cannot be set when in edit mode by [@dawoodkhan82](https://github.com/@dawoodkhan82) in [PR 3135](https://github.com/gradio-app/gradio/pull/3135)
+- A share link will automatically be created when running on Sagemaker notebooks so that the front-end is properly displayed by [@abidlabs](https://github.com/abidlabs) in [PR 3137](https://github.com/gradio-app/gradio/pull/3137)
+- Fixes a few dropdown component issues; hide checkmark next to options as expected, and keyboard hover is visible by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3145]https://github.com/gradio-app/gradio/pull/3145)
+- Fixed bug where example pagination buttons were not visible in dark mode or displayed under the examples table. By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3144](https://github.com/gradio-app/gradio/pull/3144)
+- Fixed bug where the font color of axis labels and titles for native plots did not respond to dark mode preferences. By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3146](https://github.com/gradio-app/gradio/pull/3146)
+
+### Documentation Changes:
+
+- Added a guide on the 4 kinds of Gradio Interfaces by [@yvrjsharma](https://github.com/yvrjsharma) and [@abidlabs](https://github.com/abidlabs) in [PR 3003](https://github.com/gradio-app/gradio/pull/3003)
+- Explained that the parameters in `launch` will not be respected when using reload mode, e.g. `gradio` command by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3089](https://github.com/gradio-app/gradio/pull/3089)
+- Added a demo to show how to set up variable numbers of outputs in Gradio by [@abidlabs](https://github.com/abidlabs) in [PR 3127](https://github.com/gradio-app/gradio/pull/3127)
+- Updated docs to reflect that the `equal_height` parameter should be passed to the `.style()` method of `gr.Row()` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3125](https://github.com/gradio-app/gradio/pull/3125)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Changed URL of final image for `fake_diffusion` demos by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3120](https://github.com/gradio-app/gradio/pull/3120)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.17.1
+
+### New Features:
+
+###### iOS image rotation fixed 🔄
+
+Previously photos uploaded via iOS would be rotated after processing. This has been fixed by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3089](https://github.com/gradio-app/gradio/pull/3091)
+
+######### Before
+
+
+
+######### After
+
+
+
+###### Run on Kaggle kernels 🧪
+
+A share link will automatically be created when running on Kaggle kernels (notebooks) so that the front-end is properly displayed.
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3101](https://github.com/gradio-app/gradio/pull/3101)
+
+### Bug Fixes:
+
+- Fix bug where examples were not rendered correctly for demos created with Blocks api that had multiple input compinents by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3090](https://github.com/gradio-app/gradio/pull/3090)
+- Fix change event listener for JSON, HighlightedText, Chatbot by [@aliabid94](https://github.com/aliabid94) in [PR 3095](https://github.com/gradio-app/gradio/pull/3095)
+- Fixes bug where video and file change event not working [@tomchang25](https://github.com/tomchang25) in [PR 3098](https://github.com/gradio-app/gradio/pull/3098)
+- Fixes bug where static_video play and pause event not working [@tomchang25](https://github.com/tomchang25) in [PR 3098](https://github.com/gradio-app/gradio/pull/3098)
+- Fixed `Gallery.style(grid=...)` by by [@aliabd](https://github.com/aliabd) in [PR 3107](https://github.com/gradio-app/gradio/pull/3107)
+
+### Documentation Changes:
+
+- Update chatbot guide to include blocks demo and markdown support section by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3023](https://github.com/gradio-app/gradio/pull/3023)
+
+* Fix a broken link in the Quick Start guide, by [@cakiki](https://github.com/cakiki) in [PR 3109](https://github.com/gradio-app/gradio/pull/3109)
+* Better docs navigation on mobile by [@aliabd](https://github.com/aliabd) in [PR 3112](https://github.com/gradio-app/gradio/pull/3112)
+* Add a guide on using Gradio with [Comet](https://comet.com/), by [@DN6](https://github.com/DN6/) in [PR 3058](https://github.com/gradio-app/gradio/pull/3058)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Set minimum `markdown-it-py` version to `2.0.0` so that the dollar math plugin is compatible by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3102](https://github.com/gradio-app/gradio/pull/3102)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.17.0
+
+### New Features:
+
+###### Extended support for Interface.load! 🏗️
+
+You can now load `image-to-text` and `conversational` pipelines from the hub!
+
+###### Image-to-text Demo
+
+```python
+io = gr.Interface.load("models/nlpconnect/vit-gpt2-image-captioning",
+ api_key="")
+io.launch()
+```
+
+
+
+###### conversational Demo
+
+```python
+chatbot = gr.Interface.load("models/microsoft/DialoGPT-medium",
+ api_key="")
+chatbot.launch()
+```
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3011](https://github.com/gradio-app/gradio/pull/3011)
+
+###### Download Button added to Model3D Output Component 📥
+
+No need for an additional file output component to enable model3d file downloads anymore. We now added a download button to the model3d component itself.
+
+
+
+By [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3014](https://github.com/gradio-app/gradio/pull/3014)
+
+###### Fixing Auth on Spaces 🔑
+
+Authentication on spaces works now! Third party cookies must be enabled on your browser to be able
+to log in. Some browsers disable third party cookies by default (Safari, Chrome Incognito).
+
+
+
+### Bug Fixes:
+
+- Fixes bug where interpretation event was not configured correctly by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2993](https://github.com/gradio-app/gradio/pull/2993)
+- Fix relative import bug in reload mode by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2992](https://github.com/gradio-app/gradio/pull/2992)
+- Fixes bug where png files were not being recognized when uploading images by [@abidlabs](https://github.com/abidlabs) in [PR 3002](https://github.com/gradio-app/gradio/pull/3002)
+- Fixes bug where external Spaces could not be loaded and used as functions if they returned files by [@abidlabs](https://github.com/abidlabs) in [PR 3004](https://github.com/gradio-app/gradio/pull/3004)
+- Fix bug where file serialization output was not JSON serializable by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2999](https://github.com/gradio-app/gradio/pull/2999)
+- Fixes bug where png files were not being recognized when uploading images by [@abidlabs](https://github.com/abidlabs) in [PR 3002](https://github.com/gradio-app/gradio/pull/3002)
+- Fixes bug where temporary uploaded files were not being added to temp sets by [@abidlabs](https://github.com/abidlabs) in [PR 3005](https://github.com/gradio-app/gradio/pull/3005)
+- Fixes issue where markdown support in chatbot breaks older demos [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3006](https://github.com/gradio-app/gradio/pull/3006)
+- Fixes the `/file/` route that was broken in a recent change in [PR 3010](https://github.com/gradio-app/gradio/pull/3010)
+- Fix bug where the Image component could not serialize image urls by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2957](https://github.com/gradio-app/gradio/pull/2957)
+- Fix forwarding for guides after SEO renaming by [@aliabd](https://github.com/aliabd) in [PR 3017](https://github.com/gradio-app/gradio/pull/3017)
+- Switch all pages on the website to use latest stable gradio by [@aliabd](https://github.com/aliabd) in [PR 3016](https://github.com/gradio-app/gradio/pull/3016)
+- Fix bug related to deprecated parameters in `huggingface_hub` for the HuggingFaceDatasetSaver in [PR 3025](https://github.com/gradio-app/gradio/pull/3025)
+- Added better support for symlinks in the way absolute paths are resolved by [@abidlabs](https://github.com/abidlabs) in [PR 3037](https://github.com/gradio-app/gradio/pull/3037)
+- Fix several minor frontend bugs (loading animation, examples as gallery) frontend [@aliabid94](https://github.com/3026) in [PR 2961](https://github.com/gradio-app/gradio/pull/3026).
+- Fixes bug that the chatbot sample code does not work with certain input value by [@petrov826](https://github.com/petrov826) in [PR 3039](https://github.com/gradio-app/gradio/pull/3039).
+- Fix shadows for form element and ensure focus styles more visible in dark mode [@pngwn](https://github.com/pngwn) in [PR 3042](https://github.com/gradio-app/gradio/pull/3042).
+- Fixed bug where the Checkbox and Dropdown change events were not triggered in response to other component changes by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3045](https://github.com/gradio-app/gradio/pull/3045)
+- Fix bug where the queue was not properly restarted after launching a `closed` app by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3022](https://github.com/gradio-app/gradio/pull/3022)
+- Adding missing embedded components on docs by [@aliabd](https://github.com/aliabd) in [PR 3027](https://github.com/gradio-app/gradio/pull/3027)
+- Fixes bug where app would crash if the `file_types` parameter of `gr.File` or `gr.UploadButton` was not a list by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3048](https://github.com/gradio-app/gradio/pull/3048)
+- Ensure CSS mounts correctly regardless of how many Gradio instances are on the page [@pngwn](https://github.com/pngwn) in [PR 3059](https://github.com/gradio-app/gradio/pull/3059).
+- Fix bug where input component was not hidden in the frontend for `UploadButton` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3053](https://github.com/gradio-app/gradio/pull/3053)
+- Fixes issue where after clicking submit or undo, the sketch output wouldn't clear. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 3047](https://github.com/gradio-app/gradio/pull/3047)
+- Ensure spaces embedded via the web component always use the correct URLs for server requests and change ports for testing to avoid strange collisions when users are working with embedded apps locally by [@pngwn](https://github.com/pngwn) in [PR 3065](https://github.com/gradio-app/gradio/pull/3065)
+- Preserve selected image of Gallery through updated by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3061](https://github.com/gradio-app/gradio/pull/3061)
+- Fix bug where auth was not respected on HF spaces by [@freddyaboulton](https://github.com/freddyaboulton) and [@aliabid94](https://github.com/aliabid94) in [PR 3049](https://github.com/gradio-app/gradio/pull/3049)
+- Fixes bug where tabs selected attribute not working if manually change tab by [@tomchang25](https://github.com/tomchang25) in [3055](https://github.com/gradio-app/gradio/pull/3055)
+- Change chatbot to show dots on progress, and fix bug where chatbot would not stick to bottom in the case of images by [@aliabid94](https://github.com/aliabid94) in [PR 3067](https://github.com/gradio-app/gradio/pull/3079)
+
+### Documentation Changes:
+
+- SEO improvements to guides by[@aliabd](https://github.com/aliabd) in [PR 2915](https://github.com/gradio-app/gradio/pull/2915)
+- Use `gr.LinePlot` for the `blocks_kinematics` demo by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2998](https://github.com/gradio-app/gradio/pull/2998)
+- Updated the `interface_series_load` to include some inline markdown code by [@abidlabs](https://github.com/abidlabs) in [PR 3051](https://github.com/gradio-app/gradio/pull/3051)
+
+### Testing and Infrastructure Changes:
+
+- Adds a GitHub action to test if any large files (> 5MB) are present by [@abidlabs](https://github.com/abidlabs) in [PR 3013](https://github.com/gradio-app/gradio/pull/3013)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Rewrote frontend using CSS variables for themes by [@pngwn](https://github.com/pngwn) in [PR 2840](https://github.com/gradio-app/gradio/pull/2840)
+- Moved telemetry requests to run on background threads by [@abidlabs](https://github.com/abidlabs) in [PR 3054](https://github.com/gradio-app/gradio/pull/3054)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.16.2
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fixed file upload fails for files with zero size by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2923](https://github.com/gradio-app/gradio/pull/2923)
+- Fixed bug where `mount_gradio_app` would not launch if the queue was enabled in a gradio app by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2939](https://github.com/gradio-app/gradio/pull/2939)
+- Fix custom long CSS handling in Blocks by [@anton-l](https://github.com/anton-l) in [PR 2953](https://github.com/gradio-app/gradio/pull/2953)
+- Recovers the dropdown change event by [@abidlabs](https://github.com/abidlabs) in [PR 2954](https://github.com/gradio-app/gradio/pull/2954).
+- Fix audio file output by [@aliabid94](https://github.com/aliabid94) in [PR 2961](https://github.com/gradio-app/gradio/pull/2961).
+- Fixed bug where file extensions of really long files were not kept after download by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2929](https://github.com/gradio-app/gradio/pull/2929)
+- Fix bug where outputs for examples where not being returned by the backend by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2955](https://github.com/gradio-app/gradio/pull/2955)
+- Fix bug in `blocks_plug` demo that prevented switching tabs programmatically with python [@TashaSkyUp](https://github.com/https://github.com/TashaSkyUp) in [PR 2971](https://github.com/gradio-app/gradio/pull/2971).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.16.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Fix audio file output by [@aliabid94](https://github.com/aliabid94) in [PR 2950](https://github.com/gradio-app/gradio/pull/2950).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.16.0
+
+### New Features:
+
+###### Send custom progress updates by adding a `gr.Progress` argument after the input arguments to any function. Example:
+
+```python
+def reverse(word, progress=gr.Progress()):
+ progress(0, desc="Starting")
+ time.sleep(1)
+ new_string = ""
+ for letter in progress.tqdm(word, desc="Reversing"):
+ time.sleep(0.25)
+ new_string = letter + new_string
+ return new_string
+
+demo = gr.Interface(reverse, gr.Text(), gr.Text())
+```
+
+Progress indicator bar by [@aliabid94](https://github.com/aliabid94) in [PR 2750](https://github.com/gradio-app/gradio/pull/2750).
+
+- Added `title` argument to `TabbedInterface` by @MohamedAliRashad in [#2888](https://github.com/gradio-app/gradio/pull/2888)
+- Add support for specifying file extensions for `gr.File` and `gr.UploadButton`, using `file_types` parameter (e.g `gr.File(file_count="multiple", file_types=["text", ".json", ".csv"])`) by @dawoodkhan82 in [#2901](https://github.com/gradio-app/gradio/pull/2901)
+- Added `multiselect` option to `Dropdown` by @dawoodkhan82 in [#2871](https://github.com/gradio-app/gradio/pull/2871)
+
+###### With `multiselect` set to `true` a user can now select multiple options from the `gr.Dropdown` component.
+
+```python
+gr.Dropdown(["angola", "pakistan", "canada"], multiselect=True, value=["angola"])
+```
+
+
+
+### Bug Fixes:
+
+- Fixed bug where an error opening an audio file led to a crash by [@FelixDombek](https://github.com/FelixDombek) in [PR 2898](https://github.com/gradio-app/gradio/pull/2898)
+- Fixed bug where setting `default_enabled=False` made it so that the entire queue did not start by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2876](https://github.com/gradio-app/gradio/pull/2876)
+- Fixed bug where csv preview for DataFrame examples would show filename instead of file contents by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2877](https://github.com/gradio-app/gradio/pull/2877)
+- Fixed bug where an error raised after yielding iterative output would not be displayed in the browser by
+ [@JaySmithWpg](https://github.com/JaySmithWpg) in [PR 2889](https://github.com/gradio-app/gradio/pull/2889)
+- Fixed bug in `blocks_style` demo that was preventing it from launching by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2890](https://github.com/gradio-app/gradio/pull/2890)
+- Fixed bug where files could not be downloaded by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2926](https://github.com/gradio-app/gradio/pull/2926)
+- Fixed bug where cached examples were not displaying properly by [@a-rogalska](https://github.com/a-rogalska) in [PR 2974](https://github.com/gradio-app/gradio/pull/2974)
+
+### Documentation Changes:
+
+- Added a Guide on using Google Sheets to create a real-time dashboard with Gradio's `DataFrame` and `LinePlot` component, by [@abidlabs](https://github.com/abidlabs) in [PR 2816](https://github.com/gradio-app/gradio/pull/2816)
+- Add a components - events matrix on the docs by [@aliabd](https://github.com/aliabd) in [PR 2921](https://github.com/gradio-app/gradio/pull/2921)
+
+### Testing and Infrastructure Changes:
+
+- Deployed PRs from forks to spaces by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2895](https://github.com/gradio-app/gradio/pull/2895)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- The `default_enabled` parameter of the `Blocks.queue` method has no effect by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2876](https://github.com/gradio-app/gradio/pull/2876)
+- Added typing to several Python files in codebase by [@abidlabs](https://github.com/abidlabs) in [PR 2887](https://github.com/gradio-app/gradio/pull/2887)
+- Excluding untracked files from demo notebook check action by [@aliabd](https://github.com/aliabd) in [PR 2897](https://github.com/gradio-app/gradio/pull/2897)
+- Optimize images and gifs by [@aliabd](https://github.com/aliabd) in [PR 2922](https://github.com/gradio-app/gradio/pull/2922)
+- Updated typing by [@1nF0rmed](https://github.com/1nF0rmed) in [PR 2904](https://github.com/gradio-app/gradio/pull/2904)
+
+### Contributors Shoutout:
+
+- @JaySmithWpg for making their first contribution to gradio!
+- @MohamedAliRashad for making their first contribution to gradio!
+
+## 3.15.0
+
+### New Features:
+
+Gradio's newest plotting component `gr.LinePlot`! 📈
+
+With this component you can easily create time series visualizations with customizable
+appearance for your demos and dashboards ... all without having to know an external plotting library.
+
+For an example of the api see below:
+
+```python
+gr.LinePlot(stocks,
+ x="date",
+ y="price",
+ color="symbol",
+ color_legend_position="bottom",
+ width=600, height=400, title="Stock Prices")
+```
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2807](https://github.com/gradio-app/gradio/pull/2807)
+
+### Bug Fixes:
+
+- Fixed bug where the `examples_per_page` parameter of the `Examples` component was not passed to the internal `Dataset` component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2861](https://github.com/gradio-app/gradio/pull/2861)
+- Fixes loading Spaces that have components with default values by [@abidlabs](https://github.com/abidlabs) in [PR 2855](https://github.com/gradio-app/gradio/pull/2855)
+- Fixes flagging when `allow_flagging="auto"` in `gr.Interface()` by [@abidlabs](https://github.com/abidlabs) in [PR 2695](https://github.com/gradio-app/gradio/pull/2695)
+- Fixed bug where passing a non-list value to `gr.CheckboxGroup` would crash the entire app by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2866](https://github.com/gradio-app/gradio/pull/2866)
+
+### Documentation Changes:
+
+- Added a Guide on using BigQuery with Gradio's `DataFrame` and `ScatterPlot` component,
+ by [@abidlabs](https://github.com/abidlabs) in [PR 2794](https://github.com/gradio-app/gradio/pull/2794)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fixed importing gradio can cause PIL.Image.registered_extensions() to break by `[@aliencaocao](https://github.com/aliencaocao)` in `[PR 2846](https://github.com/gradio-app/gradio/pull/2846)`
+- Fix css glitch and navigation in docs by [@aliabd](https://github.com/aliabd) in [PR 2856](https://github.com/gradio-app/gradio/pull/2856)
+- Added the ability to set `x_lim`, `y_lim` and legend positions for `gr.ScatterPlot` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2807](https://github.com/gradio-app/gradio/pull/2807)
+- Remove footers and min-height the correct way by [@aliabd](https://github.com/aliabd) in [PR 2860](https://github.com/gradio-app/gradio/pull/2860)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.14.0
+
+### New Features:
+
+###### Add Waveform Visual Support to Audio
+
+Adds a `gr.make_waveform()` function that creates a waveform video by combining an audio and an optional background image by [@dawoodkhan82](http://github.com/dawoodkhan82) and [@aliabid94](http://github.com/aliabid94) in [PR 2706](https://github.com/gradio-app/gradio/pull/2706. Helpful for making audio outputs much more shareable.
+
+
+
+###### Allows Every Component to Accept an `every` Parameter
+
+When a component's initial value is a function, the `every` parameter re-runs the function every `every` seconds. By [@abidlabs](https://github.com/abidlabs) in [PR 2806](https://github.com/gradio-app/gradio/pull/2806). Here's a code example:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ df = gr.DataFrame(run_query, every=60*60)
+
+demo.queue().launch()
+```
+
+### Bug Fixes:
+
+- Fixed issue where too many temporary files were created, all with randomly generated
+ filepaths. Now fewer temporary files are created and are assigned a path that is a
+ hash based on the file contents by [@abidlabs](https://github.com/abidlabs) in [PR 2758](https://github.com/gradio-app/gradio/pull/2758)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.13.2
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+\*No changes to highlight.
+
+-
+
+### Documentation Changes:
+
+- Improves documentation of several queuing-related parameters by [@abidlabs](https://github.com/abidlabs) in [PR 2825](https://github.com/gradio-app/gradio/pull/2825)
+
+### Testing and Infrastructure Changes:
+
+- Remove h11 pinning by [@ecederstrand](https://github.com/ecederstrand) in [PR 2820](https://github.com/gradio-app/gradio/pull/2820)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+No changes to highlight.
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.13.1
+
+### New Features:
+
+###### New Shareable Links
+
+Replaces tunneling logic based on ssh port-forwarding to that based on `frp` by [XciD](https://github.com/XciD) and [Wauplin](https://github.com/Wauplin) in [PR 2509](https://github.com/gradio-app/gradio/pull/2509)
+
+You don't need to do anything differently, but when you set `share=True` in `launch()`,
+you'll get this message and a public link that look a little bit different:
+
+```bash
+Setting up a public link... we have recently upgraded the way public links are generated. If you encounter any problems, please downgrade to gradio version 3.13.0
+.
+Running on public URL: https://bec81a83-5b5c-471e.gradio.live
+```
+
+These links are a more secure and scalable way to create shareable demos!
+
+### Bug Fixes:
+
+- Allows `gr.Dataframe()` to take a `pandas.DataFrame` that includes numpy array and other types as its initial value, by [@abidlabs](https://github.com/abidlabs) in [PR 2804](https://github.com/gradio-app/gradio/pull/2804)
+- Add `altair` to requirements.txt by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2811](https://github.com/gradio-app/gradio/pull/2811)
+- Added aria-labels to icon buttons that are built into UI components by [@emilyuhde](http://github.com/emilyuhde) in [PR 2791](https://github.com/gradio-app/gradio/pull/2791)
+
+### Documentation Changes:
+
+- Fixed some typos in the "Plot Component for Maps" guide by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2811](https://github.com/gradio-app/gradio/pull/2811)
+
+### Testing and Infrastructure Changes:
+
+- Fixed test for IP address by [@abidlabs](https://github.com/abidlabs) in [PR 2808](https://github.com/gradio-app/gradio/pull/2808)
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fixed typo in parameter `visible` in classes in `templates.py` by [@abidlabs](https://github.com/abidlabs) in [PR 2805](https://github.com/gradio-app/gradio/pull/2805)
+- Switched external service for getting IP address from `https://api.ipify.org` to `https://checkip.amazonaws.com/` by [@abidlabs](https://github.com/abidlabs) in [PR 2810](https://github.com/gradio-app/gradio/pull/2810)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+- Fixed typo in parameter `visible` in classes in `templates.py` by [@abidlabs](https://github.com/abidlabs) in [PR 2805](https://github.com/gradio-app/gradio/pull/2805)
+- Switched external service for getting IP address from `https://api.ipify.org` to `https://checkip.amazonaws.com/` by [@abidlabs](https://github.com/abidlabs) in [PR 2810](https://github.com/gradio-app/gradio/pull/2810)
+
+## 3.13.0
+
+### New Features:
+
+###### Scatter plot component
+
+It is now possible to create a scatter plot natively in Gradio!
+
+The `gr.ScatterPlot` component accepts a pandas dataframe and some optional configuration parameters
+and will automatically create a plot for you!
+
+This is the first of many native plotting components in Gradio!
+
+For an example of how to use `gr.ScatterPlot` see below:
+
+```python
+import gradio as gr
+from vega_datasets import data
+
+cars = data.cars()
+
+with gr.Blocks() as demo:
+ gr.ScatterPlot(show_label=False,
+ value=cars,
+ x="Horsepower",
+ y="Miles_per_Gallon",
+ color="Origin",
+ tooltip="Name",
+ title="Car Data",
+ y_title="Miles per Gallon",
+ color_legend_title="Origin of Car").style(container=False)
+
+demo.launch()
+```
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2764](https://github.com/gradio-app/gradio/pull/2764)
+
+###### Support for altair plots
+
+The `Plot` component can now accept altair plots as values!
+Simply return an altair plot from your event listener and gradio will display it in the front-end.
+See the example below:
+
+```python
+import gradio as gr
+import altair as alt
+from vega_datasets import data
+
+cars = data.cars()
+chart = (
+ alt.Chart(cars)
+ .mark_point()
+ .encode(
+ x="Horsepower",
+ y="Miles_per_Gallon",
+ color="Origin",
+ )
+)
+
+with gr.Blocks() as demo:
+ gr.Plot(value=chart)
+demo.launch()
+```
+
+
+
+By [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2741](https://github.com/gradio-app/gradio/pull/2741)
+
+###### Set the background color of a Label component
+
+The `Label` component now accepts a `color` argument by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2736](https://github.com/gradio-app/gradio/pull/2736).
+The `color` argument should either be a valid css color name or hexadecimal string.
+You can update the color with `gr.Label.update`!
+
+This lets you create Alert and Warning boxes with the `Label` component. See below:
+
+```python
+import gradio as gr
+import random
+
+def update_color(value):
+ if value < 0:
+ # This is bad so use red
+ return "#FF0000"
+ elif 0 <= value <= 20:
+ # Ok but pay attention (use orange)
+ return "#ff9966"
+ else:
+ # Nothing to worry about
+ return None
+
+def update_value():
+ choice = random.choice(['good', 'bad', 'so-so'])
+ color = update_color(choice)
+ return gr.Label.update(value=choice, color=color)
+
+
+with gr.Blocks() as demo:
+ label = gr.Label(value=-10)
+ demo.load(lambda: update_value(), inputs=None, outputs=[label], every=1)
+demo.queue().launch()
+```
+
+
+
+###### Add Brazilian Portuguese translation
+
+Add Brazilian Portuguese translation (pt-BR.json) by [@pstwh](http://github.com/pstwh) in [PR 2753](https://github.com/gradio-app/gradio/pull/2753):
+
+
+
+### Bug Fixes:
+
+- Fixed issue where image thumbnails were not showing when an example directory was provided
+ by [@abidlabs](https://github.com/abidlabs) in [PR 2745](https://github.com/gradio-app/gradio/pull/2745)
+- Fixed bug loading audio input models from the hub by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2779](https://github.com/gradio-app/gradio/pull/2779).
+- Fixed issue where entities were not merged when highlighted text was generated from the
+ dictionary inputs [@payoto](https://github.com/payoto) in [PR 2767](https://github.com/gradio-app/gradio/pull/2767)
+- Fixed bug where generating events did not finish running even if the websocket connection was closed by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2783](https://github.com/gradio-app/gradio/pull/2783).
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Images in the chatbot component are now resized if they exceed a max width by [@abidlabs](https://github.com/abidlabs) in [PR 2748](https://github.com/gradio-app/gradio/pull/2748)
+- Missing parameters have been added to `gr.Blocks().load()` by [@abidlabs](https://github.com/abidlabs) in [PR 2755](https://github.com/gradio-app/gradio/pull/2755)
+- Deindex share URLs from search by [@aliabd](https://github.com/aliabd) in [PR 2772](https://github.com/gradio-app/gradio/pull/2772)
+- Redirect old links and fix broken ones by [@aliabd](https://github.com/aliabd) in [PR 2774](https://github.com/gradio-app/gradio/pull/2774)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.12.0
+
+### New Features:
+
+###### The `Chatbot` component now supports a subset of Markdown (including bold, italics, code, images)
+
+You can now pass in some Markdown to the Chatbot component and it will show up,
+meaning that you can pass in images as well! by [@abidlabs](https://github.com/abidlabs) in [PR 2731](https://github.com/gradio-app/gradio/pull/2731)
+
+Here's a simple example that references a local image `lion.jpg` that is in the same
+folder as the Python script:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ gr.Chatbot([("hi", "hello **abubakar**"), ("", "cool pic")])
+
+demo.launch()
+```
+
+
+
+To see a more realistic example, see the new demo `/demo/chatbot_multimodal/run.py`.
+
+###### Latex support
+
+Added mathtext (a subset of latex) support to gr.Markdown. Added by [@kashif](https://github.com/kashif) and [@aliabid94](https://github.com/aliabid94) in [PR 2696](https://github.com/gradio-app/gradio/pull/2696).
+
+Example of how it can be used:
+
+```python
+gr.Markdown(
+ r"""
+ # Hello World! $\frac{\sqrt{x + y}}{4}$ is today's lesson.
+ """)
+```
+
+###### Update Accordion properties from the backend
+
+You can now update the Accordion `label` and `open` status with `gr.Accordion.update` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2690](https://github.com/gradio-app/gradio/pull/2690)
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Accordion(label="Open for greeting", open=False) as accordion:
+ gr.Textbox("Hello!")
+ open_btn = gr.Button(value="Open Accordion")
+ close_btn = gr.Button(value="Close Accordion")
+ open_btn.click(
+ lambda: gr.Accordion.update(open=True, label="Open Accordion"),
+ inputs=None,
+ outputs=[accordion],
+ )
+ close_btn.click(
+ lambda: gr.Accordion.update(open=False, label="Closed Accordion"),
+ inputs=None,
+ outputs=[accordion],
+ )
+demo.launch()
+```
+
+
+
+### Bug Fixes:
+
+- Fixed bug where requests timeout is missing from utils.version_check() by [@yujiehecs](https://github.com/yujiehecs) in [PR 2729](https://github.com/gradio-app/gradio/pull/2729)
+- Fixed bug where so that the `File` component can properly preprocess files to "binary" byte-string format by [CoffeeVampir3](https://github.com/CoffeeVampir3) in [PR 2727](https://github.com/gradio-app/gradio/pull/2727)
+- Fixed bug to ensure that filenames are less than 200 characters even for non-English languages by [@SkyTNT](https://github.com/SkyTNT) in [PR 2685](https://github.com/gradio-app/gradio/pull/2685)
+
+### Documentation Changes:
+
+- Performance improvements to docs on mobile by [@aliabd](https://github.com/aliabd) in [PR 2730](https://github.com/gradio-app/gradio/pull/2730)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Make try examples button more prominent by [@aliabd](https://github.com/aliabd) in [PR 2705](https://github.com/gradio-app/gradio/pull/2705)
+- Fix id clashes in docs by [@aliabd](https://github.com/aliabd) in [PR 2713](https://github.com/gradio-app/gradio/pull/2713)
+- Fix typos in guide docs by [@andridns](https://github.com/andridns) in [PR 2722](https://github.com/gradio-app/gradio/pull/2722)
+- Add option to `include_audio` in Video component. When `True`, for `source="webcam"` this will record audio and video, for `source="upload"` this will retain the audio in an uploaded video by [@mandargogate](https://github.com/MandarGogate) in [PR 2721](https://github.com/gradio-app/gradio/pull/2721)
+
+### Contributors Shoutout:
+
+- [@andridns](https://github.com/andridns) made their first contribution in [PR 2722](https://github.com/gradio-app/gradio/pull/2722)!
+
+## 3.11.0
+
+### New Features:
+
+###### Upload Button
+
+There is now a new component called the `UploadButton` which is a file upload component but in button form! You can also specify what file types it should accept in the form of a list (ex: `image`, `video`, `audio`, `text`, or generic `file`). Added by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2591](https://github.com/gradio-app/gradio/pull/2591).
+
+Example of how it can be used:
+
+```python
+import gradio as gr
+
+def upload_file(files):
+ file_paths = [file.name for file in files]
+ return file_paths
+
+with gr.Blocks() as demo:
+ file_output = gr.File()
+ upload_button = gr.UploadButton("Click to Upload a File", file_types=["image", "video"], file_count="multiple")
+ upload_button.upload(upload_file, upload_button, file_output)
+
+demo.launch()
+```
+
+###### Revamped API documentation page
+
+New API Docs page with in-browser playground and updated aesthetics. [@gary149](https://github.com/gary149) in [PR 2652](https://github.com/gradio-app/gradio/pull/2652)
+
+###### Revamped Login page
+
+Previously our login page had its own CSS, had no dark mode, and had an ugly json message on the wrong credentials. Made the page more aesthetically consistent, added dark mode support, and a nicer error message. [@aliabid94](https://github.com/aliabid94) in [PR 2684](https://github.com/gradio-app/gradio/pull/2684)
+
+###### Accessing the Requests Object Directly
+
+You can now access the Request object directly in your Python function by [@abidlabs](https://github.com/abidlabs) in [PR 2641](https://github.com/gradio-app/gradio/pull/2641). This means that you can access request headers, the client IP address, and so on. In order to use it, add a parameter to your function and set its type hint to be `gr.Request`. Here's a simple example:
+
+```py
+import gradio as gr
+
+def echo(name, request: gr.Request):
+ if request:
+ print("Request headers dictionary:", request.headers)
+ print("IP address:", request.client.host)
+ return name
+
+io = gr.Interface(echo, "textbox", "textbox").launch()
+```
+
+### Bug Fixes:
+
+- Fixed bug that limited files from being sent over websockets to 16MB. The new limit
+ is now 1GB by [@abidlabs](https://github.com/abidlabs) in [PR 2709](https://github.com/gradio-app/gradio/pull/2709)
+
+### Documentation Changes:
+
+- Updated documentation for embedding Gradio demos on Spaces as web components by
+ [@julien-c](https://github.com/julien-c) in [PR 2698](https://github.com/gradio-app/gradio/pull/2698)
+- Updated IFrames in Guides to use the host URL instead of the Space name to be consistent with the new method for embedding Spaces, by
+ [@julien-c](https://github.com/julien-c) in [PR 2692](https://github.com/gradio-app/gradio/pull/2692)
+- Colab buttons on every demo in the website! Just click open in colab, and run the demo there.
+
+https://user-images.githubusercontent.com/9021060/202878400-cb16ed47-f4dd-4cb0-b2f0-102a9ff64135.mov
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Better warnings and error messages for `gr.Interface.load()` by [@abidlabs](https://github.com/abidlabs) in [PR 2694](https://github.com/gradio-app/gradio/pull/2694)
+- Add open in colab buttons to demos in docs and /demos by [@aliabd](https://github.com/aliabd) in [PR 2608](https://github.com/gradio-app/gradio/pull/2608)
+- Apply different formatting for the types in component docstrings by [@aliabd](https://github.com/aliabd) in [PR 2707](https://github.com/gradio-app/gradio/pull/2707)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.10.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Passes kwargs into `gr.Interface.load()` by [@abidlabs](https://github.com/abidlabs) in [PR 2669](https://github.com/gradio-app/gradio/pull/2669)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Clean up printed statements in Embedded Colab Mode by [@aliabid94](https://github.com/aliabid94) in [PR 2612](https://github.com/gradio-app/gradio/pull/2612)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.10.0
+
+- Add support for `'password'` and `'email'` types to `Textbox`. [@pngwn](https://github.com/pngwn) in [PR 2653](https://github.com/gradio-app/gradio/pull/2653)
+- `gr.Textbox` component will now raise an exception if `type` is not "text", "email", or "password" [@pngwn](https://github.com/pngwn) in [PR 2653](https://github.com/gradio-app/gradio/pull/2653). This will cause demos using the deprecated `gr.Textbox(type="number")` to raise an exception.
+
+### Bug Fixes:
+
+- Updated the minimum FastApi used in tests to version 0.87 by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2647](https://github.com/gradio-app/gradio/pull/2647)
+- Fixed bug where interfaces with examples could not be loaded with `gr.Interface.load` by [@freddyaboulton](https://github.com/freddyaboulton) [PR 2640](https://github.com/gradio-app/gradio/pull/2640)
+- Fixed bug where the `interactive` property of a component could not be updated by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2639](https://github.com/gradio-app/gradio/pull/2639)
+- Fixed bug where some URLs were not being recognized as valid URLs and thus were not
+ loading correctly in various components by [@abidlabs](https://github.com/abidlabs) in [PR 2659](https://github.com/gradio-app/gradio/pull/2659)
+
+### Documentation Changes:
+
+- Fix some typos in the embedded demo names in "05_using_blocks_like_functions.md" by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2656](https://github.com/gradio-app/gradio/pull/2656)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Add support for `'password'` and `'email'` types to `Textbox`. [@pngwn](https://github.com/pngwn) in [PR 2653](https://github.com/gradio-app/gradio/pull/2653)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.9.1
+
+### New Features:
+
+No changes to highlight.
+
+### Bug Fixes:
+
+- Only set a min height on md and html when loading by [@pngwn](https://github.com/pngwn) in [PR 2623](https://github.com/gradio-app/gradio/pull/2623)
+
+### Documentation Changes:
+
+- See docs for the latest gradio commit to main as well the latest pip release:
+
+
+
+- Modified the "Connecting To a Database Guide" to use `pd.read_sql` as opposed to low-level postgres connector by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2604](https://github.com/gradio-app/gradio/pull/2604)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Dropdown for seeing docs as latest or main by [@aliabd](https://github.com/aliabd) in [PR 2544](https://github.com/gradio-app/gradio/pull/2544)
+- Allow `gr.Templates` to accept parameters to override the defaults by [@abidlabs](https://github.com/abidlabs) in [PR 2600](https://github.com/gradio-app/gradio/pull/2600)
+- Components now throw a `ValueError()` if constructed with invalid parameters for `type` or `source` (for components that take those parameters) in [PR 2610](https://github.com/gradio-app/gradio/pull/2610)
+- Allow auth with using queue by [@GLGDLY](https://github.com/GLGDLY) in [PR 2611](https://github.com/gradio-app/gradio/pull/2611)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.9
+
+### New Features:
+
+- Gradio is now embedded directly in colab without requiring the share link by [@aliabid94](https://github.com/aliabid94) in [PR 2455](https://github.com/gradio-app/gradio/pull/2455)
+
+###### Calling functions by api_name in loaded apps
+
+When you load an upstream app with `gr.Blocks.load`, you can now specify which fn
+to call with the `api_name` parameter.
+
+```python
+import gradio as gr
+english_translator = gr.Blocks.load(name="spaces/gradio/english-translator")
+german = english_translator("My name is Freddy", api_name='translate-to-german')
+```
+
+The `api_name` parameter will take precedence over the `fn_index` parameter.
+
+### Bug Fixes:
+
+- Fixed bug where None could not be used for File,Model3D, and Audio examples by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2588](https://github.com/gradio-app/gradio/pull/2588)
+- Fixed links in Plotly map guide + demo by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2578](https://github.com/gradio-app/gradio/pull/2578)
+- `gr.Blocks.load()` now correctly loads example files from Spaces [@abidlabs](https://github.com/abidlabs) in [PR 2594](https://github.com/gradio-app/gradio/pull/2594)
+- Fixed bug when image clear started upload dialog [@mezotaken](https://github.com/mezotaken) in [PR 2577](https://github.com/gradio-app/gradio/pull/2577)
+
+### Documentation Changes:
+
+- Added a Guide on how to configure the queue for maximum performance by [@abidlabs](https://github.com/abidlabs) in [PR 2558](https://github.com/gradio-app/gradio/pull/2558)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Add `api_name` to `Blocks.__call__` by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2593](https://github.com/gradio-app/gradio/pull/2593)
+- Update queue with using deque & update requirements by [@GLGDLY](https://github.com/GLGDLY) in [PR 2428](https://github.com/gradio-app/gradio/pull/2428)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.8.2
+
+### Bug Fixes:
+
+- Ensure gradio apps embedded via spaces use the correct endpoint for predictions. [@pngwn](https://github.com/pngwn) in [PR 2567](https://github.com/gradio-app/gradio/pull/2567)
+- Ensure gradio apps embedded via spaces use the correct websocket protocol. [@pngwn](https://github.com/pngwn) in [PR 2571](https://github.com/gradio-app/gradio/pull/2571)
+
+### New Features:
+
+###### Running Events Continuously
+
+Gradio now supports the ability to run an event continuously on a fixed schedule. To use this feature,
+pass `every=# of seconds` to the event definition. This will run the event every given number of seconds!
+
+This can be used to:
+
+- Create live visualizations that show the most up to date data
+- Refresh the state of the frontend automatically in response to changes in the backend
+
+Here is an example of a live plot that refreshes every half second:
+
+```python
+import math
+import gradio as gr
+import plotly.express as px
+import numpy as np
+
+
+plot_end = 2 * math.pi
+
+
+def get_plot(period=1):
+ global plot_end
+ x = np.arange(plot_end - 2 * math.pi, plot_end, 0.02)
+ y = np.sin(2*math.pi*period * x)
+ fig = px.line(x=x, y=y)
+ plot_end += 2 * math.pi
+ return fig
+
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column():
+ gr.Markdown("Change the value of the slider to automatically update the plot")
+ period = gr.Slider(label="Period of plot", value=1, minimum=0, maximum=10, step=1)
+ plot = gr.Plot(label="Plot (updates every half second)")
+
+ dep = demo.load(get_plot, None, plot, every=0.5)
+ period.change(get_plot, period, plot, every=0.5, cancels=[dep])
+
+demo.queue().launch()
+```
+
+
+
+### Bug Fixes:
+
+No changes to highlight.
+
+### Documentation Changes:
+
+- Explained how to set up `queue` and `auth` when working with reload mode by by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 3089](https://github.com/gradio-app/gradio/pull/3089)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Allows loading private Spaces by passing an an `api_key` to `gr.Interface.load()`
+ by [@abidlabs](https://github.com/abidlabs) in [PR 2568](https://github.com/gradio-app/gradio/pull/2568)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.8
+
+### New Features:
+
+- Allows event listeners to accept a single dictionary as its argument, where the keys are the components and the values are the component values. This is set by passing the input components in the event listener as a set instead of a list. [@aliabid94](https://github.com/aliabid94) in [PR 2550](https://github.com/gradio-app/gradio/pull/2550)
+
+### Bug Fixes:
+
+- Fix whitespace issue when using plotly. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2548](https://github.com/gradio-app/gradio/pull/2548)
+- Apply appropriate alt text to all gallery images. [@camenduru](https://github.com/camenduru) in [PR 2358](https://github.com/gradio-app/gradio/pull/2538)
+- Removed erroneous tkinter import in gradio.blocks by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2555](https://github.com/gradio-app/gradio/pull/2555)
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Added the `every` keyword to event listeners that runs events on a fixed schedule by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2512](https://github.com/gradio-app/gradio/pull/2512)
+- Fix whitespace issue when using plotly. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2548](https://github.com/gradio-app/gradio/pull/2548)
+- Apply appropriate alt text to all gallery images. [@camenduru](https://github.com/camenduru) in [PR 2358](https://github.com/gradio-app/gradio/pull/2538)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.7
+
+### New Features:
+
+###### Batched Functions
+
+Gradio now supports the ability to pass _batched_ functions. Batched functions are just
+functions which take in a list of inputs and return a list of predictions.
+
+For example, here is a batched function that takes in two lists of inputs (a list of
+words and a list of ints), and returns a list of trimmed words as output:
+
+```py
+import time
+
+def trim_words(words, lens):
+ trimmed_words = []
+ time.sleep(5)
+ for w, l in zip(words, lens):
+ trimmed_words.append(w[:l])
+ return [trimmed_words]
+```
+
+The advantage of using batched functions is that if you enable queuing, the Gradio
+server can automatically _batch_ incoming requests and process them in parallel,
+potentially speeding up your demo. Here's what the Gradio code looks like (notice
+the `batch=True` and `max_batch_size=16` -- both of these parameters can be passed
+into event triggers or into the `Interface` class)
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ word = gr.Textbox(label="word", value="abc")
+ leng = gr.Number(label="leng", precision=0, value=1)
+ output = gr.Textbox(label="Output")
+ with gr.Row():
+ run = gr.Button()
+
+ event = run.click(trim_words, [word, leng], output, batch=True, max_batch_size=16)
+
+demo.queue()
+demo.launch()
+```
+
+In the example above, 16 requests could be processed in parallel (for a total inference
+time of 5 seconds), instead of each request being processed separately (for a total
+inference time of 80 seconds).
+
+###### Upload Event
+
+`Video`, `Audio`, `Image`, and `File` components now support a `upload()` event that is triggered when a user uploads a file into any of these components.
+
+Example usage:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ input_video = gr.Video()
+ output_video = gr.Video()
+
+ # Clears the output video when an input video is uploaded
+ input_video.upload(lambda : None, None, output_video)
+```
+
+### Bug Fixes:
+
+- Fixes issue where plotly animations, interactivity, titles, legends, were not working properly. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2486](https://github.com/gradio-app/gradio/pull/2486)
+- Prevent requests to the `/api` endpoint from skipping the queue if the queue is enabled for that event by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2493](https://github.com/gradio-app/gradio/pull/2493)
+- Fixes a bug with `cancels` in event triggers so that it works properly if multiple
+ Blocks are rendered by [@abidlabs](https://github.com/abidlabs) in [PR 2530](https://github.com/gradio-app/gradio/pull/2530)
+- Prevent invalid targets of events from crashing the whole application. [@pngwn](https://github.com/pngwn) in [PR 2534](https://github.com/gradio-app/gradio/pull/2534)
+- Properly dequeue cancelled events when multiple apps are rendered by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2540](https://github.com/gradio-app/gradio/pull/2540)
+- Fixes videos being cropped due to height/width params not being used [@hannahblair](https://github.com/hannahblair) in [PR 4946](https://github.com/gradio-app/gradio/pull/4946)
+
+### Documentation Changes:
+
+- Added an example interactive dashboard to the "Tabular & Plots" section of the Demos page by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2508](https://github.com/gradio-app/gradio/pull/2508)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Fixes the error message if a user builds Gradio locally and tries to use `share=True` by [@abidlabs](https://github.com/abidlabs) in [PR 2502](https://github.com/gradio-app/gradio/pull/2502)
+- Allows the render() function to return self by [@Raul9595](https://github.com/Raul9595) in [PR 2514](https://github.com/gradio-app/gradio/pull/2514)
+- Fixes issue where plotly animations, interactivity, titles, legends, were not working properly. [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2486](https://github.com/gradio-app/gradio/pull/2486)
+- Gradio now supports batched functions by [@abidlabs](https://github.com/abidlabs) in [PR 2218](https://github.com/gradio-app/gradio/pull/2218)
+- Add `upload` event for `Video`, `Audio`, `Image`, and `File` components [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2448](https://github.com/gradio-app/gradio/pull/2456)
+- Changes websocket path for Spaces as it is no longer necessary to have a different URL for websocket connections on Spaces by [@abidlabs](https://github.com/abidlabs) in [PR 2528](https://github.com/gradio-app/gradio/pull/2528)
+- Clearer error message when events are defined outside of a Blocks scope, and a warning if you
+ try to use `Series` or `Parallel` with `Blocks` by [@abidlabs](https://github.com/abidlabs) in [PR 2543](https://github.com/gradio-app/gradio/pull/2543)
+- Adds support for audio samples that are in `float64`, `float16`, or `uint16` formats by [@abidlabs](https://github.com/abidlabs) in [PR 2545](https://github.com/gradio-app/gradio/pull/2545)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.6
+
+### New Features:
+
+###### Cancelling Running Events
+
+Running events can be cancelled when other events are triggered! To test this feature, pass the `cancels` parameter to the event listener.
+For this feature to work, the queue must be enabled.
+
+
+
+Code:
+
+```python
+import time
+import gradio as gr
+
+def fake_diffusion(steps):
+ for i in range(steps):
+ time.sleep(1)
+ yield str(i)
+
+def long_prediction(*args, **kwargs):
+ time.sleep(10)
+ return 42
+
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column():
+ n = gr.Slider(1, 10, value=9, step=1, label="Number Steps")
+ run = gr.Button()
+ output = gr.Textbox(label="Iterative Output")
+ stop = gr.Button(value="Stop Iterating")
+ with gr.Column():
+ prediction = gr.Number(label="Expensive Calculation")
+ run_pred = gr.Button(value="Run Expensive Calculation")
+ with gr.Column():
+ cancel_on_change = gr.Textbox(label="Cancel Iteration and Expensive Calculation on Change")
+
+ click_event = run.click(fake_diffusion, n, output)
+ stop.click(fn=None, inputs=None, outputs=None, cancels=[click_event])
+ pred_event = run_pred.click(fn=long_prediction, inputs=None, outputs=prediction)
+
+ cancel_on_change.change(None, None, None, cancels=[click_event, pred_event])
+
+
+demo.queue(concurrency_count=1, max_size=20).launch()
+```
+
+For interfaces, a stop button will be added automatically if the function uses a `yield` statement.
+
+```python
+import gradio as gr
+import time
+
+def iteration(steps):
+ for i in range(steps):
+ time.sleep(0.5)
+ yield i
+
+gr.Interface(iteration,
+ inputs=gr.Slider(minimum=1, maximum=10, step=1, value=5),
+ outputs=gr.Number()).queue().launch()
+```
+
+
+
+### Bug Fixes:
+
+- Add loading status tracker UI to HTML and Markdown components. [@pngwn](https://github.com/pngwn) in [PR 2474](https://github.com/gradio-app/gradio/pull/2474)
+- Fixed videos being mirrored in the front-end if source is not webcam by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2475](https://github.com/gradio-app/gradio/pull/2475)
+- Add clear button for timeseries component [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2487](https://github.com/gradio-app/gradio/pull/2487)
+- Removes special characters from temporary filenames so that the files can be served by components [@abidlabs](https://github.com/abidlabs) in [PR 2480](https://github.com/gradio-app/gradio/pull/2480)
+- Fixed infinite reload loop when mounting gradio as a sub application by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2477](https://github.com/gradio-app/gradio/pull/2477)
+
+### Documentation Changes:
+
+- Adds a demo to show how a sound alert can be played upon completion of a prediction by [@abidlabs](https://github.com/abidlabs) in [PR 2478](https://github.com/gradio-app/gradio/pull/2478)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Enable running events to be cancelled from other events by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2433](https://github.com/gradio-app/gradio/pull/2433)
+- Small fix for version check before reuploading demos by [@aliabd](https://github.com/aliabd) in [PR 2469](https://github.com/gradio-app/gradio/pull/2469)
+- Add loading status tracker UI to HTML and Markdown components. [@pngwn](https://github.com/pngwn) in [PR 2400](https://github.com/gradio-app/gradio/pull/2474)
+- Add clear button for timeseries component [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2487](https://github.com/gradio-app/gradio/pull/2487)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.5
+
+### Bug Fixes:
+
+- Ensure that Gradio does not take control of the HTML page title when embedding a gradio app as a web component, this behaviour flipped by adding `control_page_title="true"` to the webcomponent. [@pngwn](https://github.com/pngwn) in [PR 2400](https://github.com/gradio-app/gradio/pull/2400)
+- Decreased latency in iterative-output demos by making the iteration asynchronous [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2409](https://github.com/gradio-app/gradio/pull/2409)
+- Fixed queue getting stuck under very high load by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2374](https://github.com/gradio-app/gradio/pull/2374)
+- Ensure that components always behave as if `interactive=True` were set when the following conditions are true:
+
+ - no default value is provided,
+ - they are not set as the input or output of an event,
+ - `interactive` kwarg is not set.
+
+ [@pngwn](https://github.com/pngwn) in [PR 2459](https://github.com/gradio-app/gradio/pull/2459)
+
+### New Features:
+
+- When an `Image` component is set to `source="upload"`, it is now possible to drag and drop and image to replace a previously uploaded image by [@pngwn](https://github.com/pngwn) in [PR 1711](https://github.com/gradio-app/gradio/issues/1711)
+- The `gr.Dataset` component now accepts `HTML` and `Markdown` components by [@abidlabs](https://github.com/abidlabs) in [PR 2437](https://github.com/gradio-app/gradio/pull/2437)
+
+### Documentation Changes:
+
+- Improved documentation for the `gr.Dataset` component by [@abidlabs](https://github.com/abidlabs) in [PR 2437](https://github.com/gradio-app/gradio/pull/2437)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+- The `Carousel` component is officially deprecated. Since gradio 3.0, code containing the `Carousel` component would throw warnings. As of the next release, the `Carousel` component will raise an exception.
+
+### Full Changelog:
+
+- Speeds up Gallery component by using temporary files instead of base64 representation in the front-end by [@proxyphi](https://github.com/proxyphi), [@pngwn](https://github.com/pngwn), and [@abidlabs](https://github.com/abidlabs) in [PR 2265](https://github.com/gradio-app/gradio/pull/2265)
+- Fixed some embedded demos in the guides by not loading the gradio web component in some guides by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2403](https://github.com/gradio-app/gradio/pull/2403)
+- When an `Image` component is set to `source="upload"`, it is now possible to drag and drop and image to replace a previously uploaded image by [@pngwn](https://github.com/pngwn) in [PR 2400](https://github.com/gradio-app/gradio/pull/2410)
+- Improve documentation of the `Blocks.load()` event by [@abidlabs](https://github.com/abidlabs) in [PR 2413](https://github.com/gradio-app/gradio/pull/2413)
+- Decreased latency in iterative-output demos by making the iteration asynchronous [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2409](https://github.com/gradio-app/gradio/pull/2409)
+- Updated share link message to reference new Spaces Hardware [@abidlabs](https://github.com/abidlabs) in [PR 2423](https://github.com/gradio-app/gradio/pull/2423)
+- Automatically restart spaces if they're down by [@aliabd](https://github.com/aliabd) in [PR 2405](https://github.com/gradio-app/gradio/pull/2405)
+- Carousel component is now deprecated by [@abidlabs](https://github.com/abidlabs) in [PR 2434](https://github.com/gradio-app/gradio/pull/2434)
+- Build Gradio from source in ui tests by by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2440](https://github.com/gradio-app/gradio/pull/2440)
+- Change "return ValueError" to "raise ValueError" by [@vzakharov](https://github.com/vzakharov) in [PR 2445](https://github.com/gradio-app/gradio/pull/2445)
+- Add guide on creating a map demo using the `gr.Plot()` component [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2402](https://github.com/gradio-app/gradio/pull/2402)
+- Add blur event for `Textbox` and `Number` components [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2448](https://github.com/gradio-app/gradio/pull/2448)
+- Stops a gradio launch from hogging a port even after it's been killed [@aliabid94](https://github.com/aliabid94) in [PR 2453](https://github.com/gradio-app/gradio/pull/2453)
+- Fix embedded interfaces on touch screen devices by [@aliabd](https://github.com/aliabd) in [PR 2457](https://github.com/gradio-app/gradio/pull/2457)
+- Upload all demos to spaces by [@aliabd](https://github.com/aliabd) in [PR 2281](https://github.com/gradio-app/gradio/pull/2281)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.4.1
+
+### New Features:
+
+###### 1. See Past and Upcoming Changes in the Release History 👀
+
+You can now see gradio's release history directly on the website, and also keep track of upcoming changes. Just go [here](https://gradio.app/changelog/).
+
+
+
+### Bug Fixes:
+
+1. Fix typo in guide image path by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2357](https://github.com/gradio-app/gradio/pull/2357)
+2. Raise error if Blocks has duplicate component with same IDs by [@abidlabs](https://github.com/abidlabs) in [PR 2359](https://github.com/gradio-app/gradio/pull/2359)
+3. Catch the permission exception on the audio component by [@Ian-GL](https://github.com/Ian-GL) in [PR 2330](https://github.com/gradio-app/gradio/pull/2330)
+4. Fix image_classifier_interface_load demo by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2365](https://github.com/gradio-app/gradio/pull/2365)
+5. Fix combining adjacent components without gaps by introducing `gr.Row(variant="compact")` by [@aliabid94](https://github.com/aliabid94) in [PR 2291](https://github.com/gradio-app/gradio/pull/2291) This comes with deprecation of the following arguments for `Component.style`: `round`, `margin`, `border`.
+6. Fix audio streaming, which was previously choppy in [PR 2351](https://github.com/gradio-app/gradio/pull/2351). Big thanks to [@yannickfunk](https://github.com/yannickfunk) for the proposed solution.
+7. Fix bug where new typeable slider doesn't respect the minimum and maximum values [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2380](https://github.com/gradio-app/gradio/pull/2380)
+
+### Documentation Changes:
+
+1. New Guide: Connecting to a Database 🗄️
+
+ A new guide by [@freddyaboulton](https://github.com/freddyaboulton) that explains how you can use Gradio to connect your app to a database. Read more [here](https://gradio.app/connecting_to_a_database/).
+
+2. New Guide: Running Background Tasks 🥷
+
+ A new guide by [@freddyaboulton](https://github.com/freddyaboulton) that explains how you can run background tasks from your gradio app. Read more [here](https://gradio.app/running_background_tasks/).
+
+3. Small fixes to docs for `Image` component by [@abidlabs](https://github.com/abidlabs) in [PR 2372](https://github.com/gradio-app/gradio/pull/2372)
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- Create a guide on how to connect an app to a database hosted on the cloud by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2341](https://github.com/gradio-app/gradio/pull/2341)
+- Removes `analytics` dependency by [@abidlabs](https://github.com/abidlabs) in [PR 2347](https://github.com/gradio-app/gradio/pull/2347)
+- Add guide on launching background tasks from your app by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2350](https://github.com/gradio-app/gradio/pull/2350)
+- Fix typo in guide image path by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2357](https://github.com/gradio-app/gradio/pull/2357)
+- Raise error if Blocks has duplicate component with same IDs by [@abidlabs](https://github.com/abidlabs) in [PR 2359](https://github.com/gradio-app/gradio/pull/2359)
+- Hotfix: fix version back to 3.4 by [@abidlabs](https://github.com/abidlabs) in [PR 2361](https://github.com/gradio-app/gradio/pull/2361)
+- Change version.txt to 3.4 instead of 3.4.0 by [@aliabd](https://github.com/aliabd) in [PR 2363](https://github.com/gradio-app/gradio/pull/2363)
+- Catch the permission exception on the audio component by [@Ian-GL](https://github.com/Ian-GL) in [PR 2330](https://github.com/gradio-app/gradio/pull/2330)
+- Fix image_classifier_interface_load demo by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2365](https://github.com/gradio-app/gradio/pull/2365)
+- Small fixes to docs for `Image` component by [@abidlabs](https://github.com/abidlabs) in [PR 2372](https://github.com/gradio-app/gradio/pull/2372)
+- Automated Release Notes by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2306](https://github.com/gradio-app/gradio/pull/2306)
+- Fixed small typos in the docs [@julien-c](https://github.com/julien-c) in [PR 2373](https://github.com/gradio-app/gradio/pull/2373)
+- Adds ability to disable pre/post-processing for examples [@abidlabs](https://github.com/abidlabs) in [PR 2383](https://github.com/gradio-app/gradio/pull/2383)
+- Copy changelog file in website docker by [@aliabd](https://github.com/aliabd) in [PR 2384](https://github.com/gradio-app/gradio/pull/2384)
+- Lets users provide a `gr.update()` dictionary even if post-processing is disabled [@abidlabs](https://github.com/abidlabs) in [PR 2385](https://github.com/gradio-app/gradio/pull/2385)
+- Fix bug where errors would cause apps run in reload mode to hang forever by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2394](https://github.com/gradio-app/gradio/pull/2394)
+- Fix bug where new typeable slider doesn't respect the minimum and maximum values [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2380](https://github.com/gradio-app/gradio/pull/2380)
+
+### Contributors Shoutout:
+
+No changes to highlight.
+
+## 3.4
+
+### New Features:
+
+###### 1. Gallery Captions 🖼️
+
+You can now pass captions to images in the Gallery component. To do so you need to pass a {List} of (image, {str} caption) tuples. This is optional and the component also accepts just a list of the images.
+
+Here's an example:
+
+```python
+import gradio as gr
+
+images_with_captions = [
+ ("https://images.unsplash.com/photo-1551969014-7d2c4cddf0b6", "Cheetah by David Groves"),
+ ("https://images.unsplash.com/photo-1546182990-dffeafbe841d", "Lion by Francesco"),
+ ("https://images.unsplash.com/photo-1561731216-c3a4d99437d5", "Tiger by Mike Marrah")
+ ]
+
+with gr.Blocks() as demo:
+ gr.Gallery(value=images_with_captions)
+
+demo.launch()
+```
+
+
+
+###### 2. Type Values into the Slider 🔢
+
+You can now type values directly on the Slider component! Here's what it looks like:
+
+
+
+###### 3. Better Sketching and Inpainting 🎨
+
+We've made a lot of changes to our Image component so that it can support better sketching and inpainting.
+
+Now supports:
+
+- A standalone black-and-white sketch
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x: x, gr.Sketchpad(), gr.Image())
+demo.launch()
+```
+
+
+
+- A standalone color sketch
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x: x, gr.Paint(), gr.Image())
+demo.launch()
+```
+
+
+
+- An uploadable image with black-and-white or color sketching
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x: x, gr.Image(source='upload', tool='color-sketch'), gr.Image()) # for black and white, tool = 'sketch'
+demo.launch()
+```
+
+
+
+- Webcam with black-and-white or color sketching
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x: x, gr.Image(source='webcam', tool='color-sketch'), gr.Image()) # for black and white, tool = 'sketch'
+demo.launch()
+```
+
+
+
+As well as other fixes
+
+### Bug Fixes:
+
+1. Fix bug where max concurrency count is not respected in queue by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2286](https://github.com/gradio-app/gradio/pull/2286)
+2. fix : queue could be blocked by [@SkyTNT](https://github.com/SkyTNT) in [PR 2288](https://github.com/gradio-app/gradio/pull/2288)
+3. Supports `gr.update()` in example caching by [@abidlabs](https://github.com/abidlabs) in [PR 2309](https://github.com/gradio-app/gradio/pull/2309)
+4. Clipboard fix for iframes by [@abidlabs](https://github.com/abidlabs) in [PR 2321](https://github.com/gradio-app/gradio/pull/2321)
+5. Fix: Dataframe column headers are reset when you add a new column by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2318](https://github.com/gradio-app/gradio/pull/2318)
+6. Added support for URLs for Video, Audio, and Image by [@abidlabs](https://github.com/abidlabs) in [PR 2256](https://github.com/gradio-app/gradio/pull/2256)
+7. Add documentation about how to create and use the Gradio FastAPI app by [@abidlabs](https://github.com/abidlabs) in [PR 2263](https://github.com/gradio-app/gradio/pull/2263)
+
+### Documentation Changes:
+
+1. Adding a Playground Tab to the Website by [@aliabd](https://github.com/aliabd) in [PR 1860](https://github.com/gradio-app/gradio/pull/1860)
+2. Gradio for Tabular Data Science Workflows Guide by [@merveenoyan](https://github.com/merveenoyan) in [PR 2199](https://github.com/gradio-app/gradio/pull/2199)
+3. Promotes `postprocess` and `preprocess` to documented parameters by [@abidlabs](https://github.com/abidlabs) in [PR 2293](https://github.com/gradio-app/gradio/pull/2293)
+4. Update 2)key_features.md by [@voidxd](https://github.com/voidxd) in [PR 2326](https://github.com/gradio-app/gradio/pull/2326)
+5. Add docs to blocks context postprocessing function by [@Ian-GL](https://github.com/Ian-GL) in [PR 2332](https://github.com/gradio-app/gradio/pull/2332)
+
+### Testing and Infrastructure Changes
+
+1. Website fixes and refactoring by [@aliabd](https://github.com/aliabd) in [PR 2280](https://github.com/gradio-app/gradio/pull/2280)
+2. Don't deploy to spaces on release by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2313](https://github.com/gradio-app/gradio/pull/2313)
+
+### Full Changelog:
+
+- Website fixes and refactoring by [@aliabd](https://github.com/aliabd) in [PR 2280](https://github.com/gradio-app/gradio/pull/2280)
+- Fix bug where max concurrency count is not respected in queue by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2286](https://github.com/gradio-app/gradio/pull/2286)
+- Promotes `postprocess` and `preprocess` to documented parameters by [@abidlabs](https://github.com/abidlabs) in [PR 2293](https://github.com/gradio-app/gradio/pull/2293)
+- Raise warning when trying to cache examples but not all inputs have examples by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2279](https://github.com/gradio-app/gradio/pull/2279)
+- fix : queue could be blocked by [@SkyTNT](https://github.com/SkyTNT) in [PR 2288](https://github.com/gradio-app/gradio/pull/2288)
+- Don't deploy to spaces on release by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2313](https://github.com/gradio-app/gradio/pull/2313)
+- Supports `gr.update()` in example caching by [@abidlabs](https://github.com/abidlabs) in [PR 2309](https://github.com/gradio-app/gradio/pull/2309)
+- Respect Upstream Queue when loading interfaces/blocks from Spaces by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2294](https://github.com/gradio-app/gradio/pull/2294)
+- Clipboard fix for iframes by [@abidlabs](https://github.com/abidlabs) in [PR 2321](https://github.com/gradio-app/gradio/pull/2321)
+- Sketching + Inpainting Capabilities to Gradio by [@abidlabs](https://github.com/abidlabs) in [PR 2144](https://github.com/gradio-app/gradio/pull/2144)
+- Update 2)key_features.md by [@voidxd](https://github.com/voidxd) in [PR 2326](https://github.com/gradio-app/gradio/pull/2326)
+- release 3.4b3 by [@abidlabs](https://github.com/abidlabs) in [PR 2328](https://github.com/gradio-app/gradio/pull/2328)
+- Fix: Dataframe column headers are reset when you add a new column by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2318](https://github.com/gradio-app/gradio/pull/2318)
+- Start queue when gradio is a sub application by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2319](https://github.com/gradio-app/gradio/pull/2319)
+- Fix Web Tracker Script by [@aliabd](https://github.com/aliabd) in [PR 2308](https://github.com/gradio-app/gradio/pull/2308)
+- Add docs to blocks context postprocessing function by [@Ian-GL](https://github.com/Ian-GL) in [PR 2332](https://github.com/gradio-app/gradio/pull/2332)
+- Fix typo in iterator variable name in run_predict function by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2340](https://github.com/gradio-app/gradio/pull/2340)
+- Add captions to galleries by [@aliabid94](https://github.com/aliabid94) in [PR 2284](https://github.com/gradio-app/gradio/pull/2284)
+- Typeable value on gradio.Slider by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2329](https://github.com/gradio-app/gradio/pull/2329)
+
+### Contributors Shoutout:
+
+- [@SkyTNT](https://github.com/SkyTNT) made their first contribution in [PR 2288](https://github.com/gradio-app/gradio/pull/2288)
+- [@voidxd](https://github.com/voidxd) made their first contribution in [PR 2326](https://github.com/gradio-app/gradio/pull/2326)
+
+## 3.3
+
+### New Features:
+
+###### 1. Iterative Outputs ⏳
+
+You can now create an iterative output simply by having your function return a generator!
+
+Here's (part of) an example that was used to generate the interface below it. [See full code](https://colab.research.google.com/drive/1m9bWS6B82CT7bw-m4L6AJR8za7fEK7Ov?usp=sharing).
+
+```python
+def predict(steps, seed):
+ generator = torch.manual_seed(seed)
+ for i in range(1,steps):
+ yield pipeline(generator=generator, num_inference_steps=i)["sample"][0]
+```
+
+
+
+###### 2. Accordion Layout 🆕
+
+This version of Gradio introduces a new layout component to Blocks: the Accordion. Wrap your elements in a neat, expandable layout that allows users to toggle them as needed.
+
+Usage: ([Read the docs](https://gradio.app/docs/#accordion))
+
+```python
+with gr.Accordion("open up"):
+# components here
+```
+
+
+
+###### 3. Skops Integration 📈
+
+Our new integration with [skops](https://huggingface.co/blog/skops) allows you to load tabular classification and regression models directly from the [hub](https://huggingface.co/models).
+
+Here's a classification example showing how quick it is to set up an interface for a [model](https://huggingface.co/scikit-learn/tabular-playground).
+
+```python
+import gradio as gr
+gr.Interface.load("models/scikit-learn/tabular-playground").launch()
+```
+
+
+
+### Bug Fixes:
+
+No changes to highlight.
+
+### Documentation Changes:
+
+No changes to highlight.
+
+### Testing and Infrastructure Changes:
+
+No changes to highlight.
+
+### Breaking Changes:
+
+No changes to highlight.
+
+### Full Changelog:
+
+- safari fixes by [@pngwn](https://github.com/pngwn) in [PR 2138](https://github.com/gradio-app/gradio/pull/2138)
+- Fix roundedness and form borders by [@aliabid94](https://github.com/aliabid94) in [PR 2147](https://github.com/gradio-app/gradio/pull/2147)
+- Better processing of example data prior to creating dataset component by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2147](https://github.com/gradio-app/gradio/pull/2147)
+- Show error on Connection drops by [@aliabid94](https://github.com/aliabid94) in [PR 2147](https://github.com/gradio-app/gradio/pull/2147)
+- 3.2 release! by [@abidlabs](https://github.com/abidlabs) in [PR 2139](https://github.com/gradio-app/gradio/pull/2139)
+- Fixed Named API Requests by [@abidlabs](https://github.com/abidlabs) in [PR 2151](https://github.com/gradio-app/gradio/pull/2151)
+- Quick Fix: Cannot upload Model3D image after clearing it by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2168](https://github.com/gradio-app/gradio/pull/2168)
+- Fixed misleading log when server_name is '0.0.0.0' by [@lamhoangtung](https://github.com/lamhoangtung) in [PR 2176](https://github.com/gradio-app/gradio/pull/2176)
+- Keep embedded PngInfo metadata by [@cobryan05](https://github.com/cobryan05) in [PR 2170](https://github.com/gradio-app/gradio/pull/2170)
+- Skops integration: Load tabular classification and regression models from the hub by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2126](https://github.com/gradio-app/gradio/pull/2126)
+- Respect original filename when cached example files are downloaded by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2145](https://github.com/gradio-app/gradio/pull/2145)
+- Add manual trigger to deploy to pypi by [@abidlabs](https://github.com/abidlabs) in [PR 2192](https://github.com/gradio-app/gradio/pull/2192)
+- Fix bugs with gr.update by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2157](https://github.com/gradio-app/gradio/pull/2157)
+- Make queue per app by [@aliabid94](https://github.com/aliabid94) in [PR 2193](https://github.com/gradio-app/gradio/pull/2193)
+- Preserve Labels In Interpretation Components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2166](https://github.com/gradio-app/gradio/pull/2166)
+- Quick Fix: Multiple file download not working by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2169](https://github.com/gradio-app/gradio/pull/2169)
+- use correct MIME type for js-script file by [@daspartho](https://github.com/daspartho) in [PR 2200](https://github.com/gradio-app/gradio/pull/2200)
+- Add accordion component by [@aliabid94](https://github.com/aliabid94) in [PR 2208](https://github.com/gradio-app/gradio/pull/2208)
+
+### Contributors Shoutout:
+
+- [@lamhoangtung](https://github.com/lamhoangtung) made their first contribution in [PR 2176](https://github.com/gradio-app/gradio/pull/2176)
+- [@cobryan05](https://github.com/cobryan05) made their first contribution in [PR 2170](https://github.com/gradio-app/gradio/pull/2170)
+- [@daspartho](https://github.com/daspartho) made their first contribution in [PR 2200](https://github.com/gradio-app/gradio/pull/2200)
+
+## 3.2
+
+### New Features:
+
+###### 1. Improvements to Queuing 🥇
+
+We've implemented a brand new queuing system based on **web sockets** instead of HTTP long polling. Among other things, this allows us to manage queue sizes better on Hugging Face Spaces. There are also additional queue-related parameters you can add:
+
+- Now supports concurrent workers (parallelization)
+
+```python
+demo = gr.Interface(...)
+demo.queue(concurrency_count=3)
+demo.launch()
+```
+
+- Configure a maximum queue size
+
+```python
+demo = gr.Interface(...)
+demo.queue(max_size=100)
+demo.launch()
+```
+
+- If a user closes their tab / browser, they leave the queue, which means the demo will run faster for everyone else
+
+###### 2. Fixes to Examples
+
+- Dataframe examples will render properly, and look much clearer in the UI: (thanks to PR #2125)
+
+
+
+- Image and Video thumbnails are cropped to look neater and more uniform: (thanks to PR #2109)
+
+
+
+- Other fixes in PR #2131 and #2064 make it easier to design and use Examples
+
+###### 3. Component Fixes 🧱
+
+- Specify the width and height of an image in its style tag (thanks to PR #2133)
+
+```python
+components.Image().style(height=260, width=300)
+```
+
+- Automatic conversion of videos so they are playable in the browser (thanks to PR #2003). Gradio will check if a video's format is playable in the browser and, if it isn't, will automatically convert it to a format that is (mp4).
+- Pass in a json filepath to the Label component (thanks to PR #2083)
+- Randomize the default value of a Slider (thanks to PR #1935)
+
+
+
+- Improvements to State in PR #2100
+
+###### 4. Ability to Randomize Input Sliders and Reload Data whenever the Page Loads
+
+- In some cases, you want to be able to show a different set of input data to every user as they load the page app. For example, you might want to randomize the value of a "seed" `Slider` input. Or you might want to show a `Textbox` with the current date. We now supporting passing _functions_ as the default value in input components. When you pass in a function, it gets **re-evaluated** every time someone loads the demo, allowing you to reload / change data for different users.
+
+Here's an example loading the current date time into an input Textbox:
+
+```python
+import gradio as gr
+import datetime
+
+with gr.Blocks() as demo:
+ gr.Textbox(datetime.datetime.now)
+
+demo.launch()
+```
+
+Note that we don't evaluate the function -- `datetime.datetime.now()` -- we pass in the function itself to get this behavior -- `datetime.datetime.now`
+
+Because randomizing the initial value of `Slider` is a common use case, we've added a `randomize` keyword argument you can use to randomize its initial value:
+
+```python
+import gradio as gr
+demo = gr.Interface(lambda x:x, gr.Slider(0, 10, randomize=True), "number")
+demo.launch()
+```
+
+###### 5. New Guide 🖊️
+
+- [Gradio and W&B Integration](https://gradio.app/Gradio_and_Wandb_Integration/)
+
+### Full Changelog:
+
+- Reset components to original state by setting value to None by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2044](https://github.com/gradio-app/gradio/pull/2044)
+- Cleaning up the way data is processed for components by [@abidlabs](https://github.com/abidlabs) in [PR 1967](https://github.com/gradio-app/gradio/pull/1967)
+- version 3.1.8b by [@abidlabs](https://github.com/abidlabs) in [PR 2063](https://github.com/gradio-app/gradio/pull/2063)
+- Wandb guide by [@AK391](https://github.com/AK391) in [PR 1898](https://github.com/gradio-app/gradio/pull/1898)
+- Add a flagging callback to save json files to a hugging face dataset by [@chrisemezue](https://github.com/chrisemezue) in [PR 1821](https://github.com/gradio-app/gradio/pull/1821)
+- Add data science demos to landing page by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2067](https://github.com/gradio-app/gradio/pull/2067)
+- Hide time series + xgboost demos by default by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2079](https://github.com/gradio-app/gradio/pull/2079)
+- Encourage people to keep trying when queue full by [@apolinario](https://github.com/apolinario) in [PR 2076](https://github.com/gradio-app/gradio/pull/2076)
+- Updated our analytics on creation of Blocks/Interface by [@abidlabs](https://github.com/abidlabs) in [PR 2082](https://github.com/gradio-app/gradio/pull/2082)
+- `Label` component now accepts file paths to `.json` files by [@abidlabs](https://github.com/abidlabs) in [PR 2083](https://github.com/gradio-app/gradio/pull/2083)
+- Fix issues related to demos in Spaces by [@abidlabs](https://github.com/abidlabs) in [PR 2086](https://github.com/gradio-app/gradio/pull/2086)
+- Fix TimeSeries examples not properly displayed in UI by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2064](https://github.com/gradio-app/gradio/pull/2064)
+- Fix infinite requests when doing tab item select by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2070](https://github.com/gradio-app/gradio/pull/2070)
+- Accept deprecated `file` route as well by [@abidlabs](https://github.com/abidlabs) in [PR 2099](https://github.com/gradio-app/gradio/pull/2099)
+- Allow frontend method execution on Block.load event by [@codedealer](https://github.com/codedealer) in [PR 2108](https://github.com/gradio-app/gradio/pull/2108)
+- Improvements to `State` by [@abidlabs](https://github.com/abidlabs) in [PR 2100](https://github.com/gradio-app/gradio/pull/2100)
+- Catch IndexError, KeyError in video_is_playable by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 2113](https://github.com/gradio-app/gradio/pull/2113)
+- Fix: Download button does not respect the filepath returned by the function by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 2073](https://github.com/gradio-app/gradio/pull/2073)
+- Refactoring Layout: Adding column widths, forms, and more. by [@aliabid94](https://github.com/aliabid94) in [PR 2097](https://github.com/gradio-app/gradio/pull/2097)
+- Update CONTRIBUTING.md by [@abidlabs](https://github.com/abidlabs) in [PR 2118](https://github.com/gradio-app/gradio/pull/2118)
+- 2092 df ex by [@pngwn](https://github.com/pngwn) in [PR 2125](https://github.com/gradio-app/gradio/pull/2125)
+- feat(samples table/gallery): Crop thumbs to square by [@ronvoluted](https://github.com/ronvoluted) in [PR 2109](https://github.com/gradio-app/gradio/pull/2109)
+- Some enhancements to `gr.Examples` by [@abidlabs](https://github.com/abidlabs) in [PR 2131](https://github.com/gradio-app/gradio/pull/2131)
+- Image size fix by [@aliabid94](https://github.com/aliabid94) in [PR 2133](https://github.com/gradio-app/gradio/pull/2133)
+
+### Contributors Shoutout:
+
+- [@chrisemezue](https://github.com/chrisemezue) made their first contribution in [PR 1821](https://github.com/gradio-app/gradio/pull/1821)
+- [@apolinario](https://github.com/apolinario) made their first contribution in [PR 2076](https://github.com/gradio-app/gradio/pull/2076)
+- [@codedealer](https://github.com/codedealer) made their first contribution in [PR 2108](https://github.com/gradio-app/gradio/pull/2108)
+
+## 3.1
+
+### New Features:
+
+###### 1. Embedding Demos on Any Website 💻
+
+With PR #1444, Gradio is now distributed as a web component. This means demos can be natively embedded on websites. You'll just need to add two lines: one to load the gradio javascript, and one to link to the demos backend.
+
+Here's a simple example that embeds the demo from a Hugging Face space:
+
+```html
+
+
+```
+
+But you can also embed demos that are running anywhere, you just need to link the demo to `src` instead of `space`. In fact, all the demos on the gradio website are embedded this way:
+
+
+
+Read more in the [Embedding Gradio Demos](https://gradio.app/embedding_gradio_demos) guide.
+
+###### 2. Reload Mode 👨💻
+
+Reload mode helps developers create gradio demos faster by automatically reloading the demo whenever the code changes. It can support development on Python IDEs (VS Code, PyCharm, etc), the terminal, as well as Jupyter notebooks.
+
+If your demo code is in a script named `app.py`, instead of running `python app.py` you can now run `gradio app.py` and that will launch the demo in reload mode:
+
+```bash
+Launching in reload mode on: http://127.0.0.1:7860 (Press CTRL+C to quit)
+Watching...
+WARNING: The --reload flag should not be used in production on Windows.
+```
+
+If you're working from a Jupyter or Colab Notebook, use these magic commands instead: `%load_ext gradio` when you import gradio, and `%%blocks` in the top of the cell with the demo code. Here's an example that shows how much faster the development becomes:
+
+
+
+###### 3. Inpainting Support on `gr.Image()` 🎨
+
+We updated the Image component to add support for inpainting demos. It works by adding `tool="sketch"` as a parameter, that passes both an image and a sketchable mask to your prediction function.
+
+Here's an example from the [LAMA space](https://huggingface.co/spaces/akhaliq/lama):
+
+
+
+###### 4. Markdown and HTML support in Dataframes 🔢
+
+We upgraded the Dataframe component in PR #1684 to support rendering Markdown and HTML inside the cells.
+
+This means you can build Dataframes that look like the following:
+
+
+
+###### 5. `gr.Examples()` for Blocks 🧱
+
+We've added the `gr.Examples` component helper to allow you to add examples to any Blocks demo. This class is a wrapper over the `gr.Dataset` component.
+
+
+
+gr.Examples takes two required parameters:
+
+- `examples` which takes in a nested list
+- `inputs` which takes in a component or list of components
+
+You can read more in the [Examples docs](https://gradio.app/docs/#examples) or the [Adding Examples to your Demos guide](https://gradio.app/adding_examples_to_your_app/).
+
+###### 6. Fixes to Audio Streaming
+
+With [PR 1828](https://github.com/gradio-app/gradio/pull/1828) we now hide the status loading animation, as well as remove the echo in streaming. Check out the [stream_audio](https://github.com/gradio-app/gradio/blob/main/demo/stream_audio/run.py) demo for more or read through our [Real Time Speech Recognition](https://gradio.app/real_time_speech_recognition/) guide.
+
+
+
+### Full Changelog:
+
+- File component: list multiple files and allow for download #1446 by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1681](https://github.com/gradio-app/gradio/pull/1681)
+- Add ColorPicker to docs by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1768](https://github.com/gradio-app/gradio/pull/1768)
+- Mock out requests in TestRequest unit tests by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1794](https://github.com/gradio-app/gradio/pull/1794)
+- Add requirements.txt and test_files to source dist by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1817](https://github.com/gradio-app/gradio/pull/1817)
+- refactor: f-string for tunneling.py by [@nhankiet](https://github.com/nhankiet) in [PR 1819](https://github.com/gradio-app/gradio/pull/1819)
+- Miscellaneous formatting improvements to website by [@aliabd](https://github.com/aliabd) in [PR 1754](https://github.com/gradio-app/gradio/pull/1754)
+- `integrate()` method moved to `Blocks` by [@abidlabs](https://github.com/abidlabs) in [PR 1776](https://github.com/gradio-app/gradio/pull/1776)
+- Add python-3.7 tests by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1818](https://github.com/gradio-app/gradio/pull/1818)
+- Copy test dir in website dockers by [@aliabd](https://github.com/aliabd) in [PR 1827](https://github.com/gradio-app/gradio/pull/1827)
+- Add info to docs on how to set default values for components by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1788](https://github.com/gradio-app/gradio/pull/1788)
+- Embedding Components on Docs by [@aliabd](https://github.com/aliabd) in [PR 1726](https://github.com/gradio-app/gradio/pull/1726)
+- Remove usage of deprecated gr.inputs and gr.outputs from website by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1796](https://github.com/gradio-app/gradio/pull/1796)
+- Some cleanups to the docs page by [@abidlabs](https://github.com/abidlabs) in [PR 1822](https://github.com/gradio-app/gradio/pull/1822)
+
+### Contributors Shoutout:
+
+- [@nhankiet](https://github.com/nhankiet) made their first contribution in [PR 1819](https://github.com/gradio-app/gradio/pull/1819)
+
+## 3.0
+
+###### 🔥 Gradio 3.0 is the biggest update to the library, ever.
+
+### New Features:
+
+###### 1. Blocks 🧱
+
+Blocks is a new, low-level API that allows you to have full control over the data flows and layout of your application. It allows you to build very complex, multi-step applications. For example, you might want to:
+
+- Group together related demos as multiple tabs in one web app
+- Change the layout of your demo instead of just having all of the inputs on the left and outputs on the right
+- Have multi-step interfaces, in which the output of one model becomes the input to the next model, or have more flexible data flows in general
+- Change a component's properties (for example, the choices in a Dropdown) or its visibility based on user input
+
+Here's a simple example that creates the demo below it:
+
+```python
+import gradio as gr
+
+def update(name):
+ return f"Welcome to Gradio, {name}!"
+
+demo = gr.Blocks()
+
+with demo:
+ gr.Markdown(
+ """
+ # Hello World!
+ Start typing below to see the output.
+ """)
+ inp = gr.Textbox(placeholder="What is your name?")
+ out = gr.Textbox()
+
+ inp.change(fn=update,
+ inputs=inp,
+ outputs=out)
+
+demo.launch()
+```
+
+
+
+Read our [Introduction to Blocks](http://gradio.app/introduction_to_blocks/) guide for more, and join the 🎈 [Gradio Blocks Party](https://huggingface.co/spaces/Gradio-Blocks/README)!
+
+###### 2. Our Revamped Design 🎨
+
+We've upgraded our design across the entire library: from components, and layouts all the way to dark mode.
+
+
+
+###### 3. A New Website 💻
+
+We've upgraded [gradio.app](https://gradio.app) to make it cleaner, faster and easier to use. Our docs now come with components and demos embedded directly on the page. So you can quickly get up to speed with what you're looking for.
+
+
+
+###### 4. New Components: Model3D, Dataset, and More..
+
+We've introduced a lot of new components in `3.0`, including `Model3D`, `Dataset`, `Markdown`, `Button` and `Gallery`. You can find all the components and play around with them [here](https://gradio.app/docs/#components).
+
+
+
+### Full Changelog:
+
+- Gradio dash fe by [@pngwn](https://github.com/pngwn) in [PR 807](https://github.com/gradio-app/gradio/pull/807)
+- Blocks components by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 765](https://github.com/gradio-app/gradio/pull/765)
+- Blocks components V2 by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 843](https://github.com/gradio-app/gradio/pull/843)
+- Blocks-Backend-Events by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 844](https://github.com/gradio-app/gradio/pull/844)
+- Interfaces from Blocks by [@aliabid94](https://github.com/aliabid94) in [PR 849](https://github.com/gradio-app/gradio/pull/849)
+- Blocks dev by [@aliabid94](https://github.com/aliabid94) in [PR 853](https://github.com/gradio-app/gradio/pull/853)
+- Started updating demos to use the new `gradio.components` syntax by [@abidlabs](https://github.com/abidlabs) in [PR 848](https://github.com/gradio-app/gradio/pull/848)
+- add test infra + add browser tests to CI by [@pngwn](https://github.com/pngwn) in [PR 852](https://github.com/gradio-app/gradio/pull/852)
+- 854 textbox by [@pngwn](https://github.com/pngwn) in [PR 859](https://github.com/gradio-app/gradio/pull/859)
+- Getting old Python unit tests to pass on `blocks-dev` by [@abidlabs](https://github.com/abidlabs) in [PR 861](https://github.com/gradio-app/gradio/pull/861)
+- initialise chatbot with empty array of messages by [@pngwn](https://github.com/pngwn) in [PR 867](https://github.com/gradio-app/gradio/pull/867)
+- add test for output to input by [@pngwn](https://github.com/pngwn) in [PR 866](https://github.com/gradio-app/gradio/pull/866)
+- More Interface -> Blocks features by [@aliabid94](https://github.com/aliabid94) in [PR 864](https://github.com/gradio-app/gradio/pull/864)
+- Fixing external.py in blocks-dev to reflect the new HF Spaces paths by [@abidlabs](https://github.com/abidlabs) in [PR 879](https://github.com/gradio-app/gradio/pull/879)
+- backend_default_value_refactoring by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 871](https://github.com/gradio-app/gradio/pull/871)
+- fix default_value by [@pngwn](https://github.com/pngwn) in [PR 869](https://github.com/gradio-app/gradio/pull/869)
+- fix buttons by [@aliabid94](https://github.com/aliabid94) in [PR 883](https://github.com/gradio-app/gradio/pull/883)
+- Checking and updating more demos to use 3.0 syntax by [@abidlabs](https://github.com/abidlabs) in [PR 892](https://github.com/gradio-app/gradio/pull/892)
+- Blocks Tests by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 902](https://github.com/gradio-app/gradio/pull/902)
+- Interface fix by [@pngwn](https://github.com/pngwn) in [PR 901](https://github.com/gradio-app/gradio/pull/901)
+- Quick fix: Issue 893 by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 907](https://github.com/gradio-app/gradio/pull/907)
+- 3d Image Component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 775](https://github.com/gradio-app/gradio/pull/775)
+- fix endpoint url in prod by [@pngwn](https://github.com/pngwn) in [PR 911](https://github.com/gradio-app/gradio/pull/911)
+- rename Model3d to Image3D by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 912](https://github.com/gradio-app/gradio/pull/912)
+- update pypi to 2.9.1 by [@abidlabs](https://github.com/abidlabs) in [PR 916](https://github.com/gradio-app/gradio/pull/916)
+- blocks-with-fix by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 917](https://github.com/gradio-app/gradio/pull/917)
+- Restore Interpretation, Live, Auth, Queueing by [@aliabid94](https://github.com/aliabid94) in [PR 915](https://github.com/gradio-app/gradio/pull/915)
+- Allow `Blocks` instances to be used like a `Block` in other `Blocks` by [@abidlabs](https://github.com/abidlabs) in [PR 919](https://github.com/gradio-app/gradio/pull/919)
+- Redesign 1 by [@pngwn](https://github.com/pngwn) in [PR 918](https://github.com/gradio-app/gradio/pull/918)
+- blocks-components-tests by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 904](https://github.com/gradio-app/gradio/pull/904)
+- fix unit + browser tests by [@pngwn](https://github.com/pngwn) in [PR 926](https://github.com/gradio-app/gradio/pull/926)
+- blocks-move-test-data by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 927](https://github.com/gradio-app/gradio/pull/927)
+- remove debounce from form inputs by [@pngwn](https://github.com/pngwn) in [PR 932](https://github.com/gradio-app/gradio/pull/932)
+- reimplement webcam video by [@pngwn](https://github.com/pngwn) in [PR 928](https://github.com/gradio-app/gradio/pull/928)
+- blocks-move-test-data by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 941](https://github.com/gradio-app/gradio/pull/941)
+- allow audio components to take a string value by [@pngwn](https://github.com/pngwn) in [PR 930](https://github.com/gradio-app/gradio/pull/930)
+- static mode for textbox by [@pngwn](https://github.com/pngwn) in [PR 929](https://github.com/gradio-app/gradio/pull/929)
+- fix file upload text by [@pngwn](https://github.com/pngwn) in [PR 931](https://github.com/gradio-app/gradio/pull/931)
+- tabbed-interface-rewritten by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 958](https://github.com/gradio-app/gradio/pull/958)
+- Gan demo fix by [@abidlabs](https://github.com/abidlabs) in [PR 965](https://github.com/gradio-app/gradio/pull/965)
+- Blocks analytics by [@abidlabs](https://github.com/abidlabs) in [PR 947](https://github.com/gradio-app/gradio/pull/947)
+- Blocks page load by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 963](https://github.com/gradio-app/gradio/pull/963)
+- add frontend for page load events by [@pngwn](https://github.com/pngwn) in [PR 967](https://github.com/gradio-app/gradio/pull/967)
+- fix i18n and some tweaks by [@pngwn](https://github.com/pngwn) in [PR 966](https://github.com/gradio-app/gradio/pull/966)
+- add jinja2 to reqs by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 969](https://github.com/gradio-app/gradio/pull/969)
+- Cleaning up `Launchable()` by [@abidlabs](https://github.com/abidlabs) in [PR 968](https://github.com/gradio-app/gradio/pull/968)
+- Fix #944 by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 971](https://github.com/gradio-app/gradio/pull/971)
+- New Blocks Demo: neural instrument cloning by [@abidlabs](https://github.com/abidlabs) in [PR 975](https://github.com/gradio-app/gradio/pull/975)
+- Add huggingface_hub client library by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 973](https://github.com/gradio-app/gradio/pull/973)
+- State and variables by [@aliabid94](https://github.com/aliabid94) in [PR 977](https://github.com/gradio-app/gradio/pull/977)
+- update-components by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 986](https://github.com/gradio-app/gradio/pull/986)
+- ensure dataframe updates as expected by [@pngwn](https://github.com/pngwn) in [PR 981](https://github.com/gradio-app/gradio/pull/981)
+- test-guideline by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 990](https://github.com/gradio-app/gradio/pull/990)
+- Issue #785: add footer by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 972](https://github.com/gradio-app/gradio/pull/972)
+- indentation fix by [@abidlabs](https://github.com/abidlabs) in [PR 993](https://github.com/gradio-app/gradio/pull/993)
+- missing quote by [@aliabd](https://github.com/aliabd) in [PR 996](https://github.com/gradio-app/gradio/pull/996)
+- added interactive parameter to components by [@abidlabs](https://github.com/abidlabs) in [PR 992](https://github.com/gradio-app/gradio/pull/992)
+- custom-components by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 985](https://github.com/gradio-app/gradio/pull/985)
+- Refactor component shortcuts by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 995](https://github.com/gradio-app/gradio/pull/995)
+- Plot Component by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 805](https://github.com/gradio-app/gradio/pull/805)
+- updated PyPi version to 2.9.2 by [@abidlabs](https://github.com/abidlabs) in [PR 1002](https://github.com/gradio-app/gradio/pull/1002)
+- Release 2.9.3 by [@abidlabs](https://github.com/abidlabs) in [PR 1003](https://github.com/gradio-app/gradio/pull/1003)
+- Image3D Examples Fix by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1001](https://github.com/gradio-app/gradio/pull/1001)
+- release 2.9.4 by [@abidlabs](https://github.com/abidlabs) in [PR 1006](https://github.com/gradio-app/gradio/pull/1006)
+- templates import hotfix by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1008](https://github.com/gradio-app/gradio/pull/1008)
+- Progress indicator bar by [@aliabid94](https://github.com/aliabid94) in [PR 997](https://github.com/gradio-app/gradio/pull/997)
+- Fixed image input for absolute path by [@JefferyChiang](https://github.com/JefferyChiang) in [PR 1004](https://github.com/gradio-app/gradio/pull/1004)
+- Model3D + Plot Components by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1010](https://github.com/gradio-app/gradio/pull/1010)
+- Gradio Guides: Creating CryptoPunks with GANs by [@NimaBoscarino](https://github.com/NimaBoscarino) in [PR 1000](https://github.com/gradio-app/gradio/pull/1000)
+- [BIG PR] Gradio blocks & redesigned components by [@abidlabs](https://github.com/abidlabs) in [PR 880](https://github.com/gradio-app/gradio/pull/880)
+- fixed failing test on main by [@abidlabs](https://github.com/abidlabs) in [PR 1023](https://github.com/gradio-app/gradio/pull/1023)
+- Use smaller ASR model in external test by [@abidlabs](https://github.com/abidlabs) in [PR 1024](https://github.com/gradio-app/gradio/pull/1024)
+- updated PyPi version to 2.9.0b by [@abidlabs](https://github.com/abidlabs) in [PR 1026](https://github.com/gradio-app/gradio/pull/1026)
+- Fixing import issues so that the package successfully installs on colab notebooks by [@abidlabs](https://github.com/abidlabs) in [PR 1027](https://github.com/gradio-app/gradio/pull/1027)
+- Update website tracker slackbot by [@aliabd](https://github.com/aliabd) in [PR 1037](https://github.com/gradio-app/gradio/pull/1037)
+- textbox-autoheight by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1009](https://github.com/gradio-app/gradio/pull/1009)
+- Model3D Examples fixes by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1035](https://github.com/gradio-app/gradio/pull/1035)
+- GAN Gradio Guide: Adjustments to iframe heights by [@NimaBoscarino](https://github.com/NimaBoscarino) in [PR 1042](https://github.com/gradio-app/gradio/pull/1042)
+- added better default labels to form components by [@abidlabs](https://github.com/abidlabs) in [PR 1040](https://github.com/gradio-app/gradio/pull/1040)
+- Slackbot web tracker fix by [@aliabd](https://github.com/aliabd) in [PR 1043](https://github.com/gradio-app/gradio/pull/1043)
+- Plot fixes by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1044](https://github.com/gradio-app/gradio/pull/1044)
+- Small fixes to the demos by [@abidlabs](https://github.com/abidlabs) in [PR 1030](https://github.com/gradio-app/gradio/pull/1030)
+- fixing demo issue with website by [@aliabd](https://github.com/aliabd) in [PR 1047](https://github.com/gradio-app/gradio/pull/1047)
+- [hotfix] HighlightedText by [@aliabid94](https://github.com/aliabid94) in [PR 1046](https://github.com/gradio-app/gradio/pull/1046)
+- Update text by [@ronvoluted](https://github.com/ronvoluted) in [PR 1050](https://github.com/gradio-app/gradio/pull/1050)
+- Update CONTRIBUTING.md by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1052](https://github.com/gradio-app/gradio/pull/1052)
+- fix(ui): Increase contrast for footer by [@ronvoluted](https://github.com/ronvoluted) in [PR 1048](https://github.com/gradio-app/gradio/pull/1048)
+- UI design update by [@gary149](https://github.com/gary149) in [PR 1041](https://github.com/gradio-app/gradio/pull/1041)
+- updated PyPi version to 2.9.0b8 by [@abidlabs](https://github.com/abidlabs) in [PR 1059](https://github.com/gradio-app/gradio/pull/1059)
+- Running, testing, and fixing demos by [@abidlabs](https://github.com/abidlabs) in [PR 1060](https://github.com/gradio-app/gradio/pull/1060)
+- Form layout by [@pngwn](https://github.com/pngwn) in [PR 1054](https://github.com/gradio-app/gradio/pull/1054)
+- inputless-interfaces by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1038](https://github.com/gradio-app/gradio/pull/1038)
+- Update PULL_REQUEST_TEMPLATE.md by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1068](https://github.com/gradio-app/gradio/pull/1068)
+- Upgrading node memory to 4gb in website Docker by [@aliabd](https://github.com/aliabd) in [PR 1069](https://github.com/gradio-app/gradio/pull/1069)
+- Website reload error by [@aliabd](https://github.com/aliabd) in [PR 1079](https://github.com/gradio-app/gradio/pull/1079)
+- fixed favicon issue by [@abidlabs](https://github.com/abidlabs) in [PR 1064](https://github.com/gradio-app/gradio/pull/1064)
+- remove-queue-from-events by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1056](https://github.com/gradio-app/gradio/pull/1056)
+- Enable vertex colors for OBJs files by [@radames](https://github.com/radames) in [PR 1074](https://github.com/gradio-app/gradio/pull/1074)
+- Dark text by [@ronvoluted](https://github.com/ronvoluted) in [PR 1049](https://github.com/gradio-app/gradio/pull/1049)
+- Scroll to output by [@pngwn](https://github.com/pngwn) in [PR 1077](https://github.com/gradio-app/gradio/pull/1077)
+- Explicitly list pnpm version 6 in contributing guide by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1085](https://github.com/gradio-app/gradio/pull/1085)
+- hotfix for encrypt issue by [@abidlabs](https://github.com/abidlabs) in [PR 1096](https://github.com/gradio-app/gradio/pull/1096)
+- Release 2.9b9 by [@abidlabs](https://github.com/abidlabs) in [PR 1098](https://github.com/gradio-app/gradio/pull/1098)
+- tweak node circleci settings by [@pngwn](https://github.com/pngwn) in [PR 1091](https://github.com/gradio-app/gradio/pull/1091)
+- Website Reload Error by [@aliabd](https://github.com/aliabd) in [PR 1099](https://github.com/gradio-app/gradio/pull/1099)
+- Website Reload: README in demos docker by [@aliabd](https://github.com/aliabd) in [PR 1100](https://github.com/gradio-app/gradio/pull/1100)
+- Flagging fixes by [@abidlabs](https://github.com/abidlabs) in [PR 1081](https://github.com/gradio-app/gradio/pull/1081)
+- Backend for optional labels by [@abidlabs](https://github.com/abidlabs) in [PR 1080](https://github.com/gradio-app/gradio/pull/1080)
+- Optional labels fe by [@pngwn](https://github.com/pngwn) in [PR 1105](https://github.com/gradio-app/gradio/pull/1105)
+- clean-deprecated-parameters by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1090](https://github.com/gradio-app/gradio/pull/1090)
+- Blocks rendering fix by [@abidlabs](https://github.com/abidlabs) in [PR 1102](https://github.com/gradio-app/gradio/pull/1102)
+- Redos #1106 by [@abidlabs](https://github.com/abidlabs) in [PR 1112](https://github.com/gradio-app/gradio/pull/1112)
+- Interface types: handle input-only, output-only, and unified interfaces by [@abidlabs](https://github.com/abidlabs) in [PR 1108](https://github.com/gradio-app/gradio/pull/1108)
+- Hotfix + New pypi release 2.9b11 by [@abidlabs](https://github.com/abidlabs) in [PR 1118](https://github.com/gradio-app/gradio/pull/1118)
+- issue-checkbox by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1122](https://github.com/gradio-app/gradio/pull/1122)
+- issue-checkbox-hotfix by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1127](https://github.com/gradio-app/gradio/pull/1127)
+- Fix demos in website by [@aliabd](https://github.com/aliabd) in [PR 1130](https://github.com/gradio-app/gradio/pull/1130)
+- Guide for Gradio ONNX model zoo on Huggingface by [@AK391](https://github.com/AK391) in [PR 1073](https://github.com/gradio-app/gradio/pull/1073)
+- ONNX guide fixes by [@aliabd](https://github.com/aliabd) in [PR 1131](https://github.com/gradio-app/gradio/pull/1131)
+- Stacked form inputs css by [@gary149](https://github.com/gary149) in [PR 1134](https://github.com/gradio-app/gradio/pull/1134)
+- made default value in textbox empty string by [@abidlabs](https://github.com/abidlabs) in [PR 1135](https://github.com/gradio-app/gradio/pull/1135)
+- Examples UI by [@gary149](https://github.com/gary149) in [PR 1121](https://github.com/gradio-app/gradio/pull/1121)
+- Chatbot custom color support by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1092](https://github.com/gradio-app/gradio/pull/1092)
+- highlighted text colors by [@pngwn](https://github.com/pngwn) in [PR 1119](https://github.com/gradio-app/gradio/pull/1119)
+- pin to pnpm 6 for now by [@pngwn](https://github.com/pngwn) in [PR 1147](https://github.com/gradio-app/gradio/pull/1147)
+- Restore queue in Blocks by [@aliabid94](https://github.com/aliabid94) in [PR 1137](https://github.com/gradio-app/gradio/pull/1137)
+- add select event for tabitems by [@pngwn](https://github.com/pngwn) in [PR 1154](https://github.com/gradio-app/gradio/pull/1154)
+- max_lines + autoheight for textbox by [@pngwn](https://github.com/pngwn) in [PR 1153](https://github.com/gradio-app/gradio/pull/1153)
+- use color palette for chatbot by [@pngwn](https://github.com/pngwn) in [PR 1152](https://github.com/gradio-app/gradio/pull/1152)
+- Timeseries improvements by [@pngwn](https://github.com/pngwn) in [PR 1149](https://github.com/gradio-app/gradio/pull/1149)
+- move styling for interface panels to frontend by [@pngwn](https://github.com/pngwn) in [PR 1146](https://github.com/gradio-app/gradio/pull/1146)
+- html tweaks by [@pngwn](https://github.com/pngwn) in [PR 1145](https://github.com/gradio-app/gradio/pull/1145)
+- Issue #768: Support passing none to resize and crop image by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1144](https://github.com/gradio-app/gradio/pull/1144)
+- image gallery component + img css by [@aliabid94](https://github.com/aliabid94) in [PR 1140](https://github.com/gradio-app/gradio/pull/1140)
+- networking tweak by [@abidlabs](https://github.com/abidlabs) in [PR 1143](https://github.com/gradio-app/gradio/pull/1143)
+- Allow enabling queue per event listener by [@aliabid94](https://github.com/aliabid94) in [PR 1155](https://github.com/gradio-app/gradio/pull/1155)
+- config hotfix and v. 2.9b23 by [@abidlabs](https://github.com/abidlabs) in [PR 1158](https://github.com/gradio-app/gradio/pull/1158)
+- Custom JS calls by [@aliabid94](https://github.com/aliabid94) in [PR 1082](https://github.com/gradio-app/gradio/pull/1082)
+- Small fixes: queue default fix, ffmpeg installation message by [@abidlabs](https://github.com/abidlabs) in [PR 1159](https://github.com/gradio-app/gradio/pull/1159)
+- formatting by [@abidlabs](https://github.com/abidlabs) in [PR 1161](https://github.com/gradio-app/gradio/pull/1161)
+- enable flex grow for gr-box by [@radames](https://github.com/radames) in [PR 1165](https://github.com/gradio-app/gradio/pull/1165)
+- 1148 loading by [@pngwn](https://github.com/pngwn) in [PR 1164](https://github.com/gradio-app/gradio/pull/1164)
+- Put enable_queue kwarg back in launch() by [@aliabid94](https://github.com/aliabid94) in [PR 1167](https://github.com/gradio-app/gradio/pull/1167)
+- A few small fixes by [@abidlabs](https://github.com/abidlabs) in [PR 1171](https://github.com/gradio-app/gradio/pull/1171)
+- Hotfix for dropdown component by [@abidlabs](https://github.com/abidlabs) in [PR 1172](https://github.com/gradio-app/gradio/pull/1172)
+- use secondary buttons in interface by [@pngwn](https://github.com/pngwn) in [PR 1173](https://github.com/gradio-app/gradio/pull/1173)
+- 1183 component height by [@pngwn](https://github.com/pngwn) in [PR 1185](https://github.com/gradio-app/gradio/pull/1185)
+- 962 dataframe by [@pngwn](https://github.com/pngwn) in [PR 1186](https://github.com/gradio-app/gradio/pull/1186)
+- update-contributing by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1188](https://github.com/gradio-app/gradio/pull/1188)
+- Table tweaks by [@pngwn](https://github.com/pngwn) in [PR 1195](https://github.com/gradio-app/gradio/pull/1195)
+- wrap tab content in column by [@pngwn](https://github.com/pngwn) in [PR 1200](https://github.com/gradio-app/gradio/pull/1200)
+- WIP: Add dark mode support by [@gary149](https://github.com/gary149) in [PR 1187](https://github.com/gradio-app/gradio/pull/1187)
+- Restored /api/predict/ endpoint for Interfaces by [@abidlabs](https://github.com/abidlabs) in [PR 1199](https://github.com/gradio-app/gradio/pull/1199)
+- hltext-label by [@pngwn](https://github.com/pngwn) in [PR 1204](https://github.com/gradio-app/gradio/pull/1204)
+- add copy functionality to json by [@pngwn](https://github.com/pngwn) in [PR 1205](https://github.com/gradio-app/gradio/pull/1205)
+- Update component config by [@aliabid94](https://github.com/aliabid94) in [PR 1089](https://github.com/gradio-app/gradio/pull/1089)
+- fix placeholder prompt by [@pngwn](https://github.com/pngwn) in [PR 1215](https://github.com/gradio-app/gradio/pull/1215)
+- ensure webcam video value is propagated correctly by [@pngwn](https://github.com/pngwn) in [PR 1218](https://github.com/gradio-app/gradio/pull/1218)
+- Automatic word-break in highlighted text, combine_adjacent support by [@aliabid94](https://github.com/aliabid94) in [PR 1209](https://github.com/gradio-app/gradio/pull/1209)
+- async-function-support by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1190](https://github.com/gradio-app/gradio/pull/1190)
+- Sharing fix for assets by [@aliabid94](https://github.com/aliabid94) in [PR 1208](https://github.com/gradio-app/gradio/pull/1208)
+- Hotfixes for course demos by [@abidlabs](https://github.com/abidlabs) in [PR 1222](https://github.com/gradio-app/gradio/pull/1222)
+- Allow Custom CSS by [@aliabid94](https://github.com/aliabid94) in [PR 1170](https://github.com/gradio-app/gradio/pull/1170)
+- share-hotfix by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1226](https://github.com/gradio-app/gradio/pull/1226)
+- tweaks by [@pngwn](https://github.com/pngwn) in [PR 1229](https://github.com/gradio-app/gradio/pull/1229)
+- white space for class concatenation by [@radames](https://github.com/radames) in [PR 1228](https://github.com/gradio-app/gradio/pull/1228)
+- Tweaks by [@pngwn](https://github.com/pngwn) in [PR 1230](https://github.com/gradio-app/gradio/pull/1230)
+- css tweaks by [@pngwn](https://github.com/pngwn) in [PR 1235](https://github.com/gradio-app/gradio/pull/1235)
+- ensure defaults height match for media inputs by [@pngwn](https://github.com/pngwn) in [PR 1236](https://github.com/gradio-app/gradio/pull/1236)
+- Default Label label value by [@radames](https://github.com/radames) in [PR 1239](https://github.com/gradio-app/gradio/pull/1239)
+- update-shortcut-syntax by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1234](https://github.com/gradio-app/gradio/pull/1234)
+- Update version.txt by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1244](https://github.com/gradio-app/gradio/pull/1244)
+- Layout bugs by [@pngwn](https://github.com/pngwn) in [PR 1246](https://github.com/gradio-app/gradio/pull/1246)
+- Update demo by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1253](https://github.com/gradio-app/gradio/pull/1253)
+- Button default name by [@FarukOzderim](https://github.com/FarukOzderim) in [PR 1243](https://github.com/gradio-app/gradio/pull/1243)
+- Labels spacing by [@gary149](https://github.com/gary149) in [PR 1254](https://github.com/gradio-app/gradio/pull/1254)
+- add global loader for gradio app by [@pngwn](https://github.com/pngwn) in [PR 1251](https://github.com/gradio-app/gradio/pull/1251)
+- ui apis for dalle-mini by [@pngwn](https://github.com/pngwn) in [PR 1258](https://github.com/gradio-app/gradio/pull/1258)
+- Add precision to Number, backend only by [@freddyaboulton](https://github.com/freddyaboulton) in [PR 1125](https://github.com/gradio-app/gradio/pull/1125)
+- Website Design Changes by [@abidlabs](https://github.com/abidlabs) in [PR 1015](https://github.com/gradio-app/gradio/pull/1015)
+- Small fixes for multiple demos compatible with 3.0 by [@radames](https://github.com/radames) in [PR 1257](https://github.com/gradio-app/gradio/pull/1257)
+- Issue #1160: Model 3D component not destroyed correctly by [@dawoodkhan82](https://github.com/dawoodkhan82) in [PR 1219](https://github.com/gradio-app/gradio/pull/1219)
+- Fixes to components by [@abidlabs](https://github.com/abidlabs) in [PR 1260](https://github.com/gradio-app/gradio/pull/1260)
+- layout docs by [@abidlabs](https://github.com/abidlabs) in [PR 1263](https://github.com/gradio-app/gradio/pull/1263)
+- Static forms by [@pngwn](https://github.com/pngwn) in [PR 1264](https://github.com/gradio-app/gradio/pull/1264)
+- Cdn assets by [@pngwn](https://github.com/pngwn) in [PR 1265](https://github.com/gradio-app/gradio/pull/1265)
+- update logo by [@gary149](https://github.com/gary149) in [PR 1266](https://github.com/gradio-app/gradio/pull/1266)
+- fix slider by [@aliabid94](https://github.com/aliabid94) in [PR 1268](https://github.com/gradio-app/gradio/pull/1268)
+- maybe fix auth in iframes by [@pngwn](https://github.com/pngwn) in [PR 1261](https://github.com/gradio-app/gradio/pull/1261)
+- Improves "Getting Started" guide by [@abidlabs](https://github.com/abidlabs) in [PR 1269](https://github.com/gradio-app/gradio/pull/1269)
+- Add embedded demos to website by [@aliabid94](https://github.com/aliabid94) in [PR 1270](https://github.com/gradio-app/gradio/pull/1270)
+- Label hotfixes by [@abidlabs](https://github.com/abidlabs) in [PR 1281](https://github.com/gradio-app/gradio/pull/1281)
+- General tweaks by [@pngwn](https://github.com/pngwn) in [PR 1276](https://github.com/gradio-app/gradio/pull/1276)
+- only affect links within the document by [@pngwn](https://github.com/pngwn) in [PR 1282](https://github.com/gradio-app/gradio/pull/1282)
+- release 3.0b9 by [@abidlabs](https://github.com/abidlabs) in [PR 1283](https://github.com/gradio-app/gradio/pull/1283)
+- Dm by [@pngwn](https://github.com/pngwn) in [PR 1284](https://github.com/gradio-app/gradio/pull/1284)
+- Website fixes by [@aliabd](https://github.com/aliabd) in [PR 1286](https://github.com/gradio-app/gradio/pull/1286)
+- Create Streamables by [@aliabid94](https://github.com/aliabid94) in [PR 1279](https://github.com/gradio-app/gradio/pull/1279)
+- ensure table works on mobile by [@pngwn](https://github.com/pngwn) in [PR 1277](https://github.com/gradio-app/gradio/pull/1277)
+- changes by [@aliabid94](https://github.com/aliabid94) in [PR 1287](https://github.com/gradio-app/gradio/pull/1287)
+- demo alignment on landing page by [@aliabd](https://github.com/aliabd) in [PR 1288](https://github.com/gradio-app/gradio/pull/1288)
+- New meta img by [@aliabd](https://github.com/aliabd) in [PR 1289](https://github.com/gradio-app/gradio/pull/1289)
+- updated PyPi version to 3.0 by [@abidlabs](https://github.com/abidlabs) in [PR 1290](https://github.com/gradio-app/gradio/pull/1290)
+- Fix site by [@aliabid94](https://github.com/aliabid94) in [PR 1291](https://github.com/gradio-app/gradio/pull/1291)
+- Mobile responsive guides by [@aliabd](https://github.com/aliabd) in [PR 1293](https://github.com/gradio-app/gradio/pull/1293)
+- Update readme by [@abidlabs](https://github.com/abidlabs) in [PR 1292](https://github.com/gradio-app/gradio/pull/1292)
+- gif by [@abidlabs](https://github.com/abidlabs) in [PR 1296](https://github.com/gradio-app/gradio/pull/1296)
+- Allow decoding headerless b64 string [@1lint](https://github.com/1lint) in [PR 4031](https://github.com/gradio-app/gradio/pull/4031)
+
+### Contributors Shoutout:
+
+- [@JefferyChiang](https://github.com/JefferyChiang) made their first contribution in [PR 1004](https://github.com/gradio-app/gradio/pull/1004)
+- [@NimaBoscarino](https://github.com/NimaBoscarino) made their first contribution in [PR 1000](https://github.com/gradio-app/gradio/pull/1000)
+- [@ronvoluted](https://github.com/ronvoluted) made their first contribution in [PR 1050](https://github.com/gradio-app/gradio/pull/1050)
+- [@radames](https://github.com/radames) made their first contribution in [PR 1074](https://github.com/gradio-app/gradio/pull/1074)
+- [@freddyaboulton](https://github.com/freddyaboulton) made their first contribution in [PR 1085](https://github.com/gradio-app/gradio/pull/1085)
+- [@liteli1987gmail](https://github.com/liteli1987gmail) & [@chenglu](https://github.com/chenglu) made their first contribution in [PR 4767](https://github.com/gradio-app/gradio/pull/4767)
\ No newline at end of file
diff --git a/gradio/__init__.py b/gradio/__init__.py
new file mode 100644
index 0000000..67c0ffc
--- /dev/null
+++ b/gradio/__init__.py
@@ -0,0 +1,290 @@
+import json
+
+import gradio._simple_templates
+import gradio.image_utils
+import gradio.processing_utils
+import gradio.templates
+from gradio import components, layouts, mcp, themes, validators
+from gradio.blocks import Blocks
+from gradio.caching import Cache, cache
+from gradio.chat_interface import ChatInterface
+from gradio.cli import deploy
+from gradio.components import (
+ HTML,
+ JSON,
+ AnnotatedImage,
+ Annotatedimage,
+ Audio,
+ BarPlot,
+ BrowserState,
+ Button,
+ Chatbot,
+ ChatMessage,
+ Checkbox,
+ CheckboxGroup,
+ Checkboxgroup,
+ ClearButton,
+ Code,
+ ColorPicker,
+ Component,
+ DataFrame,
+ Dataframe,
+ Dataset,
+ DateTime,
+ DeepLinkButton,
+ Dialogue,
+ DownloadButton,
+ Dropdown,
+ DuplicateButton,
+ File,
+ FileExplorer,
+ Gallery,
+ Highlight,
+ HighlightedText,
+ Highlightedtext,
+ Image,
+ ImageEditor,
+ ImageSlider,
+ InputHTMLAttributes,
+ Json,
+ Label,
+ LinePlot,
+ LoginButton,
+ Markdown,
+ MessageDict,
+ Model3D,
+ MultimodalTextbox,
+ Navbar,
+ Number,
+ ParamViewer,
+ Plot,
+ Radio,
+ ScatterPlot,
+ Slider,
+ State,
+ Text,
+ Textbox,
+ Timer,
+ UploadButton,
+ Video,
+ WorkflowCanvas,
+ component,
+)
+from gradio.components.audio import WaveformOptions
+from gradio.components.image_editor import (
+ Brush,
+ Eraser,
+ LayerOptions,
+ WatermarkOptions,
+ WebcamOptions,
+)
+from gradio.data_classes import FileData
+from gradio.events import (
+ CopyData,
+ DeletedFileData,
+ DownloadData,
+ EditData,
+ EventData,
+ KeyUpData,
+ LikeData,
+ RetryData,
+ SelectData,
+ UndoData,
+ api,
+ on,
+)
+from gradio.exceptions import Error
+from gradio.external import load, load_chat, load_openapi
+from gradio.flagging import (
+ CSVLogger,
+ FlaggingCallback,
+ SimpleCSVLogger,
+)
+from gradio.helpers import Info, Progress, Success, Warning, skip, update, validate
+from gradio.helpers import create_examples as Examples # noqa: N812
+from gradio.i18n import I18n
+from gradio.interface import Interface, TabbedInterface, close_all
+from gradio.ipython_ext import load_ipython_extension
+from gradio.layouts import (
+ Accordion,
+ Column,
+ Draggable,
+ Group,
+ Row,
+ Sidebar,
+ Step,
+ Tab,
+ TabItem,
+ Tabs,
+ Walkthrough,
+)
+from gradio.media import get_audio, get_file, get_image, get_model3d, get_video
+from gradio.oauth import OAuthProfile, OAuthToken
+from gradio.renderable import render
+from gradio.route_utils import Header
+from gradio.routes import Request, mount_gradio_app
+from gradio.server import Server
+from gradio.templates import (
+ Files,
+ ImageMask,
+ List,
+ Matrix,
+ Mic,
+ Microphone,
+ Numpy,
+ Paint,
+ PlayableVideo,
+ Sketchpad,
+ TextArea,
+)
+from gradio.themes import Base as Theme
+from gradio.utils import NO_RELOAD, FileSize, get_package_version, set_static_paths
+from gradio.workflow import Workflow
+
+# this is the version:
+__version__ = get_package_version()
+
+__all__ = [
+ "Accordion",
+ "AnnotatedImage",
+ "Server",
+ "Annotatedimage",
+ "Audio",
+ "BarPlot",
+ "Blocks",
+ "BrowserState",
+ "Brush",
+ "Button",
+ "CSVLogger",
+ "Cache",
+ "cache",
+ "ChatInterface",
+ "ChatMessage",
+ "Chatbot",
+ "Checkbox",
+ "CheckboxGroup",
+ "Checkboxgroup",
+ "ClearButton",
+ "Code",
+ "ColorPicker",
+ "Column",
+ "Component",
+ "CopyData",
+ "DataFrame",
+ "Dataframe",
+ "Dataset",
+ "DateTime",
+ "Dialogue",
+ "DeletedFileData",
+ "DownloadButton",
+ "DownloadData",
+ "Dropdown",
+ "DuplicateButton",
+ "EditData",
+ "Eraser",
+ "Error",
+ "EventData",
+ "Examples",
+ "File",
+ "FileData",
+ "FileExplorer",
+ "FileSize",
+ "Files",
+ "FlaggingCallback",
+ "Gallery",
+ "Group",
+ "Header",
+ "HTML",
+ "Highlight",
+ "HighlightedText",
+ "Highlightedtext",
+ "Image",
+ "ImageEditor",
+ "ImageSlider",
+ "ImageMask",
+ "Info",
+ "InputHTMLAttributes",
+ "Interface",
+ "JSON",
+ "Json",
+ "KeyUpData",
+ "Label",
+ "LayerOptions",
+ "LikeData",
+ "LinePlot",
+ "List",
+ "LoginButton",
+ "Markdown",
+ "Matrix",
+ "MessageDict",
+ "Mic",
+ "Microphone",
+ "Model3D",
+ "MultimodalTextbox",
+ "NO_RELOAD",
+ "Number",
+ "Numpy",
+ "OAuthProfile",
+ "OAuthToken",
+ "Paint",
+ "ParamViewer",
+ "PlayableVideo",
+ "Plot",
+ "Progress",
+ "Radio",
+ "Request",
+ "RetryData",
+ "Row",
+ "ScatterPlot",
+ "SelectData",
+ "Sidebar",
+ "SimpleCSVLogger",
+ "Sketchpad",
+ "Slider",
+ "State",
+ "Success",
+ "Tab",
+ "TabItem",
+ "TabbedInterface",
+ "Tabs",
+ "Text",
+ "TextArea",
+ "Textbox",
+ "Theme",
+ "Timer",
+ "UndoData",
+ "UploadButton",
+ "Video",
+ "Walkthrough",
+ "Step",
+ "Warning",
+ "WaveformOptions",
+ "WebcamOptions",
+ "WatermarkOptions",
+ "__version__",
+ "close_all",
+ "deploy",
+ "get_package_version",
+ "I18n",
+ "load",
+ "load_chat",
+ "load_ipython_extension",
+ "load_openapi",
+ "mount_gradio_app",
+ "on",
+ "render",
+ "set_static_paths",
+ "skip",
+ "update",
+ "DeepLinkButton",
+ "mcp",
+ "validate",
+ "get_audio",
+ "get_image",
+ "get_video",
+ "get_model3d",
+ "get_file",
+ "validators",
+ "Workflow",
+ "WorkflowCanvas",
+]
diff --git a/gradio/__main__.py b/gradio/__main__.py
new file mode 100644
index 0000000..02e3d6c
--- /dev/null
+++ b/gradio/__main__.py
@@ -0,0 +1,4 @@
+from gradio.cli import cli
+
+if __name__ == "__main__":
+ cli()
diff --git a/gradio/_simple_templates/__init__.py b/gradio/_simple_templates/__init__.py
new file mode 100644
index 0000000..bf5acb3
--- /dev/null
+++ b/gradio/_simple_templates/__init__.py
@@ -0,0 +1,5 @@
+from .simpledropdown import SimpleDropdown
+from .simpleimage import SimpleImage
+from .simpletextbox import SimpleTextbox
+
+__all__ = ["SimpleDropdown", "SimpleTextbox", "SimpleImage"]
diff --git a/gradio/_simple_templates/simpledropdown.py b/gradio/_simple_templates/simpledropdown.py
new file mode 100644
index 0000000..36d3af0
--- /dev/null
+++ b/gradio/_simple_templates/simpledropdown.py
@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+import warnings
+from collections.abc import Callable, Sequence
+from typing import TYPE_CHECKING, Any, Literal
+
+from gradio.components.base import Component, FormComponent
+from gradio.events import Events
+from gradio.i18n import I18nData
+
+if TYPE_CHECKING:
+ from gradio.components import Timer
+
+
+class SimpleDropdown(FormComponent):
+ """
+ Creates a very simple dropdown listing choices from which entries can be selected.
+ """
+
+ EVENTS = [Events.change, Events.input, Events.select]
+
+ def __init__(
+ self,
+ choices: list[str | int | float | tuple[str | I18nData, str | int | float]]
+ | None = None,
+ *,
+ value: str | int | float | Callable | None = None,
+ label: str | I18nData | None = None,
+ info: str | I18nData | None = None,
+ every: Timer | float | None = None,
+ inputs: Component | Sequence[Component] | set[Component] | None = None,
+ show_label: bool | None = None,
+ scale: int | None = None,
+ min_width: int = 160,
+ interactive: bool | None = None,
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = "value",
+ ):
+ """
+ Parameters:
+ choices: A list of string options to choose from. An option can also be a tuple of the form (name, value), where name is the displayed name of the dropdown choice and value is the value to be passed to the function, or returned by the function.
+ value: default value selected in dropdown. If None, no value is selected by default. If a function is provided, the function will be called each time the app loads to set the initial value of this component.
+ label: the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.
+ info: additional component description, appears below the label in smaller font. Supports markdown / HTML syntax.
+ every: Continuously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
+ inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.
+ inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.
+ show_label: if True, will display label.
+ scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.
+ min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
+ interactive: if True, choices in this dropdown will be selectable; if False, selection will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
+ visible: If False, component will be hidden. If "hidden", component will be visually hidden and not take up space in the layout but still exist in the DOM
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ self.choices = (
+ [tuple(c) if isinstance(c, (tuple, list)) else (str(c), c) for c in choices]
+ if choices
+ else []
+ )
+ super().__init__(
+ label=label,
+ info=info,
+ every=every,
+ inputs=inputs,
+ show_label=show_label,
+ scale=scale,
+ min_width=min_width,
+ interactive=interactive,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ value=value,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+
+ def api_info(self) -> dict[str, Any]:
+ return {
+ "type": "string",
+ "enum": [c[1] for c in self.choices],
+ }
+
+ def example_payload(self) -> Any:
+ return self.choices[0][1] if self.choices else None
+
+ def example_value(self) -> Any:
+ return self.choices[0][1] if self.choices else None
+
+ def preprocess(self, payload: str | int | float | None) -> str | int | float | None:
+ """
+ Parameters:
+ payload: the value of the selected dropdown choice
+ Returns:
+ Passes the value of the selected dropdown choice as a `str | int | float`.
+ """
+ return payload
+
+ def _warn_if_invalid_choice(self, y):
+ if y not in [value for _, value in self.choices]:
+ warnings.warn(
+ f"The value passed into gr.Dropdown() is not in the list of choices. Please update the list of choices to include: {y}."
+ )
+
+ def postprocess(self, value):
+ """
+ Parameters:
+ value: Expects a `str | int | float` corresponding to the value of the dropdown entry to be selected.
+ Returns:
+ Returns the value of the selected dropdown entry.
+ """
+ if value is None:
+ return None
+ self._warn_if_invalid_choice(value)
+ return value
+
+ def process_example(self, value):
+ return next((c[0] for c in self.choices if c[1] == value), None)
diff --git a/gradio/_simple_templates/simpleimage.py b/gradio/_simple_templates/simpleimage.py
new file mode 100644
index 0000000..ca5c47e
--- /dev/null
+++ b/gradio/_simple_templates/simpleimage.py
@@ -0,0 +1,121 @@
+"""gr.SimpleImage() component."""
+
+from __future__ import annotations
+
+from collections.abc import Sequence
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Literal
+
+from gradio_client import handle_file
+from gradio_client.documentation import document
+
+from gradio.components.base import Component
+from gradio.data_classes import FileData
+from gradio.events import Events
+from gradio.i18n import I18nData
+
+if TYPE_CHECKING:
+ from gradio.components import Timer
+
+
+@document()
+class SimpleImage(Component):
+ """
+ Creates an image component that can be used to upload images (as an input) or display images (as an output).
+ """
+
+ EVENTS = [
+ Events.clear,
+ Events.change,
+ Events.upload,
+ ]
+
+ data_model = FileData
+
+ def __init__(
+ self,
+ value: str | None = None,
+ *,
+ label: str | I18nData | None = None,
+ every: Timer | float | None = None,
+ inputs: Component | Sequence[Component] | set[Component] | None = None,
+ show_label: bool | None = None,
+ show_download_button: bool = True,
+ container: bool = True,
+ scale: int | None = None,
+ min_width: int = 160,
+ interactive: bool | None = None,
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = "value",
+ ):
+ """
+ Parameters:
+ value: A path or URL for the default value that SimpleImage component is going to take. If a function is provided, the function will be called each time the app loads to set the initial value of this component.
+ label: the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.
+ every: Continuously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
+ inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.
+ show_label: if True, will display label.
+ show_download_button: If True, will display button to download image.
+ container: If True, will place the component in a container - providing some extra padding around the border.
+ scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.
+ min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
+ interactive: if True, will allow users to upload and edit an image; if False, can only be used to display images. If not provided, this is inferred based on whether the component is used as an input or output.
+ visible: If False, component will be hidden. If "hidden", component will be visually hidden and not take up space in the layout but still exist in the DOM
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ self.show_download_button = show_download_button
+ super().__init__(
+ label=label,
+ every=every,
+ inputs=inputs,
+ show_label=show_label,
+ container=container,
+ scale=scale,
+ min_width=min_width,
+ interactive=interactive,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ value=value,
+ )
+
+ def preprocess(self, payload: FileData | None) -> str | None:
+ """
+ Parameters:
+ payload: A FileData object containing the image data.
+ Returns:
+ A `str` containing the path to the image.
+ """
+ if payload is None:
+ return None
+ return payload.path
+
+ def postprocess(self, value: str | Path | None) -> FileData | None:
+ """
+ Parameters:
+ value: Expects a `str` or `pathlib.Path` object containing the path to the image.
+ Returns:
+ A FileData object containing the image data.
+ """
+ if value is None:
+ return None
+ return FileData(path=str(value), orig_name=Path(value).name)
+
+ def example_payload(self) -> Any:
+ return handle_file(
+ "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png"
+ )
+
+ def example_value(self) -> Any:
+ return "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png"
diff --git a/gradio/_simple_templates/simpletextbox.py b/gradio/_simple_templates/simpletextbox.py
new file mode 100644
index 0000000..ea32af3
--- /dev/null
+++ b/gradio/_simple_templates/simpletextbox.py
@@ -0,0 +1,108 @@
+from __future__ import annotations
+
+from collections.abc import Callable, Sequence
+from typing import TYPE_CHECKING, Any, Literal
+
+from gradio.components.base import Component, FormComponent
+from gradio.events import Events
+from gradio.i18n import I18nData
+
+if TYPE_CHECKING:
+ from gradio.components import Timer
+
+
+class SimpleTextbox(FormComponent):
+ """
+ Creates a very simple textbox for user to enter string input or display string output.
+ """
+
+ EVENTS = [
+ Events.change,
+ Events.input,
+ Events.submit,
+ ]
+
+ def __init__(
+ self,
+ value: str | Callable | None = None,
+ *,
+ placeholder: str | None = None,
+ label: str | I18nData | None = None,
+ every: Timer | float | None = None,
+ inputs: Component | Sequence[Component] | set[Component] | None = None,
+ show_label: bool | None = None,
+ scale: int | None = None,
+ min_width: int = 160,
+ interactive: bool | None = None,
+ visible: bool | Literal["hidden"] = True,
+ rtl: bool = False,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = "value",
+ ):
+ """
+ Parameters:
+ value: default text to provide in textbox. If a function is provided, the function will be called each time the app loads to set the initial value of this component.
+ placeholder: placeholder hint to provide behind textbox.
+ label: the label for this component, displayed above the component if `show_label` is `True` and is also used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component corresponds to.
+ every: Continuously calls `value` to recalculate it if `value` is a function (has no effect otherwise). Can provide a Timer whose tick resets `value`, or a float that provides the regular interval for the reset Timer.
+ inputs: Components that are used as inputs to calculate `value` if `value` is a function (has no effect otherwise). `value` is recalculated any time the inputs change.
+ show_label: if True, will display label.
+ scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.
+ min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
+ interactive: if True, will be rendered as an editable textbox; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
+ visible: If False, component will be hidden. If "hidden", component will be visually hidden and not take up space in the layout but still exist in the DOM
+ rtl: If True and `type` is "text", sets the direction of the text to right-to-left (cursor appears on the left of the text). Default is False, which renders cursor on the right.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ self.placeholder = placeholder
+ self.rtl = rtl
+ super().__init__(
+ label=label,
+ every=every,
+ inputs=inputs,
+ show_label=show_label,
+ scale=scale,
+ min_width=min_width,
+ interactive=interactive,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ value=value,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+
+ def preprocess(self, payload: str | None) -> str | None:
+ """
+ Parameters:
+ payload: the text entered in the textarea.
+ Returns:
+ Passes text value as a {str} into the function.
+ """
+ return None if payload is None else str(payload)
+
+ def postprocess(self, value: str | None) -> str | None:
+ """
+ Parameters:
+ value: Expects a {str} returned from function and sets textarea value to it.
+ Returns:
+ The value to display in the textarea.
+ """
+ return None if value is None else str(value)
+
+ def api_info(self) -> dict[str, Any]:
+ return {"type": "string"}
+
+ def example_payload(self) -> Any:
+ return "Hello!!"
+
+ def example_value(self) -> Any:
+ return "Hello!!"
diff --git a/gradio/_vendor/__init__.py b/gradio/_vendor/__init__.py
new file mode 100644
index 0000000..00ca497
--- /dev/null
+++ b/gradio/_vendor/__init__.py
@@ -0,0 +1 @@
+"""Vendored third-party Python dependencies used by Gradio."""
diff --git a/gradio/_vendor/aiofiles/__init__.py b/gradio/_vendor/aiofiles/__init__.py
new file mode 100644
index 0000000..5f62158
--- /dev/null
+++ b/gradio/_vendor/aiofiles/__init__.py
@@ -0,0 +1,23 @@
+"""Utilities for asyncio-friendly file handling."""
+
+from . import tempfile
+from .threadpool import (
+ open,
+ stderr,
+ stderr_bytes,
+ stdin,
+ stdin_bytes,
+ stdout,
+ stdout_bytes,
+)
+
+__all__ = [
+ "open",
+ "tempfile",
+ "stdin",
+ "stdout",
+ "stderr",
+ "stdin_bytes",
+ "stdout_bytes",
+ "stderr_bytes",
+]
diff --git a/gradio/_vendor/aiofiles/base.py b/gradio/_vendor/aiofiles/base.py
new file mode 100644
index 0000000..178bd21
--- /dev/null
+++ b/gradio/_vendor/aiofiles/base.py
@@ -0,0 +1,70 @@
+"""Various base classes."""
+
+from asyncio import get_running_loop
+from collections.abc import Awaitable
+from contextlib import AbstractAsyncContextManager
+
+
+class AsyncBase:
+ def __init__(self, file, loop, executor):
+ self._file = file
+ self._executor = executor
+ self._ref_loop = loop
+
+ @property
+ def _loop(self):
+ return self._ref_loop or get_running_loop()
+
+ def __aiter__(self):
+ """We are our own iterator."""
+ return self
+
+ def __repr__(self):
+ return super().__repr__() + " wrapping " + repr(self._file)
+
+ async def __anext__(self):
+ """Simulate normal file iteration."""
+ line = await self.readline()
+ if line:
+ return line
+ else:
+ raise StopAsyncIteration
+
+
+class AsyncIndirectBase(AsyncBase):
+ def __init__(self, name, loop, executor, indirect):
+ self._indirect = indirect
+ self._name = name
+ super().__init__(None, loop, executor)
+
+ @property
+ def _file(self):
+ return self._indirect()
+
+ @_file.setter
+ def _file(self, v):
+ pass # discard writes
+
+
+class AiofilesContextManager(Awaitable, AbstractAsyncContextManager):
+ """An adjusted async context manager for aiofiles."""
+
+ __slots__ = ("_coro", "_obj")
+
+ def __init__(self, coro):
+ self._coro = coro
+ self._obj = None
+
+ def __await__(self):
+ if self._obj is None:
+ self._obj = yield from self._coro.__await__()
+ return self._obj
+
+ async def __aenter__(self):
+ return await self
+
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
+ await get_running_loop().run_in_executor(
+ None, self._obj._file.__exit__, exc_type, exc_val, exc_tb
+ )
+ self._obj = None
diff --git a/gradio/_vendor/aiofiles/os.py b/gradio/_vendor/aiofiles/os.py
new file mode 100644
index 0000000..92243fa
--- /dev/null
+++ b/gradio/_vendor/aiofiles/os.py
@@ -0,0 +1,58 @@
+"""Async executor versions of file functions from the os module."""
+
+import os
+
+from . import ospath as path
+from .ospath import wrap
+
+__all__ = [
+ "path",
+ "stat",
+ "rename",
+ "renames",
+ "replace",
+ "remove",
+ "unlink",
+ "mkdir",
+ "makedirs",
+ "rmdir",
+ "removedirs",
+ "symlink",
+ "readlink",
+ "listdir",
+ "scandir",
+ "access",
+ "wrap",
+ "getcwd",
+]
+if hasattr(os, "link"):
+ __all__ += ["link"]
+if hasattr(os, "sendfile"):
+ __all__ += ["sendfile"]
+if hasattr(os, "statvfs"):
+ __all__ += ["statvfs"]
+
+
+stat = wrap(os.stat)
+rename = wrap(os.rename)
+renames = wrap(os.renames)
+replace = wrap(os.replace)
+remove = wrap(os.remove)
+unlink = wrap(os.unlink)
+mkdir = wrap(os.mkdir)
+makedirs = wrap(os.makedirs)
+rmdir = wrap(os.rmdir)
+removedirs = wrap(os.removedirs)
+symlink = wrap(os.symlink)
+readlink = wrap(os.readlink)
+listdir = wrap(os.listdir)
+scandir = wrap(os.scandir)
+access = wrap(os.access)
+getcwd = wrap(os.getcwd)
+
+if hasattr(os, "link"):
+ link = wrap(os.link)
+if hasattr(os, "sendfile"):
+ sendfile = wrap(os.sendfile)
+if hasattr(os, "statvfs"):
+ statvfs = wrap(os.statvfs)
diff --git a/gradio/_vendor/aiofiles/ospath.py b/gradio/_vendor/aiofiles/ospath.py
new file mode 100644
index 0000000..387d68d
--- /dev/null
+++ b/gradio/_vendor/aiofiles/ospath.py
@@ -0,0 +1,30 @@
+"""Async executor versions of file functions from the os.path module."""
+
+import asyncio
+from functools import partial, wraps
+from os import path
+
+
+def wrap(func):
+ @wraps(func)
+ async def run(*args, loop=None, executor=None, **kwargs):
+ if loop is None:
+ loop = asyncio.get_running_loop()
+ pfunc = partial(func, *args, **kwargs)
+ return await loop.run_in_executor(executor, pfunc)
+
+ return run
+
+
+exists = wrap(path.exists)
+isfile = wrap(path.isfile)
+isdir = wrap(path.isdir)
+islink = wrap(path.islink)
+ismount = wrap(path.ismount)
+getsize = wrap(path.getsize)
+getmtime = wrap(path.getmtime)
+getatime = wrap(path.getatime)
+getctime = wrap(path.getctime)
+samefile = wrap(path.samefile)
+sameopenfile = wrap(path.sameopenfile)
+abspath = wrap(path.abspath)
diff --git a/gradio/_vendor/aiofiles/tempfile/__init__.py b/gradio/_vendor/aiofiles/tempfile/__init__.py
new file mode 100644
index 0000000..b9c0db0
--- /dev/null
+++ b/gradio/_vendor/aiofiles/tempfile/__init__.py
@@ -0,0 +1,357 @@
+import asyncio
+import sys
+from functools import partial, singledispatch
+from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOBase
+from tempfile import NamedTemporaryFile as syncNamedTemporaryFile
+from tempfile import SpooledTemporaryFile as syncSpooledTemporaryFile
+from tempfile import TemporaryDirectory as syncTemporaryDirectory
+from tempfile import TemporaryFile as syncTemporaryFile
+from tempfile import _TemporaryFileWrapper as syncTemporaryFileWrapper
+
+from ..base import AiofilesContextManager
+from ..threadpool.binary import AsyncBufferedIOBase, AsyncBufferedReader, AsyncFileIO
+from ..threadpool.text import AsyncTextIOWrapper
+from .temptypes import AsyncSpooledTemporaryFile, AsyncTemporaryDirectory
+
+__all__ = [
+ "NamedTemporaryFile",
+ "TemporaryFile",
+ "SpooledTemporaryFile",
+ "TemporaryDirectory",
+]
+
+
+# ================================================================
+# Public methods for async open and return of temp file/directory
+# objects with async interface
+# ================================================================
+if sys.version_info >= (3, 12):
+
+ def NamedTemporaryFile(
+ mode="w+b",
+ buffering=-1,
+ encoding=None,
+ newline=None,
+ suffix=None,
+ prefix=None,
+ dir=None,
+ delete=True,
+ delete_on_close=True,
+ loop=None,
+ executor=None,
+ ):
+ """Async open a named temporary file"""
+ return AiofilesContextManager(
+ _temporary_file(
+ named=True,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ delete=delete,
+ delete_on_close=delete_on_close,
+ loop=loop,
+ executor=executor,
+ )
+ )
+
+else:
+
+ def NamedTemporaryFile(
+ mode="w+b",
+ buffering=-1,
+ encoding=None,
+ newline=None,
+ suffix=None,
+ prefix=None,
+ dir=None,
+ delete=True,
+ loop=None,
+ executor=None,
+ ):
+ """Async open a named temporary file"""
+ return AiofilesContextManager(
+ _temporary_file(
+ named=True,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ delete=delete,
+ loop=loop,
+ executor=executor,
+ )
+ )
+
+
+def TemporaryFile(
+ mode="w+b",
+ buffering=-1,
+ encoding=None,
+ newline=None,
+ suffix=None,
+ prefix=None,
+ dir=None,
+ loop=None,
+ executor=None,
+):
+ """Async open an unnamed temporary file"""
+ return AiofilesContextManager(
+ _temporary_file(
+ named=False,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ loop=loop,
+ executor=executor,
+ )
+ )
+
+
+def SpooledTemporaryFile(
+ max_size=0,
+ mode="w+b",
+ buffering=-1,
+ encoding=None,
+ newline=None,
+ suffix=None,
+ prefix=None,
+ dir=None,
+ loop=None,
+ executor=None,
+):
+ """Async open a spooled temporary file"""
+ return AiofilesContextManager(
+ _spooled_temporary_file(
+ max_size=max_size,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ loop=loop,
+ executor=executor,
+ )
+ )
+
+
+def TemporaryDirectory(suffix=None, prefix=None, dir=None, loop=None, executor=None):
+ """Async open a temporary directory"""
+ return AiofilesContextManagerTempDir(
+ _temporary_directory(
+ suffix=suffix, prefix=prefix, dir=dir, loop=loop, executor=executor
+ )
+ )
+
+
+# =========================================================
+# Internal coroutines to open new temp files/directories
+# =========================================================
+if sys.version_info >= (3, 12):
+
+ async def _temporary_file(
+ named=True,
+ mode="w+b",
+ buffering=-1,
+ encoding=None,
+ newline=None,
+ suffix=None,
+ prefix=None,
+ dir=None,
+ delete=True,
+ delete_on_close=True,
+ loop=None,
+ executor=None,
+ max_size=0,
+ ):
+ """Async method to open a temporary file with async interface"""
+ if loop is None:
+ loop = asyncio.get_running_loop()
+
+ if named:
+ cb = partial(
+ syncNamedTemporaryFile,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ delete=delete,
+ delete_on_close=delete_on_close,
+ )
+ else:
+ cb = partial(
+ syncTemporaryFile,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ )
+
+ f = await loop.run_in_executor(executor, cb)
+
+ # Wrap based on type of underlying IO object
+ if type(f) is syncTemporaryFileWrapper:
+ # _TemporaryFileWrapper was used (named files)
+ result = wrap(f.file, f, loop=loop, executor=executor)
+ result._closer = f._closer
+ return result
+ else:
+ # IO object was returned directly without wrapper
+ return wrap(f, f, loop=loop, executor=executor)
+
+else:
+
+ async def _temporary_file(
+ named=True,
+ mode="w+b",
+ buffering=-1,
+ encoding=None,
+ newline=None,
+ suffix=None,
+ prefix=None,
+ dir=None,
+ delete=True,
+ loop=None,
+ executor=None,
+ max_size=0,
+ ):
+ """Async method to open a temporary file with async interface"""
+ if loop is None:
+ loop = asyncio.get_running_loop()
+
+ if named:
+ cb = partial(
+ syncNamedTemporaryFile,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ delete=delete,
+ )
+ else:
+ cb = partial(
+ syncTemporaryFile,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ )
+
+ f = await loop.run_in_executor(executor, cb)
+
+ # Wrap based on type of underlying IO object
+ if type(f) is syncTemporaryFileWrapper:
+ # _TemporaryFileWrapper was used (named files)
+ result = wrap(f.file, f, loop=loop, executor=executor)
+ # add delete property
+ result.delete = f.delete
+ return result
+ else:
+ # IO object was returned directly without wrapper
+ return wrap(f, f, loop=loop, executor=executor)
+
+
+async def _spooled_temporary_file(
+ max_size=0,
+ mode="w+b",
+ buffering=-1,
+ encoding=None,
+ newline=None,
+ suffix=None,
+ prefix=None,
+ dir=None,
+ loop=None,
+ executor=None,
+):
+ """Open a spooled temporary file with async interface"""
+ if loop is None:
+ loop = asyncio.get_running_loop()
+
+ cb = partial(
+ syncSpooledTemporaryFile,
+ max_size=max_size,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ newline=newline,
+ suffix=suffix,
+ prefix=prefix,
+ dir=dir,
+ )
+
+ f = await loop.run_in_executor(executor, cb)
+
+ # Single interface provided by SpooledTemporaryFile for all modes
+ return AsyncSpooledTemporaryFile(f, loop=loop, executor=executor)
+
+
+async def _temporary_directory(
+ suffix=None, prefix=None, dir=None, loop=None, executor=None
+):
+ """Async method to open a temporary directory with async interface"""
+ if loop is None:
+ loop = asyncio.get_running_loop()
+
+ cb = partial(syncTemporaryDirectory, suffix, prefix, dir)
+ f = await loop.run_in_executor(executor, cb)
+
+ return AsyncTemporaryDirectory(f, loop=loop, executor=executor)
+
+
+class AiofilesContextManagerTempDir(AiofilesContextManager):
+ """With returns the directory location, not the object (matching sync lib)"""
+
+ async def __aenter__(self):
+ self._obj = await self._coro
+ return self._obj.name
+
+
+@singledispatch
+def wrap(base_io_obj, file, *, loop=None, executor=None):
+ """Wrap the object with interface based on type of underlying IO"""
+ raise TypeError(f"Unsupported IO type: {base_io_obj}")
+
+
+@wrap.register(TextIOBase)
+def _(base_io_obj, file, *, loop=None, executor=None):
+ return AsyncTextIOWrapper(file, loop=loop, executor=executor)
+
+
+@wrap.register(BufferedWriter)
+def _(base_io_obj, file, *, loop=None, executor=None):
+ return AsyncBufferedIOBase(file, loop=loop, executor=executor)
+
+
+@wrap.register(BufferedReader)
+@wrap.register(BufferedRandom)
+def _(base_io_obj, file, *, loop=None, executor=None):
+ return AsyncBufferedReader(file, loop=loop, executor=executor)
+
+
+@wrap.register(FileIO)
+def _(base_io_obj, file, *, loop=None, executor=None):
+ return AsyncFileIO(file, loop=loop, executor=executor)
diff --git a/gradio/_vendor/aiofiles/tempfile/temptypes.py b/gradio/_vendor/aiofiles/tempfile/temptypes.py
new file mode 100644
index 0000000..e7d29ca
--- /dev/null
+++ b/gradio/_vendor/aiofiles/tempfile/temptypes.py
@@ -0,0 +1,70 @@
+"""Async wrappers for spooled temp files and temp directory objects"""
+
+from functools import partial
+
+from ..base import AsyncBase
+from ..threadpool.utils import (
+ cond_delegate_to_executor,
+ delegate_to_executor,
+ proxy_property_directly,
+)
+
+
+@delegate_to_executor("fileno", "rollover")
+@cond_delegate_to_executor(
+ "close",
+ "flush",
+ "isatty",
+ "read",
+ "readline",
+ "readlines",
+ "seek",
+ "tell",
+ "truncate",
+)
+@proxy_property_directly("closed", "encoding", "mode", "name", "newlines")
+class AsyncSpooledTemporaryFile(AsyncBase):
+ """Async wrapper for SpooledTemporaryFile class"""
+
+ async def _check(self):
+ if self._file._rolled:
+ return
+ max_size = self._file._max_size
+ if max_size and self._file.tell() > max_size:
+ await self.rollover()
+
+ async def write(self, s):
+ """Implementation to anticipate rollover"""
+ if self._file._rolled:
+ cb = partial(self._file.write, s)
+ return await self._loop.run_in_executor(self._executor, cb)
+ else:
+ file = self._file._file # reference underlying base IO object
+ rv = file.write(s)
+ await self._check()
+ return rv
+
+ async def writelines(self, iterable):
+ """Implementation to anticipate rollover"""
+ if self._file._rolled:
+ cb = partial(self._file.writelines, iterable)
+ return await self._loop.run_in_executor(self._executor, cb)
+ else:
+ file = self._file._file # reference underlying base IO object
+ rv = file.writelines(iterable)
+ await self._check()
+ return rv
+
+
+@delegate_to_executor("cleanup")
+@proxy_property_directly("name")
+class AsyncTemporaryDirectory:
+ """Async wrapper for TemporaryDirectory class"""
+
+ def __init__(self, file, loop, executor):
+ self._file = file
+ self._loop = loop
+ self._executor = executor
+
+ async def close(self):
+ await self.cleanup()
diff --git a/gradio/_vendor/aiofiles/threadpool/__init__.py b/gradio/_vendor/aiofiles/threadpool/__init__.py
new file mode 100644
index 0000000..3ebbc48
--- /dev/null
+++ b/gradio/_vendor/aiofiles/threadpool/__init__.py
@@ -0,0 +1,140 @@
+"""Handle files using a thread pool executor."""
+
+import asyncio
+import sys
+from functools import partial, singledispatch
+from io import (
+ BufferedIOBase,
+ BufferedRandom,
+ BufferedReader,
+ BufferedWriter,
+ FileIO,
+ TextIOBase,
+)
+
+from ..base import AiofilesContextManager
+from .binary import (
+ AsyncBufferedIOBase,
+ AsyncBufferedReader,
+ AsyncFileIO,
+ AsyncIndirectBufferedIOBase,
+)
+from .text import AsyncTextIndirectIOWrapper, AsyncTextIOWrapper
+
+sync_open = open
+
+__all__ = (
+ "open",
+ "stdin",
+ "stdout",
+ "stderr",
+ "stdin_bytes",
+ "stdout_bytes",
+ "stderr_bytes",
+)
+
+
+def open(
+ file,
+ mode="r",
+ buffering=-1,
+ encoding=None,
+ errors=None,
+ newline=None,
+ closefd=True,
+ opener=None,
+ *,
+ loop=None,
+ executor=None,
+):
+ return AiofilesContextManager(
+ _open(
+ file,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ errors=errors,
+ newline=newline,
+ closefd=closefd,
+ opener=opener,
+ loop=loop,
+ executor=executor,
+ )
+ )
+
+
+async def _open(
+ file,
+ mode="r",
+ buffering=-1,
+ encoding=None,
+ errors=None,
+ newline=None,
+ closefd=True,
+ opener=None,
+ *,
+ loop=None,
+ executor=None,
+):
+ """Open an asyncio file."""
+ if loop is None:
+ loop = asyncio.get_running_loop()
+ cb = partial(
+ sync_open,
+ file,
+ mode=mode,
+ buffering=buffering,
+ encoding=encoding,
+ errors=errors,
+ newline=newline,
+ closefd=closefd,
+ opener=opener,
+ )
+ f = await loop.run_in_executor(executor, cb)
+
+ return wrap(f, loop=loop, executor=executor)
+
+
+@singledispatch
+def wrap(file, *, loop=None, executor=None):
+ raise TypeError(f"Unsupported io type: {file}.")
+
+
+@wrap.register(TextIOBase)
+def _(file, *, loop=None, executor=None):
+ return AsyncTextIOWrapper(file, loop=loop, executor=executor)
+
+
+@wrap.register(BufferedWriter)
+@wrap.register(BufferedIOBase)
+def _(file, *, loop=None, executor=None):
+ return AsyncBufferedIOBase(file, loop=loop, executor=executor)
+
+
+@wrap.register(BufferedReader)
+@wrap.register(BufferedRandom)
+def _(file, *, loop=None, executor=None):
+ return AsyncBufferedReader(file, loop=loop, executor=executor)
+
+
+@wrap.register(FileIO)
+def _(file, *, loop=None, executor=None):
+ return AsyncFileIO(file, loop=loop, executor=executor)
+
+
+stdin = AsyncTextIndirectIOWrapper("sys.stdin", None, None, indirect=lambda: sys.stdin)
+stdout = AsyncTextIndirectIOWrapper(
+ "sys.stdout", None, None, indirect=lambda: sys.stdout
+)
+stderr = AsyncTextIndirectIOWrapper(
+ "sys.stderr", None, None, indirect=lambda: sys.stderr
+)
+stdin_bytes = AsyncIndirectBufferedIOBase(
+ "sys.stdin.buffer", None, None, indirect=lambda: sys.stdin.buffer
+)
+stdout_bytes = AsyncIndirectBufferedIOBase(
+ "sys.stdout.buffer", None, None, indirect=lambda: sys.stdout.buffer
+)
+stderr_bytes = AsyncIndirectBufferedIOBase(
+ "sys.stderr.buffer", None, None, indirect=lambda: sys.stderr.buffer
+)
diff --git a/gradio/_vendor/aiofiles/threadpool/binary.py b/gradio/_vendor/aiofiles/threadpool/binary.py
new file mode 100644
index 0000000..63fcaff
--- /dev/null
+++ b/gradio/_vendor/aiofiles/threadpool/binary.py
@@ -0,0 +1,104 @@
+from ..base import AsyncBase, AsyncIndirectBase
+from .utils import delegate_to_executor, proxy_method_directly, proxy_property_directly
+
+
+@delegate_to_executor(
+ "close",
+ "flush",
+ "isatty",
+ "read",
+ "read1",
+ "readinto",
+ "readline",
+ "readlines",
+ "seek",
+ "seekable",
+ "tell",
+ "truncate",
+ "writable",
+ "write",
+ "writelines",
+)
+@proxy_method_directly("detach", "fileno", "readable")
+@proxy_property_directly("closed", "raw", "name", "mode")
+class AsyncBufferedIOBase(AsyncBase):
+ """The asyncio executor version of io.BufferedWriter and BufferedIOBase."""
+
+
+@delegate_to_executor("peek")
+class AsyncBufferedReader(AsyncBufferedIOBase):
+ """The asyncio executor version of io.BufferedReader and Random."""
+
+
+@delegate_to_executor(
+ "close",
+ "flush",
+ "isatty",
+ "read",
+ "readall",
+ "readinto",
+ "readline",
+ "readlines",
+ "seek",
+ "seekable",
+ "tell",
+ "truncate",
+ "writable",
+ "write",
+ "writelines",
+)
+@proxy_method_directly("fileno", "readable")
+@proxy_property_directly("closed", "name", "mode")
+class AsyncFileIO(AsyncBase):
+ """The asyncio executor version of io.FileIO."""
+
+
+@delegate_to_executor(
+ "close",
+ "flush",
+ "isatty",
+ "read",
+ "read1",
+ "readinto",
+ "readline",
+ "readlines",
+ "seek",
+ "seekable",
+ "tell",
+ "truncate",
+ "writable",
+ "write",
+ "writelines",
+)
+@proxy_method_directly("detach", "fileno", "readable")
+@proxy_property_directly("closed", "raw", "name", "mode")
+class AsyncIndirectBufferedIOBase(AsyncIndirectBase):
+ """The indirect asyncio executor version of io.BufferedWriter and BufferedIOBase."""
+
+
+@delegate_to_executor("peek")
+class AsyncIndirectBufferedReader(AsyncIndirectBufferedIOBase):
+ """The indirect asyncio executor version of io.BufferedReader and Random."""
+
+
+@delegate_to_executor(
+ "close",
+ "flush",
+ "isatty",
+ "read",
+ "readall",
+ "readinto",
+ "readline",
+ "readlines",
+ "seek",
+ "seekable",
+ "tell",
+ "truncate",
+ "writable",
+ "write",
+ "writelines",
+)
+@proxy_method_directly("fileno", "readable")
+@proxy_property_directly("closed", "name", "mode")
+class AsyncIndirectFileIO(AsyncIndirectBase):
+ """The indirect asyncio executor version of io.FileIO."""
diff --git a/gradio/_vendor/aiofiles/threadpool/text.py b/gradio/_vendor/aiofiles/threadpool/text.py
new file mode 100644
index 0000000..0e62590
--- /dev/null
+++ b/gradio/_vendor/aiofiles/threadpool/text.py
@@ -0,0 +1,64 @@
+from ..base import AsyncBase, AsyncIndirectBase
+from .utils import delegate_to_executor, proxy_method_directly, proxy_property_directly
+
+
+@delegate_to_executor(
+ "close",
+ "flush",
+ "isatty",
+ "read",
+ "readable",
+ "readline",
+ "readlines",
+ "seek",
+ "seekable",
+ "tell",
+ "truncate",
+ "write",
+ "writable",
+ "writelines",
+)
+@proxy_method_directly("detach", "fileno", "readable")
+@proxy_property_directly(
+ "buffer",
+ "closed",
+ "encoding",
+ "errors",
+ "line_buffering",
+ "newlines",
+ "name",
+ "mode",
+)
+class AsyncTextIOWrapper(AsyncBase):
+ """The asyncio executor version of io.TextIOWrapper."""
+
+
+@delegate_to_executor(
+ "close",
+ "flush",
+ "isatty",
+ "read",
+ "readable",
+ "readline",
+ "readlines",
+ "seek",
+ "seekable",
+ "tell",
+ "truncate",
+ "write",
+ "writable",
+ "writelines",
+)
+@proxy_method_directly("detach", "fileno", "readable")
+@proxy_property_directly(
+ "buffer",
+ "closed",
+ "encoding",
+ "errors",
+ "line_buffering",
+ "newlines",
+ "name",
+ "mode",
+)
+class AsyncTextIndirectIOWrapper(AsyncIndirectBase):
+ """The indirect asyncio executor version of io.TextIOWrapper."""
diff --git a/gradio/_vendor/aiofiles/threadpool/utils.py b/gradio/_vendor/aiofiles/threadpool/utils.py
new file mode 100644
index 0000000..5fd3bb9
--- /dev/null
+++ b/gradio/_vendor/aiofiles/threadpool/utils.py
@@ -0,0 +1,72 @@
+import functools
+
+
+def delegate_to_executor(*attrs):
+ def cls_builder(cls):
+ for attr_name in attrs:
+ setattr(cls, attr_name, _make_delegate_method(attr_name))
+ return cls
+
+ return cls_builder
+
+
+def proxy_method_directly(*attrs):
+ def cls_builder(cls):
+ for attr_name in attrs:
+ setattr(cls, attr_name, _make_proxy_method(attr_name))
+ return cls
+
+ return cls_builder
+
+
+def proxy_property_directly(*attrs):
+ def cls_builder(cls):
+ for attr_name in attrs:
+ setattr(cls, attr_name, _make_proxy_property(attr_name))
+ return cls
+
+ return cls_builder
+
+
+def cond_delegate_to_executor(*attrs):
+ def cls_builder(cls):
+ for attr_name in attrs:
+ setattr(cls, attr_name, _make_cond_delegate_method(attr_name))
+ return cls
+
+ return cls_builder
+
+
+def _make_delegate_method(attr_name):
+ async def method(self, *args, **kwargs):
+ cb = functools.partial(getattr(self._file, attr_name), *args, **kwargs)
+ return await self._loop.run_in_executor(self._executor, cb)
+
+ return method
+
+
+def _make_proxy_method(attr_name):
+ def method(self, *args, **kwargs):
+ return getattr(self._file, attr_name)(*args, **kwargs)
+
+ return method
+
+
+def _make_proxy_property(attr_name):
+ def proxy_property(self):
+ return getattr(self._file, attr_name)
+
+ return property(proxy_property)
+
+
+def _make_cond_delegate_method(attr_name):
+ """For spooled temp files, delegate only if rolled to file object"""
+
+ async def method(self, *args, **kwargs):
+ if self._file._rolled:
+ cb = functools.partial(getattr(self._file, attr_name), *args, **kwargs)
+ return await self._loop.run_in_executor(self._executor, cb)
+ else:
+ return getattr(self._file, attr_name)(*args, **kwargs)
+
+ return method
diff --git a/gradio/_vendor/ffmpy/__init__.py b/gradio/_vendor/ffmpy/__init__.py
new file mode 100644
index 0000000..fc682bb
--- /dev/null
+++ b/gradio/_vendor/ffmpy/__init__.py
@@ -0,0 +1,3 @@
+from .ffmpy import FFExecutableNotFoundError, FFmpeg, FFprobe, FFRuntimeError
+
+__all__ = ["FFmpeg", "FFprobe", "FFExecutableNotFoundError", "FFRuntimeError"]
diff --git a/gradio/_vendor/ffmpy/ffmpy.py b/gradio/_vendor/ffmpy/ffmpy.py
new file mode 100644
index 0000000..c0f4c79
--- /dev/null
+++ b/gradio/_vendor/ffmpy/ffmpy.py
@@ -0,0 +1,251 @@
+from __future__ import annotations
+
+import errno
+import itertools
+import shlex
+import subprocess
+from collections.abc import Mapping, Sequence
+from typing import IO, Any
+
+try:
+ from psutil import Popen # noqa: F401
+
+ popen: type[subprocess.Popen | Popen]
+except ImportError:
+ popen = subprocess.Popen
+else:
+ popen = Popen
+
+
+class FFmpeg:
+ """Wrapper for various `FFmpeg `_ related applications (ffmpeg,
+ ffprobe).
+ """
+
+ def __init__(
+ self,
+ executable: str = "ffmpeg",
+ global_options: Sequence[str] | str | None = None,
+ inputs: Mapping[str, Sequence[str] | str | None] | None = None,
+ outputs: Mapping[str, Sequence[str] | str | None] | None = None,
+ ) -> None:
+ """Initialize FFmpeg command line wrapper.
+
+ Compiles FFmpeg command line from passed arguments (executable path, options, inputs and
+ outputs). ``inputs`` and ``outputs`` are dictionares containing inputs/outputs as keys and
+ their respective options as values. One dictionary value (set of options) must be either a
+ single space separated string, or a list or strings without spaces (i.e. each part of the
+ option is a separate item of the list, the result of calling ``split()`` on the options
+ string). If the value is a list, it cannot be mixed, i.e. cannot contain items with spaces.
+ An exception are complex FFmpeg command lines that contain quotes: the quoted part must be
+ one string, even if it contains spaces (see *Examples* for more info).
+ For more info about FFmpeg command line format see `here
+ `_.
+
+ :param str executable: path to ffmpeg executable; by default the ``ffmpeg`` command will be
+ searched for in the ``PATH``, but can be overridden with an absolute path to ``ffmpeg``
+ executable
+ :param iterable global_options: global options passed to ``ffmpeg`` executable (e.g.
+ ``-y``, ``-v`` etc.); can be specified either as a list/tuple/set of strings, or one
+ space-separated string; by default no global options are passed
+ :param dict inputs: a dictionary specifying one or more input arguments as keys with their
+ corresponding options (either as a list of strings or a single space separated string) as
+ values
+ :param dict outputs: a dictionary specifying one or more output arguments as keys with their
+ corresponding options (either as a list of strings or a single space separated string) as
+ values
+ """
+ self.executable = executable
+ self._cmd = [executable]
+ self._cmd += _normalize_options(global_options, split_mixed=True)
+
+ if inputs is not None:
+ self._cmd += _merge_args_opts(inputs, add_minus_i_option=True)
+
+ if outputs is not None:
+ self._cmd += _merge_args_opts(outputs)
+
+ self.cmd = subprocess.list2cmdline(self._cmd)
+ self.process: subprocess.Popen | Popen | None = None
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__!r} {self.cmd!r}>"
+
+ def run(
+ self,
+ input_data: bytes | None = None,
+ stdout: IO | int | None = None,
+ stderr: IO | int | None = None,
+ env: Mapping[str, str] | None = None,
+ **kwargs: Any,
+ ) -> tuple[bytes | None, bytes | None]:
+ """Execute FFmpeg command line.
+
+ ``input_data`` can contain input for FFmpeg in case ``pipe`` protocol is used for input.
+ ``stdout`` and ``stderr`` specify where to redirect the ``stdout`` and ``stderr`` of the
+ process. By default no redirection is done, which means all output goes to running shell
+ (this mode should normally only be used for debugging purposes). If FFmpeg ``pipe`` protocol
+ is used for output, ``stdout`` must be redirected to a pipe by passing `subprocess.PIPE` as
+ ``stdout`` argument. You can pass custom environment to ffmpeg process with ``env``.
+
+ Returns a 2-tuple containing ``stdout`` and ``stderr`` of the process. If there was no
+ redirection or if the output was redirected to e.g. `os.devnull`, the value returned will
+ be a tuple of two `None` values, otherwise it will contain the actual ``stdout`` and
+ ``stderr`` data returned by ffmpeg process.
+
+ More info about ``pipe`` protocol `here `_.
+
+ :param str input_data: input data for FFmpeg to deal with (audio, video etc.) as bytes (e.g.
+ the result of reading a file in binary mode)
+ :param stdout: redirect FFmpeg ``stdout`` there (default is `None` which means no
+ redirection)
+ :param stderr: redirect FFmpeg ``stderr`` there (default is `None` which means no
+ redirection)
+ :param env: custom environment for ffmpeg process
+ :param kwargs: any other keyword arguments to be forwarded to `subprocess.Popen
+ `_
+ :return: a 2-tuple containing ``stdout`` and ``stderr`` of the process
+ :rtype: tuple
+ :raise: `FFRuntimeError` in case FFmpeg command exits with a non-zero code;
+ `FFExecutableNotFoundError` in case the executable path passed was not valid
+ """
+ try:
+ self.process = popen(
+ self._cmd,
+ stdin=subprocess.PIPE,
+ stdout=stdout,
+ stderr=stderr,
+ env=env,
+ **kwargs,
+ )
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ raise FFExecutableNotFoundError(
+ f"Executable '{self.executable}' not found"
+ )
+ else:
+ raise
+
+ o_stdout, o_stderr = self.process.communicate(input=input_data)
+ if self.process.returncode != 0:
+ raise FFRuntimeError(self.cmd, self.process.returncode, o_stdout, o_stderr)
+
+ return o_stdout, o_stderr
+
+
+class FFprobe(FFmpeg):
+ """Wrapper for `ffprobe `_."""
+
+ def __init__(
+ self,
+ executable: str = "ffprobe",
+ global_options: Sequence[str] | str | None = None,
+ inputs: Mapping[str, Sequence[str] | str | None] | None = None,
+ ) -> None:
+ """Create an instance of FFprobe.
+
+ Compiles FFprobe command line from passed arguments (executable path, options, inputs).
+ FFprobe executable by default is taken from ``PATH`` but can be overridden with an
+ absolute path. For more info about FFprobe command line format see
+ `here `_.
+
+ :param str executable: absolute path to ffprobe executable
+ :param iterable global_options: global options passed to ffmpeg executable; can be specified
+ either as a list/tuple of strings or a space-separated string
+ :param dict inputs: a dictionary specifying one or more inputs as keys with their
+ corresponding options as values
+ """
+ super().__init__(
+ executable=executable, global_options=global_options, inputs=inputs
+ )
+
+
+class FFExecutableNotFoundError(Exception):
+ """Raise when FFmpeg/FFprobe executable was not found."""
+
+
+class FFRuntimeError(Exception):
+ """Raise when FFmpeg/FFprobe command line execution returns a non-zero exit code.
+
+ The resulting exception object will contain the attributes relates to command line execution:
+ ``cmd``, ``exit_code``, ``stdout``, ``stderr``.
+ """
+
+ def __init__(
+ self,
+ cmd: str,
+ exit_code: int,
+ stdout: bytes | str | None,
+ stderr: bytes | str | None,
+ ) -> None:
+ self.cmd = cmd
+ self.exit_code = exit_code
+ self.stdout = stdout
+ self.stderr = stderr
+
+ stdout_display = _safe_decode(stdout)
+ stderr_display = _safe_decode(stderr)
+
+ message = f"`{self.cmd}` exited with status {exit_code}\n\nSTDOUT:\n{stdout_display}\n\nSTDERR:\n{stderr_display}"
+
+ super().__init__(message)
+
+
+def _merge_args_opts(
+ args_opts_dict: Mapping[str, Sequence[str] | str | None],
+ add_minus_i_option: bool = False,
+) -> list[str]:
+ """Merge options with their corresponding arguments.
+
+ Iterates over the dictionary holding arguments (keys) and options (values). Merges each
+ options string with its corresponding argument.
+
+ :param dict args_opts_dict: a dictionary of arguments and options
+ :param dict kwargs: *input_option* - if specified prepends ``-i`` to input argument
+ :return: merged list of strings with arguments and their corresponding options
+ :rtype: list
+ """
+ merged: list[str] = []
+
+ for arg, opt in args_opts_dict.items():
+ merged += _normalize_options(opt)
+
+ if not arg:
+ continue
+
+ if add_minus_i_option:
+ merged.append("-i")
+
+ merged.append(arg)
+
+ return merged
+
+
+def _normalize_options(
+ options: Sequence[str] | str | None, split_mixed: bool = False
+) -> list[str]:
+ """Normalize options string or list of strings.
+
+ Splits `options` into a list of strings. If `split_mixed` is `True`, splits (flattens) mixed
+ options (i.e. list of strings with spaces) into separate items.
+
+ :param options: options string or list of strings
+ :param bool split_mixed: whether to split mixed options into separate items
+ """
+ if options is None:
+ return []
+ elif isinstance(options, str):
+ return shlex.split(options)
+ elif split_mixed:
+ return list(itertools.chain(*[shlex.split(o) for o in options]))
+ else:
+ return list(options)
+
+
+def _safe_decode(stream_data: bytes | str | None) -> str:
+ """Convert FFmpeg output to text for error messages."""
+ if stream_data is None:
+ return ""
+ if isinstance(stream_data, bytes):
+ return stream_data.decode(errors="replace")
+ return stream_data
diff --git a/gradio/_vendor/ffmpy/py.typed b/gradio/_vendor/ffmpy/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/gradio/_vendor/licenses/aiofiles.LICENSE b/gradio/_vendor/licenses/aiofiles.LICENSE
new file mode 100644
index 0000000..e06d208
--- /dev/null
+++ b/gradio/_vendor/licenses/aiofiles.LICENSE
@@ -0,0 +1,202 @@
+Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/gradio/_vendor/licenses/aiofiles.NOTICE b/gradio/_vendor/licenses/aiofiles.NOTICE
new file mode 100644
index 0000000..d134f28
--- /dev/null
+++ b/gradio/_vendor/licenses/aiofiles.NOTICE
@@ -0,0 +1,2 @@
+Asyncio support for files
+Copyright 2016 Tin Tvrtkovic
diff --git a/gradio/_vendor/licenses/ffmpy.LICENSE b/gradio/_vendor/licenses/ffmpy.LICENSE
new file mode 100644
index 0000000..eda4fa4
--- /dev/null
+++ b/gradio/_vendor/licenses/ffmpy.LICENSE
@@ -0,0 +1,18 @@
+Copyright 2016-2024 Andrii Yurchuk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/gradio/_workflow_curated_snapshot.json b/gradio/_workflow_curated_snapshot.json
new file mode 100644
index 0000000..858b2e1
--- /dev/null
+++ b/gradio/_workflow_curated_snapshot.json
@@ -0,0 +1,383 @@
+{
+ "snapshot_version": 1,
+ "fetched_at": "2026-06-09T00:00:00Z",
+ "items": [
+ {
+ "kind": "space",
+ "id": "black-forest-labs/FLUX.1-schnell",
+ "task": "text-to-image",
+ "space_category": "image-generation",
+ "modality": "image",
+ "title": "FLUX.1 schnell",
+ "description": "Fast, high-quality text-to-image",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "zero_gpu": true,
+ "smoke_inputs": { "prompt": "a small red square" },
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 1800,
+ "error": null
+ }
+ },
+ {
+ "kind": "space",
+ "id": "stabilityai/stable-diffusion-3.5-large",
+ "task": "text-to-image",
+ "space_category": "image-generation",
+ "modality": "image",
+ "title": "Stable Diffusion 3.5 Large",
+ "description": "High-fidelity text-to-image",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "zero_gpu": true,
+ "smoke_inputs": { "prompt": "a small red square" },
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 3200,
+ "error": null
+ }
+ },
+ {
+ "kind": "space",
+ "id": "briaai/BRIA-RMBG-2.0",
+ "task": "image-to-image",
+ "space_category": "image-editing",
+ "modality": "image",
+ "title": "BRIA Background Removal",
+ "description": "Remove image backgrounds",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "zero_gpu": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 1100,
+ "error": null
+ }
+ },
+ {
+ "kind": "space",
+ "id": "multimodalart/face-to-all",
+ "task": "image-to-image",
+ "space_category": "image-editing",
+ "modality": "image",
+ "title": "Face to All",
+ "description": "Stylize a face photo into many art styles",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": false,
+ "zero_gpu": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 2400,
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "Salesforce/blip-image-captioning-large",
+ "task": "image-to-text",
+ "modality": "image",
+ "title": "BLIP Image Captioning",
+ "description": "Generate captions from images",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": false,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "facebook/detr-resnet-50",
+ "task": "object-detection",
+ "modality": "image",
+ "title": "DETR ResNet-50",
+ "description": "Open-vocabulary object detection",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "facebook/sam-vit-base",
+ "task": "image-segmentation",
+ "modality": "image",
+ "title": "Segment Anything (SAM)",
+ "description": "Segment any region of an image",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "google/vit-base-patch16-224",
+ "task": "image-classification",
+ "modality": "image",
+ "title": "ViT Base",
+ "description": "Image classification (ImageNet)",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": false,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "depth-anything/Depth-Anything-V2-Small-hf",
+ "task": "depth-estimation",
+ "modality": "image",
+ "title": "Depth Anything V2",
+ "description": "Monocular depth estimation",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "space",
+ "id": "facebook/MusicGen",
+ "task": "text-to-audio",
+ "space_category": "music-generation",
+ "modality": "audio",
+ "title": "MusicGen",
+ "description": "Generate music from a text prompt",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": false,
+ "zero_gpu": true,
+ "smoke_inputs": { "prompt": "calm piano" },
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 4200,
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "facebook/bart-large-cnn",
+ "task": "summarization",
+ "modality": "text",
+ "title": "BART CNN Summarizer",
+ "description": "News-style text summarization",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": false,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "Helsinki-NLP/opus-mt-en-fr",
+ "task": "translation",
+ "modality": "text",
+ "title": "Opus EN→FR",
+ "description": "English-to-French translation",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": false,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "deepset/roberta-base-squad2",
+ "task": "question-answering",
+ "modality": "text",
+ "title": "RoBERTa QA",
+ "description": "Extractive question answering",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": false,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "space",
+ "id": "tencent/Hunyuan3D-2",
+ "task": "image-to-3d",
+ "space_category": "3d-modeling",
+ "modality": "3d",
+ "title": "Hunyuan3D 2",
+ "description": "Generate 3D meshes from a single image",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": true,
+ "zero_gpu": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 5200,
+ "error": null
+ }
+ },
+ {
+ "kind": "space",
+ "id": "Wan-AI/Wan2.2-TI2V-5B",
+ "task": "text-to-video",
+ "space_category": "video-generation",
+ "modality": "video",
+ "title": "Wan 2.2 TI2V 5B",
+ "description": "Text-to-video generation",
+ "added_at": "2026-04-02T00:00:00Z",
+ "featured": true,
+ "zero_gpu": true,
+ "smoke_inputs": { "prompt": "a flower opening" },
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 8800,
+ "error": null
+ }
+ },
+ {
+ "kind": "space",
+ "id": "hf-audio/whisper-large-v3-turbo",
+ "task": "automatic-speech-recognition",
+ "space_category": "automatic-speech-recognition",
+ "modality": "audio",
+ "title": "Whisper Large v3 Turbo",
+ "description": "Fast speech-to-text",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "zero_gpu": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 900,
+ "error": null
+ }
+ },
+ {
+ "kind": "space",
+ "id": "coqui/xtts",
+ "task": "text-to-speech",
+ "space_category": "speech-synthesis",
+ "modality": "audio",
+ "title": "Coqui XTTS",
+ "description": "Multilingual text-to-speech with voice cloning",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "zero_gpu": true,
+ "smoke_inputs": { "text": "hello" },
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "latency_ms": 2100,
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "black-forest-labs/FLUX.1-schnell",
+ "task": "text-to-image",
+ "modality": "image",
+ "title": "FLUX.1 schnell",
+ "description": "Text-to-image",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "stabilityai/stable-diffusion-3.5-large-turbo",
+ "task": "text-to-image",
+ "modality": "image",
+ "title": "SD 3.5 Large Turbo",
+ "description": "Fast text-to-image",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "Qwen/Qwen2.5-VL-7B-Instruct",
+ "task": "image-to-text",
+ "modality": "image",
+ "title": "Qwen2.5-VL 7B",
+ "description": "Image understanding & captioning",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": false,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "openai/whisper-large-v3-turbo",
+ "task": "automatic-speech-recognition",
+ "modality": "audio",
+ "title": "Whisper Large v3 Turbo",
+ "description": "Speech-to-text",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "meta-llama/Llama-3.2-3B-Instruct",
+ "task": "text-generation",
+ "modality": "text",
+ "title": "Llama 3.2 3B Instruct",
+ "description": "Open-weights text generation",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": true,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ },
+ {
+ "kind": "model",
+ "id": "Qwen/Qwen2.5-7B-Instruct",
+ "task": "text-generation",
+ "modality": "text",
+ "title": "Qwen 2.5 7B",
+ "description": "Multilingual text generation",
+ "added_at": "2026-04-01T00:00:00Z",
+ "featured": false,
+ "validation": {
+ "last_checked": "2026-06-08T03:14:00Z",
+ "status": "ok",
+ "error": null
+ }
+ }
+ ]
+}
diff --git a/gradio/analytics.py b/gradio/analytics.py
new file mode 100644
index 0000000..9a57924
--- /dev/null
+++ b/gradio/analytics.py
@@ -0,0 +1,267 @@
+"""Functions related to analytics and telemetry."""
+
+from __future__ import annotations
+
+import asyncio
+import json
+import os
+import threading
+import urllib.parse
+import warnings
+from typing import Any
+
+import httpx
+from huggingface_hub.utils._telemetry import _send_telemetry_in_thread
+from packaging.version import Version
+
+import gradio
+from gradio.utils import core_gradio_components, get_package_version
+
+# For testability, we import the pyfetch function into this module scope and define a fallback coroutine object to be patched in tests.
+try:
+ from pyodide.http import pyfetch as pyodide_pyfetch # type: ignore
+except ImportError:
+
+ async def pyodide_pyfetch(*_args, **_kwargs):
+ raise NotImplementedError(
+ "pyodide.http.pyfetch is not available in this environment."
+ )
+
+
+ANALYTICS_URL = "https://api.gradio.app/"
+PKG_VERSION_URL = "https://api.gradio.app/pkg-version"
+
+
+def get_block_name(class_name) -> str:
+ """
+ This will return "matrix" for Matrix template, and ensures that any component name that is sent from the gradio app is part of the core components list (no false positives for custom components).
+ """
+ return class_name.__name__.lower()
+
+
+def analytics_enabled() -> bool:
+ """
+ Returns: True if analytics are enabled, False otherwise.
+ """
+ return os.getenv("GRADIO_ANALYTICS_ENABLED", "True") == "True"
+
+
+def _do_analytics_request(topic: str, data: dict[str, Any]) -> None:
+ threading.Thread(
+ target=_do_normal_analytics_request,
+ kwargs={
+ "topic": topic,
+ "data": data,
+ },
+ ).start()
+
+
+def _do_normal_analytics_request(topic: str, data: dict[str, Any]) -> None:
+ try:
+ _send_telemetry_in_thread(
+ topic=topic,
+ library_name="gradio",
+ library_version=data.get("version"),
+ user_agent=data,
+ )
+ except Exception:
+ pass
+
+
+async def _do_wasm_analytics_request(url: str, data: dict[str, Any]) -> None:
+ # We use urllib.parse.urlencode to encode the data as a form.
+ # Ref: https://docs.python.org/3/library/urllib.request.html#urllib-examples
+ body = urllib.parse.urlencode(data).encode("ascii")
+ headers = {
+ "Content-Type": "application/x-www-form-urlencoded",
+ }
+
+ try:
+ await asyncio.wait_for(
+ pyodide_pyfetch(url, method="POST", headers=headers, body=body),
+ timeout=5,
+ )
+ except asyncio.TimeoutError:
+ pass # do not push analytics if no network
+
+
+def version_check():
+ try:
+ current_pkg_version = get_package_version()
+ latest_pkg_version = httpx.get(url=PKG_VERSION_URL, timeout=3).json()["version"]
+ if Version(latest_pkg_version) > Version(current_pkg_version):
+ warnings.warn(
+ f"IMPORTANT: You are using gradio version {current_pkg_version}, "
+ f"however version {latest_pkg_version} is available, please upgrade. \n"
+ f"--------"
+ )
+ except json.decoder.JSONDecodeError: # type: ignore
+ warnings.warn("unable to parse version details from package URL.")
+ except KeyError:
+ warnings.warn("package URL does not contain version info.")
+ except Exception:
+ pass
+
+
+def initiated_analytics(data: dict[str, Any]) -> None:
+ if not analytics_enabled():
+ return
+
+ topic = f"{ANALYTICS_URL}gradio-initiated-analytics/"
+ _do_analytics_request(
+ topic=topic,
+ data=data,
+ )
+
+
+def launched_analytics(blocks: gradio.Blocks, data: dict[str, Any]) -> None:
+ if not analytics_enabled():
+ return
+
+ (
+ blocks_telemetry,
+ inputs_telemetry,
+ outputs_telemetry,
+ targets_telemetry,
+ events_telemetry,
+ ) = (
+ [],
+ [],
+ [],
+ [],
+ [],
+ )
+
+ for x in list(blocks.blocks.values()):
+ blocks_telemetry.append(x.get_block_name())
+ for x in blocks.fns.values():
+ targets_telemetry = targets_telemetry + [
+ # Sometimes the target can be the Blocks object itself, so we need to check if its in blocks.blocks
+ blocks.blocks[int(y[0])].get_block_name()
+ for y in x.targets
+ if y[0] in blocks.blocks
+ ]
+ events_telemetry = events_telemetry + [
+ y[1] for y in x.targets if y[0] in blocks.blocks
+ ]
+ inputs_telemetry = inputs_telemetry + [
+ blocks.blocks[int(y)].get_block_name() # type: ignore
+ for y in x.inputs
+ if y in blocks.blocks
+ ]
+ outputs_telemetry = outputs_telemetry + [
+ blocks.blocks[int(y)].get_block_name() # type: ignore
+ for y in x.outputs
+ if y in blocks.blocks
+ ]
+
+ def get_inputs_outputs(
+ mode: str,
+ components: list[gradio.components.Component] | None,
+ fallback: list[str],
+ ) -> list[str] | None:
+ if mode == "interface":
+ return [b.get_block_name() for b in components] if components else None
+ return fallback
+
+ core_components = [get_block_name(c) for c in core_gradio_components()]
+
+ additional_data = {
+ "version": get_package_version(),
+ "is_hosted_notebook": blocks.is_hosted_notebook,
+ "using_auth": blocks.auth is not None,
+ "dev_mode": blocks.dev_mode,
+ "inputs": get_inputs_outputs(
+ blocks.mode, blocks.input_components, inputs_telemetry
+ ),
+ "outputs": get_inputs_outputs(
+ blocks.mode, blocks.output_components, outputs_telemetry
+ ),
+ "targets": targets_telemetry,
+ "blocks": blocks_telemetry,
+ "events": events_telemetry,
+ }
+ custom_components = [b for b in blocks_telemetry if b not in core_components]
+ using_custom_component = len(custom_components) > 0
+ additional_data["using_custom_component"] = using_custom_component
+ additional_data["custom_components"] = custom_components
+
+ data.update(additional_data)
+
+ topic = f"{ANALYTICS_URL}gradio-launched-telemetry/"
+
+ _do_analytics_request(topic=topic, data=data)
+
+
+def custom_component_analytics(
+ command: str,
+ template: str | None,
+ upload_pypi: bool | None,
+ upload_demo: bool | None,
+ upload_source: bool | None,
+ generate_docs: bool | None = None,
+ bump_version: bool | None = None,
+ npm_install: str | None = None,
+ python_path: str | None = None,
+ gradio_path: str | None = None,
+) -> None:
+ data = {
+ "command": command,
+ "template": template,
+ "upload_pypi": upload_pypi,
+ "upload_demo": upload_demo,
+ "upload_source": upload_source,
+ "generate_docs": generate_docs,
+ "bump_version": bump_version,
+ "npm_install": npm_install,
+ "python_path": python_path,
+ "gradio_path": gradio_path,
+ }
+ if not analytics_enabled():
+ return
+
+ _do_analytics_request(
+ topic="gradio/custom-components",
+ data=data,
+ )
+
+
+def vibe_analytics() -> None:
+ data = {
+ "command": "vibe",
+ }
+ if not analytics_enabled():
+ return
+
+ _do_analytics_request(
+ topic="gradio/vibe",
+ data=data,
+ )
+
+
+def integration_analytics(data: dict[str, Any]) -> None:
+ if not analytics_enabled():
+ return
+
+ topic = f"{ANALYTICS_URL}gradio-integration-analytics/"
+ _do_analytics_request(
+ topic=topic,
+ data=data,
+ )
+
+
+def error_analytics(message: str) -> None:
+ """
+ Send error analytics if there is network
+ Parameters:
+ message: Details about error
+ """
+ if not analytics_enabled():
+ return
+
+ data = {"error": message}
+ topic = f"{ANALYTICS_URL}gradio-error-analytics/"
+ _do_analytics_request(
+ topic=topic,
+ data=data,
+ )
diff --git a/gradio/block_function.py b/gradio/block_function.py
new file mode 100644
index 0000000..f782e54
--- /dev/null
+++ b/gradio/block_function.py
@@ -0,0 +1,175 @@
+from __future__ import annotations
+
+import inspect
+from collections.abc import Callable, Sequence
+from typing import TYPE_CHECKING, Literal
+
+from . import utils
+
+try:
+ import spaces # type: ignore
+except Exception:
+ spaces = None # type: ignore[assignment]
+
+
+if TYPE_CHECKING: # Only import for type checking (is False at runtime).
+ from gradio.components.base import Component
+ from gradio.renderable import Renderable
+
+ from .blocks import BlockContext
+
+
+class BlockFunction:
+ def __init__(
+ self,
+ fn: Callable | None,
+ inputs: Sequence[Component | BlockContext],
+ outputs: Sequence[Component | BlockContext],
+ preprocess: bool,
+ postprocess: bool,
+ inputs_as_dict: bool,
+ targets: list[tuple[int | None, str]],
+ _id: int,
+ batch: bool = False,
+ max_batch_size: int = 4,
+ concurrency_limit: int | None | Literal["default"] = "default",
+ concurrency_id: str | None = None,
+ tracks_progress: bool = False,
+ api_name: str | None = None,
+ api_description: str | None | Literal[False] = None,
+ js: str | Literal[True] | None = None,
+ show_progress: Literal["full", "minimal", "hidden"] = "full",
+ show_progress_on: Sequence[Component] | None = None,
+ cancels: list[int] | None = None,
+ collects_event_data: bool = False,
+ trigger_after: int | None = None,
+ trigger_only_on_success: bool = False,
+ trigger_only_on_failure: bool = False,
+ trigger_mode: Literal["always_last", "once", "multiple"] = "once",
+ queue: bool = True,
+ scroll_to_output: bool = False,
+ api_visibility: Literal["public", "private", "undocumented"] = "public",
+ renderable: Renderable | None = None,
+ rendered_in: Renderable | None = None,
+ render_iteration: int | None = None,
+ is_cancel_function: bool = False,
+ connection: Literal["stream", "sse"] = "sse",
+ time_limit: float | None = None,
+ stream_every: float = 0.5,
+ event_specific_args: list[str] | None = None,
+ component_prop_inputs: list[int] | None = None,
+ page: str = "",
+ js_implementation: str | None = None,
+ key: str | int | tuple[int | str, ...] | None = None,
+ validator: Callable | None = None,
+ ):
+ self.fn = fn
+ self._id = _id
+ self.inputs = inputs
+ self.outputs = outputs
+ self.preprocess = preprocess
+ self.postprocess = postprocess
+ self.tracks_progress = tracks_progress
+ self.concurrency_limit: int | None | Literal["default"] = concurrency_limit
+ self.concurrency_id = concurrency_id or str(id(fn))
+ self.batch = batch
+ self.max_batch_size = max_batch_size
+ self.total_runtime = 0
+ self.total_runs = 0
+ self.inputs_as_dict = inputs_as_dict
+ self.targets = targets
+ self.name = getattr(fn, "__name__", "fn") if fn is not None else None
+ self.api_name = api_name
+ self.api_description = api_description
+ self.js = js
+ self.show_progress = show_progress
+ self.show_progress_on = show_progress_on
+ self.cancels = cancels or []
+ self.collects_event_data = collects_event_data
+ self.trigger_after = trigger_after
+ self.trigger_only_on_success = trigger_only_on_success
+ self.trigger_only_on_failure = trigger_only_on_failure
+ self.trigger_mode = trigger_mode
+ self.queue = False if fn is None else queue
+ self.scroll_to_output = False if utils.get_space() else scroll_to_output
+ self.api_visibility = api_visibility
+ self.types_generator = inspect.isgeneratorfunction(
+ self.fn
+ ) or inspect.isasyncgenfunction(self.fn)
+ self.renderable = renderable
+ self.rendered_in = rendered_in
+ self.render_iteration = render_iteration
+ self.page = page
+ self.validator = validator
+ if js_implementation:
+ self.fn.__js_implementation__ = js_implementation # type: ignore
+
+ # We need to keep track of which events are cancel events
+ # so that the client can call the /cancel route directly
+ self.is_cancel_function = is_cancel_function
+ self.time_limit = time_limit
+ self.stream_every = stream_every
+ self.connection = connection
+ self.event_specific_args = event_specific_args
+ self.component_prop_inputs = component_prop_inputs or []
+ self.key = key
+
+ self.spaces_auto_wrap()
+
+ def spaces_auto_wrap(self):
+ if spaces is None:
+ return
+ if utils.get_space() is None:
+ return
+ self.fn = spaces.gradio_auto_wrap(self.fn)
+
+ def __str__(self):
+ return str(
+ {
+ "fn": self.name,
+ "preprocess": self.preprocess,
+ "postprocess": self.postprocess,
+ }
+ )
+
+ def __repr__(self):
+ return str(self)
+
+ def get_config(self):
+ return {
+ "id": self._id,
+ "targets": self.targets,
+ "inputs": [block._id for block in self.inputs],
+ "outputs": [block._id for block in self.outputs],
+ "backend_fn": self.fn is not None,
+ "js": self.js,
+ "queue": self.queue,
+ "api_name": self.api_name,
+ "api_description": self.api_description,
+ "scroll_to_output": self.scroll_to_output,
+ "show_progress": self.show_progress,
+ "show_progress_on": None
+ if self.show_progress_on is None
+ else [block._id for block in self.show_progress_on],
+ "batch": self.batch,
+ "max_batch_size": self.max_batch_size,
+ "cancels": self.cancels,
+ "types": {
+ "generator": self.types_generator,
+ "cancel": self.is_cancel_function,
+ },
+ "collects_event_data": self.collects_event_data,
+ "trigger_after": self.trigger_after,
+ "trigger_only_on_success": self.trigger_only_on_success,
+ "trigger_only_on_failure": self.trigger_only_on_failure,
+ "trigger_mode": self.trigger_mode,
+ "api_visibility": self.api_visibility,
+ "rendered_in": self.rendered_in._id if self.rendered_in else None,
+ "render_id": self.renderable._id if self.renderable else None,
+ "connection": self.connection,
+ "time_limit": self.time_limit,
+ "stream_every": self.stream_every,
+ "event_specific_args": self.event_specific_args,
+ "component_prop_inputs": self.component_prop_inputs,
+ "js_implementation": getattr(self.fn, "__js_implementation__", None),
+ }
diff --git a/gradio/blocks.py b/gradio/blocks.py
new file mode 100644
index 0000000..786d05a
--- /dev/null
+++ b/gradio/blocks.py
@@ -0,0 +1,3591 @@
+from __future__ import annotations
+
+import copy
+import dataclasses
+import hashlib
+import inspect
+import json
+import os
+import random
+import re
+import secrets
+import string
+import sys
+import threading
+import time
+import warnings
+import weakref
+import webbrowser
+from collections import defaultdict
+from collections.abc import AsyncIterator, Callable, Coroutine, Sequence, Set
+from pathlib import Path
+from types import ModuleType, SimpleNamespace
+from typing import TYPE_CHECKING, Any, Literal, Union, cast
+from urllib.parse import urlparse, urlunparse
+
+import anyio
+import fastapi
+import httpx
+from anyio import CapacityLimiter
+from gradio_client import utils as client_utils
+from gradio_client.documentation import document
+from groovy import transpile
+
+from gradio import (
+ analytics,
+ components,
+ networking,
+ processing_utils,
+ queueing,
+ utils,
+)
+from gradio.block_function import BlockFunction
+from gradio.blocks_events import BLOCKS_EVENTS, BlocksEvents, BlocksMeta
+from gradio.caching import TrackManualCacheUsage, used_manual_cache
+from gradio.context import (
+ Context,
+ LocalContext,
+ get_blocks_context,
+ get_render_context,
+ set_render_context,
+)
+from gradio.data_classes import (
+ APIEndpointInfo,
+ APIInfo,
+ BlocksConfigDict,
+ DeveloperPath,
+ FileData,
+ GradioModel,
+ GradioRootModel,
+ Layout,
+)
+from gradio.events import (
+ EventData,
+ EventListener,
+ EventListenerMethod,
+)
+from gradio.exceptions import (
+ ChecksumMismatchError,
+ ComponentProcessingError,
+ DuplicateBlockError,
+ Error,
+ InvalidApiNameError,
+ InvalidComponentError,
+ ShareCertificateWriteError,
+)
+from gradio.helpers import create_tracker, skip, special_args
+from gradio.i18n import I18n, I18nData
+from gradio.node_server import start_node_server
+from gradio.route_utils import API_PREFIX, MediaStream, slugify
+from gradio.routes import INTERNAL_ROUTES, VERSION, App, Request
+from gradio.state_holder import SessionState, StateHolder
+from gradio.themes import ThemeClass as Theme
+from gradio.tunneling import (
+ BINARY_FILENAME,
+ BINARY_FOLDER,
+ BINARY_PATH,
+ BINARY_URL,
+ CURRENT_TUNNELS,
+)
+from gradio.utils import (
+ TupleNoPrint,
+ check_function_inputs_match,
+ component_or_layout_class,
+ get_cancelled_fn_indices,
+ get_node_path,
+ get_package_version,
+ get_upload_folder,
+)
+
+try:
+ import spaces # type: ignore
+except Exception:
+ spaces = None # type: ignore[assignment]
+
+
+if TYPE_CHECKING: # Only import for type checking (is False at runtime).
+ from gradio.components.base import Component
+ from gradio.mcp import GradioMCPServer
+ from gradio.renderable import Renderable
+
+
+class Block:
+ def __init__(
+ self,
+ *,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = "value",
+ visible: bool | Literal["hidden"] = True,
+ proxy_url: str | None = None,
+ ):
+ if getattr(self, "_is_initialized", False):
+ return
+ self._is_initialized = True
+ key_to_id_map = LocalContext.key_to_id_map.get(None)
+ if key is not None and key_to_id_map and key in key_to_id_map:
+ self.is_render_replacement = True
+ self._id = key_to_id_map[key]
+ else:
+ self.is_render_replacement = False
+ self._id = Context.id
+ Context.id += 1
+ if key is not None and key_to_id_map is not None:
+ key_to_id_map[key] = self._id
+ self.visible = visible
+ self.elem_id = elem_id
+ self.elem_classes = (
+ [elem_classes] if isinstance(elem_classes, str) else elem_classes
+ ) or []
+ self.proxy_url = proxy_url
+ self.share_token = secrets.token_urlsafe(32)
+ self.parent: BlockContext | None = None
+ self.rendered_in: Renderable | None = None
+ self.page: str
+ self.is_rendered: bool = False
+ self._constructor_args: list[dict]
+ self.state_session_capacity = 10000
+ self.temp_files: set[str] = set()
+ self.GRADIO_CACHE = get_upload_folder()
+ self.key = key
+ self.preserved_by_key = (
+ [preserved_by_key]
+ if isinstance(preserved_by_key, str)
+ else (preserved_by_key or [])
+ )
+ self.mcp_server_obj = None
+
+ # Keep tracks of files that should not be deleted when the delete_cache parameter is set
+ # These files are the default value of the component and files that are used in examples
+ self.keep_in_cache = set()
+ self.has_launched = False
+
+ if render:
+ self.render()
+
+ def unique_key(self) -> int | None:
+ if self.key is None:
+ return None
+ return hash((self.rendered_in._id if self.rendered_in else None, self.key))
+
+ @property
+ def stateful(self) -> bool:
+ return False
+
+ @property
+ def skip_api(self) -> bool:
+ return False
+
+ @property
+ def constructor_args(self) -> dict[str, Any]:
+ """Get the arguments passed to the component's initializer.
+
+ Only set classes whose metaclass is ComponentMeta
+ """
+ # the _constructor_args list is appended based on the mro of the class
+ # so the first entry is for the bottom of the hierarchy
+ return self._constructor_args[0] if self._constructor_args else {}
+
+ @property
+ def events(
+ self,
+ ) -> list[EventListener]:
+ return getattr(self, "EVENTS", [])
+
+ def render(self):
+ """
+ Adds self into appropriate BlockContext
+ """
+ root_context = get_blocks_context()
+ render_context = get_render_context()
+ self.rendered_in = LocalContext.renderable.get(None)
+ if (
+ root_context is not None
+ and self._id in root_context.blocks
+ and not self.is_render_replacement
+ ):
+ raise DuplicateBlockError(
+ f"A block with id: {self._id} has already been rendered in the current Blocks."
+ )
+ if render_context is not None:
+ if root_context:
+ self.page = root_context.root_block.current_page
+ render_context.add(self)
+ self.parent = render_context
+ if root_context is not None:
+ root_context.blocks[self._id] = self
+ self.is_rendered = True
+ if isinstance(self, components.Component):
+ root_context.root_block.temp_file_sets.append(self.temp_files)
+ return self
+
+ def unrender(self):
+ """
+ Removes self from BlockContext if it has been rendered (otherwise does nothing).
+ Removes self from the layout and collection of blocks, but does not delete any event triggers.
+ """
+ root_context = get_blocks_context()
+ if hasattr(self, "parent") and self.parent is not None:
+ try:
+ self.parent.children.remove(self)
+ self.parent = None
+ except ValueError:
+ pass
+ if root_context is not None:
+ try:
+ del root_context.blocks[self._id]
+ self.is_rendered = False
+ except KeyError:
+ pass
+ return self
+
+ def get_block_name(self) -> str:
+ """
+ Gets block's class name. If it is template component it gets the parent's class name.
+ This is used to identify the Svelte file to use in the frontend. Override this method
+ if a component should use a different Svelte file than the default naming convention.
+ """
+ return (
+ self.__class__.__base__.__name__.lower() # type: ignore
+ if hasattr(self, "is_template")
+ else self.__class__.__name__.lower()
+ )
+
+ def get_block_class(self) -> str:
+ """
+ Gets block's class name. If it is template component it gets the parent's class name.
+ Very similar to the get_block_name method, but this method is used to reconstruct a
+ Gradio app that is loaded from a Space using gr.load(). This should generally
+ NOT be overridden.
+ """
+ return (
+ self.__class__.__base__.__name__.lower() # type: ignore
+ if hasattr(self, "is_template")
+ else self.__class__.__name__.lower()
+ )
+
+ def get_expected_parent(self) -> type[BlockContext] | None:
+ return None
+
+ def breaks_grouping(self) -> bool:
+ """
+ Whether this component breaks FormComponent grouping chains.
+ Components that return False will not reset the pseudo_parent
+ when encountered during fill_expected_parents grouping.
+ """
+ return True
+
+ def get_config(self, cls: type[Block] | None = None) -> dict[str, Any]:
+ config = {}
+ if cls is None:
+ cls = self.__class__
+ signature = inspect.signature(cls.__init__)
+ for parameter in signature.parameters.values():
+ if hasattr(self, parameter.name):
+ value = getattr(self, parameter.name)
+ if dataclasses.is_dataclass(value):
+ value = dataclasses.asdict(value) # type: ignore
+ elif isinstance(value, Block):
+ block_instance = value
+ value = block_instance.get_config()
+ value["id"] = block_instance._id
+ elif isinstance(value, (list, tuple)):
+ serialized_list = []
+ for item in value:
+ if isinstance(item, Block):
+ item_config = item.get_config()
+ item_config["id"] = item._id
+ serialized_list.append(item_config)
+ else:
+ serialized_list.append(item)
+ value = serialized_list
+ config[parameter.name] = value
+ for e in self.events:
+ to_add = e.config_data()
+ if to_add:
+ config = {**to_add, **config}
+ config.pop("render", None)
+ config = {
+ **config,
+ "proxy_url": getattr(self, "proxy_url", None),
+ "name": self.get_block_class(),
+ }
+ for event_attribute in ["_selectable", "_undoable", "_retryable", "likeable"]:
+ if (attributable := getattr(self, event_attribute, None)) is not None:
+ config[event_attribute] = attributable
+ return config
+
+ @classmethod
+ def recover_kwargs(
+ cls, props: dict[str, Any], additional_keys: list[str] | None = None
+ ):
+ """
+ Recovers kwargs from a dict of props.
+ """
+ additional_keys = additional_keys or []
+ signature = inspect.signature(cls.__init__)
+ kwargs = {}
+ for parameter in signature.parameters.values():
+ if parameter.name in props and parameter.name not in additional_keys:
+ kwargs[parameter.name] = props[parameter.name]
+ return kwargs
+
+ async def async_move_resource_to_block_cache(
+ self, url_or_file_path: str | Path | None
+ ) -> str | None:
+ """Moves a file or downloads a file from a url to a block's cache directory, adds
+ to to the block's temp_files, and returns the path to the file in cache. This
+ ensures that the file is accessible to the Block and can be served to users.
+
+ This async version of the function is used when this is being called within
+ a FastAPI route, as this is not blocking.
+ """
+ if url_or_file_path is None:
+ return None
+ if isinstance(url_or_file_path, Path):
+ url_or_file_path = str(url_or_file_path)
+
+ if client_utils.is_http_url_like(url_or_file_path):
+ temp_file_path = await processing_utils.async_ssrf_protected_download(
+ url_or_file_path, cache_dir=self.GRADIO_CACHE
+ )
+
+ self.temp_files.add(temp_file_path)
+ else:
+ url_or_file_path = str(utils.abspath(url_or_file_path))
+ if not utils.is_in_or_equal(url_or_file_path, self.GRADIO_CACHE):
+ try:
+ temp_file_path = processing_utils.save_file_to_cache(
+ url_or_file_path, cache_dir=self.GRADIO_CACHE
+ )
+ except FileNotFoundError:
+ # This can happen if when using gr.load() and the file is on a remote Space
+ # but the file is not the `value` of the component. For example, if the file
+ # is the `avatar_image` of the `Chatbot` component. In this case, we skip
+ # copying the file to the cache and just use the remote file path.
+ return url_or_file_path
+ else:
+ temp_file_path = url_or_file_path
+ self.temp_files.add(temp_file_path)
+
+ return temp_file_path
+
+ def move_resource_to_block_cache(
+ self, url_or_file_path: str | Path | None
+ ) -> str | None:
+ """Moves a file or downloads a file from a url to a block's cache directory, adds
+ to to the block's temp_files, and returns the path to the file in cache. This
+ ensures that the file is accessible to the Block and can be served to users.
+
+ This sync version of the function is used when this is being called outside of
+ a FastAPI route, e.g. when examples are being cached.
+ """
+ if url_or_file_path is None:
+ return None
+ if isinstance(url_or_file_path, Path):
+ url_or_file_path = str(url_or_file_path)
+
+ if client_utils.is_http_url_like(url_or_file_path):
+ temp_file_path = processing_utils.save_url_to_cache(
+ url_or_file_path, cache_dir=self.GRADIO_CACHE
+ )
+
+ self.temp_files.add(temp_file_path)
+ else:
+ url_or_file_path = str(utils.abspath(url_or_file_path))
+ if not utils.is_in_or_equal(url_or_file_path, self.GRADIO_CACHE):
+ try:
+ temp_file_path = processing_utils.save_file_to_cache(
+ url_or_file_path, cache_dir=self.GRADIO_CACHE
+ )
+ except FileNotFoundError:
+ # This can happen if when using gr.load() and the file is on a remote Space
+ # but the file is not the `value` of the component. For example, if the file
+ # is the `avatar_image` of the `Chatbot` component. In this case, we skip
+ # copying the file to the cache and just use the remote file path.
+ return url_or_file_path
+ else:
+ temp_file_path = url_or_file_path
+ self.temp_files.add(temp_file_path)
+
+ return temp_file_path
+
+ def serve_static_file(
+ self, url_or_file_path: str | Path | dict | None
+ ) -> dict | None:
+ """If a file is a local file, moves it to the block's cache directory and returns
+ a FileData-type dictionary corresponding to the file. If the file is a URL, returns a
+ FileData-type dictionary corresponding to the URL. This ensures that the file is
+ accessible in the frontend and can be served to users.
+
+ Examples:
+ >>> block.serve_static_file("https://gradio.app/logo.png") -> {"path": "https://gradio.app/logo.png", "url": "https://gradio.app/logo.png"}
+ >>> block.serve_static_file("logo.png") -> {"path": "logo.png", "url": "/file=logo.png"}
+ >>> block.serve_static_file({"path": "logo.png", "url": "/file=logo.png"}) -> {"path": "logo.png", "url": "/file=logo.png"}
+ """
+ if url_or_file_path is None:
+ return None
+ if isinstance(url_or_file_path, dict):
+ return url_or_file_path
+ if isinstance(url_or_file_path, Path):
+ url_or_file_path = str(url_or_file_path)
+ if client_utils.is_http_url_like(url_or_file_path):
+ return FileData(
+ path=str(url_or_file_path), url=str(url_or_file_path)
+ ).model_dump()
+ else:
+ data = {"path": url_or_file_path, "meta": {"_type": "gradio.FileData"}}
+ try:
+ return processing_utils.move_files_to_cache(data, self)
+ except AttributeError: # Can be raised if this function is called before the Block is fully initialized.
+ return data
+
+ @classmethod
+ def get_component_class_id(cls) -> str:
+ try:
+ module_path = inspect.getfile(cls)
+ except OSError:
+ module_path = cls.__module__
+ module_hash = hashlib.sha256(
+ f"{cls.__name__}_{module_path}".encode()
+ ).hexdigest()
+ return module_hash
+
+ @property
+ def component_class_id(self):
+ return self.get_component_class_id()
+
+ def postprocess(self, value):
+ return value
+
+
+class BlockContext(Block):
+ def __init__(
+ self,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ visible: bool | Literal["hidden"] = True,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ visible: If False, this will be hidden but included in the Blocks config file (its visibility can later be updated).
+ render: If False, this will not be included in the Blocks config file at all.
+ """
+ self.children: list[Block] = []
+ Block.__init__(
+ self,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ visible=visible,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+
+ TEMPLATE_DIR = DeveloperPath("./templates/")
+ FRONTEND_DIR = "../../frontend/"
+
+ @property
+ def skip_api(self):
+ return True
+
+ def add_child(self, child: Block):
+ self.children.append(child)
+
+ def __enter__(self):
+ render_context = get_render_context()
+ self.parent = render_context
+ set_render_context(self)
+ return self
+
+ def add(self, child: Block):
+ child.parent = self
+ self.children.append(child)
+
+ def fill_expected_parents(self):
+ root_context = get_blocks_context()
+ children = []
+ pseudo_parent = None
+ for child in self.children:
+ expected_parent = child.get_expected_parent()
+ if not expected_parent or isinstance(self, expected_parent):
+ if child.breaks_grouping():
+ pseudo_parent = None
+ children.append(child)
+ else:
+ if pseudo_parent is not None and isinstance(
+ pseudo_parent, expected_parent
+ ):
+ pseudo_parent.add_child(child)
+ else:
+ key = None
+ if child.key is not None:
+ if isinstance(child.key, tuple):
+ key = child.key + ("_parent",)
+ else:
+ key = (child.key, "_parent")
+ pseudo_parent = expected_parent(render=False, key=key)
+ pseudo_parent.parent = self
+ children.append(pseudo_parent)
+ pseudo_parent.add_child(child)
+ pseudo_parent.page = child.page
+ if root_context:
+ root_context.blocks[pseudo_parent._id] = pseudo_parent
+ child.parent = pseudo_parent
+ self.children = children
+
+ def __exit__(self, exc_type: type[BaseException] | None = None, *args):
+ set_render_context(self.parent)
+ if exc_type is not None:
+ return
+ if getattr(self, "allow_expected_parents", True):
+ self.fill_expected_parents()
+
+
+def postprocess_update_dict(
+ block: Component | BlockContext, update_dict: dict, postprocess: bool = True
+):
+ """
+ Converts a dictionary of updates into a format that can be sent to the frontend to update the component.
+ E.g. {"value": "2", "visible": True, "invalid_arg": "hello"}
+ Into -> {"__type__": "update", "value": 2.0, "visible": True}
+ Parameters:
+ block: The Block that is being updated with this update dictionary.
+ update_dict: The original update dictionary
+ postprocess: Whether to postprocess the "value" key of the update dictionary.
+ """
+ from gradio.components import HTML
+
+ value = update_dict.pop("value", components._Keywords.NO_VALUE)
+
+ props = None
+ if isinstance(block, HTML):
+ sig = inspect.signature(HTML.__init__)
+ constructor_params = set(sig.parameters.keys())
+ props = {k: v for k, v in update_dict.items() if k not in constructor_params}
+ update_dict = {k: getattr(block, k) for k in update_dict if hasattr(block, k)}
+ if props:
+ update_dict["props"] = props
+
+ if value is not components._Keywords.NO_VALUE:
+ if postprocess:
+ update_dict["value"] = block.postprocess(value)
+ if isinstance(update_dict["value"], (GradioModel, GradioRootModel)):
+ update_dict["value"] = update_dict["value"].model_dump()
+ else:
+ update_dict["value"] = value
+ update_dict["__type__"] = "update"
+ return update_dict
+
+
+def convert_component_dict_to_list(
+ outputs_ids: list[int], predictions: dict
+) -> list | dict:
+ """
+ Converts a dictionary of component updates into a list of updates in the order of
+ the outputs_ids and including every output component. Leaves other types of dictionaries unchanged.
+ E.g. {"textbox": "hello", "number": {"__type__": "generic_update", "value": "2"}}
+ Into -> ["hello", {"__type__": "generic_update"}, {"__type__": "generic_update", "value": "2"}]
+ """
+ keys_are_blocks = [isinstance(key, Block) for key in predictions]
+ if all(keys_are_blocks):
+ reordered_predictions = [skip() for _ in outputs_ids]
+ for component, value in predictions.items():
+ if component._id not in outputs_ids:
+ raise ValueError(
+ f"Returned component {component} not specified as output of function."
+ )
+ output_index = outputs_ids.index(component._id)
+ reordered_predictions[output_index] = value
+ predictions = utils.resolve_singleton(reordered_predictions)
+ elif any(keys_are_blocks):
+ raise ValueError(
+ "Returned dictionary included some keys as Components. Either all keys must be Components to assign Component values, or return a List of values to assign output values in order."
+ )
+ return predictions
+
+
+def _find_free_port(host: str, start: int, try_count: int = 100) -> int:
+ """Find an available port by scanning from *start*."""
+ import socket
+
+ for port in range(start, start + try_count):
+ try:
+ s = socket.socket()
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ s.bind((host if host != "0.0.0.0" else "127.0.0.1", port))
+ s.close()
+ return port
+ except OSError:
+ continue
+ raise OSError(f"Cannot find empty port in range: {start}-{start + try_count - 1}.")
+
+
+class BlocksConfig:
+ def __init__(self, root_block: Blocks):
+ self._id: int = 0
+ self.root_block = root_block
+ self.blocks: dict[int, Component | Block] = {}
+ self.fns: dict[int, BlockFunction] = {}
+ self.fn_id: int = 0
+
+ def set_event_trigger(
+ self,
+ targets: Sequence[EventListenerMethod],
+ fn: Callable | None,
+ inputs: (
+ Component
+ | BlockContext
+ | Sequence[Component | BlockContext]
+ | Set[Component | BlockContext]
+ | None
+ ),
+ outputs: (
+ Component
+ | BlockContext
+ | Sequence[Component | BlockContext]
+ | Set[Component | BlockContext]
+ | None
+ ),
+ preprocess: bool = True,
+ postprocess: bool = True,
+ scroll_to_output: bool = False,
+ show_progress: Literal["full", "minimal", "hidden"] = "full",
+ show_progress_on: Component | Sequence[Component] | None = None,
+ api_name: str | None = None,
+ api_description: str | None | Literal[False] = None,
+ js: str | Literal[True] | None = None,
+ no_target: bool = False,
+ queue: bool = True,
+ batch: bool = False,
+ max_batch_size: int = 4,
+ cancels: list[int] | None = None,
+ collects_event_data: bool | None = None,
+ trigger_after: int | None = None,
+ trigger_only_on_success: bool = False,
+ trigger_only_on_failure: bool = False,
+ trigger_mode: Literal["once", "multiple", "always_last"] | None = "once",
+ concurrency_limit: int | None | Literal["default"] = "default",
+ concurrency_id: str | None = None,
+ api_visibility: Literal["public", "private", "undocumented"] = "public",
+ renderable: Renderable | None = None,
+ is_cancel_function: bool = False,
+ connection: Literal["stream", "sse"] = "sse",
+ time_limit: float | None = None,
+ stream_every: float = 0.5,
+ event_specific_args: list[str] | None = None,
+ js_implementation: str | None = None,
+ key: str | int | tuple[int | str, ...] | None = None,
+ validator: Callable | None = None,
+ component_prop_inputs: list[int] | None = None,
+ ) -> tuple[BlockFunction, int]:
+ """
+ Adds an event to the component's dependencies.
+ Parameters:
+ targets: a list of EventListenerMethod objects that define the event trigger
+ fn: the function to run when the event is triggered
+ inputs: the list of input components whose values will be passed to the function
+ outputs: the list of output components whose values will be updated by the function
+ preprocess: whether to run the preprocess methods of the input components before running the function
+ postprocess: whether to run the postprocess methods of the output components after running the function
+ scroll_to_output: whether to scroll to output of dependency on trigger
+ show_progress: how to show the progress animation while event is running: "full" shows a spinner which covers the output component area as well as a runtime display in the upper right corner, "minimal" only shows the runtime display, "hidden" shows no progress animation at all
+ show_progress_on: Component or list of components to show the progress animation on. If None, will show the progress animation on all of the output components.
+ api_name: defines how the endpoint appears in the API docs. Can be a string or None. If set to a string, the endpoint will be exposed in the API docs with the given name. If None (default), the name of the function will be used as the API endpoint.
+ api_description: Description of the API endpoint. Can be a string, None, or False. If set to a string, the endpoint will be exposed in the API docs with the given description. If None, the function's docstring will be used as the API endpoint description. If False, then no description will be displayed in the API docs.
+ js: Optional frontend js method to run before running 'fn'. Input arguments for js method are values of 'inputs' and 'outputs', return should be a list of values that will be passed as inputs to the Python function (`fn`)
+ no_target: if True, sets "targets" to [], used for the Blocks.load() event and .then() events
+ queue: If True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app.
+ batch: whether this function takes in a batch of inputs
+ max_batch_size: the maximum batch size to send to the function
+ cancels: a list of other events to cancel when this event is triggered. For example, setting cancels=[click_event] will cancel the click_event, where click_event is the return value of another components .click method.
+ collects_event_data: whether to collect event data for this event
+ trigger_after: if set, this event will be triggered after 'trigger_after' function index
+ trigger_only_on_success: if True, this event will only be triggered if the previous event was successful (only applies if `trigger_after` is set)
+ trigger_only_on_failure: if True, this event will only be triggered if the previous event failed i.e. raised an exception (only applies if `trigger_after` is set)
+ trigger_mode: If "once" (default for all events except `.change()`) would not allow any submissions while an event is pending. If set to "multiple", unlimited submissions are allowed while pending, and "always_last" (default for `.change()` and `.key_up()` events) would allow a second submission after the pending event is complete.
+ concurrency_limit: If set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `queue()`, which itself is 1 by default).
+ concurrency_id: If set, this is the id of the concurrency group. Events with the same concurrency_id will be limited by the lowest set concurrency_limit.
+ api_visibility: controls the visibility and accessibility of this endpoint. Can be "public" (shown in API docs and callable by clients), "private" (hidden from API docs and not callable by the Gradio client libraries), or "undocumented" (hidden from API docs but callable by clients and via gr.load). If fn is None, api_visibility will automatically be set to "private".
+ is_cancel_function: whether this event cancels another running event.
+ connection: The connection format, either "sse" or "stream".
+ time_limit: The time limit for the function to run. Parameter only used for the `.stream()` event.
+ stream_every: The latency (in seconds) at which stream chunks are sent to the backend. Defaults to 0.5 seconds. Parameter only used for the `.stream()` event.
+ validator: a function that takes in the inputs and can optionally return a gr.validate() object for each input.
+ Returns: dependency information, dependency index
+ """
+ # Support for singular parameter
+ _targets = [
+ (
+ target.block._id if not no_target and target.block else None,
+ target.event_name,
+ )
+ for target in targets
+ ]
+ if isinstance(inputs, Set):
+ inputs_as_dict = True
+ inputs = sorted(inputs, key=lambda x: x._id)
+ else:
+ inputs_as_dict = False
+ if inputs is None:
+ inputs = []
+ elif not isinstance(inputs, Sequence):
+ inputs = [inputs]
+
+ if isinstance(outputs, Set):
+ outputs = sorted(outputs, key=lambda x: x._id)
+ elif outputs is None:
+ outputs = []
+ elif not isinstance(outputs, Sequence):
+ outputs = [outputs]
+ if show_progress_on and not isinstance(show_progress_on, Sequence):
+ show_progress_on = [show_progress_on]
+
+ if fn is not None and not cancels:
+ check_function_inputs_match(fn, inputs, inputs_as_dict)
+
+ if _targets and trigger_mode is None:
+ if _targets[0][1] in ["change", "key_up"]:
+ trigger_mode = "always_last"
+ elif _targets[0][1] in ["stream"]:
+ trigger_mode = "multiple"
+ if trigger_mode is None:
+ trigger_mode = "once"
+ elif trigger_mode not in ["once", "multiple", "always_last"]:
+ raise ValueError(
+ f"Invalid value for parameter `trigger_mode`: {trigger_mode}. Please choose from: {['once', 'multiple', 'always_last']}"
+ )
+
+ fn_to_analyze = renderable.fn if renderable else fn
+ _, progress_index, event_data_index, component_prop_indices = (
+ special_args(fn_to_analyze) if fn_to_analyze else (None, None, None, [])
+ )
+ if component_prop_inputs is None:
+ component_prop_inputs = component_prop_indices or []
+
+ # If api_name is None or empty string, use the function name
+ if api_name is None or isinstance(api_name, str) and api_name.strip() == "":
+ if fn is not None:
+ if not hasattr(fn, "__name__"):
+ if hasattr(fn, "__class__") and hasattr(fn.__class__, "__name__"):
+ name = fn.__class__.__name__
+ else:
+ name = "unnamed"
+ else:
+ name = fn.__name__
+ api_name = "".join(
+ [
+ s
+ for s in str(name)
+ if s not in set(string.punctuation) - {"-", "_"}
+ ]
+ )
+ elif js is not None:
+ api_name = "js_fn"
+ api_visibility = "private"
+ else:
+ api_name = "unnamed"
+ api_visibility = "private"
+ elif api_name is False:
+ api_name = "false"
+ api_visibility = "private"
+
+ api_name = utils.append_unique_suffix(
+ api_name,
+ [fn.api_name for fn in self.fns.values() if isinstance(fn.api_name, str)],
+ )
+
+ # The `api_visibility` parameter is "private" if: (1) the user explicitly sets it (2) fn is None (there's no backend function)
+ # or (3) the function is a js function
+
+ if collects_event_data is None:
+ collects_event_data = event_data_index is not None
+
+ rendered_in = LocalContext.renderable.get(None)
+
+ if js is True and inputs:
+ raise ValueError(
+ "Cannot create event: events with js=True cannot have inputs."
+ )
+
+ reuse_id = False
+ fn_id = self.fn_id
+ render_iteration = rendered_in.render_iteration if rendered_in else None
+
+ if rendered_in and key is not None:
+ for existing_fn in self.fns.values():
+ if existing_fn.key == key:
+ reuse_id = True
+ fn_id = existing_fn._id
+ break
+
+ block_fn = BlockFunction(
+ fn,
+ inputs, # type: ignore
+ outputs, # type: ignore
+ preprocess,
+ postprocess,
+ _id=fn_id,
+ inputs_as_dict=inputs_as_dict,
+ targets=_targets,
+ batch=batch,
+ max_batch_size=max_batch_size,
+ concurrency_limit=concurrency_limit,
+ concurrency_id=concurrency_id,
+ tracks_progress=progress_index is not None,
+ api_name=api_name,
+ api_description=api_description,
+ js=js,
+ show_progress=show_progress,
+ show_progress_on=show_progress_on # type: ignore
+ if isinstance(show_progress_on, (list, tuple)) or show_progress_on is None
+ else [show_progress_on],
+ cancels=cancels,
+ collects_event_data=collects_event_data,
+ trigger_after=trigger_after,
+ trigger_only_on_success=trigger_only_on_success,
+ trigger_only_on_failure=trigger_only_on_failure,
+ trigger_mode=trigger_mode,
+ queue=queue,
+ scroll_to_output=scroll_to_output,
+ api_visibility=api_visibility,
+ renderable=renderable,
+ rendered_in=rendered_in,
+ render_iteration=render_iteration,
+ is_cancel_function=is_cancel_function,
+ connection=connection,
+ time_limit=time_limit,
+ stream_every=stream_every,
+ event_specific_args=event_specific_args,
+ component_prop_inputs=component_prop_inputs,
+ page=self.root_block.current_page,
+ js_implementation=js_implementation,
+ key=key,
+ validator=validator,
+ )
+
+ self.fns[fn_id] = block_fn
+ if not reuse_id:
+ self.fn_id += 1
+ return block_fn, block_fn._id
+
+ @staticmethod
+ def config_for_block(
+ _id: int,
+ rendered_ids: list[int],
+ block: Block | Component,
+ renderable: Renderable | None = None,
+ ) -> dict:
+ if renderable and _id not in rendered_ids:
+ return {}
+ props = block.get_config() if hasattr(block, "get_config") else {}
+
+ skip_none_deletion = []
+ if (
+ renderable and block.key
+ ): # Nones are important for replacing a value in a keyed component
+ skip_none_deletion = [
+ prop for prop, val in block.constructor_args.items() if val is None
+ ]
+ utils.delete_none(props, skip_props=skip_none_deletion)
+
+ block_config = {
+ "id": _id,
+ "type": block.get_block_name(),
+ "props": props,
+ "skip_api": block.skip_api,
+ "component_class_id": getattr(block, "component_class_id", None),
+ "key": block.unique_key(),
+ }
+ if renderable:
+ block_config["renderable"] = renderable._id
+ if block.rendered_in is not None:
+ block_config["rendered_in"] = block.rendered_in._id
+ if not block.skip_api:
+ block_config["api_info"] = block.api_info() # type: ignore
+ if hasattr(block, "api_info_as_input"):
+ block_config["api_info_as_input"] = block.api_info_as_input() # type: ignore
+ else:
+ block_config["api_info_as_input"] = block.api_info() # type: ignore
+ if hasattr(block, "api_info_as_output"):
+ block_config["api_info_as_output"] = block.api_info_as_output() # type: ignore
+ else:
+ block_config["api_info_as_output"] = block.api_info() # type: ignore
+ block_config["example_inputs"] = block.example_inputs() # type: ignore
+
+ return block_config
+
+ def get_config(self, renderable: Renderable | None = None):
+ config = {
+ "page": {},
+ "components": [],
+ "dependencies": [],
+ }
+
+ for page_tuple in self.root_block.pages:
+ page = page_tuple[0]
+ if page not in config["page"]:
+ config["page"][page] = { # type: ignore
+ "layout": {"id": self.root_block._id, "children": []},
+ "components": [],
+ "dependencies": [],
+ }
+
+ rendered_ids = []
+
+ def get_layout(block: Block) -> Layout:
+ rendered_ids.append(block._id)
+ if not isinstance(block, BlockContext):
+ return {"id": block._id}
+ children_layout = []
+ for child in block.children:
+ layout = get_layout(child)
+ children_layout.append(layout)
+ return {"id": block._id, "children": children_layout}
+
+ if renderable:
+ root_block = self.blocks[renderable.container_id]
+ else:
+ root_block = self.root_block
+ layout = get_layout(root_block)
+ config["layout"] = layout
+
+ for root_child in layout.get("children", []):
+ if isinstance(root_child, dict) and root_child["id"] in self.blocks:
+ block = self.blocks[root_child["id"]]
+ config["page"][block.page]["layout"]["children"].append(root_child) # type: ignore
+
+ blocks_items = list(
+ self.blocks.items()
+ ) # freeze as list to prevent concurrent re-renders from changing the dict during loop, see https://github.com/gradio-app/gradio/issues/9991
+ for _id, block in blocks_items:
+ block_config = self.config_for_block(_id, rendered_ids, block, renderable)
+ if not block_config:
+ continue
+ config["components"].append(block_config) # type: ignore
+ config["page"][block.page]["components"].append(block._id) # type: ignore
+
+ dependencies = []
+ for fn in self.fns.values():
+ if renderable is None or fn.rendered_in == renderable:
+ dependency_config = fn.get_config()
+ dependencies.append(dependency_config)
+ config["page"][fn.page]["dependencies"].append(dependency_config["id"]) # type: ignore
+
+ config["dependencies"] = dependencies
+ return config
+
+ def __copy__(self):
+ new = BlocksConfig(self.root_block)
+ new.blocks = copy.copy(self.blocks)
+ new.fns = copy.copy(self.fns)
+ new.fn_id = self.fn_id
+ return new
+
+ def attach_load_events(self, rendered_in: Renderable | None = None):
+ """Add a load event for every component whose initial value requires a function call to set."""
+ for component in self.blocks.values():
+ if rendered_in is not None and component.rendered_in != rendered_in:
+ continue
+ if (
+ isinstance(component, components.Component)
+ and component.load_event_to_attach
+ ):
+ load_fn, triggers, inputs = component.load_event_to_attach
+ has_target = len(triggers) > 0
+ triggers += [(self.root_block, "load")]
+ # Use set_event_trigger to avoid ambiguity between load class/instance method
+
+ dep = self.set_event_trigger(
+ [EventListenerMethod(*trigger) for trigger in triggers],
+ load_fn,
+ inputs,
+ component,
+ no_target=False,
+ show_progress="hidden" if has_target else "full",
+ )[0]
+ component.load_event = dep.get_config()
+
+
+@document("launch", "queue", "integrate", "load", "unload")
+class Blocks(BlockContext, BlocksEvents, metaclass=BlocksMeta):
+ """
+ Blocks is Gradio's low-level API that allows you to create more custom web
+ applications and demos than Interfaces (yet still entirely in Python).
+
+
+ Compared to the Interface class, Blocks offers more flexibility and control over:
+ (1) the layout of components (2) the events that
+ trigger the execution of functions (3) data flows (e.g. inputs can trigger outputs,
+ which can trigger the next level of outputs). Blocks also offers ways to group
+ together related demos such as with tabs.
+
+
+ The basic usage of Blocks is as follows: create a Blocks object, then use it as a
+ context (with the "with" statement), and then define layouts, components, or events
+ within the Blocks context. Finally, call the launch() method to launch the demo.
+
+ Example:
+ import gradio as gr
+ def update(name):
+ return f"Welcome to Gradio, {name}!"
+
+ with gr.Blocks() as demo:
+ gr.Markdown("Start typing below and then click **Run** to see the output.")
+ with gr.Row():
+ inp = gr.Textbox(placeholder="What is your name?")
+ out = gr.Textbox()
+ btn = gr.Button("Run")
+ btn.click(fn=update, inputs=inp, outputs=out)
+
+ demo.launch()
+ Demos: blocks_hello, blocks_flipper, blocks_kinematics
+ Guides: blocks-and-event-listeners, controlling-layout, state-in-blocks, more-blocks-features, using-blocks-like-functions
+ """
+
+ # stores references to all currently existing Blocks instances
+ instances: weakref.WeakSet = weakref.WeakSet()
+
+ @classmethod
+ def get_instances(cls) -> list[Blocks]:
+ """
+ :return: list of all current instances.
+ """
+ return list(Blocks.instances)
+
+ def __init__(
+ self,
+ analytics_enabled: bool | None = None,
+ mode: str = "blocks",
+ title: str | I18nData = "Gradio",
+ fill_height: bool = False,
+ fill_width: bool = False,
+ delete_cache: tuple[int, int] | None = None,
+ **kwargs,
+ ):
+ """
+ Parameters:
+ analytics_enabled: Whether to allow basic telemetry. If None, will use GRADIO_ANALYTICS_ENABLED environment variable or default to True.
+ mode: A human-friendly name for the kind of Blocks or Interface being created. Used internally for analytics.
+ title: The tab title to display when this is opened in a browser window.
+ fill_height: Whether to vertically expand top-level child components to the height of the window. If True, expansion occurs when the scale value of the child components >= 1.
+ fill_width: Whether to horizontally expand to fill container fully. If False, centers and constrains app to a maximum width. Only applies if this is the outermost `Blocks` in your Gradio app.
+ delete_cache: A tuple corresponding [frequency, age] both expressed in number of seconds. Every `frequency` seconds, the temporary files created by this Blocks instance will be deleted if more than `age` seconds have passed since the file was created. For example, setting this to (86400, 86400) will delete temporary files every day. The cache will be deleted entirely when the server restarts. If None, no cache deletion will occur.
+ """
+
+ self.limiter = None
+ self.encrypt = False
+ self.mcp_server_obj: None | GradioMCPServer = None
+ self.mcp_error: None | str = None
+ self.share = False
+ self.enable_queue = True
+ self.max_threads = 40
+ self.pending_streams = defaultdict(dict)
+ self.pending_diff_streams = defaultdict(dict)
+ self.show_error = True
+ self.fill_height = fill_height
+ self.fill_width = fill_width
+ self.delete_cache = delete_cache
+ self.extra_startup_events: list[Callable[..., Coroutine[Any, Any, Any]]] = []
+ self.renderables: list[Renderable] = []
+ self.state_holder: StateHolder
+ self.custom_mount_path: str | None = None
+ self.pwa = False
+ self.mcp_server = False
+
+ # For analytics_enabled and allow_flagging: (1) first check for
+ # parameter, (2) check for env variable, (3) default to True/"manual"
+ self.analytics_enabled = (
+ analytics_enabled
+ if analytics_enabled is not None
+ else analytics.analytics_enabled()
+ )
+ if self.analytics_enabled:
+ t = threading.Thread(target=analytics.version_check)
+ t.start()
+ else:
+ os.environ["HF_HUB_DISABLE_TELEMETRY"] = "True"
+ self.enable_monitoring: bool | None = None
+
+ deprecated_params = ["theme", "css", "css_paths", "js", "head", "head_paths"]
+ deprecated_kwargs = {k: kwargs.pop(k) for k in deprecated_params if k in kwargs}
+ if deprecated_kwargs:
+ param_list = ", ".join(deprecated_kwargs.keys())
+ warnings.warn(
+ f"The parameters have been moved from the Blocks constructor to the launch() method in Gradio 6.0: {param_list}. "
+ f"Please pass these parameters to launch() instead.",
+ UserWarning,
+ stacklevel=2,
+ )
+ self._deprecated_theme = deprecated_kwargs.get("theme")
+ self._deprecated_css = deprecated_kwargs.get("css")
+ self._deprecated_css_paths = deprecated_kwargs.get("css_paths")
+ self._deprecated_js = deprecated_kwargs.get("js")
+ self._deprecated_head = deprecated_kwargs.get("head")
+ self._deprecated_head_paths = deprecated_kwargs.get("head_paths")
+
+ self.default_config = BlocksConfig(self)
+ super().__init__(render=False, **kwargs)
+
+ self.mode = mode
+ self.is_running = False
+ self.local_url = None
+ self.share_url = None
+ self.width = None
+ self.height = None
+ self.api_open = utils.get_space() is None
+
+ self.space_id = utils.get_space()
+ self.favicon_path = None
+ self.auth = None
+ self.dev_mode = bool(os.getenv("GRADIO_WATCH_DIRS", ""))
+ self.vibe_mode = bool(os.getenv("GRADIO_VIBE_MODE", ""))
+ self.app_id = random.getrandbits(64)
+ self.upload_file_set = set()
+ self.temp_file_sets = [self.upload_file_set]
+ self.title = title
+
+ # Only used when an Interface is loaded from a config
+ self.predict = None
+ self.input_components = None
+ self.output_components = None
+ self.__name__ = None # type: ignore
+ self.api_mode = None
+
+ self.progress_tracking = None
+ self.ssl_verify = True
+ self.allowed_paths = []
+ self.blocked_paths = []
+ self.root_path = os.environ.get("GRADIO_ROOT_PATH", "")
+ self.proxy_urls = set()
+
+ self.pages: list[tuple[str, str, bool]] = [("", "Home", True)]
+ self.current_page = ""
+
+ self.css = None
+ self.js = None
+ self.head = None
+ self.theme = None # type: ignore
+ self.head_paths = None
+
+ if self.analytics_enabled:
+ data = {
+ "mode": self.mode,
+ "version": get_package_version(),
+ }
+ analytics.initiated_analytics(data)
+
+ Blocks.instances.add(self)
+
+ self.queue()
+
+ @property
+ def blocks(self) -> dict[int, Component | Block]:
+ return self.default_config.blocks
+
+ @blocks.setter
+ def blocks(self, value: dict[int, Component | Block]):
+ self.default_config.blocks = value
+
+ @property
+ def fns(self) -> dict[int, BlockFunction]:
+ return self.default_config.fns
+
+ def get_component(self, id: int) -> Component | BlockContext:
+ comp = self.blocks[id]
+ if not isinstance(comp, (components.Component, BlockContext)):
+ raise TypeError(f"Block with id {id} is not a Component or BlockContext")
+ return comp
+
+ @property
+ def _is_running_in_reload_thread(self):
+ from gradio.cli.commands.reload import reload_thread
+
+ return getattr(reload_thread, "running_reload", False)
+
+ @classmethod
+ def from_config(
+ cls,
+ config: BlocksConfigDict,
+ fns: list[Callable],
+ proxy_url: str,
+ ) -> Blocks:
+ """
+ Factory method that creates a Blocks from a config and list of functions. Used
+ internally by the gradio.external.load() method.
+
+ Parameters:
+ config: a dictionary containing the configuration of the Blocks.
+ fns: a list of functions that are used in the Blocks. Must be in the same order as the dependencies in the config.
+ proxy_url: an external url to use as a root URL when serving files for components in the Blocks.
+ """
+ config = copy.deepcopy(config)
+ components_config = config["components"]
+ original_mapping: dict[int, Block] = {}
+ proxy_urls: set[str] = set()
+ if httpx.URL(proxy_url).host.endswith(".hf.space"):
+ proxy_urls.add(proxy_url)
+
+ def get_block_instance(id: int) -> Block:
+ for block_config in components_config:
+ if block_config["id"] == id:
+ break
+ else:
+ raise ValueError(f"Cannot find block with id {id}")
+ cls = component_or_layout_class(block_config["props"]["name"])
+
+ # If a Gradio app B is loaded into a Gradio app A, and B itself loads a
+ # Gradio app C, then the proxy_urls of the components in A need to be the
+ # URL of C, not B. The else clause below handles this case.
+ if block_config["props"].get("proxy_url") is None:
+ block_config["props"]["proxy_url"] = f"{proxy_url}/"
+ postprocessed_value = block_config["props"].pop("value", None)
+
+ constructor_args = cls.recover_kwargs(block_config["props"])
+ block = cls(**constructor_args)
+ if postprocessed_value is not None:
+ block.value = postprocessed_value # type: ignore
+
+ block_proxy_url = block_config["props"]["proxy_url"]
+ block.proxy_url = block_proxy_url
+ # Only add proxy URLs that point to known Hugging Face Space
+ # hosts to prevent SSRF via malicious configs.
+ if httpx.URL(block_proxy_url).host.endswith(".hf.space"):
+ proxy_urls.add(block_proxy_url)
+ if (
+ _selectable := block_config["props"].pop("_selectable", None)
+ ) is not None:
+ block._selectable = _selectable # type: ignore
+
+ return block
+
+ def iterate_over_children(children_list):
+ for child_config in children_list:
+ id = child_config["id"]
+ block = get_block_instance(id)
+
+ original_mapping[id] = block
+
+ children = child_config.get("children")
+ if children is not None:
+ if not isinstance(block, BlockContext):
+ raise ValueError(
+ f"Invalid config, Block with id {id} has children but is not a BlockContext."
+ )
+ with block:
+ iterate_over_children(children)
+
+ derived_fields = ["types"]
+
+ with Blocks(theme=config.get("theme", None)) as blocks:
+ # ID 0 should be the root Blocks component
+ original_mapping[0] = root_block = Context.root_block or blocks
+
+ if "layout" in config:
+ iterate_over_children(config["layout"].get("children", []))
+
+ first_dependency = None
+
+ # add the event triggers
+ if "dependencies" not in config:
+ raise ValueError(
+ "This config is missing the 'dependencies' field and cannot be loaded."
+ )
+ for dependency, fn in zip(config["dependencies"], fns, strict=False):
+ # We used to add a "fake_event" to the config to cache examples
+ # without removing it. This was causing bugs in calling gr.load
+ # We fixed the issue by removing "fake_event" from the config in examples.py
+ # but we still need to skip these events when loading the config to support
+ # older demos
+ if "trigger" in dependency and dependency["trigger"] == "fake_event":
+ continue
+ for field in derived_fields:
+ dependency.pop(field, None)
+
+ # older versions had a separate trigger field, but now it is part of the
+ # targets field
+ _targets = dependency.pop("targets")
+ trigger = dependency.pop("trigger", None)
+ is_then_event = False
+
+ # This assumes that you cannot combine multiple .then() events in a single
+ # gr.on() event, which is true for now. If this changes, we will need to
+ # update this code.
+ if not isinstance(_targets[0], int) and _targets[0][1] in [
+ "then",
+ "success",
+ ]:
+ if len(_targets) != 1:
+ raise ValueError(
+ "This logic assumes that .then() events are not combined with other events in a single gr.on() event"
+ )
+ is_then_event = True
+
+ dependency.pop("backend_fn")
+ dependency.pop("documentation", None)
+ dependency["inputs"] = [
+ original_mapping[i] for i in dependency["inputs"]
+ ]
+ dependency["outputs"] = [
+ original_mapping[o] for o in dependency["outputs"]
+ ]
+ dependency.pop("status_tracker", None)
+ dependency.pop("zerogpu", None)
+ dependency.pop("id", None)
+ dependency.pop("rendered_in", None)
+ dependency.pop("render_id", None)
+ dependency.pop("every", None)
+
+ # Older versions of Gradio had a show_api field, which has been replaced
+ # by api_visibility.
+ api_visibility = dependency.pop("api_visibility", None)
+ if api_visibility is None:
+ show_api = dependency.pop("show_api", None)
+ if dependency["api_name"] is False:
+ api_visibility = "private"
+ elif show_api is True:
+ api_visibility = "public"
+ else:
+ api_visibility = "undocumented"
+ dependency["api_visibility"] = api_visibility
+
+ dependency["preprocess"] = False
+ dependency["postprocess"] = False
+ if is_then_event:
+ targets = [EventListenerMethod(None, "then")]
+ dependency["trigger_after"] = dependency.pop("trigger_after")
+ dependency["trigger_only_on_success"] = dependency.pop(
+ "trigger_only_on_success"
+ )
+ dependency["trigger_only_on_failure"] = dependency.pop(
+ "trigger_only_on_failure", False
+ )
+ dependency["no_target"] = True
+ else:
+ targets = [
+ EventListenerMethod(
+ t.__self__ if t.has_trigger else None,
+ t.event_name, # type: ignore
+ )
+ for t in Blocks.get_event_targets(
+ original_mapping, _targets, trigger
+ )
+ ]
+ dependency = root_block.default_config.set_event_trigger( # type: ignore
+ targets=targets, fn=fn, **dependency
+ )[0]
+ if first_dependency is None:
+ first_dependency = dependency
+
+ # Allows some use of Interface-specific methods with loaded Spaces
+ if first_dependency and get_blocks_context():
+ blocks.predict = [fns[0]]
+ blocks.input_components = first_dependency.inputs
+ blocks.output_components = first_dependency.outputs
+ blocks.__name__ = "Interface"
+ blocks.api_mode = True
+ blocks.proxy_urls = proxy_urls
+ return blocks
+
+ def __str__(self):
+ return self.__repr__()
+
+ def __repr__(self):
+ num_backend_fns = len([d for d in self.fns.values() if d.fn])
+ repr = f"Gradio Blocks instance: {num_backend_fns} backend functions"
+ repr += f"\n{'-' * len(repr)}"
+ for d, dependency in self.fns.items():
+ if dependency.fn:
+ repr += f"\nfn_index={d}"
+ repr += "\n inputs:"
+ for block in dependency.inputs:
+ block = self.blocks[block._id]
+ repr += f"\n |-{block}"
+ repr += "\n outputs:"
+ for block in dependency.outputs:
+ block = self.blocks[block._id]
+ repr += f"\n |-{block}"
+ return repr
+
+ @property
+ def expects_oauth(self):
+ """Return whether the app expects user to authenticate via OAuth."""
+ return any(
+ isinstance(block, components.LoginButton) for block in self.blocks.values()
+ )
+
+ def unload(self, fn: Callable[..., Any]) -> None:
+ """This listener is triggered when the user closes or refreshes the tab, ending the user session.
+ It is useful for cleaning up resources when the app is closed.
+ Parameters:
+ fn: Callable function to run to clear resources. The function should not take any arguments and the output is not used.
+ Example:
+ import gradio as gr
+ with gr.Blocks() as demo:
+ gr.Markdown("# When you close the tab, hello will be printed to the console")
+ demo.unload(lambda: print("hello"))
+ demo.launch()
+ """
+ self.default_config.set_event_trigger(
+ targets=[EventListenerMethod(None, "unload")],
+ fn=fn,
+ inputs=None,
+ outputs=None,
+ preprocess=False,
+ postprocess=False,
+ show_progress="hidden",
+ api_name=None,
+ js=None,
+ no_target=True,
+ batch=False,
+ max_batch_size=4,
+ cancels=None,
+ collects_event_data=None,
+ trigger_after=None,
+ trigger_only_on_success=False,
+ trigger_only_on_failure=False,
+ trigger_mode="once",
+ concurrency_limit="default",
+ concurrency_id=None,
+ api_visibility="private",
+ )
+
+ def render(self):
+ root_context = get_blocks_context()
+ if root_context is not None and Context.root_block is not None:
+ if self._id in root_context.blocks:
+ raise DuplicateBlockError(
+ f"A block with id: {self._id} has already been rendered in the current Blocks."
+ )
+ overlapping_ids = set(root_context.blocks).intersection(self.blocks)
+ for id in overlapping_ids:
+ # State components are allowed to be reused between Blocks
+ if not isinstance(self.blocks[id], components.State):
+ raise DuplicateBlockError(
+ "At least one block in this Blocks has already been rendered."
+ )
+
+ for block in self.blocks.values():
+ block.page = Context.root_block.current_page
+ root_context.blocks.update(self.blocks)
+ dependency_offset = max(root_context.fns.keys(), default=-1) + 1
+ existing_api_names = [
+ dep.api_name
+ for dep in root_context.fns.values()
+ if isinstance(dep.api_name, str)
+ ]
+ for dependency in self.fns.values():
+ dependency.page = Context.root_block.current_page
+ dependency._id += dependency_offset
+ # Any event -- e.g. Blocks.load() -- that is triggered by this Blocks
+ # should now be triggered by the root Blocks instead.
+ for target in dependency.targets:
+ if target[0] == self._id:
+ target = (Context.root_block._id, target[1])
+ api_name = dependency.api_name
+ if isinstance(api_name, str):
+ api_name_ = utils.append_unique_suffix(
+ api_name,
+ existing_api_names,
+ )
+ if api_name != api_name_:
+ dependency.api_name = api_name_
+ dependency.cancels = [c + dependency_offset for c in dependency.cancels]
+ if dependency.trigger_after is not None:
+ dependency.trigger_after += dependency_offset
+ # Recreate the cancel function so that it has the latest
+ # dependency fn indices. This is necessary to properly cancel
+ # events in the backend
+ if dependency.cancels:
+ updated_cancels = [
+ root_context.fns[i].get_config() for i in dependency.cancels
+ ]
+ dependency.cancels = get_cancelled_fn_indices(updated_cancels)
+ root_context.fns[dependency._id] = dependency
+ root_context.fn_id = max(root_context.fns.keys(), default=-1) + 1
+ Context.root_block.temp_file_sets.extend(self.temp_file_sets)
+ Context.root_block.proxy_urls.update(self.proxy_urls)
+ Context.root_block.extra_startup_events.extend(self.extra_startup_events)
+
+ render_context = get_render_context()
+ if render_context is not None:
+ render_context.children.extend(self.children)
+ return self
+
+ def is_callable(self, fn_index: int = 0) -> bool:
+ """Checks if a particular Blocks function is callable (i.e. not stateful or a generator)."""
+ block_fn = self.fns[fn_index]
+ dependency = self.fns[fn_index]
+
+ if inspect.isasyncgenfunction(block_fn.fn):
+ return False
+ if inspect.isgeneratorfunction(block_fn.fn):
+ return False
+ if any(block.stateful for block in dependency.inputs):
+ return False
+ return not any(block.stateful for block in dependency.outputs)
+
+ def __call__(self, *inputs, fn_index: int = 0, api_name: str | None = None):
+ """
+ Allows Blocks objects to be called as functions. Supply the parameters to the
+ function as positional arguments. To choose which function to call, use the
+ fn_index parameter, which must be a keyword argument.
+
+ Parameters:
+ *inputs: the parameters to pass to the function
+ fn_index: the index of the function to call (defaults to 0, which for Interfaces, is the default prediction function)
+ api_name: The api_name of the dependency to call. Will take precedence over fn_index.
+ """
+ if api_name is not None:
+ inferred_fn_index = next(
+ (i for i, d in self.fns.items() if d.api_name == api_name),
+ None,
+ )
+ if inferred_fn_index is None:
+ raise InvalidApiNameError(
+ f"Cannot find a function with api_name {api_name}"
+ )
+ fn_index = inferred_fn_index
+ if not (self.is_callable(fn_index)):
+ raise ValueError(
+ "This function is not callable because it is either stateful or is a generator. Please use the .launch() method instead to create an interactive user interface."
+ )
+
+ inputs = list(inputs)
+ processed_inputs = self.serialize_data(fn_index, inputs)
+ fn = self.fns[fn_index]
+ if fn.batch:
+ processed_inputs = [[inp] for inp in processed_inputs]
+
+ outputs = client_utils.synchronize_async(
+ self.process_api,
+ block_fn=fn,
+ inputs=processed_inputs,
+ request=None,
+ state={},
+ explicit_call=True,
+ )
+ outputs = outputs["data"]
+
+ if fn.batch:
+ outputs = [out[0] for out in outputs]
+
+ outputs = self.deserialize_data(fn_index, outputs)
+ processed_outputs = utils.resolve_singleton(outputs)
+
+ return processed_outputs
+
+ async def call_function(
+ self,
+ block_fn: BlockFunction | int,
+ processed_input: list[Any],
+ iterator: AsyncIterator[Any] | None = None,
+ requests: Request | list[Request] | None = None,
+ event_id: str | None = None,
+ event_data: EventData | None = None,
+ in_event_listener: bool = False,
+ state: SessionState | None = None,
+ ):
+ """
+ Calls function with given index and preprocessed input, and measures process time.
+ Parameters:
+ fn_index: index of function to call
+ processed_input: preprocessed input to pass to function
+ iterator: iterator to use if function is a generator
+ requests: requests to pass to function
+ event_id: id of event in queue
+ event_data: data associated with event trigger
+ """
+ if isinstance(block_fn, int):
+ block_fn = self.fns[block_fn]
+ if not block_fn.fn:
+ raise IndexError("function has no backend method.")
+ is_generating = False
+ request = requests[0] if isinstance(requests, list) else requests
+ start = time.time()
+
+ fn = utils.get_function_with_locals(
+ fn=block_fn.fn,
+ blocks=self,
+ event_id=event_id,
+ in_event_listener=in_event_listener,
+ request=request, # type: ignore
+ state=state,
+ )
+
+ if iterator is None: # If not a generator function that has already run
+ if block_fn.inputs_as_dict:
+ processed_input = [
+ dict(zip(block_fn.inputs, processed_input, strict=False))
+ ]
+
+ fn_to_analyze = (
+ block_fn.renderable.fn if block_fn.renderable else block_fn.fn
+ )
+ component_props = {}
+ for idx in block_fn.component_prop_inputs:
+ if idx < len(processed_input) and isinstance(
+ processed_input[idx], dict
+ ):
+ component_props[idx] = processed_input[idx]
+
+ processed_input, progress_index, _, _ = special_args(
+ fn_to_analyze,
+ processed_input,
+ request, # type: ignore
+ event_data, # type: ignore
+ component_props=component_props,
+ )
+ progress_tracker = (
+ processed_input[progress_index] if progress_index is not None else None
+ )
+
+ if progress_tracker is not None and progress_index is not None:
+ progress_tracker, fn = create_tracker(fn, progress_tracker.track_tqdm)
+ processed_input[progress_index] = progress_tracker
+
+ if inspect.iscoroutinefunction(fn):
+ prediction = await fn(*processed_input)
+ else:
+ prediction = await anyio.to_thread.run_sync( # type: ignore
+ fn, *processed_input, limiter=self.limiter
+ )
+ else:
+ prediction = None
+
+ if inspect.isgeneratorfunction(fn) or inspect.isasyncgenfunction(fn):
+ try:
+ if iterator is None:
+ iterator = cast(AsyncIterator[Any], prediction)
+ if inspect.isgenerator(iterator):
+ iterator = utils.SyncToAsyncIterator(iterator, self.limiter)
+ prediction = await utils.async_iteration(iterator)
+ is_generating = True
+ except StopAsyncIteration:
+ n_outputs = len(block_fn.outputs)
+ prediction = (
+ components._Keywords.FINISHED_ITERATING
+ if n_outputs == 1
+ else (components._Keywords.FINISHED_ITERATING,) * n_outputs
+ )
+ iterator = None
+
+ duration = time.time() - start
+
+ return {
+ "prediction": prediction,
+ "duration": duration,
+ "is_generating": is_generating,
+ "iterator": iterator,
+ }
+
+ def serialize_data(self, fn_index: int, inputs: list[Any]) -> list[Any]:
+ dependency = self.fns[fn_index]
+ processed_input = []
+
+ def format_file(s):
+ return FileData(path=s).model_dump()
+
+ for i, block in enumerate(dependency.inputs):
+ if not isinstance(block, components.Component):
+ raise InvalidComponentError(
+ f"{block.__class__} Component not a valid input component."
+ )
+ api_info = block.api_info()
+ if client_utils.value_is_file(api_info):
+ serialized_input = client_utils.traverse(
+ inputs[i],
+ format_file,
+ lambda s: (
+ client_utils.is_filepath(s) or client_utils.is_http_url_like(s)
+ ),
+ )
+ else:
+ serialized_input = inputs[i]
+ processed_input.append(serialized_input)
+
+ return processed_input
+
+ def deserialize_data(self, fn_index: int, outputs: list[Any]) -> list[Any]:
+ dependency = self.fns[fn_index]
+ predictions = []
+
+ for o, block in enumerate(dependency.outputs):
+ if not isinstance(block, components.Component):
+ raise InvalidComponentError(
+ f"{block.__class__} Component not a valid output component."
+ )
+
+ deserialized = client_utils.traverse(
+ outputs[o], lambda s: s["path"], client_utils.is_file_obj
+ )
+ predictions.append(deserialized)
+
+ return predictions
+
+ def validate_inputs(self, block_fn: BlockFunction, inputs: list[Any]):
+ dep_inputs = block_fn.inputs
+
+ # This handles incorrect inputs when args are changed by a JS function
+ # Only check not enough args case, ignore extra arguments (for now)
+ # TODO: make this stricter?
+ if len(inputs) < len(dep_inputs):
+ name = (
+ f" ({block_fn.name})"
+ if block_fn.name and block_fn.name != ""
+ else ""
+ )
+
+ wanted_args = []
+ received_args = []
+ for block in dep_inputs:
+ wanted_args.append(str(block))
+ for inp in inputs:
+ v = f'"{inp}"' if isinstance(inp, str) else str(inp)
+ received_args.append(v)
+
+ wanted = ", ".join(wanted_args)
+ received = ", ".join(received_args)
+
+ # JS func didn't pass enough arguments
+ raise ValueError(
+ f"""An event handler{name} didn't receive enough input values (needed: {len(dep_inputs)}, got: {len(inputs)}).
+Check if the event handler calls a Javascript function, and make sure its return value is correct.
+Wanted inputs:
+ [{wanted}]
+Received inputs:
+ [{received}]"""
+ )
+
+ @staticmethod
+ def _format_processing_error(
+ block_fn: BlockFunction,
+ index: int,
+ block: Block,
+ value: Any,
+ is_input: bool,
+ original_error: Exception,
+ ) -> str:
+ name = (
+ f' (named "{block_fn.name}")'
+ if block_fn.name and block_fn.name != ""
+ else ""
+ )
+ blocks = block_fn.inputs if is_input else block_fn.outputs
+ components_list = ", ".join(b.get_block_name() for b in blocks)
+
+ value_repr = repr(value)
+ if len(value_repr) > 200:
+ value_repr = value_repr[:200] + "..."
+
+ kind = "input" if is_input else "output"
+ stage = "preprocess" if is_input else "postprocess"
+ verb = "passed to" if is_input else "returned from"
+
+ method = getattr(block, "preprocess" if is_input else "postprocess", None)
+ expected_type = None
+ if method is not None:
+ try:
+ params = list(inspect.signature(method).parameters.values())
+ if params and params[0].annotation is not inspect.Parameter.empty:
+ annotation = params[0].annotation
+ expected_type = (
+ annotation
+ if isinstance(annotation, str)
+ else getattr(annotation, "__name__", str(annotation))
+ )
+ except (ValueError, TypeError):
+ expected_type = None
+
+ expected_clause = (
+ f"Expected a `{expected_type}`, but the" if expected_type else "The"
+ )
+ return (
+ f"Could not {stage} {kind} component at index {index} "
+ f"(a `{block.get_block_name()}` component) of the event handler{name} "
+ f"with {kind} components: [{components_list}].\n\n"
+ f"{expected_clause} value {verb} the event handler was: {value_repr} "
+ f"(of type `{type(value).__name__}`).\n\n"
+ f"Original error: {type(original_error).__name__}: {original_error}"
+ )
+
+ async def preprocess_data(
+ self,
+ block_fn: BlockFunction,
+ inputs: list[Any],
+ state: SessionState | None,
+ explicit_call: bool = False,
+ ):
+ state = state or SessionState(self)
+
+ self.validate_inputs(block_fn, inputs)
+
+ processed_input = []
+ for i, block in enumerate(block_fn.inputs):
+ if not isinstance(block, components.Component):
+ raise InvalidComponentError(
+ f"{block.__class__} Component not a valid input component."
+ )
+ if block.stateful:
+ processed_input.append(state[block._id])
+ else:
+ if block._id in state:
+ block = state[block._id]
+
+ is_prop_input = i in block_fn.component_prop_inputs
+ if is_prop_input:
+ processing_utils.check_all_files_in_cache(inputs[i])
+ value_to_process = (
+ inputs[i].get("value", None) if is_prop_input else inputs[i]
+ )
+
+ from gradio.profiling import trace_phase
+
+ async with trace_phase("preprocess_move_to_cache"):
+ inputs_cached = await processing_utils.async_move_files_to_cache(
+ value_to_process,
+ block,
+ check_in_upload_folder=not explicit_call,
+ )
+ if getattr(block, "data_model", None) and inputs_cached is not None:
+ data_model = cast(
+ Union[GradioModel, GradioRootModel], block.data_model
+ )
+ inputs_cached = data_model.model_validate(
+ inputs_cached, context={"validate_meta": True}
+ )
+ if isinstance(inputs_cached, (GradioModel, GradioRootModel)):
+ inputs_serialized = inputs_cached.model_dump()
+ else:
+ inputs_serialized = inputs_cached
+
+ if block._id not in state:
+ state[block._id] = block
+ state._update_value_in_config(block._id, inputs_serialized)
+
+ if block_fn.preprocess:
+ try:
+ processed_value = await anyio.to_thread.run_sync(
+ block.preprocess, inputs_cached, limiter=self.limiter
+ )
+ except Error:
+ raise
+ except Exception as err:
+ raise ComponentProcessingError(
+ self._format_processing_error(
+ block_fn,
+ i,
+ block,
+ value_to_process,
+ is_input=True,
+ original_error=err,
+ )
+ ) from err
+ else:
+ processed_value = inputs_serialized
+
+ if is_prop_input:
+ inputs[i]["value"] = processed_value
+ processed_input.append(inputs[i])
+ else:
+ processed_input.append(processed_value)
+ return processed_input
+
+ def validate_outputs(self, block_fn: BlockFunction, predictions: Any | list[Any]):
+ dep_outputs = block_fn.outputs
+
+ if not isinstance(predictions, (list, tuple)):
+ predictions = [predictions]
+
+ if len(predictions) != len(dep_outputs):
+ name = (
+ f" ({block_fn.name})"
+ if block_fn.name and block_fn.name != ""
+ else ""
+ )
+
+ wanted_args = []
+ received_args = []
+ for block in dep_outputs:
+ wanted_args.append(str(block.get_block_class()))
+ for pred in predictions:
+ v = f'"{pred}"' if isinstance(pred, str) else str(pred)
+ received_args.append(v)
+
+ wanted = ", ".join(wanted_args)
+ received = ", ".join(received_args)
+
+ if len(predictions) < len(dep_outputs):
+ raise ValueError(
+ f"""A function{name} didn't return enough output values (needed: {len(dep_outputs)}, returned: {len(predictions)}).
+ Output components:
+ [{wanted}]
+ Output values returned:
+ [{received}]"""
+ )
+ else:
+ if len(predictions) == 1 and predictions[0] is None:
+ # do not throw error if the function did not return anything
+ # https://github.com/gradio-app/gradio/issues/9742
+ return
+ warnings.warn(
+ f"""A function{name} returned too many output values (needed: {len(dep_outputs)}, returned: {len(predictions)}). Ignoring extra values.
+ Output components:
+ [{wanted}]
+ Output values returned:
+ [{received}]"""
+ )
+
+ async def postprocess_data(
+ self,
+ block_fn: BlockFunction,
+ predictions: list | dict,
+ state: SessionState | None,
+ ) -> list[Any]:
+ from gradio.profiling import trace_phase
+
+ state = state or SessionState(self)
+ if (
+ isinstance(predictions, dict)
+ and predictions == skip()
+ and len(block_fn.outputs) > 1
+ ):
+ # For developer convenience, if a function returns a single skip() with multiple outputs,
+ # we will skip updating all outputs.
+ predictions = [skip()] * len(block_fn.outputs)
+ if isinstance(predictions, dict) and len(predictions) > 0:
+ predictions = convert_component_dict_to_list(
+ [block._id for block in block_fn.outputs], predictions
+ )
+
+ if len(block_fn.outputs) == 1 and not block_fn.batch:
+ predictions = [
+ predictions,
+ ]
+
+ self.validate_outputs(block_fn, predictions) # type: ignore
+
+ output = []
+ for i, block in enumerate(block_fn.outputs):
+ try:
+ if predictions[i] is components._Keywords.FINISHED_ITERATING:
+ output.append(None)
+ continue
+ except (IndexError, KeyError) as err:
+ raise ValueError(
+ "Number of output components does not match number "
+ f"of values returned from from function {block_fn.name}"
+ ) from err
+
+ if block.stateful:
+ prediction_value = predictions[i]
+ if utils.is_prop_update(prediction_value):
+ # Support gr.update(value=...) for State components
+ if "value" in prediction_value:
+ state[block._id] = prediction_value["value"]
+ else:
+ state[block._id] = prediction_value
+ output.append(None)
+ else:
+ prediction_value = predictions[i]
+ if utils.is_prop_update(
+ prediction_value
+ ): # if update is passed directly (deprecated), remove Nones
+ prediction_value = utils.delete_none(
+ prediction_value, skip_value=True
+ )
+
+ if isinstance(prediction_value, Block):
+ prediction_value = prediction_value.constructor_args.copy()
+ prediction_value["__type__"] = "update"
+ elif isinstance(prediction_value, SimpleNamespace) and getattr(
+ prediction_value, "_is_component_update", False
+ ):
+ prediction_value = vars(prediction_value).copy()
+ keys = inspect.signature(block.__class__.__init__).parameters.keys()
+ prediction_value = {
+ k: v for k, v in prediction_value.items() if k in keys
+ }
+ prediction_value["__type__"] = "update"
+ if utils.is_prop_update(prediction_value):
+ kwargs = state[block._id].constructor_args.copy()
+ kwargs.update(prediction_value)
+ kwargs.pop("value", None)
+ kwargs.pop("__type__")
+ kwargs["render"] = False
+
+ state[block._id] = block.__class__(**kwargs)
+ state._update_config(block._id)
+ prediction_value = postprocess_update_dict(
+ block=state[block._id],
+ update_dict=prediction_value,
+ postprocess=block_fn.postprocess,
+ )
+ if "value" in prediction_value:
+ state._update_value_in_config(
+ block._id, prediction_value.get("value")
+ )
+ elif block_fn.postprocess:
+ if not isinstance(block, components.Component):
+ raise InvalidComponentError(
+ f"{block.__class__} Component not a valid output component."
+ )
+ if block._id in state:
+ block = state[block._id]
+ try:
+ prediction_value = await anyio.to_thread.run_sync(
+ block.postprocess, prediction_value, limiter=self.limiter
+ )
+ except Error:
+ raise
+ except Exception as err:
+ raise ComponentProcessingError(
+ self._format_processing_error(
+ block_fn,
+ i,
+ block,
+ predictions[i],
+ is_input=False,
+ original_error=err,
+ )
+ ) from err
+ if isinstance(prediction_value, (GradioModel, GradioRootModel)):
+ prediction_value_serialized = prediction_value.model_dump()
+ else:
+ prediction_value_serialized = prediction_value
+ async with trace_phase("postprocess_update_state_in_config"):
+ prediction_value_serialized = (
+ await processing_utils.async_move_files_to_cache(
+ prediction_value_serialized,
+ block,
+ postprocess=True,
+ )
+ )
+ if block._id not in state:
+ state[block._id] = block
+ state._update_value_in_config(
+ block._id, prediction_value_serialized
+ )
+ elif not block_fn.postprocess:
+ if block._id not in state:
+ state[block._id] = block
+ state._update_value_in_config(block._id, prediction_value)
+ async with trace_phase("postprocess_move_to_cache"):
+ outputs_cached = await processing_utils.async_move_files_to_cache(
+ prediction_value,
+ block,
+ postprocess=True,
+ )
+ output.append(outputs_cached)
+
+ return output
+
+ async def handle_streaming_outputs(
+ self,
+ block_fn: BlockFunction,
+ data: list,
+ session_hash: str | None,
+ run: int | None,
+ root_path: str | None = None,
+ final: bool = False,
+ ) -> list:
+ if session_hash is None or run is None:
+ return data
+ if run not in self.pending_streams[session_hash]:
+ self.pending_streams[session_hash][run] = {}
+ stream_run: dict[int, MediaStream] = self.pending_streams[session_hash][run]
+
+ for i, block in enumerate(block_fn.outputs):
+ output_id = block._id
+ if (
+ isinstance(block, components.StreamingOutput)
+ and block.streaming
+ and not utils.is_prop_update(data[i])
+ ):
+ if final:
+ stream_run[output_id].end_stream()
+ first_chunk = output_id not in stream_run
+ binary_data, output_data = await block.stream_output(
+ data[i],
+ f"{session_hash}/{run}/{output_id}/playlist.m3u8",
+ first_chunk,
+ )
+ if first_chunk:
+ desired_output_format = None
+ if orig_name := output_data.get("orig_name"):
+ desired_output_format = Path(orig_name).suffix[1:]
+ stream_run[output_id] = MediaStream(
+ desired_output_format=desired_output_format
+ )
+ stream_run[output_id]
+
+ await stream_run[output_id].add_segment(binary_data)
+ output_data = await processing_utils.async_move_files_to_cache(
+ output_data,
+ block,
+ postprocess=True,
+ )
+ if root_path is not None:
+ output_data = processing_utils.add_root_url(
+ output_data, root_path, None
+ )
+ data[i] = output_data
+
+ return data
+
+ def handle_streaming_diffs(
+ self,
+ block_fn: BlockFunction,
+ data: list,
+ session_hash: str | None,
+ run: int | None,
+ final: bool,
+ simple_format: bool = False,
+ ) -> list:
+ if session_hash is None or run is None:
+ return data
+ first_run = run not in self.pending_diff_streams[session_hash]
+ if first_run:
+ self.pending_diff_streams[session_hash][run] = [None] * len(data)
+ last_diffs = self.pending_diff_streams[session_hash][run]
+
+ for i in range(len(block_fn.outputs)):
+ if final:
+ data[i] = last_diffs[i]
+ continue
+
+ if first_run:
+ last_diffs[i] = data[i]
+ else:
+ prev_chunk = last_diffs[i]
+ last_diffs[i] = data[i]
+ if not simple_format:
+ data[i] = utils.diff(prev_chunk, data[i])
+
+ if final:
+ del self.pending_diff_streams[session_hash][run]
+
+ return data
+
+ async def process_api(
+ self,
+ block_fn: BlockFunction | int,
+ inputs: list[Any],
+ state: SessionState | None = None,
+ request: Request | list[Request] | None = None,
+ iterator: AsyncIterator | None = None,
+ session_hash: str | None = None,
+ event_id: str | None = None,
+ event_data: EventData | None = None,
+ in_event_listener: bool = True,
+ simple_format: bool = False,
+ explicit_call: bool = False,
+ root_path: str | None = None,
+ ) -> dict[str, Any]:
+ """
+ Processes API calls from the frontend. First preprocesses the data,
+ then runs the relevant function, then postprocesses the output.
+
+ Parameters:
+ fn_index: Index of function to run.
+ inputs: input data received from the frontend
+ state: data stored from stateful components for session (key is input block id)
+ request: the gr.Request object containing information about the network request (e.g. IP address, headers, query parameters, username)
+ iterators: the in-progress iterators for each generator function (key is function index)
+ event_id: id of event that triggered this API call
+ event_data: data associated with the event trigger itself
+ in_event_listener: whether this API call is being made in response to an event listener
+ explicit_call: whether this call is being made directly by calling the Blocks function, instead of through an event listener or API route
+ root_path: if provided, the root path of the server. All file URLs will be prefixed with this path.
+
+ Returns a dictionary with the following keys:
+ - "data": the output data from the function
+ - "is_generating": whether the function is generating output
+ - "iterator": the iterator for the function
+ - "duration": the duration of the function call
+ - "average_duration": the average duration of the function call
+ - "render_config": the render config for the function
+ """
+ if isinstance(block_fn, int):
+ block_fn = self.fns[block_fn]
+ batch = block_fn.batch
+ state_ids_to_track, hashed_values = self.get_state_ids_to_track(block_fn, state)
+ changed_state_ids = []
+ LocalContext.blocks.set(self)
+
+ if batch:
+ max_batch_size = block_fn.max_batch_size
+ batch_sizes = [len(inp) for inp in inputs]
+ batch_size = batch_sizes[0]
+ manual_cache_used = False
+ if inspect.isasyncgenfunction(block_fn.fn) or inspect.isgeneratorfunction(
+ block_fn.fn
+ ):
+ raise ValueError("Gradio does not support generators in batch mode.")
+ if not all(x == batch_size for x in batch_sizes):
+ raise ValueError(
+ f"All inputs to a batch function must have the same length but instead have sizes: {batch_sizes}."
+ )
+ if batch_size > max_batch_size:
+ raise ValueError(
+ f"Batch size ({batch_size}) exceeds the max_batch_size for this function ({max_batch_size})"
+ )
+ inputs = [
+ await self.preprocess_data(block_fn, list(i), state, explicit_call)
+ for i in zip(*inputs, strict=False)
+ ]
+ with TrackManualCacheUsage():
+ result = await self.call_function(
+ block_fn,
+ list(zip(*inputs, strict=False)),
+ None,
+ request,
+ event_id,
+ event_data,
+ in_event_listener,
+ state,
+ )
+ manual_cache_used = used_manual_cache()
+ preds = result["prediction"]
+ data = [
+ await self.postprocess_data(block_fn, list(o), state)
+ for o in zip(*preds, strict=False)
+ ]
+ if root_path is not None:
+ data = processing_utils.add_root_url(data, root_path, None) # type: ignore
+ data = list(zip(*data, strict=False))
+ is_generating, iterator = None, None
+ else:
+ from gradio.profiling import trace_phase
+
+ old_iterator = iterator
+ manual_cache_used = False
+ if old_iterator:
+ inputs = []
+ else:
+ async with trace_phase("preprocess"):
+ inputs = await self.preprocess_data(
+ block_fn, inputs, state, explicit_call
+ )
+ was_generating = old_iterator is not None
+ with TrackManualCacheUsage():
+ async with trace_phase("fn_call"):
+ result = await self.call_function(
+ block_fn,
+ inputs,
+ old_iterator,
+ request,
+ event_id,
+ event_data,
+ in_event_listener,
+ state,
+ )
+ manual_cache_used = used_manual_cache()
+
+ async with trace_phase("postprocess"):
+ data = await self.postprocess_data(
+ block_fn, result["prediction"], state
+ )
+ if state:
+ changed_state_ids = [
+ state_id
+ for hash_value, state_id in zip(
+ hashed_values, state_ids_to_track, strict=False
+ )
+ if hash_value != utils.deep_hash(state[state_id])
+ ]
+
+ if root_path is not None:
+ data = processing_utils.add_root_url(data, root_path, None)
+ is_generating, iterator = result["is_generating"], result["iterator"]
+ if is_generating or was_generating:
+ run = id(old_iterator) if was_generating else id(iterator)
+ async with trace_phase("streaming_diff"):
+ data = await self.handle_streaming_outputs(
+ block_fn,
+ data,
+ session_hash=session_hash,
+ run=run,
+ root_path=root_path,
+ final=not is_generating,
+ )
+ data = self.handle_streaming_diffs(
+ block_fn,
+ data,
+ session_hash=session_hash,
+ run=run,
+ final=not is_generating,
+ simple_format=simple_format,
+ )
+
+ if not manual_cache_used:
+ block_fn.total_runtime += result["duration"]
+ block_fn.total_runs += 1
+ output = {
+ "data": data,
+ "is_generating": is_generating,
+ "iterator": iterator,
+ "duration": result["duration"],
+ "average_duration": (
+ block_fn.total_runtime / block_fn.total_runs
+ if block_fn.total_runs > 0
+ else None
+ ),
+ "render_config": None,
+ "changed_state_ids": changed_state_ids,
+ "used_cache": "partial" if manual_cache_used else None,
+ }
+ if block_fn.renderable and state:
+ output["render_config"] = state.blocks_config.get_config(
+ block_fn.renderable
+ )
+ output["render_config"]["render_id"] = block_fn.renderable._id
+ if root_path is not None:
+ output["render_config"] = processing_utils.add_root_url(
+ output["render_config"], root_path, None
+ )
+
+ return output
+
+ def get_state_ids_to_track(
+ self, block_fn: BlockFunction, state: SessionState | None
+ ) -> tuple[list[int], list]:
+ if state is None:
+ return [], []
+ state_ids_to_track = []
+ hashed_values = []
+ for block in block_fn.outputs:
+ if block.stateful and any(
+ (block._id, "change") in fn.targets
+ for fn in state.blocks_config.fns.values()
+ ):
+ value = state[block._id]
+ state_ids_to_track.append(block._id)
+ hashed_values.append(utils.deep_hash(value))
+ return state_ids_to_track, hashed_values
+
+ def create_limiter(self):
+ self.limiter = (
+ None
+ if self.max_threads == 40
+ else CapacityLimiter(total_tokens=self.max_threads)
+ )
+
+ def get_config(self): # type: ignore[override]
+ return {"type": "column"}
+
+ def get_config_file(self) -> BlocksConfigDict:
+ config: BlocksConfigDict = {
+ "version": VERSION,
+ "api_prefix": API_PREFIX,
+ "mode": self.mode,
+ "app_id": self.app_id,
+ "dev_mode": self.dev_mode,
+ "vibe_mode": self.vibe_mode,
+ "analytics_enabled": self.analytics_enabled,
+ "components": [],
+ "css": self.css,
+ "connect_heartbeat": False,
+ "js": self.js,
+ "head": self.head,
+ "title": self.title or "Gradio",
+ "space_id": self.space_id,
+ "enable_queue": True, # launch attributes
+ "show_error": getattr(self, "show_error", False),
+ "footer_links": getattr(self, "footer_links", []),
+ "is_colab": utils.colab_check(),
+ "max_file_size": getattr(self, "max_file_size", None),
+ "stylesheets": getattr(self, "stylesheets", []),
+ "theme": self.theme.name if self.theme is not None else None,
+ "protocol": "sse_v3",
+ "body_css": { # type: ignore
+ "body_background_fill": self.theme._get_computed_value(
+ "body_background_fill"
+ ),
+ "body_text_color": self.theme._get_computed_value("body_text_color"),
+ "body_background_fill_dark": self.theme._get_computed_value(
+ "body_background_fill_dark"
+ ),
+ "body_text_color_dark": self.theme._get_computed_value(
+ "body_text_color_dark"
+ ),
+ }
+ if getattr(self, "theme", None) is not None
+ else None,
+ "fill_height": self.fill_height,
+ "fill_width": self.fill_width,
+ "theme_hash": getattr(self, "theme_hash", None), # type: ignore
+ "pwa": self.pwa,
+ "pages": self.pages, # type: ignore
+ "page": {},
+ "mcp_server": self.mcp_server,
+ "i18n_translations": (
+ getattr(self.i18n_instance, "translations_dict", None)
+ if getattr(self, "i18n_instance", None) is not None
+ else None
+ ),
+ }
+ config.update(self.default_config.get_config()) # type: ignore
+ config["connect_heartbeat"] = utils.connect_heartbeat(
+ config, self.blocks.values(), self.fns.values()
+ )
+ return config
+
+ def transpile_to_js(self, quiet: bool = False):
+ fns_to_transpile = [
+ fn.fn for fn in self.fns.values() if fn.fn and fn.js is True
+ ]
+ num_to_transpile = len(fns_to_transpile)
+ if not quiet and num_to_transpile > 0:
+ print("********************************************")
+ print("* Trying to transpile functions from Python -> JS for performance\n")
+ for index, fn in enumerate(fns_to_transpile):
+ if not quiet:
+ print(f"* ({index + 1}/{num_to_transpile}) {fn.__name__}: ", end="") # type: ignore
+ if getattr(fn, "__js_implementation__", None) is None: # type: ignore
+ try:
+ fn.__js_implementation__ = transpile(fn, validate=True) # type: ignore
+ if not quiet:
+ print("✅")
+ except Exception as e:
+ if not quiet:
+ print("❌", e, end="\n\n")
+ elif not quiet:
+ print("✅")
+ if not quiet and num_to_transpile > 0:
+ print("********************************************\n")
+
+ def __enter__(self):
+ render_context = get_render_context()
+ if render_context is None:
+ Context.root_block = self
+ self.parent = render_context
+ set_render_context(self)
+ self.exited = False
+ return self
+
+ def __exit__(self, exc_type: type[BaseException] | None = None, *args):
+ if exc_type is not None:
+ set_render_context(None)
+ Context.root_block = None
+ return
+ super().fill_expected_parents()
+ set_render_context(self.parent)
+ # Configure the load events before root_block is reset
+ self.default_config.attach_load_events()
+ if self.parent is None:
+ Context.root_block = None
+ else:
+ self.parent.children.extend(self.children)
+ self.config = self.get_config_file()
+ self.app = App.create_app(self, mcp_server=False)
+ self.progress_tracking = any(
+ block_fn.tracks_progress for block_fn in self.fns.values()
+ )
+ self.page = ""
+ self.validate_navbar_settings()
+ self.exited = True
+
+ def clear(self):
+ """Resets the layout of the Blocks object."""
+ self.default_config.blocks = {}
+ self.default_config.fns = {}
+ self.children = []
+ return self
+
+ @document()
+ def queue(
+ self,
+ status_update_rate: float | Literal["auto"] = "auto",
+ api_open: bool | None = None,
+ max_size: int | None = None,
+ *,
+ default_concurrency_limit: int | None | Literal["not_set"] = "not_set",
+ ):
+ """
+ By enabling the queue you can control when users know their position in the queue, and set a limit on maximum number of events allowed.
+ Parameters:
+ status_update_rate: If "auto", Queue will send status estimations to all clients whenever a job is finished. Otherwise Queue will send status at regular intervals set by this parameter as the number of seconds.
+ api_open: If True, the REST routes of the backend will be open, allowing requests made directly to those endpoints to skip the queue.
+ max_size: The maximum number of events the queue will store at any given moment. If the queue is full, new events will not be added and a user will receive a message saying that the queue is full. If None, the queue size will be unlimited.
+ default_concurrency_limit: The default value of `concurrency_limit` to use for event listeners that don't specify a value. Can be set by environment variable GRADIO_DEFAULT_CONCURRENCY_LIMIT. Defaults to 1 if not set otherwise.
+ Example: (Blocks)
+ with gr.Blocks() as demo:
+ button = gr.Button(label="Generate Image")
+ button.click(fn=image_generator, inputs=gr.Textbox(), outputs=gr.Image())
+ demo.queue(max_size=10)
+ demo.launch()
+ Example: (Interface)
+ demo = gr.Interface(image_generator, gr.Textbox(), gr.Image())
+ demo.queue(max_size=20)
+ demo.launch()
+ """
+ if api_open is not None:
+ self.api_open = api_open
+ if utils.is_zero_gpu_space():
+ max_size = 1 if max_size is None else max_size
+ self._queue = queueing.Queue(
+ live_updates=status_update_rate == "auto",
+ concurrency_count=self.max_threads,
+ update_intervals=status_update_rate if status_update_rate != "auto" else 1,
+ max_size=max_size,
+ blocks=self,
+ default_concurrency_limit=default_concurrency_limit,
+ )
+ self.app = App.create_app(self, mcp_server=False)
+ return self
+
+ def validate_queue_settings(self):
+ for dep in self.fns.values():
+ for i in dep.cancels:
+ if not self.fns[i].queue:
+ raise ValueError(
+ "Queue needs to be enabled! "
+ "You may get this error by either 1) passing a function that uses the yield keyword "
+ "into an interface without enabling the queue or 2) defining an event that cancels "
+ "another event without enabling the queue. Both can be solved by calling .queue() "
+ "before .launch()"
+ )
+
+ def validate_navbar_settings(self):
+ """Validates that only one Navbar component exists per page."""
+ from gradio.components.navbar import Navbar
+
+ navbar_pages = set()
+ for block in self.blocks.values():
+ if isinstance(block, Navbar):
+ if block.page in navbar_pages:
+ raise ValueError(
+ f"Only one gr.Navbar component can exist per page. "
+ f"Found multiple Navbar components on page '{block.page or 'Home'}'. "
+ "Please remove the extra Navbar components."
+ )
+ navbar_pages.add(block.page)
+
+ def _set_html_css_theme_variables(self):
+ self.theme_css = self.theme._get_theme_css()
+ self.stylesheets = self.theme._stylesheets
+ theme_hasher = hashlib.sha256()
+ theme_hasher.update(self.theme_css.encode("utf-8"))
+ self.theme_hash = theme_hasher.hexdigest()
+ css_paths = utils.none_or_singleton_to_list(self.css_paths)
+ for css_path in css_paths or []:
+ self.css = self.css or ""
+ with open(css_path, encoding="utf-8") as css_file:
+ self.css += "\n" + css_file.read()
+
+ head_paths = utils.none_or_singleton_to_list(self.head_paths)
+ for head_path in head_paths or []:
+ self.head = self.head or ""
+ with open(head_path, encoding="utf-8") as head_file:
+ self.head += "\n" + head_file.read()
+
+ def _resolve_ssr_mode(
+ self, ssr_mode: bool | None, disable_when_multi_page: bool = True
+ ) -> bool:
+ if disable_when_multi_page and len(self.config.get("pages", [])) > 1:
+ warnings.warn(
+ "SSR mode is not supported with multi-page apps when mounting on a FastAPI app. Disabling SSR mode.",
+ UserWarning,
+ )
+ return False
+ return (
+ ssr_mode
+ if ssr_mode is not None
+ else os.getenv("GRADIO_SSR_MODE", "False").lower() == "true"
+ )
+
+ def launch(
+ self,
+ inline: bool | None = None,
+ inbrowser: bool = False,
+ share: bool | None = None,
+ debug: bool = False,
+ max_threads: int = 40,
+ auth: (
+ Callable[[str, str], bool] | tuple[str, str] | list[tuple[str, str]] | None
+ ) = None,
+ auth_message: str | None = None,
+ prevent_thread_lock: bool = False,
+ show_error: bool = False,
+ server_name: str | None = None,
+ server_port: int | None = None,
+ *,
+ height: int = 500,
+ width: int | str = "100%",
+ favicon_path: str | Path | None = None,
+ ssl_keyfile: str | None = None,
+ ssl_certfile: str | None = None,
+ ssl_keyfile_password: str | None = None,
+ ssl_verify: bool = True,
+ quiet: bool = False,
+ footer_links: list[Literal["api", "gradio", "settings"] | dict[str, str]]
+ | None = None,
+ allowed_paths: list[str] | None = None,
+ blocked_paths: list[str] | None = None,
+ root_path: str | None = None,
+ app_kwargs: dict[str, Any] | None = None,
+ state_session_capacity: int = 10000,
+ share_server_address: str | None = None,
+ share_server_protocol: Literal["http", "https"] | None = None,
+ share_server_tls_certificate: str | None = None,
+ auth_dependency: Callable[[fastapi.Request], str | None] | None = None,
+ max_file_size: str | int | None = None,
+ enable_monitoring: bool | None = None,
+ strict_cors: bool = True,
+ node_server_name: str | None = None,
+ node_port: int | None = None,
+ ssr_mode: bool | None = None,
+ pwa: bool | None = None,
+ mcp_server: bool | None = None,
+ num_workers: int | None = None,
+ _app: App | None = None,
+ _frontend: bool = True,
+ i18n: I18n | None = None,
+ theme: Theme | str | None = None,
+ css: str | None = None,
+ css_paths: str | Path | Sequence[str | Path] | None = None,
+ js: str | Literal[True] | None = None,
+ head: str | None = None,
+ head_paths: str | Path | Sequence[str | Path] | None = None,
+ ) -> tuple[App, str, str]:
+ """
+ Launches a simple web server that serves the demo. Can also be used to create a
+ public link used by anyone to access the demo from their browser by setting share=True.
+ Parameters:
+ inline: whether to display in the gradio app inline in an iframe. Defaults to True in python notebooks; False otherwise.
+ inbrowser: whether to automatically launch the gradio app in a new tab on the default browser.
+ share: whether to create a publicly shareable link for the gradio app. Creates an SSH tunnel to make your UI accessible from anywhere. If not provided, it is set to False by default every time, except when running in Google Colab. When localhost is not accessible (e.g. Google Colab), setting share=False is not supported. Can be set by environment variable GRADIO_SHARE=True.
+ debug: if True, blocks the main thread from running. If running in Google Colab, this is needed to print the errors in the cell output.
+ auth: If provided, username and password (or list of username-password tuples) required to access app. Can also provide function that takes username and password and returns True if valid login.
+ auth_message: If provided, HTML message provided on login page.
+ prevent_thread_lock: By default, the gradio app blocks the main thread while the server is running. If set to True, the gradio app will not block and the gradio server will terminate as soon as the script finishes.
+ show_error: If True, any errors in the gradio app will be displayed in an alert modal and printed in the browser console log. They will also be displayed in the alert modal of downstream apps that gr.load() this app.
+ server_port: will start gradio app on this port (if available). Can be set by environment variable GRADIO_SERVER_PORT. If None, will search for an available port starting at 7860.
+ server_name: to make app accessible on local network, set this to "0.0.0.0". Can be set by environment variable GRADIO_SERVER_NAME. If None, will use "127.0.0.1".
+ max_threads: the maximum number of total threads that the Gradio app can generate in parallel. The default is inherited from the starlette library (currently 40).
+ width: The width in pixels of the iframe element containing the gradio app (used if inline=True)
+ height: The height in pixels of the iframe element containing the gradio app (used if inline=True)
+ favicon_path: If a path to a file (.png, .gif, or .ico) is provided, it will be used as the favicon for the web page.
+ ssl_keyfile: If a path to a file is provided, will use this as the private key file to create a local server running on https.
+ ssl_certfile: If a path to a file is provided, will use this as the signed certificate for https. Needs to be provided if ssl_keyfile is provided.
+ ssl_keyfile_password: If a password is provided, will use this with the ssl certificate for https.
+ ssl_verify: If False, skips certificate validation which allows self-signed certificates to be used.
+ quiet: If True, suppresses most print statements.
+ footer_links: The links to display in the footer of the app. Accepts a list, where each element of the list must be one of "api", "gradio", or "settings" corresponding to the API docs, "built with Gradio", and settings pages respectively. If None, all three links will be shown in the footer. An empty list means that no footer is shown.
+ allowed_paths: List of complete filepaths or parent directories that gradio is allowed to serve. Must be absolute paths. Warning: if you provide directories, any files in these directories or their subdirectories are accessible to all users of your app. Can be set by comma separated environment variable GRADIO_ALLOWED_PATHS. These files are generally assumed to be secure and will be displayed in the browser when possible.
+ blocked_paths: List of complete filepaths or parent directories that gradio is not allowed to serve (i.e. users of your app are not allowed to access). Must be absolute paths. Warning: takes precedence over `allowed_paths` and all other directories exposed by Gradio by default. Can be set by comma separated environment variable GRADIO_BLOCKED_PATHS.
+ root_path: The root path (or "mount point") of the application, if it's not served from the root ("/") of the domain. Often used when the application is behind a reverse proxy that forwards requests to the application. For example, if the application is served at "https://example.com/myapp", the `root_path` should be set to "/myapp". A full URL beginning with http:// or https:// can be provided, which will be used as the root path in its entirety. Can be set by environment variable GRADIO_ROOT_PATH. Defaults to "".
+ app_kwargs: Additional keyword arguments to pass to the underlying FastAPI app as a dictionary of parameter keys and argument values. For example, `{"docs_url": "/docs"}`
+ state_session_capacity: The maximum number of sessions whose information to store in memory. If the number of sessions exceeds this number, the oldest sessions will be removed. Reduce capacity to reduce memory usage when using gradio.State or returning updated components from functions. Defaults to 10000.
+ share_server_address: Use this to specify a custom FRP server and port for sharing Gradio apps (only applies if share=True). If not provided, will use the default FRP server at https://gradio.live. See https://github.com/huggingface/frp for more information.
+ share_server_protocol: Use this to specify the protocol to use for the share links. Defaults to "https", unless a custom share_server_address is provided, in which case it defaults to "http". If you are using a custom share_server_address and want to use https, you must set this to "https".
+ share_server_tls_certificate: The path to a TLS certificate file to use when connecting to a custom share server. This parameter is not used with the default FRP server at https://gradio.live. Otherwise, you must provide a valid TLS certificate file (e.g. a "cert.pem") relative to the current working directory, or the connection will not use TLS encryption, which is insecure.
+ auth_dependency: A function that takes a FastAPI request and returns a string user ID or None. If the function returns None for a specific request, that user is not authorized to access the app (they will see a 401 Unauthorized response). To be used with external authentication systems like OAuth. Cannot be used with `auth`.
+ max_file_size: The maximum file size in bytes that can be uploaded. Can be a string of the form "", where value is any positive integer and unit is one of "b", "kb", "mb", "gb", "tb". If None, no limit is set.
+ enable_monitoring: Enables traffic monitoring of the app through the /monitoring endpoint. By default is None, which enables this endpoint. If explicitly True, will also print the monitoring URL to the console. If False, will disable monitoring altogether.
+ strict_cors: If True, prevents external domains from making requests to a Gradio server running on localhost. If False, allows requests to localhost that originate from localhost but also, crucially, from "null". This parameter should normally be True to prevent CSRF attacks but may need to be False when embedding a *locally-running Gradio app* using web components.
+ ssr_mode: If True, the Gradio app will be rendered using server-side rendering mode, which is typically more performant and provides better SEO, but this requires Node 20+ to be installed on the system. If False, the app will be rendered using client-side rendering mode. If None, will use GRADIO_SSR_MODE environment variable or default to False.
+ pwa: If True, the Gradio app will be set up as an installable PWA (Progressive Web App). If set to None (default behavior), then the PWA feature will be enabled if this Gradio app is launched on Spaces, but not otherwise.
+ i18n: An I18n instance containing custom translations, which are used to translate strings in our components (e.g. the labels of components or Markdown strings). This feature can only be used to translate static text in the frontend, not values in the backend.
+ mcp_server: If True, the Gradio app will be set up as an MCP server and documented functions will be added as MCP tools. If None (default behavior), then the GRADIO_MCP_SERVER environment variable will be used to determine if the MCP server should be enabled.
+ num_workers: Number of background workers to launch in the background to serve file I/O and static assets. This offloads traffic from the main server and reduces latency. Only has an effect if ssr mode is set.
+ theme: A Theme object or a string representing a theme. If a string, will look for a built-in theme with that name (e.g. "soft" or "default"), or will attempt to load a theme from the Hugging Face Hub (e.g. "gradio/monochrome"). If None, will use the Default theme.
+ css: Custom css as a code string. This css will be included in the demo webpage.
+ css_paths: Custom css as a pathlib.Path to a css file or a list of such paths. This css files will be read, concatenated, and included in the demo webpage. If the `css` parameter is also set, the css from `css` will be included first.
+ js: Custom js as a code string. The js code will automatically be executed when the page loads. For more flexibility, use the head parameter to insert js inside '`); `head` content "
+ "is injected and loaded before `js_on_load` runs. Then, if needed, put code "
+ "that uses those libraries in the `js_on_load` parameter, which executes when "
+ "the component renders. See "
+ "https://gradio.app/guides/custom-HTML-components for details.",
+ UserWarning,
+ stacklevel=2,
+ )
+
+
+@document()
+class HTML(BlockContext, Component):
+ """
+ Creates a component with arbitrary HTML. Can include CSS and JavaScript to create highly customized and interactive components.
+ Example:
+ ```python
+ import gradio as gr
+
+ with gr.Blocks() as demo:
+ basic_html = gr.HTML("This is a basic HTML component
")
+
+ button_set = gr.HTML(["Option 1", "Option 2", "Option 3"],
+ html_template="{{#each value}}{{/each}}",
+ css_template="button { margin: 5px; padding: 10px; }",
+ js_on_load="element.querySelectorAll('.option-btn').forEach(btn => { btn.addEventListener('click', () => { trigger('click', {option: btn.innerText}); }); });")
+ clicked_option = gr.Textbox(label="Clicked Option")
+ def on_button_click(evt: gr.EventData):
+ return evt.option
+ button_set.click(on_button_click, outputs=clicked_option)
+
+ demo.launch()
+ ```
+ Demos: super_html
+ Guides: custom-HTML-components, custom-CSS-and-JS
+ """
+
+ EVENTS = all_events
+
+ def __init__(
+ self,
+ value: Any | Callable | None = None,
+ *,
+ label: str | I18nData | None = None,
+ html_template: str = "${value}",
+ css_template: str = "",
+ js_on_load: str
+ | None = "element.addEventListener('click', function() { trigger('click') });",
+ apply_default_css: bool = True,
+ every: Timer | float | None = None,
+ inputs: Component | Sequence[Component] | set[Component] | None = None,
+ show_label: bool = False,
+ scale: int | None = None,
+ min_width: int | None = None,
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = "value",
+ min_height: int | None = None,
+ max_height: int | None = None,
+ container: bool = False,
+ padding: bool = False,
+ autoscroll: bool = False,
+ buttons: list[Button] | None = None,
+ head: str | None = None,
+ server_functions: list[Callable] | None = None,
+ **props: Any,
+ ):
+ """
+ Parameters:
+ value: The HTML content in the ${value} tag in the html_template. For example, if html_template="${value}
" and value="Hello, world!", the component will render as `"Hello, world!
"`.
+ label: The label for this component. Is used as the header if there are a table of examples for this component. If None and used in a `gr.Interface`, the label will be the name of the parameter this component is assigned to.
+ html_template: A string representing the HTML template for this component as a JS template string and Handlebars template. The `${value}` tag will be replaced with the `value` parameter, and all other tags will be filled in with the values from `props`. This element can have children when used in a `with gr.HTML(...):` context, and the children will be rendered to replace `@children` substring, which cannot be nested inside any HTML tags.
+ css_template: A string representing the CSS template for this component as a JS template string and Handlebars template. The CSS will be automatically scoped to this component, and rules outside a block will target the component's root element. The `${value}` tag will be replaced with the `value` parameter, and all other tags will be filled in with the values from `props`.
+ js_on_load: A string representing the JavaScript code that will be executed when the component is loaded. The `element` variable refers to the HTML element of this component, and can be used to access children such as `element.querySelector()`. The `trigger` function can be used to trigger events, such as `trigger('click')`. The value and other props can be edited through `props`, e.g. `props.value = "new value"` which will re-render the HTML template. If `server_functions` is provided, a `server` object is also available in `js_on_load`, where each function is accessible as an async method, e.g. `server.list_files(path).then(files => ...)` or `const files = await server.list_files(path)`. The `upload` async function can be used to upload a JavaScript `File` object to the Gradio server, returning a dictionary with `path` (the server-side file path) and `url` (the public URL to access the file), e.g. `const { path, url } = await upload(file)`. The `watch` function can be used to observe prop changes when the component is an output to a Python event listener: `watch('value', () => { ... })` runs the callback after the template re-renders whenever `value` changes, or `watch(['value', 'color'], () => { ... })` to watch multiple props.`.
+ apply_default_css: If True, default Gradio CSS styles will be applied to the HTML component.
+ head: A raw HTML string to inject into the document `` before `js_on_load` runs. Typically used for `", r.text
+ ) # some basic regex to extract the config
+ try:
+ config = json.loads(result.group(1)) # type: ignore
+ except AttributeError as ae:
+ raise ValueError(f"Could not load the Space: {space_name}") from ae
+ elif config_request.status_code == 200:
+ config = config_request.json()
+ else:
+ raise ValueError(
+ f"Could not load the Space: {space_name} because the config could not be fetched."
+ )
+ if "allow_flagging" in config: # Create an Interface for Gradio 2.x Spaces
+ return from_spaces_interface(
+ space_name, config, alias, token, iframe_url, **kwargs
+ )
+ else: # Create a Blocks for Gradio 3.x Spaces
+ if kwargs:
+ warnings.warn(
+ "You cannot override parameters for this Space by passing in kwargs. "
+ "Instead, please load the Space as a function and use it to create a "
+ "Blocks or Interface locally. You may find this Guide helpful: "
+ "https://gradio.app/using_blocks_like_functions/"
+ )
+ return from_spaces_blocks(space=space_name, token=token)
+
+
+def make_event_data_fn(client, endpoint):
+ """Create a function that accepts EventData.
+ The event_data_fn has to be created in this closure so that the value of endpoint
+ is correctly captured."""
+ helper = client.new_helper(endpoint.fn_index)
+
+ def event_data_fn(event_data: gr.EventData, *args):
+ fn = endpoint.make_end_to_end_fn(helper)
+ return fn(*args, event_data=event_data._data)
+
+ return event_data_fn
+
+
+def from_spaces_blocks(space: str, token: str | None) -> Blocks:
+ client = Client(
+ space,
+ token=token,
+ download_files=False,
+ _skip_components=False,
+ )
+ # We set deserialize to False to avoid downloading output files from the server.
+ # Instead, we serve them as URLs using the /proxy/ endpoint directly from the server.
+
+ if client.app_version < version.Version("4.0.0b14"):
+ raise GradioVersionIncompatibleError(
+ f"Gradio version 4.x cannot load spaces with versions less than 4.x ({client.app_version})."
+ "Please downgrade to version 3 to load this space."
+ )
+
+ # Use end_to_end_fn here to properly upload/download all files
+ predict_fns = []
+ for endpoint in client.endpoints.values():
+ if not isinstance(endpoint, Endpoint):
+ raise TypeError(
+ f"Expected endpoint to be an Endpoint, but got {type(endpoint)}"
+ )
+ if endpoint.backend_fn:
+ dep_config = next(
+ (
+ d
+ for d in client.config["dependencies"]
+ if d.get("id") == endpoint.fn_index
+ ),
+ {},
+ )
+ if dep_config.get("collects_event_data"):
+ event_data_fn = make_event_data_fn(client, endpoint)
+ predict_fns.append(event_data_fn)
+ else:
+ helper = client.new_helper(endpoint.fn_index)
+ fn = endpoint.make_end_to_end_fn(helper)
+ predict_fns.append(fn)
+ else:
+ predict_fns.append(None)
+ blocks = gr.Blocks.from_config(client.config, predict_fns, client.src) # type: ignore
+ with blocks:
+ # Reset the session_hash when page loads
+ blocks.load(lambda: client.reset_session(), None, None)
+ return blocks
+
+
+def from_spaces_interface(
+ model_name: str,
+ config: dict,
+ alias: str | None,
+ token: str | None,
+ iframe_url: str,
+ **kwargs,
+) -> Interface:
+ config = external_utils.streamline_spaces_interface(config)
+ api_url = f"{iframe_url}/api/predict/"
+ headers = {"Content-Type": "application/json"}
+ if token not in [False, None]:
+ headers["Authorization"] = f"Bearer {token}"
+
+ # The function should call the API with preprocessed data
+ def fn(*data):
+ data = json.dumps({"data": data})
+ response = httpx.post(api_url, headers=headers, data=data) # type: ignore
+ result = json.loads(response.content.decode("utf-8"))
+ if "error" in result and "429" in result["error"]:
+ raise TooManyRequestsError("Too many requests to the Hugging Face API")
+ try:
+ output = result["data"]
+ except KeyError as ke:
+ raise KeyError(
+ f"Could not find 'data' key in response from external Space. Response received: {result}"
+ ) from ke
+ if (
+ len(config["outputs"]) == 1
+ ): # if the fn is supposed to return a single value, pop it
+ output = output[0]
+ if (
+ len(config["outputs"]) == 1 and isinstance(output, list)
+ ): # Needed to support Output.Image() returning bounding boxes as well (TODO: handle different versions of gradio since they have slightly different APIs)
+ output = output[0]
+ return output
+
+ fn.__name__ = alias if (alias is not None) else model_name
+ config["fn"] = fn
+
+ kwargs = dict(config, **kwargs)
+ kwargs["_api_mode"] = True
+
+ fn = kwargs.pop("fn", None)
+ inputs = kwargs.pop("inputs", None)
+ outputs = kwargs.pop("outputs", None)
+
+ interface = gr.Interface(fn, inputs, outputs, **kwargs)
+ return interface
+
+
+TEXT_FILE_EXTENSIONS = (
+ ".doc",
+ ".docx",
+ ".rtf",
+ ".epub",
+ ".odt",
+ ".odp",
+ ".pptx",
+ ".txt",
+ ".md",
+ ".py",
+ ".ipynb",
+ ".js",
+ ".jsx",
+ ".html",
+ ".css",
+ ".java",
+ ".cs",
+ ".php",
+ ".c",
+ ".cc",
+ ".cpp",
+ ".cxx",
+ ".cts",
+ ".h",
+ ".hh",
+ ".hpp",
+ ".rs",
+ ".R",
+ ".Rmd",
+ ".swift",
+ ".go",
+ ".rb",
+ ".kt",
+ ".kts",
+ ".ts",
+ ".tsx",
+ ".m",
+ ".mm",
+ ".mts",
+ ".scala",
+ ".dart",
+ ".lua",
+ ".pl",
+ ".pm",
+ ".t",
+ ".sh",
+ ".bash",
+ ".zsh",
+ ".bat",
+ ".coffee",
+ ".csv",
+ ".log",
+ ".ini",
+ ".cfg",
+ ".config",
+ ".json",
+ ".proto",
+ ".yaml",
+ ".yml",
+ ".toml",
+ ".sql",
+)
+IMAGE_FILE_EXTENSIONS = (".png", ".jpg", ".jpeg", ".gif", ".webp")
+
+
+def format_conversation(
+ history: list[NormalizedMessageDict], new_message: str | MultimodalValue
+) -> list[dict]:
+ conversation = []
+ for message in history:
+ new_content = []
+ for content in message["content"]:
+ if "type" not in content:
+ raise ValueError(
+ f"Invalid message format: {message['content']}. Each element must have a type key."
+ )
+ elif content["type"] == "file":
+ new_content.append(
+ {
+ "type": "image_url",
+ "image_url": {
+ "url": encode_url_or_file_to_base64(content["file"]["path"]) # type: ignore
+ },
+ }
+ )
+ else:
+ new_content.append(content)
+ message["content"] = new_content
+ conversation.append(message)
+ if isinstance(new_message, str):
+ text = new_message
+ files = []
+ else:
+ text = new_message.get("text", None)
+ files = new_message.get("files", [])
+ image_files, text_encoded = [], []
+ for file in files:
+ if file.lower().endswith(TEXT_FILE_EXTENSIONS):
+ text_encoded.append(file)
+ else:
+ image_files.append(file)
+
+ for image in image_files:
+ conversation.append(
+ {
+ "role": "user",
+ "content": [
+ {
+ "type": "image_url",
+ "image_url": {"url": encode_url_or_file_to_base64(image)},
+ }
+ ],
+ }
+ )
+ if text or text_encoded:
+ text = text or ""
+ text += "\n".join(
+ [
+ f"\n## {Path(file).name}\n{Path(file).read_text()}"
+ for file in text_encoded
+ ]
+ )
+ conversation.append(
+ {"role": "user", "content": [{"type": "text", "text": text}]}
+ )
+ return conversation
+
+
+@document()
+def load_chat(
+ base_url: str,
+ model: str,
+ token: str | None = None,
+ *,
+ file_types: Literal["text_encoded", "image"]
+ | list[Literal["text_encoded", "image"]]
+ | None = "text_encoded",
+ system_message: str | None = None,
+ streaming: bool = True,
+ **kwargs,
+) -> ChatInterface:
+ """
+ Load a chat interface from an OpenAI API chat compatible endpoint.
+ Parameters:
+ base_url: The base URL of the endpoint, e.g. "http://localhost:11434/v1/"
+ model: The name of the model you are loading, e.g. "llama3.2"
+ token: The API token or a placeholder string if you are using a local model, e.g. "ollama"
+ file_types: The file types allowed to be uploaded by the user. "text_encoded" allows uploading any text-encoded file (which is simply appended to the prompt), and "image" adds image upload support. Set to None to disable file uploads.
+ system_message: The system message to use for the conversation, if any.
+ streaming: Whether the response should be streamed.
+ kwargs: Additional keyword arguments to pass into ChatInterface for customization.
+ Example:
+ import gradio as gr
+ gr.load_chat(
+ "http://localhost:11434/v1/",
+ model="qwen2.5",
+ token="***",
+ file_types=["text_encoded", "image"],
+ system_message="You are a silly assistant.",
+ ).launch()
+ """
+ try:
+ from openai import OpenAI
+ except ImportError as e:
+ raise ImportError(
+ "To use OpenAI API Client, you must install the `openai` package. You can install it with `pip install openai`."
+ ) from e
+ from gradio.chat_interface import ChatInterface
+
+ client = OpenAI(api_key=token or "***", base_url=base_url)
+ start_message = (
+ [{"role": "system", "content": system_message}] if system_message else []
+ )
+ file_types = utils.none_or_singleton_to_list(file_types)
+
+ def open_api(message: str | MultimodalValue, history: list | None) -> str | None:
+ history = history or start_message
+ conversation = format_conversation(history, message) # type: ignore
+ return (
+ client.chat.completions.create(
+ model=model,
+ messages=conversation, # type: ignore
+ )
+ .choices[0]
+ .message.content
+ )
+
+ def open_api_stream(
+ message: str | MultimodalValue, history: list | None
+ ) -> Generator[str, None, None]:
+ history = history or start_message
+ conversation = format_conversation(history, message) # type: ignore
+ stream = client.chat.completions.create(
+ model=model,
+ messages=conversation, # type: ignore
+ stream=True,
+ )
+ response = ""
+ for chunk in stream:
+ if chunk.choices and chunk.choices[0].delta.content is not None:
+ response += chunk.choices[0].delta.content
+ yield response
+
+ supported_extensions = []
+ for file_type in file_types:
+ if file_type == "text_encoded":
+ supported_extensions += TEXT_FILE_EXTENSIONS
+ elif file_type == "image":
+ supported_extensions += IMAGE_FILE_EXTENSIONS
+ else:
+ raise ValueError(
+ f"Invalid file type: {file_type}. Must be 'text_encoded' or 'image'."
+ )
+
+ if "chatbot" not in kwargs:
+ from gradio.components import Chatbot
+
+ kwargs["chatbot"] = Chatbot(scale=1, allow_tags=True)
+
+ textbox_arg = kwargs.pop("textbox", None)
+ if textbox_arg is not None:
+ textbox = textbox_arg
+ else:
+ textbox = (
+ gr.MultimodalTextbox(file_types=supported_extensions)
+ if file_types
+ else None
+ )
+
+ return ChatInterface(
+ open_api_stream if streaming else open_api,
+ multimodal=bool(file_types),
+ textbox=textbox,
+ **kwargs,
+ )
+
+
+@document()
+def load_openapi(
+ openapi_spec: str | dict,
+ base_url: str,
+ *,
+ paths: list[str] | None = None,
+ exclude_paths: list[str] | None = None,
+ methods: list[Literal["get", "post", "put", "patch", "delete"]] | None = None,
+ auth_token: str | None = None,
+ **interface_kwargs,
+) -> Blocks:
+ """
+ Load a Gradio app from an OpenAPI v3 specification.
+
+ Parameters:
+ openapi_spec: URL, file path, or dictionary containing the OpenAPI specification (v3, JSON format only)
+ base_url: Base URL for the API endpoints, e.g. "https://api.example.com/v1". This is used to construct the full URL for each endpoint.
+ paths: Optional list of specific API paths to create Gradio endpoints from. Supports regex patterns, e.g. ["/api/v1/books", ".*user.*"]. If None, all paths in the OpenAPI spec will be included.
+ exclude_paths: Optional list of API paths to exclude from the Gradio endpoints. Supports regex patterns and takes precedence over `paths`. For example, [".*internal.*"] will exclude all paths containing "internal".
+ methods: Optional list of HTTP methods to include in the Gradio endpoints. If None, all methods will be included.
+ auth_token: Optional authentication token to be sent as a Bearer token in the Authorization header for all API requests.
+ interface_kwargs: Additional keyword arguments to pass to each generated gr.Interface instance (e.g., title, description, article, examples_per_page, etc.)
+ Returns:
+ A Gradio Blocks app with endpoints generated from the OpenAPI spec
+ """
+ if isinstance(openapi_spec, dict):
+ spec = openapi_spec
+ elif isinstance(openapi_spec, str):
+ if is_http_url_like(openapi_spec):
+ response = httpx.get(openapi_spec)
+ response.raise_for_status()
+ content = response.text
+ else:
+ with open(openapi_spec) as f:
+ content = f.read()
+
+ try:
+ spec = json.loads(content)
+ except json.JSONDecodeError as e:
+ raise ValueError("openapi_spec must be a JSON string or dictionary") from e
+ else:
+ raise ValueError("openapi_spec must be a string (URL/file path) or dictionary")
+
+ api_paths = spec.get("paths", {})
+
+ if paths is not None or exclude_paths is not None:
+ filtered_paths = {}
+ for path in api_paths:
+ if exclude_paths:
+ excluded = False
+ for exclude_pattern in exclude_paths:
+ if re.match(exclude_pattern, path):
+ excluded = True
+ break
+ if excluded:
+ continue
+
+ if paths is not None:
+ for pattern in paths:
+ if re.match(pattern, path):
+ filtered_paths[path] = api_paths[path]
+ break
+ else:
+ filtered_paths[path] = api_paths[path]
+
+ api_paths = filtered_paths
+
+ if not api_paths:
+ raise ValueError("No valid paths found in the OpenAPI specification")
+ else:
+ print(f"* Loaded {len(api_paths)} paths from the OpenAPI specification")
+
+ valid_api_paths = []
+ for path, path_item in api_paths.items():
+ for method, operation in path_item.items():
+ if methods and method.lower() not in [m.lower() for m in methods]:
+ continue
+ valid_api_paths.append((path, method, operation))
+
+ with gr.Blocks(
+ title=spec.get("info", {}).get("title", "OpenAPI Interface")
+ ) as demo:
+ with gr.Sidebar():
+ gr.Markdown(f"## {spec.get('info', {}).get('title', 'OpenAPI Interface')}")
+ gr.Markdown(spec.get("info", {}).get("description", ""))
+ gr.Markdown("### API Endpoints")
+ api_path_str = ""
+ for path, method, _ in valid_api_paths:
+ api_path_str += (
+ f"- "
+ f""
+ f"{method.upper()}
{path}"
+ f" \n"
+ )
+ api_path_str += "
"
+ gr.Markdown(api_path_str)
+
+ for path, method, operation in valid_api_paths:
+ components_list = []
+
+ for param in operation.get("parameters", []):
+ component = external_utils.component_from_parameter_schema(param)
+ components_list.append(component)
+
+ request_body = operation.get("requestBody")
+ if request_body:
+ body_component = external_utils.component_from_request_body_schema(
+ request_body, spec
+ )
+ if body_component:
+ components_list.append(body_component)
+
+ endpoint_fn = external_utils.create_endpoint_fn(
+ path, method, operation, base_url, auth_token
+ )
+ endpoint_fn.__name__ = (
+ f"{method}_{path.replace('/', '_').replace('{', '').replace('}', '')}"
+ )
+
+ gr.Markdown(
+ f""
+ f"{external_utils.method_box(method)} {path}
"
+ )
+ if operation.get("summary"):
+ gr.Markdown(operation["summary"])
+
+ inputs = components_list if components_list else []
+ output = gr.JSON(label="Response")
+
+ gr.Interface(
+ fn=endpoint_fn,
+ inputs=inputs,
+ outputs=output,
+ **interface_kwargs,
+ )
+
+ return demo
diff --git a/gradio/external_utils.py b/gradio/external_utils.py
new file mode 100644
index 0000000..15bcb3e
--- /dev/null
+++ b/gradio/external_utils.py
@@ -0,0 +1,616 @@
+"""Utility function for gradio/external.py, designed for internal use."""
+
+from __future__ import annotations
+
+import base64
+import inspect
+import json
+import math
+import re
+import warnings
+
+import httpx
+import yaml
+from gradio_client.utils import encode_url_or_file_to_base64
+from huggingface_hub import HfApi, ImageClassificationOutputElement, InferenceClient
+from huggingface_hub.errors import HFValidationError
+
+from gradio import components
+from gradio.exceptions import Error, TooManyRequestsError
+
+
+def get_model_info(model_name, token=None):
+ hf_api = HfApi(token=token)
+ print(f"Fetching model from: https://huggingface.co/{model_name}")
+ try:
+ model_info = hf_api.model_info(model_name)
+ except HFValidationError as e:
+ if ":fastest" in model_name or ":cheapest" in model_name:
+ raise ValueError(
+ "To use :cheapest or :fastest, upgrade huggingface_hub to huggingface_hub>=1.0"
+ ) from e
+ raise
+ pipeline = model_info.pipeline_tag
+ tags = model_info.tags
+ return pipeline, tags
+
+
+##################
+# Helper functions for processing tabular data
+##################
+
+
+def get_tabular_examples(model_name: str) -> dict[str, list[float]]:
+ readme = httpx.get(f"https://huggingface.co/{model_name}/resolve/main/README.md")
+ if readme.status_code != 200:
+ warnings.warn(f"Cannot load examples from README for {model_name}", UserWarning)
+ example_data = {}
+ else:
+ yaml_regex = re.search(
+ "(?:^|[\r\n])---[\n\r]+([\\S\\s]*?)[\n\r]+---([\n\r]|$)", readme.text
+ )
+ if yaml_regex is None:
+ example_data = {}
+ else:
+ example_yaml = next(
+ yaml.safe_load_all(readme.text[: yaml_regex.span()[-1]])
+ )
+ example_data = example_yaml.get("widget", {}).get("structuredData", {})
+ if not example_data:
+ raise ValueError(
+ f"No example data found in README.md of {model_name} - Cannot build gradio demo. "
+ "See the README.md here: https://huggingface.co/scikit-learn/tabular-playground/blob/main/README.md "
+ "for a reference on how to provide example data to your model."
+ )
+ # replace nan with string NaN for inference Endpoints
+ for data in example_data.values():
+ for i, val in enumerate(data):
+ if isinstance(val, float) and math.isnan(val):
+ data[i] = "NaN"
+ return example_data
+
+
+def cols_to_rows(
+ example_data: dict[str, list[float | str] | None],
+) -> tuple[list[str], list[list[float]]]:
+ headers = list(example_data.keys())
+ n_rows = max(len(example_data[header] or []) for header in headers)
+ data = []
+ for row_index in range(n_rows):
+ row_data = []
+ for header in headers:
+ col = example_data[header] or []
+ if row_index >= len(col):
+ row_data.append("NaN")
+ else:
+ row_data.append(col[row_index])
+ data.append(row_data)
+ return headers, data
+
+
+def rows_to_cols(incoming_data: dict) -> dict[str, dict[str, dict[str, list[str]]]]:
+ data_column_wise = {}
+ for i, header in enumerate(incoming_data["headers"]):
+ data_column_wise[header] = [str(row[i]) for row in incoming_data["data"]]
+ return {"inputs": {"data": data_column_wise}}
+
+
+##################
+# Helper functions for processing other kinds of data
+##################
+
+
+def postprocess_label(scores: list[ImageClassificationOutputElement]) -> dict:
+ return {c.label: c.score for c in scores}
+
+
+def postprocess_mask_tokens(scores: list[dict[str, str | float]]) -> dict:
+ return {c["token_str"]: c["score"] for c in scores}
+
+
+def postprocess_question_answering(answer: dict) -> tuple[str, dict]:
+ return answer["answer"], {answer["answer"]: answer["score"]}
+
+
+def postprocess_visual_question_answering(scores: list[dict[str, str | float]]) -> dict:
+ return {c["answer"]: c["score"] for c in scores}
+
+
+def zero_shot_classification_wrapper(client: InferenceClient):
+ def zero_shot_classification_inner(input: str, labels: str, multi_label: bool):
+ return client.zero_shot_classification(
+ input, labels.split(","), multi_label=multi_label
+ )
+
+ return zero_shot_classification_inner
+
+
+def sentence_similarity_wrapper(client: InferenceClient):
+ def sentence_similarity_inner(input: str, sentences: str):
+ return client.sentence_similarity(input, sentences.split("\n"))
+
+ return sentence_similarity_inner
+
+
+def text_generation_wrapper(client: InferenceClient):
+ def text_generation_inner(input: str):
+ return input + client.text_generation(input)
+
+ return text_generation_inner
+
+
+def conversational_wrapper(client: InferenceClient):
+ def chat_fn(message, history):
+ if not history:
+ history = []
+ history.append({"role": "user", "content": message})
+ try:
+ out = ""
+ for chunk in client.chat_completion(messages=history, stream=True):
+ out += chunk.choices[0].delta.content or "" if chunk.choices else ""
+ yield out
+ except Exception as e:
+ handle_hf_error(e)
+
+ return chat_fn
+
+
+def encode_to_base64(r: httpx.Response) -> str:
+ # Handles the different ways HF API returns the prediction
+ base64_repr = base64.b64encode(r.content).decode("utf-8")
+ data_prefix = ";base64,"
+ # Case 1: base64 representation already includes data prefix
+ if data_prefix in base64_repr:
+ return base64_repr
+ else:
+ content_type = r.headers.get("content-type")
+ # Case 2: the data prefix is a key in the response
+ if content_type == "application/json":
+ try:
+ data = r.json()[0]
+ content_type = data["content-type"]
+ base64_repr = data["blob"]
+ except KeyError as ke:
+ raise ValueError(
+ "Cannot determine content type returned by external API."
+ ) from ke
+ # Case 3: the data prefix is included in the response headers
+ else:
+ pass
+ new_base64 = f"data:{content_type};base64,{base64_repr}"
+ return new_base64
+
+
+def format_ner_list(input_string: str, ner_groups: list[dict[str, str | int]]):
+ if len(ner_groups) == 0:
+ return [(input_string, None)]
+
+ output = []
+ end = 0
+ prev_end = 0
+
+ for group in ner_groups:
+ entity, start, end = group["entity_group"], group["start"], group["end"]
+ output.append((input_string[prev_end:start], None))
+ output.append((input_string[start:end], entity))
+ prev_end = end
+
+ output.append((input_string[end:], None))
+ return output
+
+
+def token_classification_wrapper(client: InferenceClient):
+ def token_classification_inner(input: str):
+ ner_list = client.token_classification(input)
+ return format_ner_list(input, ner_list) # type: ignore
+
+ return token_classification_inner
+
+
+def object_detection_wrapper(client: InferenceClient):
+ def object_detection_inner(input: str):
+ annotations = client.object_detection(input)
+ formatted_annotations = [
+ (
+ (
+ a["box"]["xmin"],
+ a["box"]["ymin"],
+ a["box"]["xmax"],
+ a["box"]["ymax"],
+ ),
+ a["label"],
+ )
+ for a in annotations
+ ]
+ return (input, formatted_annotations)
+
+ return object_detection_inner
+
+
+def image_text_to_text_wrapper(client: InferenceClient):
+ def chat_fn(image, text):
+ messages = [
+ {
+ "role": "user",
+ "content": [
+ {
+ "type": "image_url",
+ "image_url": {"url": encode_url_or_file_to_base64(image)},
+ },
+ {"type": "text", "text": text},
+ ],
+ }
+ ]
+
+ try:
+ response = client.chat_completion(messages=messages, stream=False)
+ return response.choices[0].message.content
+ except Exception as e:
+ # Fallback to image_to_text for models that don't support chat_completion
+ try:
+ # Try image_to_text (standard image captioning)
+ result = client.image_to_text(image)
+ return f"Image description: {result}\n\nUser question: {text}\n\nNote: This model doesn't support question-answering about images, only image captioning."
+ except Exception:
+ handle_hf_error(e)
+
+ return chat_fn
+
+
+def chatbot_preprocess(text, state):
+ if not state:
+ return text, [], []
+ return (
+ text,
+ state["conversation"]["generated_responses"],
+ state["conversation"]["past_user_inputs"],
+ )
+
+
+def chatbot_postprocess(response):
+ chatbot_history = list(
+ zip(
+ response["conversation"]["past_user_inputs"],
+ response["conversation"]["generated_responses"],
+ strict=False,
+ )
+ )
+ return chatbot_history, response
+
+
+def tabular_wrapper(client: InferenceClient, pipeline: str):
+ # This wrapper is needed to handle an issue in the InfereneClient where the model name is not
+ # automatically loaded when using the tabular_classification and tabular_regression methods.
+ # See: https://github.com/huggingface/huggingface_hub/issues/2015
+ def tabular_inner(data):
+ if pipeline not in ("tabular_classification", "tabular_regression"):
+ raise TypeError(f"pipeline type {pipeline!r} not supported")
+ assert client.model # noqa: S101
+ if pipeline == "tabular_classification":
+ return client.tabular_classification(data, model=client.model)
+ else:
+ return client.tabular_regression(data, model=client.model)
+
+ return tabular_inner
+
+
+##################
+# Helper function for cleaning up an Interface loaded from HF Spaces
+##################
+
+
+def streamline_spaces_interface(config: dict) -> dict:
+ """Streamlines the interface config dictionary to remove unnecessary keys."""
+ config["inputs"] = [
+ components.get_component_instance(component)
+ for component in config["input_components"]
+ ]
+ config["outputs"] = [
+ components.get_component_instance(component)
+ for component in config["output_components"]
+ ]
+ parameters = {
+ "article",
+ "description",
+ "flagging_options",
+ "inputs",
+ "outputs",
+ "title",
+ }
+ config = {k: config[k] for k in parameters}
+ return config
+
+
+def handle_hf_error(e: Exception):
+ if "429" in str(e):
+ raise TooManyRequestsError() from e
+ elif "401" in str(e) or "You must provide an api_key" in str(e):
+ raise Error("Unauthorized, please make sure you are signed in.") from e
+ elif isinstance(e, StopIteration):
+ raise Error(
+ "This model is not supported by any Hugging Face Inference Provider. "
+ "Please check the supported models at https://huggingface.co/docs/inference-providers."
+ ) from e
+ else:
+ raise Error(str(e) or f"An error occurred: {type(e).__name__}") from e
+
+
+def create_endpoint_fn(
+ endpoint_path: str,
+ endpoint_method: str,
+ endpoint_operation: dict,
+ base_url: str,
+ auth_token: str | None = None,
+):
+ # Get request body info for docstring generation
+ request_body = endpoint_operation.get("requestBody", {})
+
+ def endpoint_fn(*args):
+ url = f"{base_url.rstrip('/')}{endpoint_path}"
+
+ headers = {"Content-Type": "application/json"}
+ if auth_token:
+ headers["Authorization"] = f"Bearer {auth_token}"
+
+ params = {}
+ body_data = {}
+
+ operation_params = endpoint_operation.get("parameters", [])
+ request_body = endpoint_operation.get("requestBody", {})
+
+ param_index = 0
+ for param in operation_params:
+ if param_index < len(args):
+ if param.get("in") == "query":
+ params[param["name"]] = args[param_index]
+ elif param.get("in") == "path":
+ url = url.replace(f"{{{param['name']}}}", str(args[param_index]))
+ param_index += 1
+
+ is_file_upload = False
+ if request_body and param_index < len(args):
+ content = request_body.get("content", {})
+ for content_type in content:
+ if content_type in ["application/octet-stream", "multipart/form-data"]:
+ is_file_upload = True
+ break
+
+ if request_body and param_index < len(args):
+ if is_file_upload:
+ file_data = args[param_index]
+ if file_data:
+ headers["Content-Type"] = "application/octet-stream"
+ body_data = file_data
+ else:
+ body_data = b""
+ else:
+ body_data = json.loads(args[param_index])
+ try:
+ if endpoint_method.lower() == "get":
+ response = httpx.get(url, params=params, headers=headers)
+ elif endpoint_method.lower() == "post":
+ response = httpx.post(
+ url,
+ params=params,
+ content=body_data if is_file_upload else None,
+ json=body_data if not is_file_upload else None,
+ headers=headers,
+ )
+ elif endpoint_method.lower() == "put":
+ response = httpx.put(
+ url,
+ params=params,
+ content=body_data if is_file_upload else None,
+ json=body_data if not is_file_upload else None,
+ headers=headers,
+ )
+ elif endpoint_method.lower() == "patch":
+ response = httpx.patch(
+ url,
+ params=params,
+ content=body_data if is_file_upload else None,
+ json=body_data if not is_file_upload else None,
+ headers=headers,
+ )
+ elif endpoint_method.lower() == "delete":
+ response = httpx.delete(url, params=params, headers=headers)
+ else:
+ raise ValueError(f"Unsupported HTTP method: {endpoint_method}")
+
+ if response.status_code in [200, 201, 202, 204]:
+ return response.json()
+ else:
+ return {
+ "__status__": "error",
+ "status_code": response.status_code,
+ "message": response.text,
+ }
+ except Exception as e:
+ return f"Error: {str(e)}"
+
+ summary = endpoint_operation.get("summary", "")
+ description = endpoint_operation.get("description", "")
+
+ param_docs = []
+ param_names = []
+
+ for param in endpoint_operation.get("parameters", []):
+ param_name = param.get("name", "")
+ param_desc = param.get("description", "")
+ param_schema = param.get("schema", {})
+ param_enum = param_schema.get("enum", [])
+ if param_enum:
+ param_desc += f" (Choices: {', '.join(param_enum)})"
+ param_names.append(param_name)
+ param_docs.append(f" {param_name}: {param_desc}")
+
+ if request_body:
+ body_desc = request_body.get("description", "URL of file")
+ param_docs.append(f" request_body: {body_desc}")
+ param_names.append("request_body")
+
+ docstring_parts = []
+ if description or summary:
+ docstring_parts.append(description or summary)
+ if param_docs:
+ docstring_parts.append("Parameters:")
+ docstring_parts.extend(param_docs)
+
+ endpoint_fn.__doc__ = "\n".join(docstring_parts)
+
+ if param_names:
+ sig_params = []
+ for name in param_names:
+ sig_params.append(
+ inspect.Parameter(
+ name=name, kind=inspect.Parameter.POSITIONAL_OR_KEYWORD
+ )
+ )
+ sig_params.append(
+ inspect.Parameter(name="args", kind=inspect.Parameter.VAR_POSITIONAL)
+ )
+
+ new_sig = inspect.Signature(parameters=sig_params)
+
+ endpoint_fn.__signature__ = new_sig # type: ignore
+
+ return endpoint_fn
+
+
+def component_from_parameter_schema(param_info: dict) -> components.Component:
+ import gradio as gr
+
+ param_name = param_info.get("name")
+ param_description = param_info.get("description")
+
+ param_schema = param_info.get("schema", {})
+ param_type = param_schema.get("type")
+ enum_values = param_schema.get("enum")
+ default_value = param_schema.get("default")
+
+ if enum_values is not None:
+ component = gr.Dropdown(
+ choices=enum_values,
+ label=param_name,
+ value=default_value,
+ allow_custom_value=False,
+ info=param_description,
+ )
+ elif param_type in ("number", "integer"):
+ component = gr.Number(
+ label=param_name,
+ value=default_value,
+ info=param_description,
+ )
+ elif param_type == "boolean":
+ component = gr.Checkbox(
+ label=param_name,
+ value=default_value,
+ info=param_description,
+ )
+ elif param_type == "array":
+ component = gr.Textbox(
+ label=f"{param_name} (JSON array)",
+ value="[]",
+ info=param_description,
+ )
+ else:
+ component = gr.Textbox(
+ label=param_name,
+ value=default_value,
+ info=param_description,
+ )
+
+ return component
+
+
+def resolve_schema_ref(schema: dict, spec: dict) -> dict:
+ """Resolve schema references in OpenAPI spec."""
+ if "$ref" in schema:
+ ref_path = schema["$ref"]
+ if ref_path.startswith("#/components/schemas/"):
+ schema_name = ref_path.split("/")[-1]
+ return spec.get("components", {}).get("schemas", {}).get(schema_name, {})
+ elif ref_path.startswith("#/"):
+ path_parts = ref_path.split("/")[1:]
+ current = spec
+ for part in path_parts:
+ current = current.get(part, {})
+ return current
+ return schema
+
+
+def component_from_request_body_schema(
+ request_body: dict, spec: dict
+) -> components.Component | None:
+ """Create a Gradio component from an OpenAPI request body schema."""
+ import gradio as gr
+
+ if not request_body:
+ return None
+
+ content = request_body.get("content", {})
+ description = request_body.get("description", "Request Body")
+
+ for content_type, content_schema in content.items():
+ if content_type in ["application/octet-stream", "multipart/form-data"]:
+ schema = resolve_schema_ref(content_schema.get("schema", {}), spec)
+ if schema.get("type") == "string" and schema.get("format") == "binary":
+ return gr.File(label="File")
+
+ json_content = content.get("application/json", {})
+ if not json_content:
+ for content_type, content_schema in content.items():
+ if content_type.startswith("application/"):
+ json_content = content_schema
+ break
+
+ if not json_content:
+ return None
+
+ schema = resolve_schema_ref(json_content.get("schema", {}), spec)
+
+ default_value = schema.get("example", {})
+ if not default_value and schema.get("type") == "object":
+ properties = schema.get("properties", {})
+ default_value = {}
+ for prop_name, prop_schema in properties.items():
+ prop_schema = resolve_schema_ref(prop_schema, spec)
+ prop_type = prop_schema.get("type")
+ if prop_type == "string":
+ default_value[prop_name] = prop_schema.get("example", "")
+ elif prop_type in ("number", "integer"):
+ default_value[prop_name] = prop_schema.get("example", 0)
+ elif prop_type == "boolean":
+ default_value[prop_name] = prop_schema.get("example", False)
+ elif prop_type == "array":
+ default_value[prop_name] = prop_schema.get("example", [])
+ elif prop_type == "object":
+ default_value[prop_name] = prop_schema.get("example", {})
+
+ component = gr.Textbox(
+ label="Request Body",
+ value=json.dumps(default_value, indent=2),
+ info=description,
+ )
+
+ return component
+
+
+def method_box(method: str) -> str:
+ color_map = {
+ "GET": "#61affe",
+ "POST": "#49cc90",
+ "PUT": "#fca130",
+ "DELETE": "#f93e3e",
+ "PATCH": "#50e3c2",
+ }
+ color = color_map.get(method.upper(), "#999")
+ return (
+ f"{method.upper()}"
+ )
diff --git a/gradio/flagging.py b/gradio/flagging.py
new file mode 100644
index 0000000..07c74c0
--- /dev/null
+++ b/gradio/flagging.py
@@ -0,0 +1,426 @@
+from __future__ import annotations
+
+import csv
+import datetime
+import json
+import os
+import re
+import time
+from abc import ABC, abstractmethod
+from collections.abc import Sequence
+from multiprocessing import Lock
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
+
+from gradio_client import utils as client_utils
+from gradio_client.documentation import document
+
+import gradio as gr
+from gradio import utils
+from gradio.events import LikeData
+
+if TYPE_CHECKING:
+ from gradio.components import Component
+
+
+class FlaggingCallback(ABC):
+ """
+ An abstract class for defining the methods that any FlaggingCallback should have.
+ """
+
+ @abstractmethod
+ def setup(self, components: Sequence[Component], flagging_dir: str):
+ """
+ This method should be overridden and ensure that everything is set up correctly for flag().
+ This method gets called once at the beginning of the Interface.launch() method.
+ Parameters:
+ components: Set of components that will provide flagged data.
+ flagging_dir: A string, typically containing the path to the directory where the flagging file should be stored (provided as an argument to Interface.__init__()).
+ """
+ pass
+
+ @abstractmethod
+ def flag(
+ self,
+ flag_data: list[Any],
+ flag_option: str | None = None,
+ username: str | None = None,
+ ) -> int:
+ """
+ This method should be overridden by the FlaggingCallback subclass and may contain optional additional arguments.
+ This gets called every time the button is pressed.
+ Parameters:
+ interface: The Interface object that is being used to launch the flagging interface.
+ flag_data: The data to be flagged.
+ flag_option (optional): In the case that flagging_options are provided, the flag option that is being used.
+ username (optional): The username of the user that is flagging the data, if logged in.
+ Returns:
+ (int) The total number of samples that have been flagged.
+ """
+ pass
+
+
+@document()
+class SimpleCSVLogger(FlaggingCallback):
+ """
+ A simplified implementation of the FlaggingCallback abstract class
+ provided for illustrative purposes. Each flagged sample (both the input and output data)
+ is logged to a CSV file on the machine running the gradio app.
+ Example:
+ import gradio as gr
+ def image_classifier(inp):
+ return {'cat': 0.3, 'dog': 0.7}
+ demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label",
+ flagging_callback=SimpleCSVLogger())
+ """
+
+ def __init__(self):
+ pass
+
+ def setup(self, components: Sequence[Component], flagging_dir: str | Path):
+ self.components = components
+ self.flagging_dir = flagging_dir
+ os.makedirs(flagging_dir, exist_ok=True)
+
+ def flag(
+ self,
+ flag_data: list[Any],
+ flag_option: str | None = None, # noqa: ARG002
+ username: str | None = None, # noqa: ARG002
+ ) -> int:
+ flagging_dir = self.flagging_dir
+ log_filepath = Path(flagging_dir) / "log.csv"
+
+ csv_data = []
+ for component, sample in zip(self.components, flag_data, strict=False):
+ save_dir = Path(
+ flagging_dir
+ ) / client_utils.strip_invalid_filename_characters(
+ str(component.label) if component.label is not None else ""
+ )
+ save_dir.mkdir(exist_ok=True)
+ csv_data.append(
+ component.flag(
+ sample,
+ save_dir,
+ )
+ )
+
+ with open(log_filepath, "a", encoding="utf-8", newline="") as csvfile:
+ writer = csv.writer(csvfile)
+ writer.writerow(utils.sanitize_list_for_csv(csv_data))
+
+ with open(log_filepath, encoding="utf-8") as csvfile:
+ line_count = len(list(csv.reader(csvfile))) - 1
+ return line_count
+
+
+@document()
+class ClassicCSVLogger(FlaggingCallback):
+ """
+ The classic implementation of the FlaggingCallback abstract class in gradio<5.0. Each flagged
+ sample (both the input and output data) is logged to a CSV file with headers on the machine running the gradio app.
+ Example:
+ import gradio as gr
+ def image_classifier(inp):
+ return {'cat': 0.3, 'dog': 0.7}
+ demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label",
+ flagging_callback=ClassicCSVLogger())
+ Guides: using-flagging
+ """
+
+ def __init__(self, simplify_file_data: bool = True):
+ self.simplify_file_data = simplify_file_data
+
+ def setup(
+ self,
+ components: Sequence[Component],
+ flagging_dir: str | Path,
+ ):
+ self.components = components
+ self.flagging_dir = flagging_dir
+ os.makedirs(flagging_dir, exist_ok=True)
+
+ def flag(
+ self,
+ flag_data: list[Any],
+ flag_option: str | None = None,
+ username: str | None = None,
+ ) -> int:
+ flagging_dir = self.flagging_dir
+ log_filepath = Path(flagging_dir) / "log.csv"
+ is_new = not Path(log_filepath).exists()
+ headers = [
+ getattr(component, "label", None) or f"component {idx}"
+ for idx, component in enumerate(self.components)
+ ] + [
+ "flag",
+ "username",
+ "timestamp",
+ ]
+
+ csv_data = []
+ for idx, (component, sample) in enumerate(
+ zip(self.components, flag_data, strict=False)
+ ):
+ save_dir = Path(
+ flagging_dir
+ ) / client_utils.strip_invalid_filename_characters(
+ str(getattr(component, "label", None) or f"component {idx}")
+ )
+ if utils.is_prop_update(sample):
+ csv_data.append(str(sample))
+ else:
+ data = (
+ component.flag(sample, flag_dir=save_dir)
+ if sample is not None
+ else ""
+ )
+ if self.simplify_file_data:
+ data = utils.simplify_file_data_in_str(data)
+ csv_data.append(data)
+ csv_data.append(flag_option)
+ csv_data.append(username if username is not None else "")
+ csv_data.append(str(datetime.datetime.now()))
+
+ with open(log_filepath, "a", newline="", encoding="utf-8") as csvfile:
+ writer = csv.writer(csvfile)
+ if is_new:
+ writer.writerow(utils.sanitize_list_for_csv(headers))
+ writer.writerow(utils.sanitize_list_for_csv(csv_data))
+
+ with open(log_filepath, encoding="utf-8") as csvfile:
+ line_count = len(list(csv.reader(csvfile))) - 1
+ return line_count
+
+
+@document()
+class CSVLogger(FlaggingCallback):
+ """
+ The default implementation of the FlaggingCallback abstract class in gradio>=5.0. Each flagged
+ sample (both the input and output data) is logged to a CSV file with headers on the machine running
+ the gradio app. Unlike ClassicCSVLogger, this implementation is concurrent-safe and it creates a new
+ dataset file every time the headers of the CSV (derived from the labels of the components) change. It also
+ only creates columns for "username" and "flag" if the flag_option and username are provided, respectively.
+
+ Example:
+ import gradio as gr
+ def image_classifier(inp):
+ return {'cat': 0.3, 'dog': 0.7}
+ demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label",
+ flagging_callback=CSVLogger())
+ Guides: using-flagging
+ """
+
+ def __init__(
+ self,
+ simplify_file_data: bool = True,
+ verbose: bool = True,
+ dataset_file_name: str | None = None,
+ ):
+ """
+ Parameters:
+ simplify_file_data: If True, the file data will be simplified before being written to the CSV file. If CSVLogger is being used to cache examples, this is set to False to preserve the original FileData class
+ verbose: If True, prints messages to the console about the dataset file creation
+ dataset_file_name: The name of the dataset file to be created (should end in ".csv"). If None, the dataset file will be named "dataset1.csv" or the next available number.
+ """
+ self.simplify_file_data = simplify_file_data
+ self.verbose = verbose
+ self.dataset_file_name = dataset_file_name
+ self.lock = Lock()
+
+ def setup(
+ self,
+ components: Sequence[Component],
+ flagging_dir: str | Path,
+ ):
+ self.components = components
+ self.flagging_dir = Path(flagging_dir)
+ self.first_time = True
+
+ def _create_dataset_file(self, additional_headers: list[str] | None = None):
+ os.makedirs(self.flagging_dir, exist_ok=True)
+
+ if additional_headers is None:
+ additional_headers = []
+ headers = (
+ [
+ getattr(component, "label", None) or f"component {idx}"
+ for idx, component in enumerate(self.components)
+ ]
+ + additional_headers
+ + [
+ "timestamp",
+ ]
+ )
+ headers = utils.sanitize_list_for_csv(headers)
+ dataset_files = list(Path(self.flagging_dir).glob("dataset*.csv"))
+
+ if self.dataset_file_name:
+ self.dataset_filepath = self.flagging_dir / self.dataset_file_name
+ elif dataset_files:
+ try:
+ latest_file = max(
+ dataset_files, key=lambda f: int(re.findall(r"\d+", f.stem)[0])
+ )
+ latest_num = int(re.findall(r"\d+", latest_file.stem)[0])
+
+ with open(latest_file, newline="", encoding="utf-8") as csvfile:
+ reader = csv.reader(csvfile)
+ existing_headers = next(reader, None)
+
+ if existing_headers != headers:
+ new_num = latest_num + 1
+ self.dataset_filepath = self.flagging_dir / f"dataset{new_num}.csv"
+ else:
+ self.dataset_filepath = latest_file
+ except Exception:
+ self.dataset_filepath = self.flagging_dir / "dataset1.csv"
+ else:
+ self.dataset_filepath = self.flagging_dir / "dataset1.csv"
+
+ if not Path(self.dataset_filepath).exists():
+ with open(
+ self.dataset_filepath, "w", newline="", encoding="utf-8"
+ ) as csvfile:
+ writer = csv.writer(csvfile)
+ writer.writerow(utils.sanitize_list_for_csv(headers))
+ if self.verbose:
+ print("Created dataset file at:", self.dataset_filepath)
+ elif self.verbose:
+ print("Using existing dataset file at:", self.dataset_filepath)
+
+ def flag(
+ self,
+ flag_data: list[Any],
+ flag_option: str | None = None,
+ username: str | None = None,
+ ) -> int:
+ if self.first_time:
+ additional_headers = []
+ if flag_option is not None:
+ additional_headers.append("flag")
+ if username is not None:
+ additional_headers.append("username")
+ self._create_dataset_file(additional_headers=additional_headers)
+ self.first_time = False
+
+ csv_data = []
+ for idx, (component, sample) in enumerate(
+ zip(self.components, flag_data, strict=False)
+ ):
+ save_dir = (
+ self.flagging_dir
+ / client_utils.strip_invalid_filename_characters(
+ str(getattr(component, "label", None) or f"component {idx}")
+ )
+ )
+ if utils.is_prop_update(sample):
+ csv_data.append(str(sample))
+ else:
+ data = (
+ component.flag(sample, flag_dir=save_dir)
+ if sample is not None
+ else ""
+ )
+ if self.simplify_file_data:
+ data = utils.simplify_file_data_in_str(data)
+ csv_data.append(data)
+
+ if flag_option is not None:
+ csv_data.append(flag_option)
+ if username is not None:
+ csv_data.append(username)
+ csv_data.append(str(datetime.datetime.now()))
+
+ with self.lock:
+ with open(
+ self.dataset_filepath, "a", newline="", encoding="utf-8"
+ ) as csvfile:
+ writer = csv.writer(csvfile)
+ writer.writerow(utils.sanitize_list_for_csv(csv_data))
+ with open(self.dataset_filepath, encoding="utf-8") as csvfile:
+ line_count = len(list(csv.reader(csvfile))) - 1
+
+ return line_count
+
+
+class ChatCSVLogger:
+ """
+ Flagging callback for chat conversations.
+ Flagged conversations and like/dislike reactions are logged to a CSV file on the machine running the gradio app.
+ """
+
+ def __init__(self):
+ pass
+
+ def setup(self, flagging_dir: str):
+ self.flagging_dir = flagging_dir
+ os.makedirs(flagging_dir, exist_ok=True)
+
+ def flag(
+ self,
+ like_data: LikeData,
+ messages: list,
+ ):
+ flagging_dir = self.flagging_dir
+ log_filepath = Path(flagging_dir) / "log.csv"
+ is_new = not Path(log_filepath).exists()
+
+ feedback = (
+ "Like"
+ if like_data.liked is True
+ else "Dislike"
+ if like_data.liked is False
+ else like_data.liked
+ )
+ csv_data = [
+ json.dumps(messages),
+ like_data.index,
+ feedback,
+ str(datetime.datetime.now()),
+ ]
+
+ with open(log_filepath, "a", encoding="utf-8", newline="") as csvfile:
+ if is_new:
+ writer = csv.writer(csvfile)
+ writer.writerow(["conversation", "index", "value", "flag", "timestamp"])
+ writer = csv.writer(csvfile)
+ writer.writerow(utils.sanitize_list_for_csv(csv_data))
+
+
+class FlagMethod:
+ """
+ Helper class that contains the flagging options and calls the flagging method. Also
+ provides visual feedback to the user when flag is clicked.
+ """
+
+ def __init__(
+ self,
+ flagging_callback: FlaggingCallback,
+ label: str,
+ value: str | None,
+ visual_feedback: bool = True,
+ ):
+ self.flagging_callback = flagging_callback
+ self.label = label
+ self.value = value
+ self.__name__ = "Flag"
+ self.visual_feedback = visual_feedback
+
+ def __call__(self, request: gr.Request, *flag_data):
+ try:
+ self.flagging_callback.flag(
+ list(flag_data), flag_option=self.value, username=request.username
+ )
+ except Exception as e:
+ print(f"Error while flagging: {e}")
+ if self.visual_feedback:
+ return "Error!"
+ if not self.visual_feedback:
+ return
+ time.sleep(0.8) # to provide enough time for the user to observe button change
+ return self.reset()
+
+ def reset(self):
+ return gr.Button(value=self.label, interactive=True)
diff --git a/gradio/helpers.py b/gradio/helpers.py
new file mode 100644
index 0000000..1f58850
--- /dev/null
+++ b/gradio/helpers.py
@@ -0,0 +1,1257 @@
+"""
+Defines helper methods useful for loading and caching Interface examples.
+"""
+
+from __future__ import annotations
+
+import ast
+import copy
+import csv
+import inspect
+import os
+import shutil
+import warnings
+from collections.abc import Callable, Iterable, MutableMapping, Sequence
+from functools import partial
+from pathlib import Path
+from types import SimpleNamespace
+from typing import TYPE_CHECKING, Any, Literal, Optional, get_origin
+
+from gradio_client import utils as client_utils
+from gradio_client.documentation import document
+
+from gradio import components, oauth, processing_utils, routes, utils
+from gradio.caching import Cache, resolve_generator
+from gradio.context import Context, LocalContext, get_blocks_context
+from gradio.data_classes import GradioModel, GradioRootModel
+from gradio.events import Dependency, EventData
+from gradio.exceptions import Error
+from gradio.flagging import CSVLogger
+from gradio.i18n import I18nData
+from gradio.route_utils import Header
+from gradio.utils import UnhashableKeyDict
+
+if TYPE_CHECKING: # Only import for type checking (to avoid circular imports).
+ from gradio.components import Component
+
+LOG_FILE = "log.csv"
+
+
+def create_examples(
+ examples: list[Any] | list[list[Any]] | str,
+ inputs: Component | Sequence[Component],
+ outputs: Component | Sequence[Component] | None = None,
+ fn: Callable | None = None,
+ cache_examples: bool | None = None,
+ cache_mode: Literal["eager", "lazy"] | None = None,
+ examples_per_page: int = 10,
+ _api_mode: bool = False,
+ label: str | I18nData | None = None,
+ elem_id: str | None = None,
+ run_on_click: bool = False,
+ preprocess: bool = True,
+ postprocess: bool = True,
+ api_visibility: Literal["public", "private", "undocumented"] = "undocumented",
+ api_name: str | None = "load_example",
+ api_description: str | None | Literal[False] = None,
+ batch: bool = False,
+ *,
+ example_labels: list[str] | None = None,
+ visible: bool | Literal["hidden"] = True,
+ preload: int | Literal[False] = 0,
+):
+ """Top-level synchronous function that creates Examples. Provided for backwards compatibility, i.e. so that gr.Examples(...) can be used to create the Examples component."""
+ examples_obj = Examples(
+ examples=examples,
+ inputs=inputs,
+ outputs=outputs,
+ fn=fn,
+ cache_examples=cache_examples,
+ cache_mode=cache_mode,
+ examples_per_page=examples_per_page,
+ _api_mode=_api_mode,
+ label=label,
+ elem_id=elem_id,
+ run_on_click=run_on_click,
+ preprocess=preprocess,
+ postprocess=postprocess,
+ api_visibility=api_visibility,
+ api_name=api_name,
+ api_description=api_description,
+ batch=batch,
+ example_labels=example_labels,
+ visible=visible,
+ _initiated_directly=False,
+ preload=preload,
+ )
+ examples_obj.create()
+ return examples_obj
+
+
+@document()
+class Examples:
+ """
+ This class is a wrapper over the Dataset component and can be used to create Examples
+ for Blocks / Interfaces. Populates the Dataset component with examples and
+ assigns event listener so that clicking on an example populates the input/output
+ components. Optionally handles example caching for fast inference.
+
+ Demos: calculator_blocks
+ Guides: more-on-examples-and-flagging
+ """
+
+ def __init__(
+ self,
+ examples: list[Any] | list[list[Any]] | str,
+ inputs: Component | Sequence[Component],
+ outputs: Component | Sequence[Component] | None = None,
+ fn: Callable | None = None,
+ cache_examples: bool | None = None,
+ cache_mode: Literal["eager", "lazy"] | None = None,
+ examples_per_page: int = 10,
+ _api_mode: bool = False,
+ label: str | I18nData | None = "Examples",
+ elem_id: str | None = None,
+ run_on_click: bool = False,
+ preprocess: bool = True,
+ postprocess: bool = True,
+ api_visibility: Literal["public", "private", "undocumented"] = "undocumented",
+ api_name: str | None = "load_example",
+ api_description: str | None | Literal[False] = None,
+ batch: bool = False,
+ *,
+ example_labels: list[str] | None = None,
+ visible: bool | Literal["hidden"] = True,
+ preload: int | Literal[False] = 0,
+ _initiated_directly: bool = True,
+ ):
+ """
+ Parameters:
+ examples: example inputs that can be clicked to populate specific components. Should be nested list, in which the outer list consists of samples and each inner list consists of an input corresponding to each input component. A string path to a directory of examples can also be provided but it should be within the directory with the python file running the gradio app. If there are multiple input components and a directory is provided, a log.csv file must be present in the directory to link corresponding inputs.
+ inputs: the component or list of components corresponding to the examples
+ outputs: optionally, provide the component or list of components corresponding to the output of the examples. Required if `cache_examples` is not False.
+ fn: optionally, provide the function to run to generate the outputs corresponding to the examples. Required if `cache_examples` is not False. Also required if `run_on_click` is True.
+ cache_examples: If True, caches examples in the server for fast runtime in examples. If "lazy", then examples are cached (for all users of the app) after their first use (by any user of the app). If None, will use the GRADIO_CACHE_EXAMPLES environment variable, which should be either "true" or "false". In HuggingFace Spaces, this parameter is True (as long as `fn` and `outputs` are also provided). The default option otherwise is False. Note that examples are cached separately from Gradio's queue() so certain features, such as gr.Progress(), gr.Info(), gr.Warning(), etc. will not be displayed in Gradio's UI for cached examples.
+ cache_mode: if "lazy", examples are cached after their first use. If "eager", all examples are cached at app launch. If None, will use the GRADIO_CACHE_MODE environment variable if defined, or default to "eager".
+ examples_per_page: how many examples to show per page.
+ label: the label to use for the examples component (by default, "Examples")
+ elem_id: an optional string that is assigned as the id of this component in the HTML DOM.
+ run_on_click: if cache_examples is False, clicking on an example does not run the function when an example is clicked. Set this to True to run the function when an example is clicked. Has no effect if cache_examples is True.
+ preprocess: if True, preprocesses the example input before running the prediction function and caching the output. Only applies if `cache_examples` is not False.
+ postprocess: if True, postprocesses the example output after running the prediction function and before caching. Only applies if `cache_examples` is not False.
+ api_visibility: Controls the visibility of the event associated with clicking on the examples. Can be "public" (shown in API docs and callable), "private" (hidden from API docs and not callable by the Gradio client libraries), or "undocumented" (hidden from API docs but callable).
+ api_name: Defines how the event associated with clicking on the examples appears in the API docs. Can be a string or None. If set to a string, the endpoint will be exposed in the API docs with the given name. If None, an auto-generated name will be used.
+ api_description: Description of the event associated with clicking on the examples in the API docs. Can be a string, None, or False. If set to a string, the endpoint will be exposed in the API docs with the given description. If None, the function's docstring will be used as the API endpoint description. If False, then no description will be displayed in the API docs.
+ batch: If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. Used only if cache_examples is not False.
+ example_labels: A list of labels for each example. If provided, the length of this list should be the same as the number of examples, and these labels will be used in the UI instead of rendering the example values.
+ visible: If False, the examples component will be hidden in the UI.
+ preload: If an integer is provided (and examples are being cached eagerly and none of the input components have a developer-provided `value`), the example at that index in the examples list will be preloaded when the Gradio app is first loaded. If False, no example will be preloaded.
+ """
+ if _initiated_directly:
+ warnings.warn(
+ "Please use gr.Examples(...) instead of gr.helpers.Examples(...) to create the Examples.",
+ )
+
+ self.cache_examples = False
+ if cache_examples is None:
+ if (
+ os.getenv("GRADIO_CACHE_EXAMPLES", "").lower() in ["true", "lazy"]
+ and fn is not None
+ and outputs is not None
+ ):
+ self.cache_examples = True
+ elif cache_examples in [True, False]:
+ self.cache_examples = cache_examples
+ else:
+ raise ValueError(
+ f"The `cache_examples` parameter should be either True or False, not {cache_examples}"
+ )
+ if self.cache_examples and (fn is None or outputs is None):
+ raise ValueError("If caching examples, `fn` and `outputs` must be provided")
+
+ if (cache_mode_env := os.getenv("GRADIO_CACHE_MODE")) and cache_mode is None:
+ if cache_mode_env.lower() == "eager":
+ cache_mode = "eager"
+ elif cache_mode_env.lower() == "lazy":
+ cache_mode = "lazy"
+ else:
+ cache_mode = "eager"
+ warnings.warn(
+ "The `GRADIO_CACHE_MODE` environment variable must be either 'eager' or 'lazy'. "
+ "Defaulting to 'eager'."
+ )
+
+ if self.cache_examples and cache_mode == "lazy":
+ self.cache_examples = "lazy"
+
+ if not isinstance(inputs, Sequence):
+ inputs = [inputs]
+ if outputs and not isinstance(outputs, Sequence):
+ outputs = [outputs]
+
+ working_directory = Path().absolute()
+
+ if examples is None:
+ raise ValueError("The parameter `examples` cannot be None")
+ elif isinstance(examples, list) and (
+ len(examples) == 0 or isinstance(examples[0], list)
+ ):
+ pass
+ elif (
+ isinstance(examples, list) and len(inputs) == 1
+ ): # If there is only one input component, examples can be provided as a regular list instead of a list of lists
+ examples = [[e] for e in examples]
+ elif isinstance(examples, str):
+ if not Path(examples).exists():
+ raise FileNotFoundError(
+ f"Could not find examples directory: {examples}"
+ )
+ working_directory = examples
+ if not (Path(examples) / LOG_FILE).exists():
+ if len(inputs) == 1:
+ examples = [[e] for e in os.listdir(examples)]
+ else:
+ raise FileNotFoundError(
+ "Could not find log file (required for multiple inputs): "
+ + LOG_FILE
+ )
+ else:
+ with open(Path(examples) / LOG_FILE) as logs:
+ examples = list(csv.reader(logs))
+ examples = [
+ examples[i][: len(inputs)] for i in range(1, len(examples))
+ ] # remove header and unnecessary columns
+
+ else:
+ raise ValueError(
+ "The parameter `examples` must either be a string directory or a list"
+ "(if there is only 1 input component) or (more generally), a nested "
+ "list, where each sublist represents a set of inputs."
+ )
+
+ input_has_examples = [False] * len(inputs)
+ for example in examples:
+ for idx, example_for_input in enumerate(example):
+ if example_for_input is not None:
+ try:
+ input_has_examples[idx] = True
+ except IndexError:
+ pass # If there are more example components than inputs, ignore. This can sometimes be intentional (e.g. loading from a log file where outputs and timestamps are also logged)
+
+ inputs_with_examples = [
+ inp for (inp, keep) in zip(inputs, input_has_examples, strict=False) if keep
+ ]
+ non_none_examples = [
+ [
+ ex
+ for (ex, keep) in zip(example, input_has_examples, strict=False)
+ if keep
+ ]
+ for example in examples
+ ]
+ if example_labels is not None and len(example_labels) != len(examples):
+ raise ValueError(
+ "If `example_labels` are provided, the length of `example_labels` must be the same as the number of examples."
+ )
+
+ self.examples = examples
+ self.non_none_examples = non_none_examples
+ self.inputs = inputs
+ self.input_has_examples = input_has_examples
+ self.inputs_with_examples = inputs_with_examples
+ self.outputs = outputs or []
+ self.fn = fn
+ self._api_mode = _api_mode
+ self.preprocess = preprocess
+ self.postprocess = postprocess
+ self.api_visibility = api_visibility
+ self.api_name: str | None = api_name
+ self.api_description: str | None | Literal[False] = api_description
+ self.batch = batch
+ self.example_labels = example_labels
+ self.working_directory = working_directory
+ self.preload = preload
+
+ from gradio import components
+
+ with utils.set_directory(working_directory):
+ self.dataset = components.Dataset(
+ components=inputs_with_examples,
+ samples=copy.deepcopy(non_none_examples),
+ type="tuple",
+ label=label,
+ samples_per_page=examples_per_page,
+ elem_id=elem_id,
+ visible=visible,
+ sample_labels=example_labels,
+ )
+
+ self.cache_logger = CSVLogger(
+ simplify_file_data=False, verbose=False, dataset_file_name="log.csv"
+ )
+ self.cached_folder = utils.get_cache_folder() / str(self.dataset._id)
+ if (
+ os.environ.get("GRADIO_RESET_EXAMPLES_CACHE") == "True"
+ and self.cached_folder.exists()
+ ):
+ shutil.rmtree(self.cached_folder)
+ self.cached_file = Path(self.cached_folder) / "log.csv"
+ self.cached_indices_file = Path(self.cached_folder) / "indices.csv"
+ self.run_on_click = run_on_click
+ self.cache_event: Dependency | None = None
+ self.non_none_processed_examples = UnhashableKeyDict()
+
+ if self.dataset.samples:
+ for index, example in enumerate(self.non_none_examples):
+ self.non_none_processed_examples[self.dataset.samples[index]] = (
+ self._get_processed_example(example)
+ )
+
+ if self.cache_examples == "lazy":
+ print(
+ f"Will cache examples in '{utils.abspath(self.cached_folder)}' directory at first use.",
+ end="",
+ )
+ if Path(self.cached_file).exists():
+ print(
+ "If method or examples have changed since last caching, delete this folder to reset cache."
+ )
+ print("\n")
+
+ def _get_processed_example(self, example):
+ """
+ This function is used to get the post-processed example values, ready to be used
+ in the frontend for each input component. For example, if the input components are
+ image components, the post-processed example values will be the a list of ImageData dictionaries
+ with the path, url, size, mime_type, orig_name, and is_stream keys. For any input components
+ that should be skipped (b/c they are None for all samples), they will simply be absent
+ from the returned list
+
+ Parameters:
+ example: a list of example values for each input component, excluding those components
+ that have all None values
+ """
+ if example in self.non_none_processed_examples:
+ return self.non_none_processed_examples[example]
+ with utils.set_directory(self.working_directory):
+ sub = []
+ for component, sample in zip(
+ self.inputs_with_examples, example, strict=False
+ ):
+ prediction_value = component.postprocess(sample)
+ if isinstance(prediction_value, (GradioRootModel, GradioModel)):
+ prediction_value = prediction_value.model_dump()
+ prediction_value = processing_utils.move_files_to_cache(
+ prediction_value,
+ component,
+ postprocess=True,
+ )
+ sub.append(prediction_value)
+ return sub
+
+ def create(self) -> None:
+ """Creates the Dataset component to hold the examples"""
+ blocks_config = get_blocks_context()
+ self.root_block = Context.root_block or (
+ blocks_config.root_block if blocks_config else None
+ )
+ if blocks_config:
+ if self.root_block:
+ self.root_block.extra_startup_events.append(self._start_caching)
+
+ if self.cache_examples:
+
+ def load_example_input(example_tuple):
+ _, example_value = example_tuple
+ processed_example = self._get_processed_example(example_value)
+ return utils.resolve_singleton(processed_example)
+
+ def load_example_output(example_tuple):
+ example_id, _ = example_tuple
+ cached_outputs = self.load_from_cache(example_id)
+ return utils.resolve_singleton(cached_outputs)
+
+ self.cache_event = self.load_input_event = self.dataset.click(
+ load_example_input,
+ inputs=[self.dataset],
+ outputs=self.inputs,
+ show_progress="hidden",
+ postprocess=False,
+ queue=False,
+ api_visibility="undocumented",
+ ).then(
+ load_example_output,
+ inputs=[self.dataset],
+ outputs=self.outputs,
+ postprocess=False,
+ api_name=self.api_name,
+ api_description=self.api_description,
+ api_visibility=self.api_visibility,
+ )
+
+ if (
+ self.preload is not False
+ and self.cache_examples != "lazy"
+ and self.root_block
+ and not any(
+ "value" in inp.constructor_args
+ for inp in self.inputs_with_examples
+ )
+ ):
+ self.root_block.load(
+ load_example_input,
+ inputs=[
+ components.State(
+ (self.preload, self.non_none_examples[self.preload])
+ )
+ ],
+ outputs=self.inputs,
+ show_progress="hidden",
+ postprocess=False,
+ queue=False,
+ api_visibility="undocumented",
+ )
+ self.root_block.load(
+ load_example_output,
+ inputs=[
+ components.State(
+ (self.preload, self.non_none_examples[self.preload])
+ )
+ ],
+ outputs=self.outputs,
+ postprocess=False,
+ show_progress="hidden",
+ api_visibility="undocumented",
+ )
+
+ else:
+
+ def load_example(example_tuple):
+ _, example_value = example_tuple
+ processed_example = self._get_processed_example(example_value)
+ if len(self.inputs_with_examples) == 1:
+ return update(
+ value=processed_example[0],
+ **self.dataset.component_props[0], # type: ignore
+ )
+ return [
+ update(
+ value=processed_example[i],
+ **self.dataset.component_props[i], # type: ignore
+ )
+ for i in range(len(self.inputs_with_examples))
+ ]
+
+ self.load_input_event = self.dataset.click(
+ load_example,
+ inputs=[self.dataset],
+ outputs=self.inputs_with_examples,
+ show_progress="hidden",
+ postprocess=False,
+ queue=False,
+ api_name=self.api_name,
+ api_description=self.api_description,
+ api_visibility=self.api_visibility,
+ )
+
+ if self.run_on_click:
+ if self.fn is None:
+ raise ValueError(
+ "Cannot run_on_click if no function is provided"
+ )
+ self.load_input_event.then(
+ self.fn,
+ inputs=self.inputs,
+ outputs=self.outputs,
+ api_visibility="undocumented",
+ )
+ else:
+ warnings.warn(
+ f"If an Examples object is created outside a Blocks Context, make sure to call `examples.dataset.render()`{'and `examples.create()`' if self.cache_examples else ''} to render the examples in the interface."
+ )
+
+ async def _postprocess_output(self, output) -> list:
+ """
+ This is a way that we can postprocess the data manually, since we set postprocess=False in the lazy_cache
+ event handler. The reason we did that is because we don't want to postprocess data if we are loading from
+ the cache, since that has already been postprocessed. We postprocess this data manually if we are calling
+ the function using the _handle_callable_as_generator() method.
+ """
+ import gradio as gr
+
+ with gr.Blocks() as demo:
+ [output.render() for output in self.outputs] # type: ignore
+ demo.load(self.fn, self.inputs, self.outputs)
+ demo.unrender()
+ return await demo.postprocess_data(demo.default_config.fns[0], output, None)
+
+ def _get_cached_index_if_cached(self, example_index) -> int | None:
+ if Path(self.cached_indices_file).exists():
+ with open(self.cached_indices_file) as f:
+ cached_indices = [int(line.strip()) for line in f]
+ if example_index in cached_indices:
+ cached_index = cached_indices.index(example_index)
+ return cached_index
+ return None
+
+ async def _start_caching(self):
+ if self.cache_examples:
+ for example in self.examples:
+ if len([ex for ex in example if ex is not None]) != len(self.inputs):
+ warnings.warn(
+ "Examples will be cached but not all input components have "
+ "example values. This may result in an exception being thrown by "
+ "your function. If you do get an error while caching examples, make "
+ "sure all of your inputs have example values for all of your examples "
+ "or you provide default values for those particular parameters in your function."
+ )
+ break
+ if self.cache_examples is True:
+ await self.cache()
+
+ async def cache(self, example_id: int | None = None) -> None:
+ """
+ Caches examples so that their predictions can be shown immediately.
+ Parameters:
+ example_id: The id of the example to process (zero-indexed). If None, all examples are cached.
+ """
+ if self.root_block is None:
+ raise Error("Cannot cache examples if not in a Blocks context.")
+ if Path(self.cached_file).exists() and example_id is None:
+ print(
+ f"Using cache from '{utils.abspath(self.cached_folder)}' directory. If method or examples have changed since last caching, delete this folder to clear cache.\n"
+ )
+ else:
+ print(f"Caching examples at: '{utils.abspath(self.cached_folder)}'")
+ self.cache_logger.setup(self.outputs, self.cached_folder) # type: ignore
+ assert self.fn is not None # noqa: S101
+ fn, generated_values = resolve_generator(self.fn)
+ if generated_values is None:
+ generated_values = []
+
+ # create a fake dependency to process the examples and get the predictions
+ from gradio.events import EventListenerMethod
+
+ _, fn_index = self.root_block.default_config.set_event_trigger(
+ [EventListenerMethod(Context.root_block, "load")],
+ fn=fn,
+ inputs=self.inputs,
+ outputs=self.outputs,
+ preprocess=self.preprocess and not self._api_mode,
+ postprocess=self.postprocess and not self._api_mode,
+ batch=self.batch,
+ )
+
+ if self.outputs is None:
+ raise ValueError("self.outputs is missing")
+ for i, example in enumerate(self.non_none_examples):
+ if example_id is not None and i != example_id:
+ continue
+ processed_input = self._get_processed_example(example)
+ for index, keep in enumerate(self.input_has_examples):
+ if not keep:
+ processed_input.insert(index, None)
+ if self.batch:
+ processed_input = [[value] for value in processed_input]
+ with utils.MatplotlibBackendMananger():
+ # When caching examples lazily, set in_event_listener to False
+ # so that all components are properly instantiated
+ # See https://github.com/gradio-app/gradio/issues/12564
+ prediction = await self.root_block.process_api(
+ block_fn=self.root_block.default_config.fns[fn_index],
+ inputs=processed_input,
+ request=None,
+ in_event_listener=self.cache_examples != "lazy",
+ )
+ output = prediction["data"]
+ if generated_values:
+ output = await merge_generated_values_into_output(
+ self.outputs, # type: ignore
+ generated_values,
+ output, # type: ignore
+ )
+ if self.batch:
+ output = [value[0] for value in output]
+ self.cache_logger.flag(output)
+ with open(self.cached_indices_file, "a") as f:
+ f.write(f"{example_id or i}\n")
+
+ # Remove the "fake_event" to prevent bugs in loading interfaces from spaces
+ self.root_block.default_config.fns.pop(fn_index)
+
+ def load_from_cache(self, example_id: int) -> list[Any]:
+ """Loads a particular cached example for the interface.
+ Parameters:
+ example_id: The id of the example to process (zero-indexed).
+ """
+ cached_index = self._get_cached_index_if_cached(example_id)
+ if cached_index is None:
+ client_utils.synchronize_async(self.cache, example_id)
+ with open(self.cached_indices_file) as f:
+ cached_index = len(f.readlines()) - 1
+
+ with open(self.cached_file, encoding="utf-8") as cache:
+ examples = list(csv.reader(cache))
+
+ if cached_index + 1 >= len(examples):
+ raise IndexError("Cached example not found in cache file")
+ example = examples[cached_index + 1] # +1 to adjust for header
+
+ output = []
+ if self.outputs is None:
+ raise ValueError("self.outputs is missing")
+ for component, value in zip(self.outputs, example, strict=False): # type: ignore
+ value_to_use = value
+ try:
+ value_as_dict = ast.literal_eval(value)
+ # File components that output multiple files get saved as a python list
+ # need to pass the parsed list to serialize
+ # TODO: Better file serialization in 4.0
+ if isinstance(value_as_dict, list) and isinstance(
+ component, components.File
+ ):
+ value_to_use = value_as_dict
+ if not utils.is_prop_update(value_as_dict):
+ raise TypeError("value wasn't an update") # caught below
+ output.append(value_as_dict)
+ except (ValueError, TypeError, SyntaxError):
+ output.append(component.read_from_flag(value_to_use))
+ return output
+
+
+async def merge_generated_values_into_output(
+ components: Sequence[Component], generated_values: list, output: list
+):
+ from gradio.components.base import StreamingOutput
+
+ for output_index, output_component in enumerate(components):
+ if isinstance(output_component, StreamingOutput) and output_component.streaming:
+ binary_chunks = []
+ desired_output_format = None
+ for i, chunk in enumerate(generated_values):
+ if len(components) > 1:
+ chunk = chunk[output_index]
+ processed_chunk = output_component.postprocess(chunk)
+ if isinstance(processed_chunk, (GradioModel, GradioRootModel)):
+ processed_chunk = processed_chunk.model_dump()
+ stream_chunk = await output_component.stream_output(
+ processed_chunk, "", i == 0
+ )
+ if i == 0 and (orig_name := stream_chunk[1].get("orig_name")):
+ desired_output_format = Path(orig_name).suffix[1:]
+ if stream_chunk[0]:
+ binary_chunks.append(stream_chunk[0]["data"])
+ combined_output = await output_component.combine_stream(
+ binary_chunks, desired_output_format=desired_output_format
+ )
+ output[output_index] = combined_output.model_dump()
+
+ return output
+
+
+class TrackedIterable:
+ def __init__(
+ self,
+ iterable: Iterable | None,
+ index: int | float | None,
+ length: int | float | None,
+ desc: str | None,
+ unit: str | None,
+ _tqdm=None,
+ progress: float | None = None,
+ ) -> None:
+ self.iterable = iterable
+ self.index = index
+ self.length = length
+ self.desc = desc
+ self.unit = unit
+ self._tqdm = _tqdm
+ self.progress = progress
+
+
+@document("__call__", "tqdm")
+class Progress(Iterable):
+ """
+ The Progress class provides a custom progress tracker that is used in a function signature.
+ To attach a Progress tracker to a function, simply add a parameter right after the input parameters that has a default value set to a `gradio.Progress()` instance.
+ The Progress tracker can then be updated in the function by calling the Progress object or using the `tqdm` method on an Iterable.
+ Example:
+ import gradio as gr
+ import time
+ def my_function(x, progress=gr.Progress()):
+ progress(0, desc="Starting...")
+ time.sleep(1)
+ for i in progress.tqdm(range(100)):
+ time.sleep(0.1)
+ return x
+ gr.Interface(my_function, gr.Textbox(), gr.Textbox()).launch()
+ Guides: progress-bars
+ """
+
+ def __init__(
+ self,
+ track_tqdm: bool = False,
+ ):
+ """
+ Parameters:
+ track_tqdm: If True, the Progress object will track any tqdm.tqdm iterations with the tqdm library in the function.
+ """
+ if track_tqdm:
+ patch_tqdm()
+ self.track_tqdm = track_tqdm
+ self.iterables: list[TrackedIterable] = []
+
+ def __len__(self):
+ if not self.iterables:
+ return 0
+ return self.iterables[-1].length
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ """
+ Updates progress tracker with next item in iterable.
+ """
+ callback = self._progress_callback()
+ if callback:
+ current_iterable = self.iterables[-1]
+ while (
+ not hasattr(current_iterable.iterable, "__next__")
+ and len(self.iterables) > 0
+ ):
+ current_iterable = self.iterables.pop()
+ callback(self.iterables)
+ if current_iterable.index is None:
+ raise IndexError("Index not set.")
+ current_iterable.index += 1
+ try:
+ return next(current_iterable.iterable) # type: ignore
+ except StopIteration:
+ self.iterables.pop()
+ raise
+ else:
+ return self
+
+ def __call__(
+ self,
+ progress: float | tuple[int, int | None] | None,
+ desc: str | None = None,
+ total: int | float | None = None,
+ unit: str = "steps",
+ _tqdm=None,
+ ):
+ """
+ Updates progress tracker with progress and message text.
+ Parameters:
+ progress: If float, should be between 0 and 1 representing completion. If Tuple, first number represents steps completed, and second value represents total steps or None if unknown. If None, hides progress bar.
+ desc: description to display.
+ total: estimated total number of steps.
+ unit: unit of iterations.
+ """
+ callback = self._progress_callback()
+ if callback:
+ if isinstance(progress, tuple):
+ index, total = progress
+ progress = None
+ else:
+ index = None
+ callback(
+ self.iterables
+ + [TrackedIterable(None, index, total, desc, unit, _tqdm, progress)] # type: ignore
+ )
+ else:
+ return progress
+
+ def tqdm(
+ self,
+ iterable: Iterable | None,
+ desc: str | None = None,
+ total: int | float | None = None,
+ unit: str = "steps",
+ _tqdm=None,
+ ):
+ """
+ Attaches progress tracker to iterable, like tqdm.
+ Parameters:
+ iterable: iterable to attach progress tracker to.
+ desc: description to display.
+ total: estimated total number of steps.
+ unit: unit of iterations.
+ """
+ callback = self._progress_callback()
+ if callback:
+ if iterable is None:
+ new_iterable = TrackedIterable(None, 0, total, desc, unit, _tqdm)
+ self.iterables.append(new_iterable)
+ callback(self.iterables)
+ return self
+ length = len(iterable) if hasattr(iterable, "__len__") else total # type: ignore
+ self.iterables.append(
+ TrackedIterable(iter(iterable), 0, length, desc, unit, _tqdm)
+ )
+ return self
+ if iterable is None:
+ return iter([])
+ return iter(iterable)
+
+ def update(self, n: int | float = 1):
+ """
+ Increases latest iterable with specified number of steps.
+ Parameters:
+ n: number of steps completed.
+ """
+ callback = self._progress_callback()
+ if callback and len(self.iterables) > 0:
+ current_iterable = self.iterables[-1]
+ if current_iterable.index is None:
+ raise IndexError("Index not set.")
+ current_iterable.index += n
+ callback(self.iterables)
+ else:
+ return
+
+ def close(self, _tqdm):
+ """
+ Removes iterable with given _tqdm.
+ """
+ callback = self._progress_callback()
+ if callback:
+ for i in range(len(self.iterables)):
+ if id(self.iterables[i]._tqdm) == id(_tqdm):
+ self.iterables.pop(i)
+ break
+ callback(self.iterables)
+ else:
+ return
+
+ @staticmethod
+ def _progress_callback():
+ blocks = LocalContext.blocks.get(None)
+ event_id = LocalContext.event_id.get(None)
+ if not (blocks and event_id):
+ return None
+ return partial(blocks._queue.set_progress, event_id)
+
+
+def patch_tqdm() -> None:
+ try:
+ _tqdm = __import__("tqdm")
+ except ModuleNotFoundError:
+ return
+
+ def init_tqdm(
+ self, iterable=None, desc=None, total=None, unit="steps", *args, **kwargs
+ ):
+ self._progress = LocalContext.progress.get(None)
+ if self._progress is not None:
+ callback = self._progress._progress_callback()
+ if callback is not None:
+ self._progress.tqdm(iterable, desc, total, unit, _tqdm=self)
+ kwargs["file"] = open(os.devnull, "w") # noqa: SIM115
+ self.__init__orig__(iterable, desc, total, *args, unit=unit, **kwargs)
+
+ def iter_tqdm(self):
+ if self._progress is not None:
+ callback = self._progress._progress_callback()
+ if callback is not None:
+ return self._progress
+ return self.__iter__orig__()
+
+ def update_tqdm(self, n=1):
+ if self._progress is not None:
+ callback = self._progress._progress_callback()
+ if callback is not None:
+ self._progress.update(n)
+ return self.__update__orig__(n)
+
+ def close_tqdm(self):
+ if self._progress is not None:
+ callback = self._progress._progress_callback()
+ if callback is not None:
+ self._progress.close(self)
+ return self.__close__orig__()
+
+ def exit_tqdm(self, exc_type, exc_value, traceback):
+ if self._progress is not None:
+ callback = self._progress._progress_callback()
+ if callback is not None:
+ self._progress.close(self)
+ return self.__exit__orig__(exc_type, exc_value, traceback)
+
+ # Backup
+ if not hasattr(_tqdm.tqdm, "__init__orig__"):
+ _tqdm.tqdm.__init__orig__ = _tqdm.tqdm.__init__ # type: ignore
+ if not hasattr(_tqdm.tqdm, "__update__orig__"):
+ _tqdm.tqdm.__update__orig__ = _tqdm.tqdm.update # type: ignore
+ if not hasattr(_tqdm.tqdm, "__close__orig__"):
+ _tqdm.tqdm.__close__orig__ = _tqdm.tqdm.close # type: ignore
+ if not hasattr(_tqdm.tqdm, "__exit__orig__"):
+ _tqdm.tqdm.__exit__orig__ = _tqdm.tqdm.__exit__ # type: ignore
+ if not hasattr(_tqdm.tqdm, "__iter__orig__"):
+ _tqdm.tqdm.__iter__orig__ = _tqdm.tqdm.__iter__ # type: ignore
+
+ # Patch
+ _tqdm.tqdm.__init__ = init_tqdm # type: ignore
+ _tqdm.tqdm.update = update_tqdm # type: ignore
+ _tqdm.tqdm.close = close_tqdm # type: ignore
+ _tqdm.tqdm.__exit__ = exit_tqdm # type: ignore
+ _tqdm.tqdm.__iter__ = iter_tqdm # type: ignore
+
+ if hasattr(_tqdm, "auto") and hasattr(_tqdm.auto, "tqdm"):
+ _tqdm.auto.tqdm = _tqdm.tqdm
+
+
+def create_tracker(fn, track_tqdm):
+ progress = Progress(track_tqdm=track_tqdm)
+ if not track_tqdm:
+ return progress, fn
+ return progress, utils.function_wrapper(
+ f=fn,
+ before_fn=LocalContext.progress.set,
+ before_args=(progress,),
+ after_fn=LocalContext.progress.set,
+ after_args=(None,),
+ )
+
+
+def _session_from_request(request: Any) -> MutableMapping[str, Any]:
+ """Return the request's session, or an empty mapping if there is no session
+ to read (the normal case for apps without OAuth / SessionMiddleware).
+
+ For a real Starlette/fastapi request the session lives in `scope["session"]`;
+ touching `.session` directly would raise Starlette's "SessionMiddleware must
+ be installed" assertion when it's absent, so we gate on `scope` membership
+ instead. A `gr.Request` exposes the fastapi request as `.request`; objects
+ without a Starlette `scope` (a queued `gr.Request` rebuilt from kwargs) may
+ still carry a plain `.session`.
+ """
+ if request is None:
+ return {}
+ underlying = getattr(request, "request", None) or request
+ scope = getattr(underlying, "scope", None)
+ if isinstance(scope, dict):
+ return scope.get("session", {})
+ return getattr(underlying, "session", None) or {}
+
+
+def special_args(
+ fn: Callable,
+ inputs: list[Any] | None = None,
+ request: routes.Request | None = None,
+ event_data: EventData | None = None,
+ component_props: dict[int, dict[str, Any]] | None = None,
+ token: oauth.OAuthToken | None = None,
+) -> tuple[list, int | None, int | None, list[int]]:
+ """
+ Checks if function has special arguments Request or EventData (via annotation) or Progress (via default value).
+ Also checks if any parameters are type-hinted with gr.Component types, in which case all component props should be passed.
+ If inputs is provided, these values will be loaded into the inputs array.
+ Parameters:
+ fn: function to check.
+ inputs: array to load special arguments into.
+ request: request to load into inputs.
+ event_data: event-related data to load into inputs.
+ component_props: dictionary mapping input indices to their full component props.
+ token: an OAuthToken to inject into OAuthToken-annotated params when one cannot
+ be derived from the request session (e.g. server-side workflow calls that
+ resolve the token outside of a browser session).
+ Returns:
+ updated inputs, progress index, event data index, list of input indices that need component props.
+ """
+ try:
+ signature = inspect.signature(fn)
+ except ValueError:
+ return inputs or [], None, None, []
+ from gradio.components.base import Component
+
+ type_hints = utils.get_type_hints(fn)
+ positional_args = []
+ for param in signature.parameters.values():
+ if param.kind not in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
+ break
+ positional_args.append(param)
+ progress_index = None
+ event_data_index = None
+ component_prop_indices = []
+ for i, param in enumerate(positional_args):
+ type_hint = type_hints.get(param.name)
+ if isinstance(param.default, Progress):
+ progress_index = i
+ if inputs is not None:
+ inputs.insert(i, param.default)
+ elif isinstance(param.default, Cache):
+ if inputs is not None:
+ inputs.insert(i, param.default)
+ elif type_hint in (routes.Request, Optional[routes.Request]):
+ if inputs is not None:
+ inputs.insert(i, request)
+ elif type_hint in (
+ oauth.OAuthProfile | None,
+ oauth.OAuthToken | None,
+ oauth.OAuthProfile,
+ oauth.OAuthToken,
+ ):
+ if inputs is not None:
+ # Read the session if a SessionMiddleware is installed (i.e. the
+ # user may be logged in); otherwise treat as logged out. Required
+ # OAuth params still raise an explicit error below.
+ session = _session_from_request(request)
+
+ # Expiry means "treat as logged out" here; the session entry
+ # itself is only removed on the next LoginButton page-load check,
+ # which operates on the raw session.
+ oauth_info = oauth._get_valid_oauth_info_from_session(session)
+
+ # Inject user profile
+ if type_hint in (Optional[oauth.OAuthProfile], oauth.OAuthProfile):
+ oauth_profile = (
+ oauth_info["userinfo"] if oauth_info is not None else None
+ )
+ if oauth_profile is not None:
+ oauth_profile = oauth.OAuthProfile(oauth_profile)
+ elif type_hint == oauth.OAuthProfile:
+ raise Error(
+ "This action requires a logged in user. Please sign in and retry."
+ )
+ inputs.insert(i, oauth_profile)
+
+ # Inject user token
+ elif type_hint in (Optional[oauth.OAuthToken], oauth.OAuthToken):
+ oauth_token = (
+ oauth.OAuthToken(
+ token=oauth_info["access_token"],
+ scope=oauth_info["scope"],
+ expires_at=oauth_info["expires_at"],
+ )
+ if oauth_info is not None
+ else None
+ )
+ # Fall back to a directly-supplied token when the session does
+ # not yield one (e.g. server-side workflow calls).
+ if oauth_token is None and token is not None:
+ oauth_token = token
+ if oauth_token is None and type_hint == oauth.OAuthToken:
+ raise Error(
+ "This action requires a logged in user. Please sign in and retry."
+ )
+ inputs.insert(i, oauth_token)
+ elif type_hint in (Header, Optional[Header]):
+ if inputs is not None and request is not None:
+ header_name = param.name.replace("_", "-").lower()
+ header_value = None
+ if hasattr(request, "headers"):
+ for k, v in dict(request.headers).items():
+ if k.lower() == header_name:
+ header_value = v
+ break
+ if len(inputs) > i:
+ inputs[i] = header_value
+ else:
+ inputs.insert(i, header_value)
+ elif (
+ type_hint
+ and inspect.isclass(type_hint)
+ and issubclass(type_hint, EventData)
+ ):
+ event_data_index = i
+ if inputs is not None and event_data is not None:
+ processing_utils.check_all_files_in_cache(event_data._data)
+ inputs.insert(i, type_hint(event_data.target, event_data._data))
+ elif (
+ type_hint
+ and get_origin(type_hint) is None
+ and inspect.isclass(type_hint)
+ and issubclass(type_hint, Component)
+ ):
+ component_prop_indices.append(i)
+ if inputs is not None and component_props and i in component_props:
+ inputs[i] = SimpleNamespace(
+ **component_props[i], _is_component_update=True
+ )
+ elif (
+ param.default is not param.empty and inputs is not None and len(inputs) <= i
+ ):
+ inputs.insert(i, param.default)
+ if inputs is not None:
+ while len(inputs) < len(positional_args):
+ i = len(inputs)
+ param = positional_args[i]
+ if param.default == param.empty:
+ warnings.warn("Unexpected argument. Filling with None.")
+ inputs.append(None)
+ else:
+ inputs.append(param.default)
+ return inputs or [], progress_index, event_data_index, component_prop_indices
+
+
+def update(
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ visible: bool | Literal["hidden"] | None = None,
+ **kwargs: Any,
+) -> dict[str, Any]:
+ """
+ Updates a component's properties. When a function passed into a Gradio Interface or a Blocks
+ events returns a value, it typically updates the value of the output component. But it is also possible
+ to update the *properties* of an output component (such as the number of lines of a `Textbox` or
+ the visibility of an `Row`) by returning a component and passing in the parameters to update in
+ the constructor of the component. Alternatively, you can return `gr.update(...)` with any arbitrary
+ parameters to update. (This is useful as a shorthand or if the same function can be called with different
+ components to update.) For `gr.State` components, only the `value` parameter is supported.
+
+ Parameters:
+ elem_id: Use this to update the id of the component in the HTML DOM
+ elem_classes: Use this to update the classes of the component in the HTML DOM
+ visible: Use this to update the visibility of the component
+ kwargs: Any other keyword arguments to update the component's properties.
+ Example:
+ import gradio as gr
+ with gr.Blocks() as demo:
+ radio = gr.Radio([1, 2, 4], label="Set the value of the number")
+ number = gr.Number(value=2, interactive=True)
+ radio.change(fn=lambda value: gr.update(value=value), inputs=radio, outputs=number)
+ demo.launch()
+ """
+ kwargs["__type__"] = "update"
+ if elem_id is not None:
+ kwargs["elem_id"] = elem_id
+ if elem_classes is not None:
+ kwargs["elem_classes"] = elem_classes
+ if visible is not None:
+ kwargs["visible"] = visible
+ return kwargs
+
+
+@document()
+def validate(is_valid: bool, message: str):
+ """
+ A special function that can be returned from a Gradio function to set the validation error of an output component.
+ """
+ return {"__type__": "validate", "is_valid": is_valid, "message": message}
+
+
+@document()
+def skip() -> dict:
+ """
+ A special function that can be returned from a Gradio function to skip updating the output component. This may be useful when
+ you want to update the output component conditionally, and in some cases, you want to skip updating the output component.
+ If you have multiple output components, you can return `gr.skip()` as part of a tuple to skip updating a specific output component,
+ or you can return a single `gr.skip()` to skip updating all output components.
+ """
+ return {"__type__": "update"}
+
+
+def log_message(
+ message: str,
+ title: str,
+ level: Literal["info", "warning", "success"] = "info",
+ duration: float | None = 10,
+ visible: bool = True,
+):
+ from gradio.context import LocalContext
+
+ blocks = LocalContext.blocks.get(None)
+ event_id = LocalContext.event_id.get(None)
+ if blocks is None or event_id is None:
+ # Function called outside of Gradio if blocks is None
+ # Or from /api/predict if event_id is None
+ if level in ("info", "success"):
+ print(message)
+ elif level == "warning":
+ warnings.warn(message)
+ return
+ blocks._queue.log_message(
+ event_id=event_id,
+ log=message,
+ title=title,
+ level=level,
+ duration=duration,
+ visible=visible,
+ )
+
+
+@document(documentation_group="modals")
+def Warning( # noqa: N802
+ message: str = "Warning issued.",
+ duration: float | None = 10,
+ visible: bool = True,
+ title: str = "Warning",
+):
+ """
+ This function allows you to pass custom warning messages to the user. You can do so simply by writing `gr.Warning('message here')` in your function, and when that line is executed the custom message will appear in a modal on the demo. The modal is yellow by default and has the heading: "Warning." Queue must be enabled for this behavior; otherwise, the warning will be printed to the console using the `warnings` library.
+ Demos: blocks_chained_events
+ Parameters:
+ message: The warning message to be displayed to the user. Can be HTML, which will be rendered in the modal.
+ duration: The duration in seconds that the warning message should be displayed for. If None or 0, the message will be displayed indefinitely until the user closes it.
+ visible: Whether the error message should be displayed in the UI.
+ title: The title to be displayed to the user at the top of the modal.
+ Example:
+ import gradio as gr
+ def hello_world():
+ gr.Warning('This is a warning message.')
+ return "hello world"
+ with gr.Blocks() as demo:
+ md = gr.Markdown()
+ demo.load(hello_world, inputs=None, outputs=[md])
+ demo.launch()
+ """
+ log_message(
+ message, title=title, level="warning", duration=duration, visible=visible
+ )
+
+
+@document(documentation_group="modals")
+def Info( # noqa: N802
+ message: str = "Info issued.",
+ duration: float | None = 10,
+ visible: bool = True,
+ title: str = "Info",
+):
+ """
+ This function allows you to pass custom info messages to the user. You can do so simply by writing `gr.Info('message here')` in your function, and when that line is executed the custom message will appear in a modal on the demo. The modal is gray by default and has the heading: "Info." Queue must be enabled for this behavior; otherwise, the message will be printed to the console.
+ Demos: blocks_chained_events
+ Parameters:
+ message: The info message to be displayed to the user. Can be HTML, which will be rendered in the modal.
+ duration: The duration in seconds that the info message should be displayed for. If None or 0, the message will be displayed indefinitely until the user closes it.
+ visible: Whether the error message should be displayed in the UI.
+ title: The title to be displayed to the user at the top of the modal.
+ Example:
+ import gradio as gr
+ def hello_world():
+ gr.Info('This is some info.')
+ return "hello world"
+ with gr.Blocks() as demo:
+ md = gr.Markdown()
+ demo.load(hello_world, inputs=None, outputs=[md])
+ demo.launch()
+ """
+ log_message(message, title=title, level="info", duration=duration, visible=visible)
+
+
+@document(documentation_group="modals")
+def Success( # noqa: N802
+ message: str = "Success.",
+ duration: float | None = 10,
+ visible: bool = True,
+ title: str = "Success",
+):
+ """
+ This function allows you to pass custom success messages to the user. You can do so simply by writing `gr.Success('message here')` in your function, and when that line is executed the custom message will appear in a modal on the demo. The modal is green by default and has the heading: "Success." Queue must be enabled for this behavior; otherwise, the message will be printed to the console.
+ Parameters:
+ message: The success message to be displayed to the user. Can be HTML, which will be rendered in the modal.
+ duration: The duration in seconds that the success message should be displayed for. If None or 0, the message will be displayed indefinitely until the user closes it.
+ visible: Whether the error message should be displayed in the UI.
+ title: The title to be displayed to the user at the top of the modal.
+ Example:
+ def hello_world():
+ gr.Success('Operation completed successfully!')
+ return "hello world"
+ with gr.Blocks() as demo:
+ md = gr.Markdown()
+ demo.load(hello_world, inputs=None, outputs=[md])
+ demo.launch()
+ """
+ log_message(
+ message, title=title, level="success", duration=duration, visible=visible
+ )
diff --git a/gradio/http_server.py b/gradio/http_server.py
new file mode 100644
index 0000000..84924f1
--- /dev/null
+++ b/gradio/http_server.py
@@ -0,0 +1,191 @@
+from __future__ import annotations
+
+import os
+import socket
+import sys
+import threading
+import time
+from collections.abc import Callable
+from functools import partial
+from typing import TYPE_CHECKING, TypeVar
+
+import uvicorn
+from uvicorn.config import Config
+
+from gradio.exceptions import ServerFailedToStartError
+from gradio.routes import App
+from gradio.utils import (
+ ServerReloader,
+ SourceFileReloader,
+ SpacesReloader,
+ get_space,
+ watchfn,
+ watchfn_spaces,
+)
+
+if TYPE_CHECKING: # Only import for type checking (to avoid circular imports).
+ _ServerReloaderT = TypeVar("_ServerReloaderT", bound=ServerReloader)
+
+# By default, the local server will try to open on localhost, port 7860.
+# If that is not available, then it will try 7861, 7862, ... 7959.
+INITIAL_PORT_VALUE = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
+TRY_NUM_PORTS = int(os.getenv("GRADIO_NUM_PORTS", "100"))
+LOCALHOST_NAME = os.getenv("GRADIO_SERVER_NAME", "127.0.0.1")
+
+GRADIO_HOT_RELOAD = os.getenv("GRADIO_HOT_RELOAD", "false").lower()
+
+should_watch = bool(os.getenv("GRADIO_WATCH_DIRS", ""))
+GRADIO_WATCH_DIRS = (
+ os.getenv("GRADIO_WATCH_DIRS", "").split(",") if should_watch else []
+)
+GRADIO_WATCH_MODULE_NAME = os.getenv("GRADIO_WATCH_MODULE_NAME", "app")
+GRADIO_WATCH_DEMO_NAME = os.getenv("GRADIO_WATCH_DEMO_NAME", "")
+GRADIO_WATCH_DEMO_PATH = os.getenv("GRADIO_WATCH_DEMO_PATH", "")
+
+
+class Server(uvicorn.Server):
+ def __init__(
+ self,
+ config: Config,
+ reloader: _ServerReloaderT | None = None,
+ watchfn: Callable[[_ServerReloaderT], None] = watchfn, # ty: ignore[invalid-parameter-default]
+ ) -> None:
+ self.running_app = config.app
+ super().__init__(config)
+ self.reloader = reloader
+ if self.reloader:
+ self.event = threading.Event()
+ self.watch = partial(watchfn, self.reloader)
+
+ def install_signal_handlers(self):
+ pass
+
+ def run_in_thread(self):
+ self.thread = threading.Thread(target=self.run, daemon=True)
+ if self.reloader:
+ self.watch_thread = threading.Thread(target=self.watch, daemon=True)
+ self.watch_thread.start()
+ self.thread.start()
+ start = time.time()
+ while not self.started:
+ time.sleep(1e-3)
+ if time.time() - start > 5:
+ raise ServerFailedToStartError(
+ "Server failed to start. Please check that the port is available."
+ )
+
+ def close(self):
+ self.should_exit = True
+ if self.reloader:
+ self.reloader.stop()
+ self.watch_thread.join()
+ self.thread.join(timeout=5)
+
+
+def start_server(
+ app: App,
+ server_name: str | None = None,
+ server_port: int | None = None,
+ ssl_keyfile: str | None = None,
+ ssl_certfile: str | None = None,
+ ssl_keyfile_password: str | None = None,
+) -> tuple[str, int, str, Server]:
+ """Launches a local server running the provided Interface
+ Parameters:
+ app: the FastAPI app object to run
+ server_name: to make app accessible on local network, set this to "0.0.0.0". Can be set by environment variable GRADIO_SERVER_NAME.
+ server_port: will start gradio app on this port (if available). Can be set by environment variable GRADIO_SERVER_PORT.
+ auth: If provided, username and password (or list of username-password tuples) required to access the Blocks. Can also provide function that takes username and password and returns True if valid login.
+ ssl_keyfile: If a path to a file is provided, will use this as the private key file to create a local server running on https.
+ ssl_certfile: If a path to a file is provided, will use this as the signed certificate for https. Needs to be provided if ssl_keyfile is provided.
+ ssl_keyfile_password: If a password is provided, will use this with the ssl certificate for https.
+
+ Returns:
+ server_name: the name of the server (default is "localhost")
+ port: the port number the server is running on
+ path_to_local_server: the complete address that the local server can be accessed at
+ server: the server object that is a subclass of uvicorn.Server (used to close the server)
+ """
+ if ssl_keyfile is not None and ssl_certfile is None:
+ raise ValueError("ssl_certfile must be provided if ssl_keyfile is provided.")
+
+ server_name = server_name or LOCALHOST_NAME
+ url_host_name = "localhost" if server_name == "0.0.0.0" else server_name
+
+ # Strip IPv6 brackets from the address if they exist.
+ # This is needed as http://[::1]:port/ is a valid browser address,
+ # but not a valid IPv6 address, so asyncio will throw an exception.
+ if server_name.startswith("[") and server_name.endswith("]"):
+ host = server_name[1:-1]
+ else:
+ host = server_name
+
+ server_ports = (
+ [server_port]
+ if server_port is not None
+ else range(INITIAL_PORT_VALUE, INITIAL_PORT_VALUE + TRY_NUM_PORTS)
+ )
+
+ for port in server_ports:
+ try:
+ # The fastest way to check if a port is available is to try to bind to it with socket.
+ # If the port is not available, socket will throw an OSError.
+ s = socket.socket()
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ # Really, we should be checking if (server_name, server_port) is available, but
+ # socket.bind() doesn't seem to throw an OSError with ipv6 addresses, based on my testing.
+ # Instead, we just check if the port is available on localhost.
+ s.bind((LOCALHOST_NAME, port))
+ s.close()
+
+ # To avoid race conditions, so we also check if the port by trying to start the uvicorn server.
+ # If the port is not available, this will throw a ServerFailedToStartError.
+ config = uvicorn.Config(
+ app=app,
+ port=port,
+ host=host,
+ log_level="warning",
+ ssl_keyfile=ssl_keyfile,
+ ssl_certfile=ssl_certfile,
+ ssl_keyfile_password=ssl_keyfile_password,
+ )
+ if get_space() is not None:
+ reloader = SpacesReloader(
+ app=app,
+ watch_dirs=GRADIO_WATCH_DIRS,
+ demo_name=GRADIO_WATCH_DEMO_NAME,
+ stop_event=threading.Event(),
+ watch_module=sys.modules["__main__"],
+ )
+ server = Server(
+ config=config, reloader=reloader, watchfn=watchfn_spaces
+ )
+ elif GRADIO_WATCH_DIRS:
+ reloader = SourceFileReloader(
+ app=app,
+ watch_dirs=GRADIO_WATCH_DIRS,
+ watch_module_name=GRADIO_WATCH_MODULE_NAME,
+ demo_name=GRADIO_WATCH_DEMO_NAME,
+ stop_event=threading.Event(),
+ demo_file=GRADIO_WATCH_DEMO_PATH,
+ watch_module=sys.modules["__main__"],
+ encoding=os.getenv("GRADIO_WATCH_ENCODING", "utf-8"),
+ )
+ server = Server(config=config, reloader=reloader)
+ else:
+ server = Server(config=config)
+ server.run_in_thread()
+ break
+ except (OSError, ServerFailedToStartError):
+ pass
+ else:
+ raise OSError(
+ f"Cannot find empty port in range: {min(server_ports)}-{max(server_ports)}. You can specify a different port by setting the GRADIO_SERVER_PORT environment variable or passing the `server_port` parameter to `launch()`."
+ )
+
+ if ssl_keyfile is not None:
+ path_to_local_server = f"https://{url_host_name}:{port}/"
+ else:
+ path_to_local_server = f"http://{url_host_name}:{port}/"
+
+ return server_name, port, path_to_local_server, server
diff --git a/gradio/i18n.py b/gradio/i18n.py
new file mode 100644
index 0000000..7097444
--- /dev/null
+++ b/gradio/i18n.py
@@ -0,0 +1,142 @@
+from __future__ import annotations
+
+import re
+import warnings
+from typing import Any
+
+
+class I18nData:
+ """
+ A class that wraps a translation key with metadata.
+
+ This object will be serialized and sent to the frontend, where the actual
+ translation will happen using the frontend's i18n system.
+ """
+
+ def __init__(self, key: str):
+ """
+ Initialize a I18nData object.
+
+ Args:
+ key: The translation key to be translated in the frontend.
+ """
+ self.key = key
+ self._type = "translation_metadata"
+
+ def to_dict(self) -> dict[str, Any]:
+ """
+ Convert the I18nData object to a dictionary for serialization.
+ This allows the frontend to recognize it as a translatable object.
+ """
+ return {"__type__": self._type, "key": self.key}
+
+ def __str__(self) -> str:
+ """
+ String representation of the I18nData object.
+ Used when the object is converted to a string.
+ This returns a special format that can be recognized by the frontend
+ as needing translation.
+ """
+ import json
+
+ return f"__i18n__{json.dumps(self.to_dict())}"
+
+ def __repr__(self) -> str:
+ """
+ Representation of the I18nData object for debugging.
+ """
+ return self.__str__()
+
+ def __add__(self, other):
+ """
+ Handle string concatenation (self + other).
+ """
+ return str(self) + str(other)
+
+ def __radd__(self, other):
+ """
+ Handle string concatenation (other + self).
+ """
+ return str(other) + str(self)
+
+ def __getattr__(self, name):
+ """
+ Handle attribute access for I18nData.
+ This makes it possible to use I18nData objects in contexts
+ that expect strings with methods.
+ """
+ if name.startswith("__") and name.endswith("__"):
+ raise AttributeError(f"{self.__class__.__name__} has no attribute {name}")
+
+ def method(*_args, **_kwargs):
+ return self
+
+ return method
+
+ def tojson(self) -> dict[str, Any]:
+ """
+ Convert the I18nData object to a JSON-serializable dictionary.
+ This is used by the default Python JSON serializer.
+ """
+ return self.to_dict()
+
+
+class I18n:
+ """
+ Handles internationalization (i18n) for Gradio applications.
+
+ Stores translation dictionaries and provides a method to retrieve translation keys.
+ The translation lookup happens on the frontend based on the browser's locale
+ and the provided translation dictionaries.
+ """
+
+ # BCP 47 language tag regex pattern
+ _LOCALE_PATTERN = re.compile(r"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$")
+
+ def __init__(self, **translations: dict[str, str]):
+ """
+ Initializes the I18n class.
+
+ Args:
+ **translations: Each keyword argument should be a locale code (e.g., "en", "fr") with a
+ dictionary value, which maps translation keys to translated strings.
+ Example: gr.I18n(en={"greeting": "Hello"}, es={"greeting": "Hola"})
+
+ These translations can be passed to the frontend for use there.
+ """
+ self.translations = {}
+
+ for locale, translation_dict in translations.items():
+ if not self._is_valid_locale(locale):
+ warnings.warn(
+ f"Invalid locale code: '{locale}'. Locale codes should follow BCP 47 format (e.g., 'en', 'en-US'). "
+ f"This locale will still be included, but may not work correctly.",
+ UserWarning,
+ )
+ self.translations[locale] = translation_dict
+
+ def _is_valid_locale(self, locale: str) -> bool:
+ return bool(self._LOCALE_PATTERN.match(locale))
+
+ def __call__(self, key: str) -> I18nData:
+ """
+ Returns a I18nData object containing the translation key.
+
+ This metadata object will be serialized and sent to the frontend,
+ where it will be translated by the frontend's i18n system.
+
+ Args:
+ key: The key to identify the translation string (e.g., "submit_button").
+
+ Returns:
+ A I18nData object containing the translation key.
+ """
+ return I18nData(key)
+
+ @property
+ def translations_dict(self) -> dict[str, dict[str, str]]:
+ """
+ Returns the dictionary of translations provided during initialization.
+ These can be passed to the frontend for use in its translation system.
+ """
+ return self.translations
diff --git a/gradio/icons/README.md b/gradio/icons/README.md
new file mode 100644
index 0000000..f38df88
--- /dev/null
+++ b/gradio/icons/README.md
@@ -0,0 +1,2 @@
+The icons in this directory are loaded via `gradio.utils.get_icon_path` and
+can be used directly in backend code (e.g. to populate icons in components).
\ No newline at end of file
diff --git a/gradio/icons/huggingface-logo.svg b/gradio/icons/huggingface-logo.svg
new file mode 100644
index 0000000..43c5d3c
--- /dev/null
+++ b/gradio/icons/huggingface-logo.svg
@@ -0,0 +1,37 @@
+
diff --git a/gradio/icons/link.svg b/gradio/icons/link.svg
new file mode 100644
index 0000000..25f5c5d
--- /dev/null
+++ b/gradio/icons/link.svg
@@ -0,0 +1,4 @@
+
diff --git a/gradio/icons/plus.svg b/gradio/icons/plus.svg
new file mode 100644
index 0000000..cb2ec6f
--- /dev/null
+++ b/gradio/icons/plus.svg
@@ -0,0 +1,12 @@
+
diff --git a/gradio/image_utils.py b/gradio/image_utils.py
new file mode 100644
index 0000000..6e7a86a
--- /dev/null
+++ b/gradio/image_utils.py
@@ -0,0 +1,359 @@
+from __future__ import annotations
+
+import base64
+import warnings
+from io import BytesIO
+from pathlib import Path
+from typing import Literal, cast
+from urllib.parse import quote
+
+import numpy as np
+import PIL.Image
+from gradio_client import utils as client_utils
+from gradio_client.utils import get_mimetype, is_http_url_like
+from PIL import ImageOps
+
+from gradio import processing_utils
+from gradio.components.image_editor import WatermarkOptions
+from gradio.data_classes import ImageData
+from gradio.exceptions import Error
+from gradio.profiling import traced_sync
+
+PIL.Image.init() # fixes https://github.com/gradio-app/gradio/issues/2843 (remove when requiring Pillow 9.4+)
+
+
+def open_image(orig_img: np.ndarray | PIL.Image.Image | str | Path) -> PIL.Image.Image:
+ """
+ Provided an array, PIL Image or filepath, return a PIL Image.
+ Parameters:
+ orig_img: Local image file. If a filepath, it must be a webp, png, jpeg, or bmp.
+ Returns:
+ open_img: A PIL.Image.Image.
+ """
+
+ if isinstance(orig_img, np.ndarray):
+ open_img = PIL.Image.fromarray(orig_img)
+ elif isinstance(orig_img, (str, Path)):
+ open_img = PIL.Image.open(orig_img)
+ elif isinstance(orig_img, PIL.Image.Image):
+ open_img = orig_img
+ else:
+ raise ValueError(
+ "Expected image or path to image of type webp, png, bmp or jpeg; PIL image; or numpy array. Received "
+ + str(type(orig_img))
+ )
+ return open_img
+
+
+def format_image(
+ im: PIL.Image.Image | None,
+ type: Literal["numpy", "pil", "filepath"],
+ cache_dir: str,
+ name: str = "image",
+ format: str = "webp",
+) -> np.ndarray | PIL.Image.Image | str | None:
+ """Helper method to format an image based on self.type"""
+ if im is None:
+ return im
+ if type == "pil":
+ return im
+ elif type == "numpy":
+ return np.array(im)
+ elif type == "filepath":
+ try:
+ path = processing_utils.save_pil_to_cache(
+ im, cache_dir=cache_dir, name=name, format=format
+ )
+ # Catch error if format is not supported by PIL
+ except (KeyError, ValueError):
+ path = processing_utils.save_pil_to_cache(
+ im,
+ cache_dir=cache_dir,
+ name=name,
+ format="png", # type: ignore
+ )
+ return path
+ else:
+ raise ValueError(
+ "Unknown type: "
+ + str(type)
+ + ". Please choose from: 'numpy', 'pil', 'filepath'."
+ )
+
+
+def save_image(
+ y: np.ndarray | PIL.Image.Image | str | Path, cache_dir: str, format: str = "webp"
+):
+ if isinstance(y, np.ndarray):
+ path = processing_utils.save_img_array_to_cache(
+ y, cache_dir=cache_dir, format=format
+ )
+ elif isinstance(y, PIL.Image.Image):
+ try:
+ path = processing_utils.save_pil_to_cache(
+ y, cache_dir=cache_dir, format=format
+ )
+ # Catch error if format is not supported by PIL
+ except (KeyError, ValueError):
+ path = processing_utils.save_pil_to_cache(
+ y, cache_dir=cache_dir, format="png"
+ )
+ elif isinstance(y, Path):
+ path = str(y)
+ elif isinstance(y, str):
+ path = y
+ else:
+ raise ValueError(
+ "Cannot process this value as an Image, it is of type: " + str(type(y))
+ )
+
+ return path
+
+
+def add_watermark(
+ base_img: np.ndarray | PIL.Image.Image | str | Path,
+ watermark_option: WatermarkOptions,
+) -> PIL.Image.Image:
+ """Overlays a watermark image on a base image.
+ Parameters:
+ base_img: Base image onto which the watermark is applied. Can be an array, PIL Image, or filepath.
+ watermarkOption: WatermarkOptions instance containing watermark image and position settings.
+ Returns:
+ watermarked_img: A PIL Image of the base image overlaid with the watermark image.
+ """
+ base_img = open_image(base_img)
+ base_img_width, base_img_height = base_img.size
+ watermark_option.watermark = open_image(
+ cast(np.ndarray | PIL.Image.Image | str | Path, watermark_option.watermark)
+ )
+ watermark_width, watermark_height = watermark_option.watermark.size
+
+ if isinstance(watermark_option.position, str):
+ padding = 10
+ if watermark_option.position == "top-left":
+ x, y = padding, padding
+ elif watermark_option.position == "top-right":
+ x, y = base_img_width - watermark_width - padding, padding
+ elif watermark_option.position == "bottom-left":
+ x, y = padding, base_img_height - watermark_height - padding
+ elif watermark_option.position == "bottom-right":
+ x, y = (
+ base_img_width - watermark_width - padding,
+ base_img_height - watermark_height - padding,
+ )
+ else:
+ x, y = watermark_option.position
+
+ if (
+ x < 0
+ or x + watermark_width > base_img_width
+ or y < 0
+ or y + watermark_height > base_img_height
+ ):
+ x = base_img_width - watermark_width - 10
+ y = base_img_height - watermark_height - 10
+
+ watermark_position = (x, y)
+ orig_img_mode = base_img.mode
+ base_img = base_img.convert("RGBA")
+ watermark_option.watermark = watermark_option.watermark.convert("RGBA")
+ base_img.paste(
+ watermark_option.watermark, watermark_position, mask=watermark_option.watermark
+ )
+ base_img = base_img.convert(orig_img_mode)
+
+ return base_img
+
+
+def crop_scale(img: PIL.Image.Image, final_width: int, final_height: int):
+ original_width, original_height = img.size
+ target_aspect_ratio = final_width / final_height
+
+ if original_width / original_height > target_aspect_ratio:
+ crop_height = original_height
+ crop_width = crop_height * target_aspect_ratio
+ else:
+ crop_width = original_width
+ crop_height = crop_width / target_aspect_ratio
+
+ left = (original_width - crop_width) / 2
+ top = (original_height - crop_height) / 2
+
+ img_cropped = img.crop(
+ (int(left), int(top), int(left + crop_width), int(top + crop_height))
+ )
+
+ img_resized = img_cropped.resize((final_width, final_height))
+
+ return img_resized
+
+
+def decode_base64_to_image(encoding: str) -> PIL.Image.Image:
+ image_encoded = processing_utils.extract_base64_data(encoding)
+ img = PIL.Image.open(BytesIO(base64.b64decode(image_encoded)))
+ try:
+ if hasattr(ImageOps, "exif_transpose"):
+ img = ImageOps.exif_transpose(img)
+ except Exception:
+ print(
+ "Failed to transpose image %s based on EXIF data.",
+ img,
+ )
+ assert img is not None # noqa: S101
+ return img
+
+
+def decode_base64_to_image_array(encoding: str) -> np.ndarray:
+ img = decode_base64_to_image(encoding)
+ return np.asarray(img)
+
+
+def decode_base64_to_file(encoding: str, cache_dir: str, format: str = "webp") -> str:
+ img = decode_base64_to_image(encoding)
+ return save_image(img, cache_dir, format)
+
+
+def encode_image_array_to_base64(image_array: np.ndarray) -> str:
+ with BytesIO() as output_bytes:
+ pil_image = PIL.Image.fromarray(
+ processing_utils._convert(image_array, np.uint8, force_copy=False)
+ )
+ pil_image.save(output_bytes, "JPEG")
+ bytes_data = output_bytes.getvalue()
+ base64_str = str(base64.b64encode(bytes_data), "utf-8")
+ return "data:image/jpeg;base64," + base64_str
+
+
+def encode_image_to_base64(image: PIL.Image.Image) -> str:
+ with BytesIO() as output_bytes:
+ image.save(output_bytes, "JPEG")
+ bytes_data = output_bytes.getvalue()
+ base64_str = str(base64.b64encode(bytes_data), "utf-8")
+ return "data:image/jpeg;base64," + base64_str
+
+
+def encode_image_file_to_base64(image_file: str | Path) -> str:
+ mime_type = get_mimetype(str(image_file))
+ with open(image_file, "rb") as f:
+ bytes_data = f.read()
+ base64_str = str(base64.b64encode(bytes_data), "utf-8")
+ return f"data:{mime_type};base64," + base64_str
+
+
+def extract_svg_content(image_file: str | Path) -> str:
+ """
+ Provided a path or URL to an SVG file, return the SVG content as a string.
+ Parameters:
+ image_file: Local file path or URL to an SVG file
+ Returns:
+ str: The SVG content as a string
+ """
+ image_file = str(image_file)
+ if is_http_url_like(image_file):
+ response = client_utils.synchronize_async(
+ processing_utils.async_ssrf_protected_get, image_file
+ )
+ response.raise_for_status()
+ return response.text
+ else:
+ with open(image_file) as file:
+ svg_content = file.read()
+ return svg_content
+
+
+@traced_sync("preprocess_format_image")
+def preprocess_image(
+ payload: ImageData | None,
+ cache_dir: str,
+ format: str,
+ image_mode: Literal[
+ "1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F"
+ ]
+ | None,
+ type: Literal["numpy", "pil", "filepath"],
+) -> np.ndarray | PIL.Image.Image | str | None:
+ if payload is None:
+ return payload
+ if payload.url and payload.url.startswith("data:"):
+ if type == "pil":
+ return decode_base64_to_image(payload.url)
+ elif type == "numpy":
+ return decode_base64_to_image_array(payload.url)
+ elif type == "filepath":
+ return decode_base64_to_file(payload.url, cache_dir, format)
+ if payload.path is None:
+ raise ValueError("Image path is None.")
+ file_path = Path(payload.path)
+ if payload.orig_name:
+ p = Path(payload.orig_name)
+ name = p.stem
+ suffix = p.suffix.replace(".", "")
+ if suffix in ["jpg", "jpeg"]:
+ suffix = "jpeg"
+ else:
+ name = "image"
+ suffix = "webp"
+
+ if suffix.lower() == "svg":
+ if type == "filepath":
+ return str(file_path)
+ raise Error("SVG files are not supported as input images for this app.")
+
+ im = PIL.Image.open(file_path)
+ if type == "filepath" and (image_mode in [None, im.mode]):
+ return str(file_path)
+
+ exif = im.getexif()
+ # 274 is the code for image rotation and 1 means "correct orientation"
+ if exif.get(274, 1) != 1 and hasattr(ImageOps, "exif_transpose"):
+ try:
+ im = ImageOps.exif_transpose(im)
+ except Exception:
+ warnings.warn(f"Failed to transpose image {file_path} based on EXIF data.")
+ if suffix.lower() != "gif" and im is not None:
+ with warnings.catch_warnings():
+ warnings.simplefilter("ignore")
+ if image_mode is not None:
+ im = im.convert(image_mode)
+
+ return format_image(
+ im,
+ type=type,
+ cache_dir=cache_dir,
+ name=name,
+ format=suffix,
+ )
+
+
+def postprocess_image(
+ value: np.ndarray | PIL.Image.Image | str | Path | None,
+ cache_dir: str,
+ format: str,
+ watermark: WatermarkOptions | None = None,
+) -> ImageData | None:
+ """
+ Parameters:
+ value: Expects a `numpy.array`, `PIL.Image`, or `str` or `pathlib.Path` filepath to an image which is displayed.
+ watermark: An optional `WatermarkOptions` instance to apply a watermark to the image.
+ Returns:
+ Returns the image as a `FileData` object.
+ """
+ from gradio import Warning
+
+ if value is None:
+ return None
+ if isinstance(value, str) and value.lower().endswith(".svg"):
+ svg_content = extract_svg_content(value)
+ if watermark is not None:
+ Warning(
+ "Watermarking for SVG images is currently not supported. No watermark will be applied."
+ )
+ return ImageData(
+ orig_name=Path(value).name,
+ url=f"data:image/svg+xml,{quote(svg_content)}",
+ )
+ if watermark and watermark.watermark is not None:
+ value = add_watermark(value, watermark)
+ saved = save_image(value, cache_dir=cache_dir, format=format)
+ orig_name = Path(saved).name if Path(saved).exists() else None
+ return ImageData(path=saved, orig_name=orig_name)
diff --git a/gradio/interface.py b/gradio/interface.py
new file mode 100644
index 0000000..f8addfc
--- /dev/null
+++ b/gradio/interface.py
@@ -0,0 +1,1009 @@
+"""
+This file defines two useful high-level abstractions to build Gradio apps: Interface and TabbedInterface.
+"""
+
+from __future__ import annotations
+
+import inspect
+import json
+import os
+import warnings
+from collections.abc import Callable, Sequence
+from typing import TYPE_CHECKING, Any, Literal
+
+from gradio_client.documentation import document
+
+from gradio import Examples, utils
+from gradio.blocks import Blocks
+from gradio.components import (
+ Button,
+ ClearButton,
+ Component,
+ DeepLinkButton,
+ DuplicateButton,
+ Markdown,
+ State,
+ get_component_instance,
+)
+from gradio.data_classes import InterfaceTypes
+from gradio.events import Dependency, Events, on
+from gradio.exceptions import RenderError
+from gradio.flagging import CSVLogger, FlaggingCallback, FlagMethod
+from gradio.i18n import I18nData
+from gradio.layouts import Accordion, Column, Row, Tab, Tabs
+from gradio.pipelines import load_from_pipeline
+
+if TYPE_CHECKING: # Only import for type checking (is False at runtime).
+ from diffusers import DiffusionPipeline # type: ignore
+ from transformers.pipelines.base import Pipeline
+
+
+@document("launch", "load", "from_pipeline", "integrate", "queue")
+class Interface(Blocks):
+ """
+ Interface is Gradio's main high-level class, and allows you to create a web-based GUI / demo
+ around a machine learning model (or any Python function) in a few lines of code.
+ You must specify three parameters: (1) the function to create a GUI for (2) the desired input components and
+ (3) the desired output components. Additional parameters can be used to control the appearance
+ and behavior of the demo.
+
+ Example:
+ import gradio as gr
+
+ def image_classifier(inp):
+ return {'cat': 0.3, 'dog': 0.7}
+
+ demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label")
+ demo.launch()
+ Demos: hello_world, hello_world_2, hello_world_3
+ Guides: the-interface-class, interface-state, reactive-interfaces, four-kinds-of-interfaces, sharing-your-app
+ """
+
+ @classmethod
+ def from_pipeline(
+ cls, pipeline: Pipeline | DiffusionPipeline, **kwargs
+ ) -> Interface:
+ """
+ Class method that constructs an Interface from a Hugging Face transformers.Pipeline or diffusers.DiffusionPipeline object.
+ The input and output components are automatically determined from the pipeline.
+ Parameters:
+ pipeline: the pipeline object to use.
+ Returns:
+ a Gradio Interface object from the given Pipeline
+ Example:
+ import gradio as gr
+ from transformers import pipeline
+ pipe = pipeline("image-classification")
+ gr.Interface.from_pipeline(pipe).launch()
+ """
+ interface_info = load_from_pipeline(pipeline)
+ kwargs = dict(interface_info, **kwargs)
+ interface = cls(**kwargs)
+ return interface
+
+ def __init__(
+ self,
+ fn: Callable,
+ inputs: str | Component | Sequence[str | Component] | None,
+ outputs: str | Component | Sequence[str | Component] | None,
+ examples: list[Any] | list[list[Any]] | str | None = None,
+ *,
+ cache_examples: bool | None = None,
+ cache_mode: Literal["eager", "lazy"] | None = None,
+ examples_per_page: int = 10,
+ example_labels: list[str] | None = None,
+ preload_example: int | Literal[False] = 0,
+ live: bool = False,
+ title: str | I18nData | None = None,
+ description: str | None = None,
+ article: str | None = None,
+ flagging_mode: Literal["never"]
+ | Literal["auto"]
+ | Literal["manual"]
+ | None = None,
+ flagging_options: list[str] | list[tuple[str, str]] | None = None,
+ flagging_dir: str = ".gradio/flagged",
+ flagging_callback: FlaggingCallback | None = None,
+ analytics_enabled: bool | None = None,
+ batch: bool = False,
+ max_batch_size: int = 4,
+ api_visibility: Literal["public", "private", "undocumented"] = "public",
+ api_name: str | None = None,
+ api_description: str | None | Literal[False] = None,
+ _api_mode: bool = False,
+ allow_duplication: bool = False,
+ concurrency_limit: int | None | Literal["default"] = "default",
+ additional_inputs: str | Component | Sequence[str | Component] | None = None,
+ additional_inputs_accordion: str | Accordion | None = None,
+ submit_btn: str | Button = "Submit",
+ stop_btn: str | Button = "Stop",
+ clear_btn: str | Button | None = "Clear",
+ delete_cache: tuple[int, int] | None = None,
+ show_progress: Literal["full", "minimal", "hidden"] = "full",
+ fill_width: bool = False,
+ time_limit: int | None = 30,
+ stream_every: float = 0.5,
+ deep_link: str | DeepLinkButton | bool | None = None,
+ validator: Callable | None = None,
+ **kwargs,
+ ):
+ """
+ Parameters:
+ fn: the function to wrap an interface around. Often a machine learning model's prediction function. Each parameter of the function corresponds to one input component, and the function should return a single value or a tuple of values, with each element in the tuple corresponding to one output component.
+ inputs: a single Gradio component, or list of Gradio components. Components can either be passed as instantiated objects, or referred to by their string shortcuts. The number of input components should match the number of parameters in fn. If set to None, then only the output components will be displayed.
+ outputs: a single Gradio component, or list of Gradio components. Components can either be passed as instantiated objects, or referred to by their string shortcuts. The number of output components should match the number of values returned by fn. If set to None, then only the input components will be displayed.
+ examples: sample inputs for the function; if provided, appear below the UI components and can be clicked to populate the interface. Should be nested list, in which the outer list consists of samples and each inner list consists of an input corresponding to each input component. A string path to a directory of examples can also be provided, but it should be within the directory with the python file running the gradio app. If there are multiple input components and a directory is provided, a log.csv file must be present in the directory to link corresponding inputs.
+ cache_examples: If True, caches examples in the server for fast runtime in examples. If "lazy", then examples are cached (for all users of the app) after their first use (by any user of the app). If None, will use the GRADIO_CACHE_EXAMPLES environment variable, which should be either "true" or "false". In HuggingFace Spaces, this parameter defaults to True (as long as `fn` and `outputs` are also provided). Note that examples are cached separately from Gradio's queue() so certain features, such as gr.Progress(), gr.Info(), gr.Warning(), etc. will not be displayed in Gradio's UI for cached examples.
+ cache_mode: if "lazy", examples are cached after their first use. If "eager", all examples are cached at app launch. If None, will use the GRADIO_CACHE_MODE environment variable if defined, or default to "eager". In HuggingFace Spaces, this parameter defaults to "eager" except for ZeroGPU Spaces, in which case it defaults to "lazy".
+ examples_per_page: if examples are provided, how many to display per page.
+ preload_example: If an integer is provided (and examples are being cached eagerly and none of the input components have a developer-provided `value`), the example at that index in the examples list will be preloaded when the Gradio app is first loaded. If False, no example will be preloaded.
+ live: whether the interface should automatically rerun if any of the inputs change.
+ title: a title for the interface; if provided, appears above the input and output components in large font. Also used as the tab title when opened in a browser window.
+ description: a description for the interface; if provided, appears above the input and output components and beneath the title in regular font. Accepts Markdown and HTML content.
+ article: an expanded article explaining the interface; if provided, appears below the input and output components in regular font. Accepts Markdown and HTML content. If it is an HTTP(S) link to a downloadable remote file, the content of this file is displayed.
+ flagging_mode: one of "never", "auto", or "manual". If "never" or "auto", users will not see a button to flag an input and output. If "manual", users will see a button to flag. If "auto", every input the user submits will be automatically flagged, along with the generated output. If "manual", both the input and outputs are flagged when the user clicks flag button. This parameter can be set with environmental variable GRADIO_FLAGGING_MODE; otherwise defaults to "manual".
+ flagging_options: if provided, allows user to select from the list of options when flagging. Only applies if flagging_mode is "manual". Can either be a list of tuples of the form (label, value), where label is the string that will be displayed on the button and value is the string that will be stored in the flagging CSV; or it can be a list of strings ["X", "Y"], in which case the values will be the list of strings and the labels will ["Flag as X", "Flag as Y"], etc.
+ flagging_dir: path to the directory where flagged data is stored. If the directory does not exist, it will be created.
+ flagging_callback: either None or an instance of a subclass of FlaggingCallback which will be called when a sample is flagged. If set to None, an instance of gradio.flagging.CSVLogger will be created and logs will be saved to a local CSV file in flagging_dir. Default to None.
+ analytics_enabled: whether to allow basic telemetry. If None, will use GRADIO_ANALYTICS_ENABLED environment variable if defined, or default to True.
+ batch: if True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component.
+ max_batch_size: the maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True)
+ api_name: defines how the prediction endpoint appears in the API docs. Can be a string or None. If set to a string, the endpoint will be exposed in the API docs with the given name. If None, the name of the function will be used.
+ api_description: Description of the API endpoint. Can be a string, None, or False. If set to a string, the endpoint will be exposed in the API docs with the given description. If None, the function's docstring will be used as the API endpoint description. If False, then no description will be displayed in the API docs.
+ api_visibility: Controls the visibility of the prediction endpoint. Can be "public" (shown in API docs and callable), "private" (hidden from API docs and not callable by the Gradio client libraries), or "undocumented" (hidden from API docs but callable).
+ allow_duplication: if True, then will show a 'Duplicate Spaces' button on Hugging Face Spaces.
+ concurrency_limit: if set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `.queue()`, which itself is 1 by default).
+ additional_inputs: a single Gradio component, or list of Gradio components. Components can either be passed as instantiated objects, or referred to by their string shortcuts. These components will be rendered in an accordion below the main input components. By default, no additional input components will be displayed.
+ additional_inputs_accordion: if a string is provided, this is the label of the `gr.Accordion` to use to contain additional inputs. A `gr.Accordion` object can be provided as well to configure other properties of the container holding the additional inputs. Defaults to a `gr.Accordion(label="Additional Inputs", open=False)`. This parameter is only used if `additional_inputs` is provided.
+ submit_btn: the button to use for submitting inputs. Defaults to a `gr.Button("Submit", variant="primary")`. This parameter does not apply if the Interface is output-only, in which case the submit button always displays "Generate". Can be set to a string (which becomes the button label) or a `gr.Button` object (which allows for more customization).
+ stop_btn: the button to use for stopping the interface. Defaults to a `gr.Button("Stop", variant="stop", visible=False)`. Can be set to a string (which becomes the button label) or a `gr.Button` object (which allows for more customization).
+ clear_btn: the button to use for clearing the inputs. Defaults to a `gr.Button("Clear", variant="secondary")`. Can be set to a string (which becomes the button label) or a `gr.Button` object (which allows for more customization). Can be set to None, which hides the button.
+ delete_cache: a tuple corresponding [frequency, age] both expressed in number of seconds. Every `frequency` seconds, the temporary files created by this Blocks instance will be deleted if more than `age` seconds have passed since the file was created. For example, setting this to (86400, 86400) will delete temporary files every day. The cache will be deleted entirely when the server restarts. If None, no cache deletion will occur.
+ show_progress: how to show the progress animation while event is running: "full" shows a spinner which covers the output component area as well as a runtime display in the upper right corner, "minimal" only shows the runtime display, "hidden" shows no progress animation at all
+ example_labels: a list of labels for each example. If provided, the length of this list should be the same as the number of examples, and these labels will be used in the UI instead of rendering the example values.
+ fill_width: whether to horizontally expand to fill container fully. If False, centers and constrains app to a maximum width.
+ time_limit: The time limit for the stream to run. Default is 30 seconds. Parameter only used for streaming images or audio if the interface is live and the input components are set to "streaming=True".
+ stream_every: The latency (in seconds) at which stream chunks are sent to the backend. Defaults to 0.5 seconds. Parameter only used for streaming images or audio if the interface is live and the input components are set to "streaming=True".
+ deep_link: a string or `gr.DeepLinkButton` object that creates a unique URL you can use to share your app and all components **as they currently are** with others. Automatically enabled on Hugging Face Spaces unless explicitly set to False.
+ validator: a function that takes in the inputs and can optionally return a gr.validate() object for each input.
+
+ """
+ super().__init__(
+ analytics_enabled=analytics_enabled,
+ mode="interface",
+ title=title or "Gradio",
+ delete_cache=delete_cache,
+ fill_width=fill_width,
+ **kwargs,
+ )
+ if isinstance(deep_link, str):
+ deep_link = DeepLinkButton(value=deep_link, render=False, interactive=False)
+ elif deep_link is True:
+ deep_link = DeepLinkButton(render=False, interactive=False)
+ if utils.get_space() and deep_link is None:
+ deep_link = DeepLinkButton(render=False, interactive=False)
+ if deep_link is False:
+ deep_link = None
+ self.deep_link = deep_link
+ self.time_limit = time_limit
+ self.stream_every = stream_every
+ self.api_name: str | None = api_name
+ self.api_description: str | None | Literal[False] = api_description
+ self.api_visibility = api_visibility
+ self.interface_type = InterfaceTypes.STANDARD
+ if (inputs is None or inputs == []) and (outputs is None or outputs == []):
+ raise ValueError("Must provide at least one of `inputs` or `outputs`")
+ elif outputs is None or outputs == []:
+ outputs = []
+ self.interface_type = InterfaceTypes.INPUT_ONLY
+ elif inputs is None or inputs == []:
+ inputs = []
+ self.interface_type = InterfaceTypes.OUTPUT_ONLY
+ if additional_inputs is None:
+ self.additional_input_components = []
+ else:
+ if not isinstance(additional_inputs, Sequence):
+ additional_inputs = [additional_inputs]
+ self.additional_input_components = [
+ get_component_instance(i, unrender=True) # type: ignore
+ for i in additional_inputs # type: ignore
+ ]
+
+ if not isinstance(inputs, (Sequence, Component)):
+ raise TypeError(
+ f"inputs must be a string, list, or Component, not {inputs}"
+ )
+ if not isinstance(outputs, (Sequence, Component)):
+ raise TypeError(
+ f"outputs must be a string, list, or Component, not {outputs}"
+ )
+
+ if isinstance(inputs, (str, Component)):
+ inputs = [inputs]
+ if isinstance(outputs, (str, Component)):
+ outputs = [outputs]
+
+ self.cache_examples = cache_examples
+ self.cache_mode: Literal["eager", "lazy"] | None = cache_mode
+ self.preload_example = preload_example
+
+ self.main_input_components = [
+ get_component_instance(i, unrender=True) for i in inputs
+ ]
+ self.input_components = (
+ self.main_input_components + self.additional_input_components
+ )
+ self.output_components = [
+ get_component_instance(o, unrender=True)
+ for o in outputs # type: ignore
+ ]
+
+ state_input_indexes = [
+ idx
+ for idx, i in enumerate(self.input_components)
+ if i == "state" or isinstance(i, State)
+ ]
+ state_output_indexes = [
+ idx
+ for idx, o in enumerate(self.output_components)
+ if o == "state" or isinstance(o, State)
+ ]
+
+ if len(state_input_indexes) == 0 and len(state_output_indexes) == 0:
+ pass
+ elif len(state_input_indexes) != 1 or len(state_output_indexes) != 1:
+ raise ValueError(
+ "If using 'state', there must be exactly one state input and one state output."
+ )
+ else:
+ state_input_index = state_input_indexes[0]
+ state_output_index = state_output_indexes[0]
+ if self.input_components[state_input_index] == "state":
+ default = utils.get_default_args(fn)[state_input_index]
+ state_variable = State(value=default)
+ else:
+ state_variable = self.input_components[state_input_index]
+
+ self.input_components[state_input_index] = state_variable
+ self.output_components[state_output_index] = state_variable
+
+ if cache_examples:
+ warnings.warn(
+ "Cache examples cannot be used with state inputs and outputs. "
+ "Setting cache_examples to False."
+ )
+ self.cache_examples = False
+
+ if additional_inputs_accordion is None:
+ self.additional_inputs_accordion_params = {
+ "label": "Additional Inputs",
+ "open": False,
+ }
+ elif isinstance(additional_inputs_accordion, str):
+ self.additional_inputs_accordion_params = {
+ "label": additional_inputs_accordion
+ }
+ elif isinstance(additional_inputs_accordion, Accordion):
+ self.additional_inputs_accordion_params = (
+ additional_inputs_accordion.recover_kwargs(
+ additional_inputs_accordion.get_config()
+ )
+ )
+ else:
+ raise ValueError(
+ f"The `additional_inputs_accordion` parameter must be a string or gr.Accordion, not {type(additional_inputs_accordion)}"
+ )
+
+ for component in self.input_components + self.output_components:
+ if not (isinstance(component, Component)):
+ raise ValueError(
+ f"{component} is not a valid input/output component for Interface."
+ )
+
+ if len(self.input_components) == len(self.output_components):
+ same_components = [
+ i is o
+ for i, o in zip(
+ self.input_components, self.output_components, strict=False
+ )
+ ]
+ if all(same_components):
+ self.interface_type = InterfaceTypes.UNIFIED
+
+ if self.interface_type in [
+ InterfaceTypes.STANDARD,
+ InterfaceTypes.OUTPUT_ONLY,
+ ]:
+ for o in self.output_components:
+ if not isinstance(o, Component):
+ raise TypeError(
+ f"Output component must be a Component, not {type(o)}"
+ )
+ if o.interactive is None:
+ # Unless explicitly otherwise specified, force output components to
+ # be non-interactive
+ o.interactive = False
+
+ self.api_mode = _api_mode
+ self.fn = fn
+ self.validator = validator
+ self.fn_durations = [0, 0]
+ self.__name__ = getattr(fn, "__name__", "fn")
+ self.live = live
+ self.title = title
+
+ self.simple_description = utils.remove_html_tags(description)
+ self.description = description
+ if article is not None:
+ article = utils.download_if_url(article)
+ self.article = article
+
+ self.examples = examples
+ self.examples_per_page = examples_per_page
+ self.example_labels = example_labels
+
+ if isinstance(submit_btn, Button):
+ self.submit_btn_parms = submit_btn.recover_kwargs(submit_btn.get_config())
+ elif isinstance(submit_btn, str):
+ self.submit_btn_parms = {
+ "value": submit_btn,
+ "variant": "primary",
+ }
+ else:
+ raise ValueError(
+ f"The submit_btn parameter must be a gr.Button or string, not {type(submit_btn)}"
+ )
+
+ if isinstance(stop_btn, Button):
+ self.stop_btn_parms = stop_btn.recover_kwargs(stop_btn.get_config())
+ elif isinstance(stop_btn, str):
+ self.stop_btn_parms = {
+ "value": stop_btn,
+ "variant": "stop",
+ "visible": False,
+ }
+ else:
+ raise ValueError(
+ f"The stop_btn parameter must be a gr.Button or string, not {type(stop_btn)}"
+ )
+
+ if clear_btn is None:
+ self.clear_btn_params = {
+ "visible": False,
+ "variant": "secondary",
+ }
+ elif isinstance(clear_btn, Button):
+ self.clear_btn_params = clear_btn.recover_kwargs(clear_btn.get_config())
+ elif isinstance(clear_btn, str):
+ self.clear_btn_params = {
+ "value": clear_btn,
+ "variant": "secondary",
+ }
+ else:
+ raise ValueError(
+ f"The clear_btn parameter must be a gr.Button, a string, or None, not {type(clear_btn)}"
+ )
+
+ self.simple_server = None
+
+ # For flagging_mode: (1) first check for `flagging_mode` parameter,
+ # (2) check for env variable, (3) default to "manual"
+ if flagging_mode is None:
+ self.flagging_mode = os.getenv("GRADIO_FLAGGING_MODE", "manual")
+ elif flagging_mode in ["manual", "never", "auto"]:
+ self.flagging_mode = flagging_mode
+ else:
+ raise ValueError(
+ "Invalid value for `flagging_mode` parameter."
+ "Must be: 'auto', 'manual', or 'never'."
+ )
+
+ if flagging_options is None:
+ self.flagging_options = [("Flag", None)]
+ elif not (isinstance(flagging_options, list)):
+ raise ValueError(
+ "flagging_options must be a list of strings or list of (string, string) tuples."
+ )
+ elif all(isinstance(x, str) for x in flagging_options):
+ self.flagging_options = [(f"Flag as {x}", x) for x in flagging_options]
+ elif all(isinstance(x, tuple) for x in flagging_options):
+ self.flagging_options = flagging_options
+ else:
+ raise ValueError(
+ "flagging_options must be a list of strings or list of (string, string) tuples."
+ )
+
+ if flagging_callback is None:
+ flagging_callback = CSVLogger()
+
+ self.flagging_callback = flagging_callback
+ self.flagging_dir = flagging_dir
+ self.show_progress: Literal["full", "hidden", "minimal"] = show_progress
+
+ self.batch = batch
+ self.max_batch_size = max_batch_size
+ self.allow_duplication = allow_duplication
+ self.concurrency_limit: int | None | Literal["default"] = concurrency_limit
+
+ self.share = None
+ self.share_url = None
+ self.local_url = None
+
+ self.favicon_path = None
+ self.i18n_instance = None
+
+ param_types = utils.get_type_hints(self.fn)
+ # param_names = inspect.getfullargspec(self.fn)[0]
+ param_names = []
+ try:
+ param_names = inspect.getfullargspec(self.fn)[0]
+ if len(param_names) > 0 and inspect.ismethod(self.fn):
+ param_names = param_names[1:]
+ for param_name in param_names.copy():
+ if utils.is_special_typed_parameter(param_name, param_types):
+ param_names.remove(param_name)
+ except (TypeError, ValueError):
+ param_names = utils.default_input_labels()
+ for component, param_name in zip(
+ self.input_components, param_names, strict=False
+ ):
+ if not isinstance(component, Component):
+ raise TypeError(
+ f"Input component must be a Component, not {type(component)}"
+ )
+ if component.label is None:
+ component.label = param_name
+ for i, component in enumerate(self.output_components):
+ if not isinstance(component, Component):
+ raise TypeError(
+ f"Output component must be a Component, not {type(component)}"
+ )
+ if component.label is None:
+ if len(self.output_components) == 1:
+ component.label = "output"
+ else:
+ component.label = f"output {i}"
+
+ if self.flagging_mode != "never":
+ if self.interface_type == InterfaceTypes.UNIFIED:
+ self.flagging_callback.setup(self.input_components, self.flagging_dir) # type: ignore
+ elif self.interface_type == InterfaceTypes.INPUT_ONLY:
+ pass
+ else:
+ self.flagging_callback.setup(
+ self.input_components + self.output_components,
+ self.flagging_dir, # type: ignore
+ )
+
+ # Render the Gradio UI
+ with self:
+ if self.deep_link:
+ self.deep_link.activate()
+ self.render_title_description()
+
+ _submit_btn, _clear_btn, _stop_btn, flag_btns, duplicate_btn = (
+ None,
+ None,
+ None,
+ None,
+ None,
+ ) # type: ignore
+ input_component_column = None
+
+ with Row():
+ if self.interface_type in [
+ InterfaceTypes.STANDARD,
+ InterfaceTypes.INPUT_ONLY,
+ InterfaceTypes.UNIFIED,
+ ]:
+ (
+ _submit_btn,
+ _clear_btn,
+ _stop_btn,
+ flag_btns,
+ input_component_column,
+ ) = self.render_input_column() # type: ignore
+ if self.interface_type in [
+ InterfaceTypes.STANDARD,
+ InterfaceTypes.OUTPUT_ONLY,
+ ]:
+ (
+ _submit_btn_out,
+ _clear_btn_2_out,
+ duplicate_btn,
+ _stop_btn_2_out,
+ flag_btns_out,
+ ) = self.render_output_column(_submit_btn)
+ _submit_btn = _submit_btn or _submit_btn_out
+ _clear_btn = _clear_btn or _clear_btn_2_out
+ _stop_btn = _stop_btn or _stop_btn_2_out
+ flag_btns = flag_btns or flag_btns_out
+
+ if _clear_btn is None:
+ raise RenderError("Clear button not rendered")
+
+ _submit_event = self.attach_submit_events(_submit_btn, _stop_btn)
+ self.attach_clear_events(_clear_btn, input_component_column)
+ if duplicate_btn is not None:
+ duplicate_btn.activate()
+
+ self.attach_flagging_events(flag_btns, _clear_btn, _submit_event)
+ if _submit_event and self.deep_link:
+ _submit_event.then(
+ lambda: DeepLinkButton(interactive=True),
+ inputs=None,
+ outputs=[self.deep_link],
+ js=True,
+ api_visibility="undocumented",
+ )
+ self.render_examples()
+ self.render_article()
+
+ self.config = self.get_config_file()
+
+ def render_title_description(self) -> None:
+ if self.title:
+ Markdown(
+ f"{self.title}
"
+ )
+ if self.description:
+ Markdown(self.description)
+
+ def render_flag_btns(self) -> list[Button]:
+ return [Button(label) for label, _ in self.flagging_options]
+
+ def render_input_column(
+ self,
+ ) -> tuple[
+ Button | None,
+ ClearButton | None,
+ Button | None,
+ list[Button] | None,
+ Column,
+ ]:
+ _submit_btn, _clear_btn, _stop_btn, flag_btns = None, None, None, None
+
+ with Column():
+ input_component_column = Column()
+ with input_component_column:
+ for component in self.main_input_components:
+ component.render()
+ if self.additional_input_components:
+ with Accordion(**self.additional_inputs_accordion_params): # type: ignore
+ for component in self.additional_input_components:
+ component.render()
+ with Row():
+ if self.interface_type in [
+ InterfaceTypes.STANDARD,
+ InterfaceTypes.INPUT_ONLY,
+ ]:
+ _clear_btn = ClearButton(**self.clear_btn_params) # type: ignore
+ if not self.live:
+ if (
+ self.deep_link
+ and self.interface_type == InterfaceTypes.INPUT_ONLY
+ ):
+ self.deep_link.render()
+ _submit_btn = Button(**self.submit_btn_parms) # type: ignore
+ # Stopping jobs only works if the queue is enabled
+ # We don't know if the queue is enabled when the interface
+ # is created. We use whether a generator function is provided
+ # as a proxy of whether the queue will be enabled.
+ # Using a generator function without the queue will raise an error.
+ if inspect.isgeneratorfunction(
+ self.fn
+ ) or inspect.isasyncgenfunction(self.fn):
+ _stop_btn = Button(**self.stop_btn_parms) # type: ignore # type: ignore
+ elif self.interface_type == InterfaceTypes.UNIFIED:
+ _clear_btn = ClearButton(**self.clear_btn_params) # type: ignore
+ _submit_btn = Button(**self.submit_btn_parms) # type: ignore
+ if self.deep_link:
+ self.deep_link.render()
+ if (
+ inspect.isgeneratorfunction(self.fn)
+ or inspect.isasyncgenfunction(self.fn)
+ ) and not self.live:
+ _stop_btn = Button(**self.stop_btn_parms) # type: ignore
+ if self.flagging_mode == "manual":
+ flag_btns = self.render_flag_btns()
+ elif self.flagging_mode == "auto":
+ flag_btns = [_submit_btn]
+ return (
+ _submit_btn,
+ _clear_btn,
+ _stop_btn,
+ flag_btns,
+ input_component_column,
+ )
+
+ def render_output_column(
+ self,
+ _submit_btn_in: Button | None,
+ ) -> tuple[
+ Button | None,
+ ClearButton | None,
+ DuplicateButton | None,
+ Button | None,
+ list | None,
+ ]:
+ _submit_btn = _submit_btn_in
+ _clear_btn, duplicate_btn, flag_btns, _stop_btn = (
+ None,
+ None,
+ None,
+ None,
+ )
+
+ with Column():
+ for component in self.output_components: # type: ignore
+ if not (isinstance(component, State)):
+ component.render()
+ with Row():
+ if self.deep_link:
+ self.deep_link.render()
+ if self.interface_type == InterfaceTypes.OUTPUT_ONLY:
+ _clear_btn = ClearButton(**self.clear_btn_params) # type: ignore
+ _submit_btn = Button("Generate", variant="primary")
+ if (
+ inspect.isgeneratorfunction(self.fn)
+ or inspect.isasyncgenfunction(self.fn)
+ ) and not self.live:
+ # Stopping jobs only works if the queue is enabled
+ # We don't know if the queue is enabled when the interface
+ # is created. We use whether a generator function is provided
+ # as a proxy of whether the queue will be enabled.
+ # Using a generator function without the queue will raise an error.
+ _stop_btn = Button(**self.stop_btn_parms) # type: ignore
+ if self.flagging_mode == "manual":
+ flag_btns = self.render_flag_btns()
+ elif self.flagging_mode == "auto":
+ if _submit_btn is None:
+ raise RenderError("Submit button not rendered")
+ flag_btns = [_submit_btn]
+
+ if self.allow_duplication:
+ duplicate_btn = DuplicateButton(scale=1, size="lg", _activate=False)
+
+ return (
+ _submit_btn,
+ _clear_btn,
+ duplicate_btn,
+ _stop_btn,
+ flag_btns,
+ )
+
+ def render_article(self):
+ if self.article:
+ Markdown(self.article)
+
+ def attach_submit_events(
+ self, _submit_btn: Button | None, _stop_btn: Button | None
+ ) -> Dependency:
+ if self.live:
+ if self.interface_type == InterfaceTypes.OUTPUT_ONLY:
+ if _submit_btn is None:
+ raise RenderError("Submit button not rendered")
+ super().load(self.fn, None, self.output_components)
+ # For output-only interfaces, the user probably still want a "generate"
+ # button even if the Interface is live
+ return _submit_btn.click(
+ self.fn,
+ None,
+ self.output_components,
+ api_name=self.api_name,
+ api_visibility=self.api_visibility,
+ api_description=self.api_description,
+ preprocess=not (self.api_mode),
+ postprocess=not (self.api_mode),
+ batch=self.batch,
+ max_batch_size=self.max_batch_size,
+ validator=self.validator,
+ )
+ else:
+ events: list[Callable] = []
+ streaming_event = False
+ for component in self.input_components: # type: ignore
+ if component.has_event("stream") and component.streaming: # type: ignore
+ events.append(component.stream) # type: ignore
+ streaming_event = True
+ elif component.has_event("change"):
+ events.append(component.change) # type: ignore
+ return on(
+ events,
+ self.fn,
+ self.input_components,
+ self.output_components,
+ api_name=self.api_name,
+ api_description=self.api_description,
+ api_visibility=self.api_visibility,
+ preprocess=not (self.api_mode),
+ postprocess=not (self.api_mode),
+ show_progress="hidden" if streaming_event else self.show_progress,
+ trigger_mode="always_last" if not streaming_event else "multiple",
+ time_limit=self.time_limit,
+ stream_every=self.stream_every,
+ validator=self.validator,
+ )
+ else:
+ if _submit_btn is None:
+ raise RenderError("Submit button not rendered")
+ fn = self.fn
+ extra_output = []
+
+ triggers = [_submit_btn.click] + [
+ component.submit # type: ignore
+ for component in self.input_components # type: ignore
+ if component.has_event(Events.submit)
+ ]
+
+ for component in self.input_components: # type: ignore
+ if getattr(component, "streaming", None):
+ warnings.warn(
+ "Streaming components are only supported in live interfaces."
+ )
+
+ if _stop_btn:
+ extra_output = [_submit_btn, _stop_btn]
+
+ async def cleanup():
+ return [Button(visible=True), Button(visible=False)]
+
+ predict_event = on(
+ triggers,
+ utils.async_lambda(
+ lambda: (
+ Button(visible=False),
+ Button(visible=True),
+ )
+ ),
+ inputs=None,
+ outputs=[_submit_btn, _stop_btn],
+ queue=False,
+ api_visibility="undocumented",
+ ).then(
+ self.fn,
+ self.input_components,
+ self.output_components,
+ api_name=self.api_name,
+ api_visibility=self.api_visibility,
+ api_description=self.api_description,
+ scroll_to_output=True,
+ preprocess=not (self.api_mode),
+ postprocess=not (self.api_mode),
+ batch=self.batch,
+ max_batch_size=self.max_batch_size,
+ concurrency_limit=self.concurrency_limit,
+ show_progress=self.show_progress,
+ validator=self.validator,
+ )
+
+ final_event = predict_event.then(
+ cleanup,
+ inputs=None,
+ outputs=extra_output, # type: ignore
+ queue=False,
+ api_visibility="undocumented",
+ )
+
+ _stop_btn.click(
+ cleanup,
+ inputs=None,
+ outputs=[_submit_btn, _stop_btn],
+ cancels=predict_event,
+ queue=False,
+ api_visibility="undocumented",
+ )
+ return final_event
+ else:
+ return on(
+ triggers,
+ fn,
+ self.input_components,
+ self.output_components,
+ api_name=self.api_name,
+ api_visibility=self.api_visibility,
+ api_description=self.api_description,
+ scroll_to_output=True,
+ preprocess=not (self.api_mode),
+ postprocess=not (self.api_mode),
+ batch=self.batch,
+ max_batch_size=self.max_batch_size,
+ concurrency_limit=self.concurrency_limit,
+ show_progress=self.show_progress,
+ validator=self.validator,
+ )
+
+ def attach_clear_events(
+ self,
+ _clear_btn: ClearButton,
+ input_component_column: Column | None,
+ ):
+ _clear_btn.add(self.input_components + self.output_components) # type: ignore
+ _clear_btn.click(
+ None,
+ [],
+ ([input_component_column] if input_component_column else []), # type: ignore
+ js=f"""() => {
+ json.dumps(
+ [{"variant": None, "visible": True, "__type__": "update"}]
+ if self.interface_type
+ in [
+ InterfaceTypes.STANDARD,
+ InterfaceTypes.INPUT_ONLY,
+ InterfaceTypes.UNIFIED,
+ ]
+ else []
+ )
+ }
+ """,
+ )
+
+ def attach_flagging_events(
+ self,
+ flag_btns: list[Button] | None,
+ _clear_btn: ClearButton,
+ _submit_event: Dependency,
+ ):
+ if not (
+ flag_btns
+ and self.interface_type
+ in (
+ InterfaceTypes.STANDARD,
+ InterfaceTypes.OUTPUT_ONLY,
+ InterfaceTypes.UNIFIED,
+ )
+ ):
+ return
+
+ if self.flagging_mode == "auto":
+ flag_method = FlagMethod(
+ self.flagging_callback, "", None, visual_feedback=False
+ )
+ _submit_event.success(
+ flag_method,
+ inputs=self.input_components + self.output_components, # type: ignore
+ outputs=None,
+ preprocess=False,
+ queue=False,
+ api_visibility="undocumented",
+ )
+ return
+
+ if self.interface_type == InterfaceTypes.UNIFIED:
+ flag_components = self.input_components # type: ignore
+ else:
+ flag_components = self.input_components + self.output_components # type: ignore
+
+ for flag_btn, (label, value) in zip(
+ flag_btns, self.flagging_options, strict=False
+ ):
+ if value is not None and not isinstance(value, str):
+ raise TypeError(
+ f"Flagging option value must be a string, not {value!r}"
+ )
+ flag_method = FlagMethod(self.flagging_callback, label, value)
+ flag_btn.click(
+ utils.async_lambda(
+ lambda: Button(value="Saving...", interactive=False)
+ ),
+ None,
+ flag_btn,
+ queue=False,
+ api_visibility="undocumented",
+ )
+ flag_btn.click(
+ flag_method,
+ inputs=flag_components,
+ outputs=flag_btn,
+ preprocess=False,
+ queue=False,
+ api_visibility="undocumented",
+ )
+ _clear_btn.click(
+ utils.async_lambda(flag_method.reset),
+ None,
+ flag_btn,
+ queue=False,
+ api_visibility="undocumented",
+ )
+
+ def render_examples(self):
+ if self.examples:
+ non_state_inputs = [
+ c
+ for c in self.input_components # type: ignore
+ if not isinstance(c, State) # type: ignore
+ ]
+ non_state_outputs = [
+ c
+ for c in self.output_components # type: ignore
+ if not isinstance(c, State) # type: ignore
+ ]
+ self.examples_handler = Examples(
+ examples=self.examples,
+ inputs=non_state_inputs,
+ outputs=non_state_outputs,
+ fn=self.fn,
+ cache_examples=self.cache_examples,
+ cache_mode=self.cache_mode,
+ examples_per_page=self.examples_per_page,
+ _api_mode=self.api_mode or False, # type: ignore
+ batch=self.batch,
+ example_labels=self.example_labels,
+ preload=self.preload_example,
+ )
+ if self.deep_link and self.examples_handler.cache_event:
+ self.examples_handler.cache_event.then(
+ lambda: DeepLinkButton(interactive=True),
+ inputs=None,
+ outputs=[self.deep_link],
+ js=True,
+ api_visibility="undocumented",
+ )
+
+ def __str__(self):
+ return self.__repr__()
+
+ def __repr__(self):
+ repr = f"Gradio Interface for: {self.__name__}"
+ repr += f"\n{'-' * len(repr)}"
+ repr += "\ninputs:"
+ for component in self.input_components: # type: ignore
+ repr += f"\n|-{component}"
+ repr += "\noutputs:"
+ for component in self.output_components: # type: ignore
+ repr += f"\n|-{component}"
+ return repr
+
+
+@document()
+class TabbedInterface(Blocks):
+ """
+ A TabbedInterface is created by providing a list of Interfaces or Blocks, each of which gets
+ rendered in a separate tab. Only the components from the Interface/Blocks will be rendered in the tab.
+
+ Demos: tabbed_interface_lite
+ """
+
+ def __init__(
+ self,
+ interface_list: Sequence[Blocks],
+ tab_names: list[str] | None = None,
+ title: str | None = None,
+ analytics_enabled: bool | None = None,
+ ):
+ """
+ Parameters:
+ interface_list: A list of Interfaces (or Blocks) to be rendered in the tabs.
+ tab_names: A list of tab names. If None, the tab names will be "Tab 1", "Tab 2", etc.
+ title: The tab title to display when this demo is opened in a browser window.
+ analytics_enabled: Whether to allow basic telemetry. If None, will use GRADIO_ANALYTICS_ENABLED environment variable or default to True.
+ Returns:
+ a Gradio Tabbed Interface for the given interfaces
+ """
+ super().__init__(
+ title=title or "Gradio",
+ analytics_enabled=analytics_enabled,
+ mode="tabbed_interface",
+ fill_height=True,
+ )
+ if tab_names is None:
+ tab_names = [f"Tab {i}" for i in range(len(interface_list))]
+ with self:
+ if title:
+ Markdown(
+ f"{title}
"
+ )
+ with Tabs():
+ for interface, tab_name in zip(interface_list, tab_names, strict=False):
+ with Tab(
+ label=tab_name,
+ scale=1 if interface.fill_height else None,
+ ):
+ interface.render()
+
+
+def close_all(verbose: bool = True) -> None:
+ for instance in Blocks.get_instances():
+ if instance.is_running:
+ instance.close(verbose)
diff --git a/gradio/ipython_ext.py b/gradio/ipython_ext.py
new file mode 100644
index 0000000..0c37bbc
--- /dev/null
+++ b/gradio/ipython_ext.py
@@ -0,0 +1,89 @@
+try:
+ from IPython.core.magic import ( # ty: ignore[unresolved-import]
+ needs_local_scope,
+ register_cell_magic,
+ )
+ from IPython.core.magic_arguments import ( # ty: ignore[unresolved-import]
+ argument,
+ magic_arguments,
+ parse_argstring,
+ )
+except ImportError:
+ pass
+
+import gradio as gr
+from gradio.routes import App
+from gradio.utils import BaseReloader
+
+
+class CellIdTracker:
+ """Determines the most recently run cell in the notebook.
+
+ Needed to keep track of which demo the user is updating.
+ """
+
+ def __init__(self, ipython):
+ ipython.events.register("pre_run_cell", self.pre_run_cell)
+ self.shell = ipython
+ self.current_cell: str = ""
+
+ def pre_run_cell(self, info):
+ self._current_cell = info.cell_id
+
+
+class JupyterReloader(BaseReloader):
+ """Swap a running blocks class in a notebook with the latest cell contents."""
+
+ def __init__(self, ipython) -> None:
+ super().__init__()
+ self._cell_tracker = CellIdTracker(ipython)
+ self._running: dict[str, gr.Blocks] = {}
+
+ @property
+ def current_cell(self):
+ return self._cell_tracker.current_cell
+
+ @property
+ def running_app(self) -> App:
+ if not self.running_demo.server:
+ raise RuntimeError("Server not running")
+ return self.running_demo.server.running_app # type: ignore
+
+ @property
+ def running_demo(self):
+ return self._running[self.current_cell]
+
+ def demo_tracked(self) -> bool:
+ return self.current_cell in self._running
+
+ def track(self, demo: gr.Blocks):
+ self._running[self.current_cell] = demo
+
+
+def load_ipython_extension(ipython):
+ reloader = JupyterReloader(ipython)
+
+ @magic_arguments() # type: ignore
+ @argument("--demo-name", default="demo", help="Name of gradio blocks instance.") # type: ignore
+ @argument( # type: ignore
+ "--share",
+ default=False,
+ const=True,
+ nargs="?",
+ help="Whether to launch with sharing. Will slow down reloading.",
+ )
+ @register_cell_magic # type: ignore
+ @needs_local_scope # type: ignore
+ def blocks(line, cell, local_ns):
+ """Launch a demo defined in a cell in reload mode."""
+
+ args = parse_argstring(blocks, line) # type: ignore
+
+ exec(cell, None, local_ns)
+ demo: gr.Blocks = local_ns[args.demo_name]
+ if not reloader.demo_tracked():
+ demo.launch(share=args.share)
+ reloader.track(demo)
+ else:
+ reloader.swap_blocks(demo)
+ return reloader.running_demo.artifact
diff --git a/gradio/layouts/__init__.py b/gradio/layouts/__init__.py
new file mode 100644
index 0000000..0d63199
--- /dev/null
+++ b/gradio/layouts/__init__.py
@@ -0,0 +1,24 @@
+from .accordion import Accordion
+from .column import Column
+from .draggable import Draggable
+from .form import Form
+from .group import Group
+from .row import Row
+from .sidebar import Sidebar
+from .tabs import Tab, TabItem, Tabs
+from .walkthrough import Step, Walkthrough
+
+__all__ = [
+ "Accordion",
+ "Column",
+ "Draggable",
+ "Form",
+ "Row",
+ "Group",
+ "Tabs",
+ "Tab",
+ "TabItem",
+ "Sidebar",
+ "Walkthrough",
+ "Step",
+]
diff --git a/gradio/layouts/accordion.py b/gradio/layouts/accordion.py
new file mode 100644
index 0000000..429b7be
--- /dev/null
+++ b/gradio/layouts/accordion.py
@@ -0,0 +1,60 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Literal
+
+from gradio_client.documentation import document
+
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+from gradio.events import Events
+from gradio.i18n import I18nData
+
+if TYPE_CHECKING:
+ pass
+
+
+@document()
+class Accordion(BlockContext, metaclass=ComponentMeta):
+ """
+ Accordion is a layout element which can be toggled to show/hide the contained content.
+ Example:
+ with gr.Accordion("See Details"):
+ gr.Markdown("lorem ipsum")
+ Guides: controlling-layout
+ """
+
+ EVENTS = [Events.expand, Events.collapse]
+
+ def __init__(
+ self,
+ label: str | I18nData | None = None,
+ *,
+ open: bool = True,
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ label: name of accordion section.
+ open: if True, accordion is open by default.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ self.label = label
+ self.open = open
+ BlockContext.__init__(
+ self,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
diff --git a/gradio/layouts/column.py b/gradio/layouts/column.py
new file mode 100644
index 0000000..6880069
--- /dev/null
+++ b/gradio/layouts/column.py
@@ -0,0 +1,77 @@
+from __future__ import annotations
+
+import warnings
+from typing import Literal
+
+from gradio_client.documentation import document
+
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+
+
+@document()
+class Column(BlockContext, metaclass=ComponentMeta):
+ """
+ Column is a layout element within Blocks that renders all children vertically. The widths of columns can be set through the `scale` and `min_width` parameters.
+ If a certain scale results in a column narrower than min_width, the min_width parameter will win.
+ Example:
+ with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column(scale=1):
+ text1 = gr.Textbox()
+ text2 = gr.Textbox()
+ with gr.Column(scale=4):
+ btn1 = gr.Button("Button 1")
+ btn2 = gr.Button("Button 2")
+ Guides: controlling-layout
+ """
+
+ EVENTS = []
+
+ def __init__(
+ self,
+ *,
+ scale: int = 1,
+ min_width: int = 320,
+ variant: Literal["default", "panel", "compact"] = "default",
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ show_progress: bool = False,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ scale: relative width compared to adjacent Columns. For example, if Column A has scale=2, and Column B has scale=1, A will be twice as wide as B.
+ min_width: minimum pixel width of Column, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in a column narrower than min_width, the min_width parameter will be respected first.
+ variant: column type, 'default' (no background), 'panel' (gray background color and rounded corners), or 'compact' (rounded corners and no internal gap).
+ visible: If False, column will be hidden.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, component will not render be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ show_progress: If True, shows progress animation when being updated.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ if scale != round(scale):
+ warnings.warn(
+ f"'scale' value should be an integer. Using {scale} will cause issues."
+ )
+
+ self.scale = scale
+ self.min_width = min_width
+ self.variant = variant
+ if variant == "compact":
+ self.allow_expected_parents = False
+ self.show_progress = show_progress
+ BlockContext.__init__(
+ self,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
diff --git a/gradio/layouts/draggable.py b/gradio/layouts/draggable.py
new file mode 100644
index 0000000..afde12c
--- /dev/null
+++ b/gradio/layouts/draggable.py
@@ -0,0 +1,53 @@
+from __future__ import annotations
+
+from typing import Literal
+
+from gradio_client.documentation import document
+
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+
+
+@document()
+class Draggable(BlockContext, metaclass=ComponentMeta):
+ """
+ Draggable is a layout element within Blocks that renders children with drag and drop functionality.
+ A user can reorder children by dragging them around and snapping them into place. If a child is a
+ layout (e.g. gr.Row, gr.Group), all the components in the child layout will drag together.
+
+ Demos: draggable_dashboard
+ """
+
+ EVENTS = []
+
+ def __init__(
+ self,
+ *,
+ orientation: Literal["row", "column"] = "column",
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ orientation: The direction in which children are arranged. 'row' arranges children horizontally, 'column' arranges them vertically.
+ visible: If False, draggable container will be hidden.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ self.orientation = orientation
+
+ super().__init__(
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
diff --git a/gradio/layouts/form.py b/gradio/layouts/form.py
new file mode 100644
index 0000000..f7b9683
--- /dev/null
+++ b/gradio/layouts/form.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from gradio.blocks import BlockContext, Blocks
+from gradio.component_meta import ComponentMeta
+from gradio.layouts.column import Column
+from gradio.layouts.row import Row
+
+if TYPE_CHECKING:
+ from gradio.blocks import Block
+
+
+class Form(BlockContext, metaclass=ComponentMeta):
+ EVENTS = []
+
+ def __init__(
+ self,
+ *,
+ scale: int = 0,
+ min_width: int = 0,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ scale: relative width compared to adjacent Columns. For example, if Column A has scale=2, and Column B has scale=1, A will be twice as wide as B.
+ min_width: minimum pixel width of Column, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in a column narrower than min_width, the min_width parameter will be respected first.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ self.scale = scale
+ self.min_width = min_width
+ BlockContext.__init__(
+ self,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+
+ def add_child(self, child: Block):
+ if isinstance(self.parent, Row):
+ scale = getattr(child, "scale", None)
+ self.scale += 1 if scale is None else scale
+ self.min_width += getattr(child, "min_width", 0) or 0
+ elif (
+ isinstance(self.parent, Column)
+ and isinstance(self.parent.parent, Row)
+ and self.parent.parent.equal_height
+ ):
+ scale = getattr(child, "scale", None)
+ self.scale += 1 if scale is None else scale
+ elif isinstance(self.parent, Blocks) and self.parent.fill_height:
+ scale = getattr(child, "scale", None)
+ self.scale += 0 if scale is None else scale
+ BlockContext.add_child(self, child)
diff --git a/gradio/layouts/group.py b/gradio/layouts/group.py
new file mode 100644
index 0000000..7a72aaa
--- /dev/null
+++ b/gradio/layouts/group.py
@@ -0,0 +1,51 @@
+from __future__ import annotations
+
+from typing import Literal
+
+from gradio_client.documentation import document
+
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+
+
+@document()
+class Group(BlockContext, metaclass=ComponentMeta):
+ """
+ Group is a layout element within Blocks which groups together children so that
+ they do not have any padding or margin between them.
+ Example:
+ with gr.Group():
+ gr.Textbox(label="First")
+ gr.Textbox(label="Last")
+ """
+
+ EVENTS = []
+
+ def __init__(
+ self,
+ *,
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ visible: If False, group will be hidden.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ BlockContext.__init__(
+ self,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
diff --git a/gradio/layouts/row.py b/gradio/layouts/row.py
new file mode 100644
index 0000000..34ae76a
--- /dev/null
+++ b/gradio/layouts/row.py
@@ -0,0 +1,92 @@
+from __future__ import annotations
+
+import warnings
+from typing import Literal
+
+from gradio_client.documentation import document
+
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+
+
+@document()
+class Row(BlockContext, metaclass=ComponentMeta):
+ """
+ Row is a layout element within Blocks that renders all children horizontally.
+ Example:
+ with gr.Blocks() as demo:
+ with gr.Row():
+ gr.Image("lion.jpg", scale=2)
+ gr.Image("tiger.jpg", scale=1)
+ demo.launch()
+ Guides: controlling-layout
+ """
+
+ EVENTS = []
+
+ def __init__(
+ self,
+ *,
+ variant: Literal["default", "panel", "compact"] = "default",
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ scale: int | None = None,
+ render: bool = True,
+ height: int | str | None = None,
+ max_height: int | str | None = None,
+ min_height: int | str | None = None,
+ equal_height: bool = False,
+ show_progress: bool = False,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ variant: row type, 'default' (no background), 'panel' (gray background color and rounded corners), or 'compact' (rounded corners and no internal gap).
+ visible: If False, row will be hidden.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ scale: relative height compared to adjacent elements. 1 or greater indicates the Row will expand in height, and any child columns will also expand to fill the height.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ height: The height of the row, specified in pixels if a number is passed, or in CSS units if a string is passed. If content exceeds the height, the row will scroll vertically. If not set, the row will expand to fit the content.
+ max_height: The maximum height of the row, specified in pixels if a number is passed, or in CSS units if a string is passed. If content exceeds the height, the row will scroll vertically. If content is shorter than the height, the row will shrink to fit the content. Will not have any effect if `height` is set and is smaller than `max_height`.
+ min_height: The minimum height of the row, specified in pixels if a number is passed, or in CSS units if a string is passed. If content exceeds the height, the row will expand to fit the content. Will not have any effect if `height` is set and is larger than `min_height`.
+ equal_height: If True, makes every child element have equal height
+ show_progress: If True, shows progress animation when being updated.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ self.variant = variant
+ self.equal_height = equal_height
+ if variant == "compact":
+ self.allow_expected_parents = False
+ self.show_progress = show_progress
+ self.height = height
+ self.max_height = max_height
+ self.min_height = min_height
+ if scale and scale != round(scale):
+ warnings.warn(
+ f"'scale' value should be an integer. Using {scale} will cause issues."
+ )
+
+ self.scale = scale
+
+ BlockContext.__init__(
+ self,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+
+ @staticmethod
+ def update(
+ visible: bool | None = None,
+ ):
+ return {
+ "visible": visible,
+ "__type__": "update",
+ }
diff --git a/gradio/layouts/sidebar.py b/gradio/layouts/sidebar.py
new file mode 100644
index 0000000..349f13d
--- /dev/null
+++ b/gradio/layouts/sidebar.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+from typing import Literal
+
+from gradio_client.documentation import document
+
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+from gradio.events import Events
+from gradio.i18n import I18nData
+
+
+@document()
+class Sidebar(BlockContext, metaclass=ComponentMeta):
+ """
+ Sidebar is a collapsible panel that renders child components on the left side of the screen within a Blocks layout.
+ Example:
+ with gr.Blocks() as demo:
+ with gr.Sidebar():
+ gr.Textbox()
+ gr.Button()
+ Guides: controlling-layout
+ """
+
+ EVENTS = [Events.expand, Events.collapse]
+
+ def __init__(
+ self,
+ label: str | I18nData | None = None,
+ *,
+ open: bool = True,
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ width: int | str = 320,
+ position: Literal["left", "right"] = "left",
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ label: name of the sidebar. Not displayed to the user.
+ open: if True, sidebar is open by default.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ width: The width of the sidebar, specified in pixels if a number is passed, or in CSS units if a string is passed.
+ position: The position of the sidebar in the layout, either "left" or "right". Defaults to "left".
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ self.label = label
+ self.open = open
+ self.width = width
+ self.position = position
+ BlockContext.__init__(
+ self,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
diff --git a/gradio/layouts/tabs.py b/gradio/layouts/tabs.py
new file mode 100644
index 0000000..834469d
--- /dev/null
+++ b/gradio/layouts/tabs.py
@@ -0,0 +1,160 @@
+from __future__ import annotations
+
+import warnings
+from typing import Literal
+
+from gradio_client.documentation import document
+
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+from gradio.events import EventListener, Events
+from gradio.i18n import I18nData
+
+
+class Tabs(BlockContext, metaclass=ComponentMeta):
+ """
+ Tabs is a layout element within Blocks that can contain multiple "Tab" Components.
+ Guides: controlling-layout
+ """
+
+ EVENTS = [Events.change, Events.select]
+
+ def __init__(
+ self,
+ *,
+ selected: int | str | None = None,
+ visible: bool | Literal["hidden"] = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ selected: The currently selected tab. Must correspond to an id passed to the one of the child TabItems. Defaults to the first TabItem.
+ visible: If False, Tabs will be hidden.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ BlockContext.__init__(
+ self,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+ self.selected = selected
+
+ def __exit__(self, exc_type=None, *args):
+ super().__exit__(exc_type, *args)
+ if exc_type is not None:
+ return
+ for child in self.children:
+ if isinstance(child, Tab):
+ continue
+ # Invisible utility components (gr.State, gr.BrowserState, gr.Timer, ...)
+ # have no DOM and never cause the IntersectionObserver crash this
+ # validation is guarding against. They are conventionally marked by
+ # `breaks_grouping() == False`, the same flag fill_expected_parents
+ # already uses to allow them to live anywhere in the tree.
+ if not child.breaks_grouping():
+ continue
+ # Surface the component(s) the user actually wrote rather than a
+ # gradio-generated auto-wrap (e.g. gr.Form grouping consecutive
+ # FormComponents into a single wrapper with multiple children).
+ if (
+ isinstance(child, BlockContext)
+ and not getattr(child, "is_rendered", True)
+ and child.children
+ ):
+ names = ", ".join(f"gr.{type(c).__name__}()" for c in child.children)
+ else:
+ names = f"gr.{type(child).__name__}()"
+ warnings.warn(
+ f"gr.Tabs() can only contain gr.Tab() (or gr.TabItem()) components as direct children, "
+ f"but received {names}. Wrap inside a gr.Tab(...) block to fix this.",
+ UserWarning,
+ stacklevel=2,
+ )
+ return
+
+
+@document()
+class Tab(BlockContext, metaclass=ComponentMeta):
+ """
+ Tab (or its alias TabItem) is a layout element. Components defined within the Tab will be visible when this tab is selected tab.
+ Example:
+ with gr.Blocks() as demo:
+ with gr.Tab("Lion"):
+ gr.Image("lion.jpg")
+ gr.Button("New Lion")
+ with gr.Tab("Tiger"):
+ gr.Image("tiger.jpg")
+ gr.Button("New Tiger")
+ Guides: controlling-layout
+ """
+
+ EVENTS = [
+ EventListener(
+ "select",
+ callback=lambda block: setattr(block, "_selectable", True),
+ doc="Event listener for when the user selects the Tab. Uses event data gradio.SelectData to carry `value` referring to the label of the Tab, and `selected` to refer to state of the Tab. See https://www.gradio.app/main/docs/gradio/eventdata documentation for more details.",
+ )
+ ]
+
+ def __init__(
+ self,
+ label: str | I18nData | None = None,
+ visible: bool | Literal["hidden"] = True,
+ interactive: bool = True,
+ *,
+ id: int | str | None = None,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ scale: int | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ render_children: bool = False,
+ ):
+ """
+ Parameters:
+ label: The visual label for the tab
+ id: An optional identifier for the tab, required if you wish to control the selected tab from a predict function.
+ elem_id: An optional string that is assigned as the id of the containing the contents of the Tab layout. The same string followed by "-button" is attached to the Tab button. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ scale: relative size compared to adjacent elements. 1 or greater indicates the Tab will expand in size.
+ visible: If False, Tab will be hidden.
+ interactive: If False, Tab will not be clickable.
+ render_children: If True, the children of this Tab will be rendered on the page (but hidden) when the Tab is visible but inactive. This can be useful if you want to ensure that any components (e.g. videos or audio) within the Tab are pre-loaded before the user clicks on the Tab.
+ """
+ BlockContext.__init__(
+ self,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+ self.label = label
+ self.id = id
+ self.visible = visible
+ self.scale = scale
+ self.interactive = interactive
+ self.render_children = render_children
+
+ def get_expected_parent(self) -> type[Tabs]:
+ return Tabs
+
+ def get_block_name(self):
+ return "tabitem"
+
+
+TabItem = Tab
diff --git a/gradio/layouts/walkthrough.py b/gradio/layouts/walkthrough.py
new file mode 100644
index 0000000..c528850
--- /dev/null
+++ b/gradio/layouts/walkthrough.py
@@ -0,0 +1,116 @@
+from __future__ import annotations
+
+from gradio_client.documentation import document
+
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+from gradio.events import Events
+from gradio.i18n import I18nData
+
+
+@document()
+class Walkthrough(BlockContext, metaclass=ComponentMeta):
+ """
+ Walkthrough is a layout element within Blocks that can contain multiple "Step" Components, which can be used to create a step-by-step workflow.
+ Example:
+ with gr.Walkthrough(selected=1) as walkthrough:
+ with gr.Step("Step 1", id=1):
+ btn = gr.Button("go to Step 2")
+ btn.click(lambda: gr.Walkthrough(selected=2), outputs=walkthrough)
+ with gr.Step("Step 2", id=2):
+ txt = gr.Textbox("Welcome to Step 2")
+
+ Guides: controlling-layout
+ Demos: walkthrough
+ """
+
+ EVENTS = [Events.change, Events.select]
+
+ def __init__(
+ self,
+ *,
+ selected: int | None = None,
+ visible: bool = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ selected: The currently selected step. Must be a number corresponding to the step number. Defaults to the first step.
+ visible: If False, Walkthrough will be hidden.
+ elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ key: in a gr.render, Components with the same key across re-renders are treated as the same component, not a new component. Properties set in 'preserved_by_key' are not reset across a re-render.
+ preserved_by_key: A list of parameters from this component's constructor. Inside a gr.render() function, if a component is re-rendered with the same key, these (and only these) parameters will be preserved in the UI (if they have been changed by the user or an event listener) instead of re-rendered based on the values provided during constructor.
+ """
+ BlockContext.__init__(
+ self,
+ visible=visible,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+ self.selected = selected
+
+ def get_block_name(self):
+ return "walkthrough"
+
+
+@document()
+class Step(BlockContext, metaclass=ComponentMeta):
+ """
+ Step is a layout element. A step is a single step in a step-by-step workflow.
+ """
+
+ EVENTS = [Events.select]
+
+ def __init__(
+ self,
+ label: str | I18nData | None = None,
+ visible: bool = True,
+ interactive: bool = True,
+ *,
+ id: int | None = None,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ scale: int | None = None,
+ render: bool = True,
+ key: int | str | tuple[int | str, ...] | None = None,
+ preserved_by_key: list[str] | str | None = None,
+ ):
+ """
+ Parameters:
+ label: The visual label for the step
+ id: An optional numeric identifier for the step, required if you wish to control the selected step from a predict function. Must be a number.
+ elem_id: An optional string that is assigned as the id of the containing the contents of the Step layout. The same string followed by "-button" is attached to the Step button. Can be used for targeting CSS styles.
+ elem_classes: An optional string or list of strings that are assigned as the class of this component in the HTML DOM. Can be used for targeting CSS styles.
+ render: If False, this layout will not be rendered in the Blocks context. Should be used if the intention is to assign event listeners now but render the component later.
+ scale: relative size compared to adjacent elements. 1 or greater indicates the Step will expand in size.
+ visible: If False, Step will be hidden.
+ interactive: If False, Step will not be clickable.
+ """
+ BlockContext.__init__(
+ self,
+ elem_id=elem_id,
+ elem_classes=elem_classes,
+ render=render,
+ key=key,
+ preserved_by_key=preserved_by_key,
+ )
+ self.label = label
+ self.id = id
+ self.visible = visible
+ self.scale = scale
+ self.interactive = interactive
+
+ def get_expected_parent(self) -> type[Walkthrough]:
+ return Walkthrough
+
+ def get_block_name(self):
+ return "walkthroughstep"
diff --git a/gradio/mcp.py b/gradio/mcp.py
new file mode 100644
index 0000000..6429b4d
--- /dev/null
+++ b/gradio/mcp.py
@@ -0,0 +1,1632 @@
+import base64
+import contextlib
+import copy
+import html
+import json
+import os
+import re
+import tempfile
+import warnings
+from collections.abc import AsyncIterator, Sequence
+from io import BytesIO
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, Optional, cast
+from urllib.parse import unquote, urlparse
+
+import gradio_client.utils as client_utils
+import httpx
+from anyio.to_thread import run_sync
+from gradio_client import Client, handle_file
+from gradio_client.utils import Status, StatusUpdate
+from PIL import Image
+from pydantic import AnyUrl
+from starlette.applications import Starlette
+from starlette.requests import Request
+from starlette.responses import HTMLResponse, JSONResponse, Response
+from starlette.routing import Mount, Route
+from starlette.types import Receive, Scope, Send
+
+from gradio import processing_utils, route_utils, utils
+from gradio.blocks import BlockFunction
+from gradio.components import State
+from gradio.route_utils import Header
+from gradio.state_holder import SessionState
+
+if TYPE_CHECKING:
+ from mcp import types # noqa: F401
+ from mcp.server import Server # noqa: F401
+ from mcp.server.lowlevel.helper_types import ReadResourceContents # noqa: F401
+
+ from gradio.blocks import BlockContext, Blocks
+ from gradio.components import Component
+
+
+DEFAULT_TEMP_DIR = os.environ.get("GRADIO_TEMP_DIR") or str(
+ Path(tempfile.gettempdir()) / "gradio"
+)
+
+
+# Landing page served on a plain browser GET to the MCP endpoint. The
+# `__SERVER_URL__` / `__DOCS_URL__` (JSON-encoded, for the inline
+
+
+"""
+
+
+class GradioMCPServer:
+ """
+ A class for creating an MCP server around a Gradio app. This class
+ requires `mcp` to be installed.
+
+ Args:
+ blocks: The Blocks app to create the MCP server for.
+ """
+
+ # Imports are here to avoid needing to install `mcp` when not using this class.
+ # This way, we are able to export `gr.tool`, `gr.resource`, etc. to `__init__.py`
+ # without the user needing to have `mcp` installed.
+ try:
+ from mcp import types
+ from mcp.server import Server
+ from mcp.server.lowlevel.helper_types import ReadResourceContents
+ from mcp.server.sse import SseServerTransport
+ from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
+ except ImportError:
+ pass
+
+ def __init__(self, blocks: "Blocks"):
+ try:
+ import mcp # noqa: F401
+ except ImportError as e:
+ raise ImportError(
+ "The `mcp` package is required to use the Gradio MCP integration. Please install it with the `mcp` extra: `pip install gradio[mcp]`."
+ ) from e
+
+ self.blocks = blocks
+ self.api_info = self.blocks.get_api_info()
+ self.mcp_server = self.create_mcp_server()
+ self.root_path = ""
+ space_id = utils.get_space()
+ self.tool_prefix = space_id.split("/")[-1] + "_" if space_id else ""
+ self.tool_to_endpoint = self.get_tool_to_endpoint()
+ self.warn_about_state_inputs()
+ self._local_url: str | None = None
+ self._client_instance: Client | None = None
+
+ manager = self.StreamableHTTPSessionManager( # type: ignore
+ app=self.mcp_server, json_response=False, stateless=True
+ )
+
+ async def handle_streamable_http(
+ scope: Scope, receive: Receive, send: Send
+ ) -> None:
+ path = scope.get("path", "")
+ if not path.endswith(
+ (
+ "/gradio_api/mcp",
+ "/gradio_api/mcp/",
+ "/gradio_api/mcp/http",
+ "/gradio_api/mcp/http/",
+ )
+ ):
+ response = Response(
+ content=f"Path '{path}' not found. The MCP HTTP transport is available at /gradio_api/mcp.",
+ status_code=404,
+ )
+ await response(scope, receive, send)
+ return
+
+ # MCP clients connect using the streamable HTTP transport, which
+ # requires an `Accept: text/event-stream` header. A plain browser
+ # GET (e.g. a user clicking the MCP link printed in the terminal)
+ # would otherwise receive a raw JSON-RPC "Not Acceptable" error, so
+ # serve a human-friendly landing page instead.
+ if scope.get("method") == "GET":
+ accept = b""
+ for key, value in scope.get("headers", []):
+ if key.lower() == b"accept":
+ accept = value
+ break
+ if b"text/event-stream" not in accept:
+ server_url = self._server_url_from_scope(scope)
+ response = HTMLResponse(content=self._landing_page_html(server_url))
+ await response(scope, receive, send)
+ return
+
+ await manager.handle_request(scope, receive, send)
+
+ @contextlib.asynccontextmanager
+ async def lifespan(app: Starlette) -> AsyncIterator[None]: # noqa: ARG001
+ """Context manager for managing session manager lifecycle."""
+ async with manager.run():
+ try:
+ yield
+ finally:
+ pass
+
+ self.lifespan = lifespan
+ self.manager = manager
+ self.handle_streamable_http = handle_streamable_http
+
+ @property
+ def local_url(self) -> str | None:
+ return self._local_url
+
+ @staticmethod
+ def _server_url_from_scope(scope: Scope) -> str:
+ """Reconstruct the public URL of the MCP endpoint from an ASGI scope.
+
+ Honours ``X-Forwarded-Proto``/``X-Forwarded-Host`` so that the URL
+ shown on the landing page matches what the user typed into the browser
+ even when Gradio is behind a proxy (e.g. a Hugging Face Space).
+ """
+ headers = {key.lower(): value for key, value in scope.get("headers", []) or []}
+
+ scheme = scope.get("scheme", "http")
+ if (forwarded_proto := headers.get(b"x-forwarded-proto")) is not None:
+ scheme = forwarded_proto.decode("latin-1").split(",")[0].strip()
+
+ host = b""
+ if (forwarded_host := headers.get(b"x-forwarded-host")) is not None:
+ host = forwarded_host.split(b",")[0].strip()
+ elif (host_header := headers.get(b"host")) is not None:
+ host = host_header
+ host_str = host.decode("latin-1") or "localhost"
+
+ # Normalise to the canonical streamable HTTP path with a trailing slash,
+ # matching the URL Gradio prints to the terminal at launch.
+ path = scope.get("path", "/gradio_api/mcp")
+ path = "/" + path.strip("/")
+ if path.endswith(("/http", "/http/")):
+ path = path[: path.rfind("/http")]
+ path = path.rstrip("/") + "/"
+
+ return f"{scheme}://{host_str}{path}"
+
+ @staticmethod
+ def _landing_page_html(server_url: str) -> str:
+ """HTML landing page shown when a browser navigates to the MCP endpoint.
+
+ MCP clients connect using the streamable HTTP transport (which requires
+ an ``Accept: text/event-stream`` header). A plain browser ``GET`` would
+ otherwise receive a raw JSON-RPC "Not Acceptable" error, so we serve
+ this informational page instead. It surfaces the server URL and ready
+ to paste configuration snippets for popular MCP clients.
+ """
+ docs_url = "https://www.gradio.app/guides/building-mcp-server-with-gradio"
+ server_url_js = json.dumps(server_url)
+ docs_url_js = json.dumps(docs_url)
+ parsed = urlparse(server_url)
+ path = parsed.path
+ if "/gradio_api/mcp" in path:
+ root_path = path.split("/gradio_api/mcp")[0]
+ else:
+ root_path = path.rstrip("/")
+ static_prefix = f"{root_path}/static"
+ return (
+ _MCP_LANDING_PAGE_TEMPLATE.replace("__SERVER_URL__", server_url_js)
+ .replace("__DOCS_URL__", docs_url_js)
+ .replace("__SERVER_URL_TEXT__", html.escape(server_url))
+ .replace("__STATIC_PREFIX__", static_prefix)
+ )
+
+ def get_route_path(self, request: Request) -> str: # type: ignore
+ """
+ Gets the route path of the MCP server based on the incoming request.
+ Can be different depending on whether the request is coming from the MCP SSE transport or the HTTP transport.
+ """
+ url = httpx.URL(str(request.url))
+ url = url.copy_with(query=None)
+ url = str(url).rstrip("/")
+ if url.endswith("/gradio_api/mcp/messages"):
+ return "/gradio_api/mcp/messages"
+ else:
+ return "/gradio_api/mcp"
+
+ def get_selected_tools_from_request(self) -> list[str] | None:
+ """
+ Extract the selected tools from the request query parameters and return the full tool names (with the tool prefix).
+ Returns None if no tools parameter is specified (meaning all tools are available).
+ """
+ context_request: Request | None = self.mcp_server.request_context.request
+ if context_request is None:
+ return None
+ query_params = dict(getattr(context_request, "query_params", {}))
+ if "tools" in query_params:
+ tools = query_params["tools"].split(",")
+ full_tool_names = [self.tool_prefix + tool for tool in tools]
+ return full_tool_names
+ return None
+
+ @staticmethod
+ def valid_and_unique_tool_name(
+ tool_name: str, existing_tool_names: set[str]
+ ) -> str:
+ """
+ Sanitizes a tool name to make it a valid MCP tool name (only
+ alphanumeric characters, underscores, <= 128 characters)
+ and is unique among the existing tool names.
+ """
+ tool_name = re.sub(r"[^a-zA-Z0-9]", "_", tool_name)
+ tool_name = tool_name[:120] # Leave room for suffix if needed
+ tool_name_base = tool_name
+ suffix = 1
+ while tool_name in existing_tool_names:
+ tool_name = tool_name_base + f"_{suffix}"
+ suffix += 1
+ return tool_name
+
+ def get_tool_to_endpoint(self) -> dict[str, str]:
+ """
+ Gets all of the tools that are exposed by the Gradio app and also
+ creates a mapping from the tool names to the endpoint names in the API docs.
+ """
+ tool_to_endpoint = {}
+ for endpoint_name in self.api_info["named_endpoints"]:
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ if block_fn is None or block_fn.fn is None:
+ continue
+ fn_name = (
+ getattr(block_fn.fn, "__name__", None)
+ or (
+ hasattr(block_fn.fn, "__class__")
+ and getattr(block_fn.fn.__class__, "__name__", None)
+ )
+ or endpoint_name.lstrip("/")
+ )
+ tool_name = self.tool_prefix + fn_name
+ tool_name = self.valid_and_unique_tool_name(
+ tool_name, set(tool_to_endpoint.keys())
+ )
+ tool_to_endpoint[tool_name] = endpoint_name
+ return tool_to_endpoint
+
+ def warn_about_state_inputs(self) -> None:
+ """
+ Warn about tools that have gr.State inputs.
+ """
+ for _, endpoint_name in self.tool_to_endpoint.items():
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ if block_fn and any(isinstance(input, State) for input in block_fn.inputs):
+ warnings.warn(
+ "This MCP server includes a tool that has a gr.State input, which will not be "
+ "updated between tool calls. The original, default value of the State will be "
+ "used each time."
+ )
+
+ def _get_or_create_client(self) -> Client:
+ if self._client_instance is None:
+ context_request: Request | None = self.mcp_server.request_context.request
+ if context_request is None:
+ raise ValueError(
+ "Could not find the request object in the MCP server context. This is not expected to happen. Please raise an issue: https://github.com/gradio-app/gradio."
+ )
+ route_path = self.get_route_path(context_request)
+ root_url = route_utils.get_root_url(
+ request=context_request,
+ route_path=route_path,
+ root_path=self.root_path,
+ )
+ self._client_instance = Client(
+ self.local_url or root_url,
+ download_files=False,
+ verbose=False,
+ analytics_enabled=False,
+ ssl_verify=False,
+ _skip_components=False,
+ headers={"x-gradio-user": "mcp"},
+ )
+ return self._client_instance
+
+ def _prepare_tool_call_args(
+ self, name: str, arguments: dict[str, Any]
+ ) -> tuple[str, list[Any], dict[str, str], "BlockFunction"]:
+ """
+ Prepare and validate arguments for a tool call.
+
+ Returns:
+ A tuple of (endpoint_name, processed_args, request_headers, block_fn)
+ """
+ selected_tools = self.get_selected_tools_from_request()
+ _, filedata_positions = self.get_input_schema(name)
+ processed_kwargs = self.convert_strings_to_filedata(
+ arguments, filedata_positions
+ )
+ endpoint_name = self.tool_to_endpoint.get(name)
+ if endpoint_name is None:
+ raise ValueError(f"Unknown tool for this Gradio app: {name}")
+
+ if selected_tools is not None and name not in selected_tools:
+ raise ValueError(f"Tool '{name}' is not in the selected tools list")
+
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ assert block_fn is not None # noqa: S101
+
+ if endpoint_name in self.api_info["named_endpoints"]:
+ parameters_info = self.api_info["named_endpoints"][endpoint_name][
+ "parameters"
+ ]
+ processed_args = client_utils.construct_args(
+ parameters_info,
+ (),
+ processed_kwargs,
+ )
+ else:
+ processed_args = []
+
+ context_request: Request | None = self.mcp_server.request_context.request
+ if context_request is None:
+ raise ValueError(
+ "Could not find the request object in the MCP server context. This is not expected to happen. Please raise an issue: https://github.com/gradio-app/gradio."
+ )
+ request_headers = dict(context_request.headers.items())
+ request_headers.pop("content-length", None)
+ request_headers.pop("x-gradio-user", None)
+
+ return endpoint_name, processed_args, request_headers, block_fn
+
+ async def _execute_tool_without_progress(self, job: Any) -> list[Any]:
+ """
+ Execute a tool call without progress tracking (fast path).
+
+ Calls job.result() to get the final output without processing
+ intermediate status updates.
+
+ Returns:
+ The output data as a list.
+ """
+ result = await run_sync(job.result)
+ return [result]
+
+ @staticmethod
+ def _format_progress_message(update: StatusUpdate) -> str | None:
+ """
+ Format a status update into a human-readable progress message.
+
+ Returns:
+ A formatted message string, or None if no message should be shown.
+ """
+ if update.code in [Status.JOINING_QUEUE, Status.STARTING]:
+ return "Joined server queue."
+ elif update.code in [Status.IN_QUEUE]:
+ message = f"In queue. Position {update.rank} out of {update.queue_size}."
+ if update.eta is not None:
+ message += f" Estimated time remaining: {update.eta} seconds."
+ return message
+ elif update.code in [Status.PROGRESS]:
+ for progress_unit in update.progress_data or []:
+ title = (
+ "Progress"
+ if progress_unit.desc is None
+ else f"Progress {progress_unit.desc}"
+ )
+ if progress_unit.index is not None and progress_unit.length is not None:
+ return (
+ f"{title}: Step {progress_unit.index} of {progress_unit.length}"
+ )
+ elif progress_unit.index is not None and progress_unit.length is None:
+ return f"{title}: Step {progress_unit.index}"
+ elif update.code in [Status.PROCESSING, Status.ITERATING]:
+ return "Processing"
+ return None
+
+ async def _execute_tool_with_progress( # type: ignore
+ self, job: Any, progress_token: str | int
+ ) -> dict[str, Any]:
+ """
+ Execute a tool call with progress tracking (streaming path).
+
+ Iterates through job updates to send progress notifications to the client.
+
+ Returns:
+ The output data as a list.
+ """
+ step = 0
+ async for update in job:
+ if update.type == "status":
+ update = cast(StatusUpdate, update)
+ message = self._format_progress_message(update)
+
+ await (
+ self.mcp_server.request_context.session.send_progress_notification(
+ progress_token=progress_token,
+ progress=step,
+ message=message, # type: ignore
+ related_request_id=str(
+ self.mcp_server.request_context.request_id
+ ),
+ )
+ )
+ step += 1
+ elif update.type == "output" and update.final:
+ output = update.outputs
+ if not update.success:
+ error_title = output.get("title")
+ error_message = output.get("error")
+ if error_title and error_message:
+ msg = f"{error_title}: {error_message}"
+ elif error_message:
+ msg = error_message
+ elif error_title:
+ msg = error_title
+ else:
+ msg = "Error!"
+ raise RuntimeError(msg)
+ if job.exception():
+ raise job.exception()
+ return output["data"]
+
+ def create_mcp_server(self) -> "Server":
+ """
+ Create an MCP server for the given Gradio Blocks app.
+
+ Parameters:
+ blocks: The Blocks app to create the MCP server for.
+
+ Returns:
+ The MCP server.
+ """
+ server = self.Server(str(self.blocks.title or "Gradio App")) # type: ignore
+
+ @server.call_tool()
+ async def call_tool(
+ name: str, arguments: dict[str, Any]
+ ) -> self.types.CallToolResult: # type: ignore
+ """
+ Call a tool on the Gradio app.
+
+ Args:
+ name: The name of the tool to call.
+ arguments: The arguments to pass to the tool.
+ """
+ endpoint_name, processed_args, request_headers, block_fn = (
+ self._prepare_tool_call_args(name, arguments)
+ )
+ processed_args = self.insert_empty_state(block_fn.inputs, processed_args)
+
+ if not block_fn.queue:
+ # Fast path for non-queued events: call blocks.process_api()
+ # directly instead of the HTTP loopback through gradio_client.
+ # This eliminates thread dispatches, TCP round-trips, and SSE
+ # overhead — reducing MCP tool-call latency significantly.
+ session_state = SessionState(self.blocks)
+ raw_output = await self.blocks.process_api(
+ block_fn=block_fn,
+ inputs=processed_args,
+ state=session_state,
+ request=self.mcp_server.request_context.request,
+ )
+ output_data = raw_output["data"]
+ else:
+ # Queued path: use the HTTP loopback to preserve streaming
+ # updates, progress notifications, and queue-based features.
+ progress_token = None
+ if self.mcp_server.request_context.meta is not None:
+ progress_token = self.mcp_server.request_context.meta.progressToken
+
+ client = await run_sync(self._get_or_create_client)
+ job = client.submit(
+ *processed_args,
+ api_name=endpoint_name,
+ headers=request_headers,
+ )
+
+ if progress_token is None:
+ output_data = await self._execute_tool_without_progress(job)
+ else:
+ output_data = await self._execute_tool_with_progress(
+ job,
+ progress_token,
+ )
+
+ output_data = self.pop_returned_state(block_fn.outputs, output_data)
+
+ context_request: Request | None = self.mcp_server.request_context.request
+ route_path = self.get_route_path(context_request) # type: ignore
+ root_url = route_utils.get_root_url( # type: ignore
+ request=context_request, # type: ignore
+ route_path=route_path, # type: ignore
+ root_path=self.root_path, # type: ignore
+ )
+ content = self.postprocess_output_data(output_data, root_url)
+ if getattr(block_fn.fn, "_mcp_structured_output", False):
+ structured_content = {"result": content}
+ else:
+ structured_content = None
+ return self.types.CallToolResult( # type: ignore
+ content=content, # type: ignore
+ structuredContent=structured_content, # type: ignore
+ _meta=getattr(block_fn.fn, "_mcp_meta", None), # type: ignore
+ )
+
+ @server.list_tools()
+ async def list_tools() -> list[self.types.Tool]: # type: ignore
+ """
+ List all tools on the Gradio app.
+ """
+ selected_tools = self.get_selected_tools_from_request()
+
+ tools = []
+ for tool_name, endpoint_name in self.tool_to_endpoint.items():
+ if selected_tools is not None and tool_name not in selected_tools:
+ continue
+
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ if (
+ block_fn is None
+ or block_fn.fn is None
+ or (
+ hasattr(block_fn.fn, "_mcp_type")
+ and block_fn.fn._mcp_type != "tool"
+ )
+ ):
+ continue
+
+ description, parameters = self.get_fn_description(block_fn, tool_name)
+ schema, _ = self.get_input_schema(tool_name, parameters)
+ tool_meta = getattr(block_fn.fn, "_mcp_meta", None)
+
+ tools.append(
+ self.types.Tool( # type: ignore
+ name=tool_name,
+ description=description,
+ inputSchema=schema,
+ _meta=tool_meta, # type: ignore
+ )
+ )
+ return tools
+
+ @server.list_resources()
+ async def list_resources() -> list[self.types.Resource]: # type: ignore
+ """
+ List all available resources.
+ """
+ resources = []
+
+ selected_tools = self.get_selected_tools_from_request()
+ for tool_name, endpoint_name in self.tool_to_endpoint.items():
+ if selected_tools is not None and tool_name not in selected_tools:
+ continue
+
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ if (
+ block_fn
+ and block_fn.fn
+ and hasattr(block_fn.fn, "_mcp_type")
+ and block_fn.fn._mcp_type == "resource"
+ ):
+ uri_template = block_fn.fn._mcp_uri_template # type: ignore
+ parameters = re.findall(r"\{([^}]+)\}", uri_template)
+ description, parameters, _ = utils.get_function_description(
+ block_fn.fn
+ )
+ if not parameters:
+ resources.append(
+ self.types.Resource( # type: ignore
+ uri=uri_template,
+ name=block_fn.fn.__name__, # type: ignore
+ description=description,
+ mimeType=block_fn.fn._mcp_mime_type, # type: ignore
+ )
+ )
+ return resources
+
+ @server.list_resource_templates()
+ async def list_resource_templates() -> list[self.types.ResourceTemplate]: # type: ignore
+ """
+ List all available resource templates.
+ """
+ templates = []
+ selected_tools = self.get_selected_tools_from_request()
+ for tool_name, endpoint_name in self.tool_to_endpoint.items():
+ if selected_tools is not None and tool_name not in selected_tools:
+ continue
+
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ if (
+ block_fn
+ and block_fn.fn
+ and hasattr(block_fn.fn, "_mcp_type")
+ and block_fn.fn._mcp_type == "resource"
+ ):
+ uri_template = block_fn.fn._mcp_uri_template # type: ignore
+ parameters = re.findall(r"\{([^}]+)\}", uri_template)
+ description, parameters, _ = utils.get_function_description(
+ block_fn.fn
+ )
+ if parameters:
+ templates.append(
+ self.types.ResourceTemplate( # type: ignore
+ uriTemplate=uri_template,
+ name=block_fn.fn.__name__, # type: ignore
+ description=description,
+ mimeType=block_fn.fn._mcp_mime_type, # type: ignore
+ )
+ )
+ return templates
+
+ @server.read_resource()
+ async def read_resource(uri: AnyUrl | str) -> list[self.ReadResourceContents]: # type: ignore
+ """
+ Read a specific resource by URI.
+ """
+ uri = str(uri)
+ client = await run_sync(self._get_or_create_client)
+ for endpoint_name in self.tool_to_endpoint.values():
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+
+ if (
+ block_fn
+ and block_fn.fn
+ and hasattr(block_fn.fn, "_mcp_type")
+ and block_fn.fn._mcp_type == "resource"
+ ):
+ uri_template = block_fn.fn._mcp_uri_template # type: ignore
+ parameters = re.findall(r"\{([^}]+)\}", uri_template) # type: ignore
+
+ kwargs = {}
+ matched = False
+
+ if parameters:
+ pattern = re.escape(uri_template)
+ for param in parameters:
+ pattern = pattern.replace(
+ f"\\{{{param}\\}}", f"(?P<{param}>[^/]+)"
+ )
+ match = re.match(f"^{pattern}$", uri)
+ if match:
+ kwargs = match.groupdict()
+ matched = True
+ elif uri_template == uri:
+ matched = True
+
+ if matched:
+ if endpoint_name in self.api_info["named_endpoints"]:
+ parameters_info = self.api_info["named_endpoints"][
+ endpoint_name
+ ]["parameters"]
+ processed_args = client_utils.construct_args(
+ parameters_info,
+ (),
+ kwargs,
+ )
+ else:
+ processed_args = list(kwargs.values())
+
+ async for update in client.submit(
+ *processed_args, api_name=endpoint_name
+ ):
+ if update.type == "output" and update.final: # type: ignore
+ output = update.outputs # type: ignore
+ result = output["data"][0]
+ break
+
+ mime_type = block_fn.fn._mcp_mime_type # type: ignore
+ if mime_type and not mime_type.startswith("text/"):
+ result = base64.b64decode(result.encode("ascii"))
+ return [
+ self.ReadResourceContents( # type: ignore
+ content=result, mime_type=mime_type
+ )
+ ]
+
+ raise ValueError(f"Resource not found: {uri}")
+
+ @server.list_prompts()
+ async def list_prompts() -> list[self.types.Prompt]: # type: ignore
+ """
+ List all available prompts.
+ """
+ prompts = []
+ selected_tools = self.get_selected_tools_from_request()
+ for tool_name, endpoint_name in self.tool_to_endpoint.items():
+ if selected_tools is not None and tool_name not in selected_tools:
+ continue
+
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ if (
+ block_fn
+ and block_fn.fn
+ and hasattr(block_fn.fn, "_mcp_type")
+ and block_fn.fn._mcp_type == "prompt"
+ ):
+ description, parameters, _ = utils.get_function_description(
+ block_fn.fn
+ )
+ function_params = utils.get_function_params(block_fn.fn)
+ arguments = [
+ self.types.PromptArgument( # type: ignore
+ name=param_name,
+ description=parameters.get(param_name, ""),
+ required=not has_default,
+ )
+ for param_name, has_default, _, _ in function_params
+ ]
+ prompts.append(
+ self.types.Prompt( # type: ignore
+ name=tool_name,
+ description=description,
+ arguments=arguments,
+ )
+ )
+ return prompts
+
+ @server.get_prompt()
+ async def get_prompt(
+ name: str, arguments: dict[str, Any] | None = None
+ ) -> self.types.GetPromptResult: # type: ignore
+ """
+ Get a specific prompt with filled-in arguments.
+ """
+ client = await run_sync(self._get_or_create_client)
+
+ endpoint_name = None
+ for endpoint_name in self.tool_to_endpoint.values():
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ if (
+ block_fn
+ and block_fn.fn
+ and hasattr(block_fn.fn, "_mcp_type")
+ and block_fn.fn._mcp_type == "prompt"
+ and block_fn.fn._mcp_name == name # type: ignore
+ ):
+ break
+
+ if not endpoint_name:
+ raise ValueError(f"Prompt not found: {name}")
+
+ arguments = arguments or {}
+
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ assert block_fn is not None # noqa: S101
+
+ if endpoint_name in self.api_info["named_endpoints"]:
+ parameters_info = self.api_info["named_endpoints"][endpoint_name][
+ "parameters"
+ ]
+ processed_args = client_utils.construct_args(
+ parameters_info,
+ (),
+ arguments,
+ )
+ else:
+ processed_args = list(arguments.values())
+
+ async for update in client.submit(*processed_args, api_name=endpoint_name):
+ if update.type == "output" and update.final: # type: ignore
+ output = update.outputs # type: ignore
+ result = output["data"][0]
+ break
+
+ return self.types.GetPromptResult( # type: ignore
+ messages=[
+ self.types.PromptMessage( # type: ignore
+ role="user",
+ content=self.types.TextContent(type="text", text=str(result)), # type: ignore
+ )
+ ]
+ )
+
+ return server
+
+ def launch_mcp_on_sse(self, app: Starlette, subpath: str, root_path: str) -> None:
+ """
+ Launch the MCP server on the SSE transport.
+
+ Parameters:
+ app: The Gradio app to mount the MCP server on.
+ subpath: The subpath to mount the MCP server on. E.g. "/gradio_api/mcp"
+ root_path: The root path of the Gradio Blocks app.
+ """
+ messages_path = "/messages/"
+ sse = self.SseServerTransport(messages_path) # type: ignore
+ self.root_path = root_path
+
+ async def handle_sse(request):
+ try:
+ async with sse.connect_sse(
+ request.scope, request.receive, request._send
+ ) as streams:
+ await self.mcp_server.run(
+ streams[0],
+ streams[1],
+ self.mcp_server.create_initialization_options(),
+ )
+ return Response()
+ except Exception as e:
+ print(f"MCP SSE connection error: {str(e)}")
+ raise
+
+ app.mount(
+ subpath,
+ Starlette(
+ routes=[
+ Route(
+ "/schema",
+ endpoint=self.get_complete_schema, # Not required for MCP but used by the Hugging Face MCP server to get the schema for MCP Spaces without needing to establish an SSE connection
+ ),
+ Route("/sse", endpoint=handle_sse),
+ Mount("/messages/", app=sse.handle_post_message),
+ Mount("/", app=self.handle_streamable_http),
+ ],
+ ),
+ )
+
+ def get_block_fn_from_endpoint_name(
+ self, endpoint_name: str
+ ) -> "BlockFunction | None":
+ """
+ Get the BlockFunction for a given endpoint name (e.g. "/predict").
+
+ Parameters:
+ endpoint_name: The name of the endpoint to get the BlockFunction for.
+
+ Returns:
+ The BlockFunction for the given endpoint name, or None if it is not found.
+ """
+ block_fn = next(
+ (
+ fn
+ for fn in self.blocks.fns.values()
+ if fn.api_name == endpoint_name.lstrip("/")
+ ),
+ None,
+ )
+ return block_fn
+
+ @property
+ def _file_data_tool_description(self) -> str:
+ """
+ Sentence prompting the agent to use the upload_file_to_gradio tool if a file is passed as an input.
+ """
+ return " If a user passes a file as an input, use the upload_file_to_gradio tool, if present, to upload the file to the gradio app and create a Gradio File Input. Then use the returned path as the input to the tool"
+
+ def get_fn_description(
+ self, block_fn: "BlockFunction", tool_name: str
+ ) -> tuple[str, dict[str, str]]:
+ """
+ Get the description of a function, which is used to describe the tool in the MCP server.
+ Also returns the description of each parameter of the function as a dictionary.
+ """
+ description, parameters, returns = utils.get_function_description(block_fn.fn) # type: ignore
+ _, filedata_positions = self.get_input_schema(tool_name, parameters)
+ if block_fn.api_description is False:
+ description = ""
+ elif block_fn.api_description is None:
+ if len(filedata_positions) > 0:
+ description += self._file_data_tool_description
+ if returns:
+ description += (
+ ("" if description.endswith(".") else ".")
+ + " Returns: "
+ + ", ".join(returns)
+ )
+ else:
+ description = block_fn.api_description
+ if len(filedata_positions) > 0:
+ description += self._file_data_tool_description # type: ignore
+ assert isinstance(description, str) # noqa: S101
+ return description, parameters
+
+ @staticmethod
+ def insert_empty_state(
+ inputs: Sequence["Component | BlockContext"], data: list
+ ) -> list:
+ """
+ Insert None placeholder values for any State input components, as State inputs
+ are not included in the endpoint schema.
+ """
+ for i, input_component_type in enumerate(inputs):
+ if isinstance(input_component_type, State):
+ data.insert(i, None)
+ return data
+
+ @staticmethod
+ def pop_returned_state(
+ components: Sequence["Component | BlockContext"], data: Any
+ ) -> list:
+ """
+ Remove any values corresponding to State output components from the data
+ as State outputs are not included in the endpoint schema.
+ """
+ for i, component_type in enumerate(components):
+ if isinstance(component_type, State):
+ data.pop(i)
+ return data
+
+ def get_input_schema(
+ self,
+ tool_name: str,
+ parameters: dict[str, str] | None = None,
+ ) -> tuple[dict[str, Any], list[list[str | int]]]:
+ """
+ Get the input schema of the Gradio app API, appropriately formatted for MCP.
+
+ Parameters:
+ tool_name: The name of the tool to get the schema for, e.g. "predict"
+ parameters: The description and parameters of the tool to get the schema for.
+ Returns:
+ - The input schema of the Gradio app API.
+ - A list of positions of FileData objects in the input schema.
+ """
+ endpoint_name = self.tool_to_endpoint.get(tool_name)
+ if endpoint_name is None:
+ raise ValueError(f"Unknown tool for this Gradio app: {tool_name}")
+ named_endpoints = self.api_info["named_endpoints"]
+ endpoint_info = named_endpoints.get(endpoint_name)
+ assert endpoint_info is not None # noqa: S101
+
+ schema = {
+ "type": "object",
+ "properties": {
+ p["parameter_name"]: {
+ **p["type"],
+ **(
+ {"description": parameters[p["parameter_name"]]}
+ if parameters and p["parameter_name"] in parameters
+ else {}
+ ),
+ **(
+ {"default": p["parameter_default"]}
+ if "parameter_default" in p and p["parameter_default"]
+ else {}
+ ),
+ }
+ for p in endpoint_info["parameters"]
+ },
+ }
+ return self.simplify_filedata_schema(schema)
+
+ async def get_complete_schema(self, request) -> JSONResponse:
+ """
+ Get the complete schema of the Gradio app API. For debugging purposes, also used by
+ the Hugging Face MCP server to get the schema for MCP Spaces without needing to
+ establish an SSE connection.
+
+ Parameters:
+ request: The Starlette request object.
+
+ Returns:
+ A JSONResponse containing a dictionary mapping tool names to their input schemas.
+ """
+ if not self.api_info:
+ return JSONResponse({})
+
+ query_params = dict(getattr(request, "query_params", {}))
+ selected_tools = None
+ if "tools" in query_params:
+ tools = query_params["tools"].split(",")
+ selected_tools = set(tools)
+
+ file_data_present = False
+
+ schemas = []
+ for tool_name, endpoint_name in self.tool_to_endpoint.items():
+ if selected_tools is not None and tool_name not in selected_tools:
+ continue
+ block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
+ assert block_fn is not None and block_fn.fn is not None # noqa: S101
+
+ description, parameters = self.get_fn_description(block_fn, tool_name)
+ schema, filedata_positions = self.get_input_schema(tool_name, parameters)
+ if len(filedata_positions) > 0 and not file_data_present:
+ file_data_present = True
+
+ type_hints = utils.get_type_hints(block_fn.fn)
+ required_headers = []
+ for param_name, type_hint in type_hints.items():
+ if type_hint is Header or type_hint is Optional[Header]:
+ header_name = param_name.replace("_", "-").lower()
+ required_headers.append(header_name)
+
+ mcp_type = "tool" # Default
+ if hasattr(block_fn.fn, "_mcp_type"):
+ mcp_type = block_fn.fn._mcp_type
+
+ meta = {
+ "file_data_present": file_data_present,
+ "mcp_type": mcp_type,
+ "endpoint_name": block_fn.api_name,
+ }
+ if required_headers:
+ meta["headers"] = required_headers
+
+ info = {
+ "name": tool_name,
+ "description": description,
+ "inputSchema": schema,
+ "meta": meta,
+ }
+ schemas.append(info)
+
+ return JSONResponse(schemas)
+
+ def simplify_filedata_schema(
+ self, schema: dict[str, Any]
+ ) -> tuple[dict[str, Any], list[list[str | int]]]:
+ """
+ Parses a schema of a Gradio app API to identify positions of FileData objects. Replaces them with base64
+ strings while keeping track of their positions so that they can be converted back to FileData objects
+ later.
+
+ Parameters:
+ schema: The original schema of the Gradio app API.
+
+ Returns:
+ A tuple containing the simplified schema and the positions of the FileData objects.
+ """
+
+ def is_gradio_filedata(obj: Any, defs: dict[str, Any]) -> bool:
+ if not isinstance(obj, dict):
+ return False
+
+ if "$ref" in obj:
+ ref = obj["$ref"]
+ if ref.startswith("#/$defs/"):
+ key = ref.split("/")[-1]
+ obj = defs.get(key, {})
+ else:
+ return False
+
+ props = obj.get("properties", {})
+ meta = props.get("meta", {})
+
+ if "$ref" in meta:
+ ref = meta["$ref"]
+ if ref.startswith("#/$defs/"):
+ key = ref.split("/")[-1]
+ meta = defs.get(key, {})
+ else:
+ return False
+
+ type_field = meta.get("properties", {}).get("_type", {})
+ default_type = meta.get("default", {}).get("_type")
+ return (
+ type_field.get("const") == "gradio.FileData"
+ or default_type == "gradio.FileData"
+ )
+
+ def traverse(
+ node: Any,
+ path: list[str | int] | None = None,
+ defs: dict[str, Any] | None = None,
+ ) -> Any:
+ if path is None:
+ path = []
+ if defs is None:
+ defs = {}
+ # Deep copy the node to avoid modifying the original node
+ node = copy.deepcopy(node)
+
+ if isinstance(node, dict):
+ if "$defs" in node:
+ defs.update(node["$defs"])
+
+ if is_gradio_filedata(node, defs):
+ filedata_positions.append(path.copy())
+ for key in ["properties", "additional_description", "$defs"]:
+ node.pop(key, None)
+ node["type"] = "string"
+ node["format"] = "Gradio File Input - a http or https url to a file"
+
+ result = {}
+ is_schema_root = "type" in node and "properties" in node
+ for key, value in node.items():
+ if is_schema_root and key == "properties":
+ result[key] = traverse(value, path, defs)
+ else:
+ path.append(key)
+ result[key] = traverse(value, path, defs)
+ path.pop()
+ return result
+
+ elif isinstance(node, list):
+ result = []
+ for i, item in enumerate(node):
+ path.append(i)
+ result.append(traverse(item, path, defs))
+ path.pop()
+ return result
+
+ return node
+
+ filedata_positions: list[list[str | int]] = []
+ simplified_schema = traverse(schema)
+ return simplified_schema, filedata_positions
+
+ def convert_strings_to_filedata(
+ self, value: Any, filedata_positions: list[list[str | int]]
+ ) -> Any:
+ """
+ Convert specific string values back to FileData objects based on their positions.
+ This is used to convert string values (as base64 encoded strings) to FileData
+ dictionaries so that they can be passed into .preprocess() logic of a Gradio app.
+
+ Parameters:
+ value: The input data to process, which can be an arbitrary nested data structure
+ that may or may not contain strings that should be converted to FileData objects.
+ filedata_positions: List of paths to positions in the input data that should be converted to FileData objects.
+
+ Returns:
+ The processed data with strings converted to FileData objects where appropriate. Base64
+ encoded strings are first saved to a temporary file and then converted to a FileData object.
+
+ Example:
+ >>> convert_strings_to_filedata(
+ {"image": "data:image/jpeg;base64,..."},
+ [["image"]]
+ )
+ >>> {'image': FileData(path='')},
+ """
+
+ def traverse(node: Any, path: list[str | int] | None = None) -> Any:
+ if path is None:
+ path = []
+
+ if isinstance(node, dict):
+ return {
+ key: traverse(value, path + [key]) for key, value in node.items()
+ }
+ elif isinstance(node, list):
+ return [traverse(item, path + [i]) for i, item in enumerate(node)]
+ elif isinstance(node, str) and path in filedata_positions:
+ if node.startswith("data:"):
+ # Even though base64 is not officially part of our schema, some MCP clients
+ # might return base64 encoded strings, so try to save it to a temporary file.
+ return handle_file(
+ processing_utils.save_base64_to_cache(node, DEFAULT_TEMP_DIR)
+ )
+ elif node.startswith(("http://", "https://")):
+ return handle_file(node)
+ else:
+ raise ValueError(
+ f"Invalid file data format, provide a url ('http://...' or 'https://...'). Received: {node}"
+ )
+ return node
+
+ return traverse(value)
+
+ @staticmethod
+ def get_image(file_path: str) -> Image.Image | None:
+ """
+ If a filepath is a valid image, returns a PIL Image object. Otherwise returns None.
+ """
+ if not os.path.exists(file_path):
+ return None
+ ext = os.path.splitext(file_path.lower())[1]
+ if ext not in Image.registered_extensions():
+ return None
+ try:
+ return Image.open(file_path)
+ except Exception:
+ return None
+
+ @staticmethod
+ def get_svg(file_data: Any) -> bytes | None:
+ """
+ If a file_data is a valid FileDataDict with a url that is a data:image/svg+xml, returns bytes of the svg. Otherwise returns None.
+ """
+ if isinstance(file_data, dict) and (url := file_data.get("url")):
+ if isinstance(url, str) and url.startswith("data:image/svg"):
+ return unquote(url.split(",", 1)[1]).encode()
+ else:
+ return None
+ else:
+ return None
+
+ @staticmethod
+ def get_base64_data(image: Image.Image, format: str) -> str:
+ """
+ Returns a base64 encoded string of the image.
+ """
+ buffer = BytesIO()
+ image.save(buffer, format=format)
+ return base64.b64encode(buffer.getvalue()).decode("utf-8")
+
+ def postprocess_output_data(
+ self, data: Any, root_url: str
+ ) -> list["types.TextContent | types.ImageContent"]:
+ """
+ Postprocess the output data from the Gradio app to convert FileData objects back to base64 encoded strings.
+
+ Parameters:
+ data: The output data to postprocess.
+ """
+ return_values = []
+ data = processing_utils.add_root_url(data, root_url, None)
+ for output in data:
+ if svg_bytes := self.get_svg(output):
+ base64_data = base64.b64encode(svg_bytes).decode("utf-8")
+ mimetype = "image/svg+xml"
+ svg_path = processing_utils.save_bytes_to_cache(
+ svg_bytes, f"{output['orig_name']}", DEFAULT_TEMP_DIR
+ )
+ svg_url = f"{root_url}/gradio_api/file={svg_path}"
+ return_value = [
+ self.types.ImageContent( # type: ignore
+ type="image", data=base64_data, mimeType=mimetype
+ ),
+ self.types.TextContent( # type: ignore
+ type="text",
+ text=f"SVG Image URL: {svg_url}",
+ ),
+ ]
+ elif client_utils.is_file_obj_with_meta(output):
+ if image := self.get_image(output["path"]):
+ image_format = image.format or "png"
+ base64_data = self.get_base64_data(image, image_format)
+ mimetype = f"image/{image_format.lower()}"
+ return_value = [
+ self.types.ImageContent( # type: ignore
+ type="image", data=base64_data, mimeType=mimetype
+ ),
+ self.types.TextContent( # type: ignore
+ type="text",
+ text=f"Image URL: {output['url'] or output['path']}",
+ ),
+ ]
+ else:
+ return_value = [
+ self.types.TextContent( # type: ignore
+ type="text", text=str(output["url"] or output["path"])
+ )
+ ]
+ else:
+ return_value = [self.types.TextContent(type="text", text=str(output))] # type: ignore
+ return_values.extend(return_value)
+ return return_values
+
+
+######################################################
+### MCP decorators that add metadata to functions.
+######################################################
+
+
+def resource(
+ uri_template: str, description: str | None = None, mime_type: str | None = None
+):
+ """Decorator to mark a function as an MCP resource."""
+
+ def decorator(fn):
+ fn._mcp_type = "resource"
+ fn._mcp_uri_template = uri_template
+ fn._mcp_description = description
+ fn._mcp_mime_type = mime_type or "text/plain"
+ return fn
+
+ return decorator
+
+
+def prompt(name: str | None = None, description: str | None = None):
+ """Decorator to mark a function as an MCP prompt."""
+
+ def decorator(fn):
+ fn._mcp_type = "prompt"
+ fn._mcp_name = name or fn.__name__
+ fn._mcp_description = description
+ return fn
+
+ return decorator
+
+
+def tool(
+ name: str | None = None,
+ description: str | None = None,
+ structured_output: bool = False,
+ _meta: dict[str, Any] | None = None,
+):
+ """
+ Decorator to mark a function as an MCP tool (optional, since functions are registered as tools by default).
+ Can be used to configure various aspects of the tool.
+
+ Parameters:
+ name: The name of the tool. Overrides the default name of the function.
+ description: The description of the tool. Overrides the default description from the function's docstring.
+ structured_output: Whether the tool should return structured output (implementation is quite limited at the moment). If True, the output will be wrapped in a dictionary with the key "result" and the value being the output of the function. Recommended to keep this False unless you have a specific reason to need the structured output.
+ _meta: Additional metadata for the tool.
+ """
+
+ def decorator(fn):
+ fn._mcp_type = "tool"
+ fn._mcp_name = name
+ fn._mcp_structured_output = structured_output
+ fn._mcp_description = description
+ fn._mcp_meta = _meta
+ return fn
+
+ return decorator
diff --git a/gradio/media.py b/gradio/media.py
new file mode 100644
index 0000000..50ece7a
--- /dev/null
+++ b/gradio/media.py
@@ -0,0 +1,157 @@
+"""
+Media Registry for Gradio Demos
+
+This module provides a centralized way to access media files.
+
+Usage:
+ from gradio.media import get_image, get_video, get_audio, get_model3d, get_file
+
+ # Get specific media files
+ cheetah_img = get_image("cheetah1.jpg")
+ world_video = get_video("world.mp4")
+ cantina_audio = get_audio("cantina.wav")
+ bunny_model = get_model3d("Bunny.obj")
+ titanic_data = get_file("titanic.csv")
+
+ # Get random media of a type
+ random_img = get_image()
+ random_video = get_video()
+ random_audio = get_audio()
+
+"""
+
+import random
+from pathlib import Path
+from typing import Optional
+
+MEDIA_ROOT = Path(__file__).parent / "media_assets"
+MEDIA_PATHS = [
+ MEDIA_ROOT / "images",
+ MEDIA_ROOT / "videos",
+ MEDIA_ROOT / "audio",
+ MEDIA_ROOT / "models3d",
+ MEDIA_ROOT / "data",
+]
+
+
+def _get_media_path(media_type: str, filename: Optional[str] = None) -> str:
+ """
+ Internal function to get the path to a media file.
+
+ Args:
+ media_type: Type of media (images, videos, audio, models3d, data)
+ filename: Optional filename of the media file. If None, returns a random file.
+
+ Returns:
+ Absolute path to the media file
+
+ Raises:
+ ValueError: If media_type is invalid
+ FileNotFoundError: If the media file doesn't exist
+ """
+ media_dir = MEDIA_ROOT / media_type
+
+ if not media_dir.exists():
+ raise ValueError(f"Media directory not found: {media_dir}")
+
+ if filename is None:
+ # Get a random file from the directory
+ media_files = list(media_dir.glob("*"))
+ if not media_files:
+ raise ValueError(f"No media files found in {media_dir}")
+ file_path = random.choice(media_files)
+ else:
+ if filename.startswith(("http://", "https://")):
+ return filename
+
+ file_path = media_dir / filename
+
+ if not file_path.exists():
+ raise FileNotFoundError(f"Media file not found: {file_path}")
+
+ return str(file_path.absolute())
+
+
+def get_image(filename: Optional[str] = None) -> str:
+ """
+ Get path to an image file.
+
+ Args:
+ filename: Filename of the image (e.g., "tower.jpg"). If None, returns a random image.
+
+ Returns:
+ Absolute path to the image file
+
+ Examples:
+ >>> get_image("tower.jpg") # Get specific image
+ >>> get_image() # Get random image
+ """
+ return _get_media_path("images", filename)
+
+
+def get_video(filename: Optional[str] = None) -> str:
+ """
+ Get path to a video file.
+
+ Args:
+ filename: Filename of the video (e.g., "world.mp4"). If None, returns a random video.
+
+ Returns:
+ Absolute path to the video file
+
+ Examples:
+ >>> get_video("world.mp4") # Get specific video
+ >>> get_video() # Get random video
+ """
+ return _get_media_path("videos", filename)
+
+
+def get_audio(filename: Optional[str] = None) -> str:
+ """
+ Get path to an audio file.
+
+ Args:
+ filename: Filename of the audio (e.g., "cantina.wav"). If None, returns a random audio file.
+
+ Returns:
+ Absolute path to the audio file
+
+ Examples:
+ >>> get_audio("cantina.wav") # Get specific audio
+ >>> get_audio() # Get random audio
+ """
+ return _get_media_path("audio", filename)
+
+
+def get_model3d(filename: Optional[str] = None) -> str:
+ """
+ Get path to a 3D model file.
+
+ Args:
+ filename: Filename of the 3D model (e.g., "Duck.glb"). If None, returns a random model.
+
+ Returns:
+ Absolute path to the 3D model file
+
+ Examples:
+ >>> get_model3d("Duck.glb") # Get specific model
+ >>> get_model3d() # Get random 3D model
+ """
+ return _get_media_path("models3d", filename)
+
+
+def get_file(filename: Optional[str] = None) -> str:
+ """
+ Get path to a data file (CSV, JSON, text, etc.).
+
+ Args:
+ filename: Filename of the data file (e.g., "titanic.csv"). If None, returns a random file.
+
+ Returns:
+ Absolute path to the data file
+
+ Examples:
+ >>> get_file("titanic.csv") # Get specific file
+ >>> get_file() # Get random data file
+ """
+ return _get_media_path("data", filename)
diff --git a/gradio/media_assets/audio/audio_sample.wav b/gradio/media_assets/audio/audio_sample.wav
new file mode 100644
index 0000000..495f00d
Binary files /dev/null and b/gradio/media_assets/audio/audio_sample.wav differ
diff --git a/gradio/media_assets/audio/cantina.wav b/gradio/media_assets/audio/cantina.wav
new file mode 100644
index 0000000..41f0204
Binary files /dev/null and b/gradio/media_assets/audio/cantina.wav differ
diff --git a/gradio/media_assets/audio/cate_blanch.mp3 b/gradio/media_assets/audio/cate_blanch.mp3
new file mode 100644
index 0000000..4beee74
Binary files /dev/null and b/gradio/media_assets/audio/cate_blanch.mp3 differ
diff --git a/gradio/media_assets/audio/cate_blanch_2.mp3 b/gradio/media_assets/audio/cate_blanch_2.mp3
new file mode 100644
index 0000000..1acc212
Binary files /dev/null and b/gradio/media_assets/audio/cate_blanch_2.mp3 differ
diff --git a/gradio/media_assets/audio/heath_ledger.mp3 b/gradio/media_assets/audio/heath_ledger.mp3
new file mode 100644
index 0000000..eb63071
Binary files /dev/null and b/gradio/media_assets/audio/heath_ledger.mp3 differ
diff --git a/gradio/media_assets/audio/recording1.wav b/gradio/media_assets/audio/recording1.wav
new file mode 100644
index 0000000..a58ba50
Binary files /dev/null and b/gradio/media_assets/audio/recording1.wav differ
diff --git a/gradio/media_assets/audio/sax.wav b/gradio/media_assets/audio/sax.wav
new file mode 100644
index 0000000..de6d16a
Binary files /dev/null and b/gradio/media_assets/audio/sax.wav differ
diff --git a/gradio/media_assets/data/imagenet_labels.json b/gradio/media_assets/data/imagenet_labels.json
new file mode 100644
index 0000000..fa059ce
--- /dev/null
+++ b/gradio/media_assets/data/imagenet_labels.json
@@ -0,0 +1,1000 @@
+["tench",
+ "goldfish",
+ "great white shark",
+ "tiger shark",
+ "hammerhead shark",
+ "electric ray",
+ "stingray",
+ "cock",
+ "hen",
+ "ostrich",
+ "brambling",
+ "goldfinch",
+ "house finch",
+ "junco",
+ "indigo bunting",
+ "American robin",
+ "bulbul",
+ "jay",
+ "magpie",
+ "chickadee",
+ "American dipper",
+ "kite",
+ "bald eagle",
+ "vulture",
+ "great grey owl",
+ "fire salamander",
+ "smooth newt",
+ "newt",
+ "spotted salamander",
+ "axolotl",
+ "American bullfrog",
+ "tree frog",
+ "tailed frog",
+ "loggerhead sea turtle",
+ "leatherback sea turtle",
+ "mud turtle",
+ "terrapin",
+ "box turtle",
+ "banded gecko",
+ "green iguana",
+ "Carolina anole",
+ "desert grassland whiptail lizard",
+ "agama",
+ "frilled-necked lizard",
+ "alligator lizard",
+ "Gila monster",
+ "European green lizard",
+ "chameleon",
+ "Komodo dragon",
+ "Nile crocodile",
+ "American alligator",
+ "triceratops",
+ "worm snake",
+ "ring-necked snake",
+ "eastern hog-nosed snake",
+ "smooth green snake",
+ "kingsnake",
+ "garter snake",
+ "water snake",
+ "vine snake",
+ "night snake",
+ "boa constrictor",
+ "African rock python",
+ "Indian cobra",
+ "green mamba",
+ "sea snake",
+ "Saharan horned viper",
+ "eastern diamondback rattlesnake",
+ "sidewinder",
+ "trilobite",
+ "harvestman",
+ "scorpion",
+ "yellow garden spider",
+ "barn spider",
+ "European garden spider",
+ "southern black widow",
+ "tarantula",
+ "wolf spider",
+ "tick",
+ "centipede",
+ "black grouse",
+ "ptarmigan",
+ "ruffed grouse",
+ "prairie grouse",
+ "peacock",
+ "quail",
+ "partridge",
+ "grey parrot",
+ "macaw",
+ "sulphur-crested cockatoo",
+ "lorikeet",
+ "coucal",
+ "bee eater",
+ "hornbill",
+ "hummingbird",
+ "jacamar",
+ "toucan",
+ "duck",
+ "red-breasted merganser",
+ "goose",
+ "black swan",
+ "tusker",
+ "echidna",
+ "platypus",
+ "wallaby",
+ "koala",
+ "wombat",
+ "jellyfish",
+ "sea anemone",
+ "brain coral",
+ "flatworm",
+ "nematode",
+ "conch",
+ "snail",
+ "slug",
+ "sea slug",
+ "chiton",
+ "chambered nautilus",
+ "Dungeness crab",
+ "rock crab",
+ "fiddler crab",
+ "red king crab",
+ "American lobster",
+ "spiny lobster",
+ "crayfish",
+ "hermit crab",
+ "isopod",
+ "white stork",
+ "black stork",
+ "spoonbill",
+ "flamingo",
+ "little blue heron",
+ "great egret",
+ "bittern",
+ "crane (bird)",
+ "limpkin",
+ "common gallinule",
+ "American coot",
+ "bustard",
+ "ruddy turnstone",
+ "dunlin",
+ "common redshank",
+ "dowitcher",
+ "oystercatcher",
+ "pelican",
+ "king penguin",
+ "albatross",
+ "grey whale",
+ "killer whale",
+ "dugong",
+ "sea lion",
+ "Chihuahua",
+ "Japanese Chin",
+ "Maltese",
+ "Pekingese",
+ "Shih Tzu",
+ "King Charles Spaniel",
+ "Papillon",
+ "toy terrier",
+ "Rhodesian Ridgeback",
+ "Afghan Hound",
+ "Basset Hound",
+ "Beagle",
+ "Bloodhound",
+ "Bluetick Coonhound",
+ "Black and Tan Coonhound",
+ "Treeing Walker Coonhound",
+ "English foxhound",
+ "Redbone Coonhound",
+ "borzoi",
+ "Irish Wolfhound",
+ "Italian Greyhound",
+ "Whippet",
+ "Ibizan Hound",
+ "Norwegian Elkhound",
+ "Otterhound",
+ "Saluki",
+ "Scottish Deerhound",
+ "Weimaraner",
+ "Staffordshire Bull Terrier",
+ "American Staffordshire Terrier",
+ "Bedlington Terrier",
+ "Border Terrier",
+ "Kerry Blue Terrier",
+ "Irish Terrier",
+ "Norfolk Terrier",
+ "Norwich Terrier",
+ "Yorkshire Terrier",
+ "Wire Fox Terrier",
+ "Lakeland Terrier",
+ "Sealyham Terrier",
+ "Airedale Terrier",
+ "Cairn Terrier",
+ "Australian Terrier",
+ "Dandie Dinmont Terrier",
+ "Boston Terrier",
+ "Miniature Schnauzer",
+ "Giant Schnauzer",
+ "Standard Schnauzer",
+ "Scottish Terrier",
+ "Tibetan Terrier",
+ "Australian Silky Terrier",
+ "Soft-coated Wheaten Terrier",
+ "West Highland White Terrier",
+ "Lhasa Apso",
+ "Flat-Coated Retriever",
+ "Curly-coated Retriever",
+ "Golden Retriever",
+ "Labrador Retriever",
+ "Chesapeake Bay Retriever",
+ "German Shorthaired Pointer",
+ "Vizsla",
+ "English Setter",
+ "Irish Setter",
+ "Gordon Setter",
+ "Brittany",
+ "Clumber Spaniel",
+ "English Springer Spaniel",
+ "Welsh Springer Spaniel",
+ "Cocker Spaniels",
+ "Sussex Spaniel",
+ "Irish Water Spaniel",
+ "Kuvasz",
+ "Schipperke",
+ "Groenendael",
+ "Malinois",
+ "Briard",
+ "Australian Kelpie",
+ "Komondor",
+ "Old English Sheepdog",
+ "Shetland Sheepdog",
+ "collie",
+ "Border Collie",
+ "Bouvier des Flandres",
+ "Rottweiler",
+ "German Shepherd Dog",
+ "Dobermann",
+ "Miniature Pinscher",
+ "Greater Swiss Mountain Dog",
+ "Bernese Mountain Dog",
+ "Appenzeller Sennenhund",
+ "Entlebucher Sennenhund",
+ "Boxer",
+ "Bullmastiff",
+ "Tibetan Mastiff",
+ "French Bulldog",
+ "Great Dane",
+ "St. Bernard",
+ "husky",
+ "Alaskan Malamute",
+ "Siberian Husky",
+ "Dalmatian",
+ "Affenpinscher",
+ "Basenji",
+ "pug",
+ "Leonberger",
+ "Newfoundland",
+ "Pyrenean Mountain Dog",
+ "Samoyed",
+ "Pomeranian",
+ "Chow Chow",
+ "Keeshond",
+ "Griffon Bruxellois",
+ "Pembroke Welsh Corgi",
+ "Cardigan Welsh Corgi",
+ "Toy Poodle",
+ "Miniature Poodle",
+ "Standard Poodle",
+ "Mexican hairless dog",
+ "grey wolf",
+ "Alaskan tundra wolf",
+ "red wolf",
+ "coyote",
+ "dingo",
+ "dhole",
+ "African wild dog",
+ "hyena",
+ "red fox",
+ "kit fox",
+ "Arctic fox",
+ "grey fox",
+ "tabby cat",
+ "tiger cat",
+ "Persian cat",
+ "Siamese cat",
+ "Egyptian Mau",
+ "cougar",
+ "lynx",
+ "leopard",
+ "snow leopard",
+ "jaguar",
+ "lion",
+ "tiger",
+ "cheetah",
+ "brown bear",
+ "American black bear",
+ "polar bear",
+ "sloth bear",
+ "mongoose",
+ "meerkat",
+ "tiger beetle",
+ "ladybug",
+ "ground beetle",
+ "longhorn beetle",
+ "leaf beetle",
+ "dung beetle",
+ "rhinoceros beetle",
+ "weevil",
+ "fly",
+ "bee",
+ "ant",
+ "grasshopper",
+ "cricket",
+ "stick insect",
+ "cockroach",
+ "mantis",
+ "cicada",
+ "leafhopper",
+ "lacewing",
+ "dragonfly",
+ "damselfly",
+ "red admiral",
+ "ringlet",
+ "monarch butterfly",
+ "small white",
+ "sulphur butterfly",
+ "gossamer-winged butterfly",
+ "starfish",
+ "sea urchin",
+ "sea cucumber",
+ "cottontail rabbit",
+ "hare",
+ "Angora rabbit",
+ "hamster",
+ "porcupine",
+ "fox squirrel",
+ "marmot",
+ "beaver",
+ "guinea pig",
+ "common sorrel",
+ "zebra",
+ "pig",
+ "wild boar",
+ "warthog",
+ "hippopotamus",
+ "ox",
+ "water buffalo",
+ "bison",
+ "ram",
+ "bighorn sheep",
+ "Alpine ibex",
+ "hartebeest",
+ "impala",
+ "gazelle",
+ "dromedary",
+ "llama",
+ "weasel",
+ "mink",
+ "European polecat",
+ "black-footed ferret",
+ "otter",
+ "skunk",
+ "badger",
+ "armadillo",
+ "three-toed sloth",
+ "orangutan",
+ "gorilla",
+ "chimpanzee",
+ "gibbon",
+ "siamang",
+ "guenon",
+ "patas monkey",
+ "baboon",
+ "macaque",
+ "langur",
+ "black-and-white colobus",
+ "proboscis monkey",
+ "marmoset",
+ "white-headed capuchin",
+ "howler monkey",
+ "titi",
+ "Geoffroy's spider monkey",
+ "common squirrel monkey",
+ "ring-tailed lemur",
+ "indri",
+ "Asian elephant",
+ "African bush elephant",
+ "red panda",
+ "giant panda",
+ "snoek",
+ "eel",
+ "coho salmon",
+ "rock beauty",
+ "clownfish",
+ "sturgeon",
+ "garfish",
+ "lionfish",
+ "pufferfish",
+ "abacus",
+ "abaya",
+ "academic gown",
+ "accordion",
+ "acoustic guitar",
+ "aircraft carrier",
+ "airliner",
+ "airship",
+ "altar",
+ "ambulance",
+ "amphibious vehicle",
+ "analog clock",
+ "apiary",
+ "apron",
+ "waste container",
+ "assault rifle",
+ "backpack",
+ "bakery",
+ "balance beam",
+ "balloon",
+ "ballpoint pen",
+ "Band-Aid",
+ "banjo",
+ "baluster",
+ "barbell",
+ "barber chair",
+ "barbershop",
+ "barn",
+ "barometer",
+ "barrel",
+ "wheelbarrow",
+ "baseball",
+ "basketball",
+ "bassinet",
+ "bassoon",
+ "swimming cap",
+ "bath towel",
+ "bathtub",
+ "station wagon",
+ "lighthouse",
+ "beaker",
+ "military cap",
+ "beer bottle",
+ "beer glass",
+ "bell-cot",
+ "bib",
+ "tandem bicycle",
+ "bikini",
+ "ring binder",
+ "binoculars",
+ "birdhouse",
+ "boathouse",
+ "bobsleigh",
+ "bolo tie",
+ "poke bonnet",
+ "bookcase",
+ "bookstore",
+ "bottle cap",
+ "bow",
+ "bow tie",
+ "brass",
+ "bra",
+ "breakwater",
+ "breastplate",
+ "broom",
+ "bucket",
+ "buckle",
+ "bulletproof vest",
+ "high-speed train",
+ "butcher shop",
+ "taxicab",
+ "cauldron",
+ "candle",
+ "cannon",
+ "canoe",
+ "can opener",
+ "cardigan",
+ "car mirror",
+ "carousel",
+ "tool kit",
+ "carton",
+ "car wheel",
+ "automated teller machine",
+ "cassette",
+ "cassette player",
+ "castle",
+ "catamaran",
+ "CD player",
+ "cello",
+ "mobile phone",
+ "chain",
+ "chain-link fence",
+ "chain mail",
+ "chainsaw",
+ "chest",
+ "chiffonier",
+ "chime",
+ "china cabinet",
+ "Christmas stocking",
+ "church",
+ "movie theater",
+ "cleaver",
+ "cliff dwelling",
+ "cloak",
+ "clogs",
+ "cocktail shaker",
+ "coffee mug",
+ "coffeemaker",
+ "coil",
+ "combination lock",
+ "computer keyboard",
+ "confectionery store",
+ "container ship",
+ "convertible",
+ "corkscrew",
+ "cornet",
+ "cowboy boot",
+ "cowboy hat",
+ "cradle",
+ "crane (machine)",
+ "crash helmet",
+ "crate",
+ "infant bed",
+ "Crock Pot",
+ "croquet ball",
+ "crutch",
+ "cuirass",
+ "dam",
+ "desk",
+ "desktop computer",
+ "rotary dial telephone",
+ "diaper",
+ "digital clock",
+ "digital watch",
+ "dining table",
+ "dishcloth",
+ "dishwasher",
+ "disc brake",
+ "dock",
+ "dog sled",
+ "dome",
+ "doormat",
+ "drilling rig",
+ "drum",
+ "drumstick",
+ "dumbbell",
+ "Dutch oven",
+ "electric fan",
+ "electric guitar",
+ "electric locomotive",
+ "entertainment center",
+ "envelope",
+ "espresso machine",
+ "face powder",
+ "feather boa",
+ "filing cabinet",
+ "fireboat",
+ "fire engine",
+ "fire screen sheet",
+ "flagpole",
+ "flute",
+ "folding chair",
+ "football helmet",
+ "forklift",
+ "fountain",
+ "fountain pen",
+ "four-poster bed",
+ "freight car",
+ "French horn",
+ "frying pan",
+ "fur coat",
+ "garbage truck",
+ "gas mask",
+ "gas pump",
+ "goblet",
+ "go-kart",
+ "golf ball",
+ "golf cart",
+ "gondola",
+ "gong",
+ "gown",
+ "grand piano",
+ "greenhouse",
+ "grille",
+ "grocery store",
+ "guillotine",
+ "barrette",
+ "hair spray",
+ "half-track",
+ "hammer",
+ "hamper",
+ "hair dryer",
+ "hand-held computer",
+ "handkerchief",
+ "hard disk drive",
+ "harmonica",
+ "harp",
+ "harvester",
+ "hatchet",
+ "holster",
+ "home theater",
+ "honeycomb",
+ "hook",
+ "hoop skirt",
+ "horizontal bar",
+ "horse-drawn vehicle",
+ "hourglass",
+ "iPod",
+ "clothes iron",
+ "jack-o'-lantern",
+ "jeans",
+ "jeep",
+ "T-shirt",
+ "jigsaw puzzle",
+ "pulled rickshaw",
+ "joystick",
+ "kimono",
+ "knee pad",
+ "knot",
+ "lab coat",
+ "ladle",
+ "lampshade",
+ "laptop computer",
+ "lawn mower",
+ "lens cap",
+ "paper knife",
+ "library",
+ "lifeboat",
+ "lighter",
+ "limousine",
+ "ocean liner",
+ "lipstick",
+ "slip-on shoe",
+ "lotion",
+ "speaker",
+ "loupe",
+ "sawmill",
+ "magnetic compass",
+ "mail bag",
+ "mailbox",
+ "tights",
+ "tank suit",
+ "manhole cover",
+ "maraca",
+ "marimba",
+ "mask",
+ "match",
+ "maypole",
+ "maze",
+ "measuring cup",
+ "medicine chest",
+ "megalith",
+ "microphone",
+ "microwave oven",
+ "military uniform",
+ "milk can",
+ "minibus",
+ "miniskirt",
+ "minivan",
+ "missile",
+ "mitten",
+ "mixing bowl",
+ "mobile home",
+ "Model T",
+ "modem",
+ "monastery",
+ "monitor",
+ "moped",
+ "mortar",
+ "square academic cap",
+ "mosque",
+ "mosquito net",
+ "scooter",
+ "mountain bike",
+ "tent",
+ "computer mouse",
+ "mousetrap",
+ "moving van",
+ "muzzle",
+ "nail",
+ "neck brace",
+ "necklace",
+ "nipple",
+ "notebook computer",
+ "obelisk",
+ "oboe",
+ "ocarina",
+ "odometer",
+ "oil filter",
+ "organ",
+ "oscilloscope",
+ "overskirt",
+ "bullock cart",
+ "oxygen mask",
+ "packet",
+ "paddle",
+ "paddle wheel",
+ "padlock",
+ "paintbrush",
+ "pajamas",
+ "palace",
+ "pan flute",
+ "paper towel",
+ "parachute",
+ "parallel bars",
+ "park bench",
+ "parking meter",
+ "passenger car",
+ "patio",
+ "payphone",
+ "pedestal",
+ "pencil case",
+ "pencil sharpener",
+ "perfume",
+ "Petri dish",
+ "photocopier",
+ "plectrum",
+ "Pickelhaube",
+ "picket fence",
+ "pickup truck",
+ "pier",
+ "piggy bank",
+ "pill bottle",
+ "pillow",
+ "ping-pong ball",
+ "pinwheel",
+ "pirate ship",
+ "pitcher",
+ "hand plane",
+ "planetarium",
+ "plastic bag",
+ "plate rack",
+ "plow",
+ "plunger",
+ "Polaroid camera",
+ "pole",
+ "police van",
+ "poncho",
+ "billiard table",
+ "soda bottle",
+ "pot",
+ "potter's wheel",
+ "power drill",
+ "prayer rug",
+ "printer",
+ "prison",
+ "projectile",
+ "projector",
+ "hockey puck",
+ "punching bag",
+ "purse",
+ "quill",
+ "quilt",
+ "race car",
+ "racket",
+ "radiator",
+ "radio",
+ "radio telescope",
+ "rain barrel",
+ "recreational vehicle",
+ "reel",
+ "reflex camera",
+ "refrigerator",
+ "remote control",
+ "restaurant",
+ "revolver",
+ "rifle",
+ "rocking chair",
+ "rotisserie",
+ "eraser",
+ "rugby ball",
+ "ruler",
+ "running shoe",
+ "safe",
+ "safety pin",
+ "salt shaker",
+ "sandal",
+ "sarong",
+ "saxophone",
+ "scabbard",
+ "weighing scale",
+ "school bus",
+ "schooner",
+ "scoreboard",
+ "CRT screen",
+ "screw",
+ "screwdriver",
+ "seat belt",
+ "sewing machine",
+ "shield",
+ "shoe store",
+ "shoji",
+ "shopping basket",
+ "shopping cart",
+ "shovel",
+ "shower cap",
+ "shower curtain",
+ "ski",
+ "ski mask",
+ "sleeping bag",
+ "slide rule",
+ "sliding door",
+ "slot machine",
+ "snorkel",
+ "snowmobile",
+ "snowplow",
+ "soap dispenser",
+ "soccer ball",
+ "sock",
+ "solar thermal collector",
+ "sombrero",
+ "soup bowl",
+ "space bar",
+ "space heater",
+ "space shuttle",
+ "spatula",
+ "motorboat",
+ "spider web",
+ "spindle",
+ "sports car",
+ "spotlight",
+ "stage",
+ "steam locomotive",
+ "through arch bridge",
+ "steel drum",
+ "stethoscope",
+ "scarf",
+ "stone wall",
+ "stopwatch",
+ "stove",
+ "strainer",
+ "tram",
+ "stretcher",
+ "couch",
+ "stupa",
+ "submarine",
+ "suit",
+ "sundial",
+ "sunglass",
+ "sunglasses",
+ "sunscreen",
+ "suspension bridge",
+ "mop",
+ "sweatshirt",
+ "swimsuit",
+ "swing",
+ "switch",
+ "syringe",
+ "table lamp",
+ "tank",
+ "tape player",
+ "teapot",
+ "teddy bear",
+ "television",
+ "tennis ball",
+ "thatched roof",
+ "front curtain",
+ "thimble",
+ "threshing machine",
+ "throne",
+ "tile roof",
+ "toaster",
+ "tobacco shop",
+ "toilet seat",
+ "torch",
+ "totem pole",
+ "tow truck",
+ "toy store",
+ "tractor",
+ "semi-trailer truck",
+ "tray",
+ "trench coat",
+ "tricycle",
+ "trimaran",
+ "tripod",
+ "triumphal arch",
+ "trolleybus",
+ "trombone",
+ "tub",
+ "turnstile",
+ "typewriter keyboard",
+ "umbrella",
+ "unicycle",
+ "upright piano",
+ "vacuum cleaner",
+ "vase",
+ "vault",
+ "velvet",
+ "vending machine",
+ "vestment",
+ "viaduct",
+ "violin",
+ "volleyball",
+ "waffle iron",
+ "wall clock",
+ "wallet",
+ "wardrobe",
+ "military aircraft",
+ "sink",
+ "washing machine",
+ "water bottle",
+ "water jug",
+ "water tower",
+ "whiskey jug",
+ "whistle",
+ "wig",
+ "window screen",
+ "window shade",
+ "Windsor tie",
+ "wine bottle",
+ "wing",
+ "wok",
+ "wooden spoon",
+ "wool",
+ "split-rail fence",
+ "shipwreck",
+ "yawl",
+ "yurt",
+ "website",
+ "comic book",
+ "crossword",
+ "traffic sign",
+ "traffic light",
+ "dust jacket",
+ "menu",
+ "plate",
+ "guacamole",
+ "consomme",
+ "hot pot",
+ "trifle",
+ "ice cream",
+ "ice pop",
+ "baguette",
+ "bagel",
+ "pretzel",
+ "cheeseburger",
+ "hot dog",
+ "mashed potato",
+ "cabbage",
+ "broccoli",
+ "cauliflower",
+ "zucchini",
+ "spaghetti squash",
+ "acorn squash",
+ "butternut squash",
+ "cucumber",
+ "artichoke",
+ "bell pepper",
+ "cardoon",
+ "mushroom",
+ "Granny Smith",
+ "strawberry",
+ "orange",
+ "lemon",
+ "fig",
+ "pineapple",
+ "banana",
+ "jackfruit",
+ "custard apple",
+ "pomegranate",
+ "hay",
+ "carbonara",
+ "chocolate syrup",
+ "dough",
+ "meatloaf",
+ "pizza",
+ "pot pie",
+ "burrito",
+ "red wine",
+ "espresso",
+ "cup",
+ "eggnog",
+ "alp",
+ "bubble",
+ "cliff",
+ "coral reef",
+ "geyser",
+ "lakeshore",
+ "promontory",
+ "shoal",
+ "seashore",
+ "valley",
+ "volcano",
+ "baseball player",
+ "bridegroom",
+ "scuba diver",
+ "rapeseed",
+ "daisy",
+ "yellow lady's slipper",
+ "corn",
+ "acorn",
+ "rose hip",
+ "horse chestnut seed",
+ "coral fungus",
+ "agaric",
+ "gyromitra",
+ "stinkhorn mushroom",
+ "earth star",
+ "hen-of-the-woods",
+ "bolete",
+ "ear",
+ "toilet paper"]
\ No newline at end of file
diff --git a/gradio/media_assets/data/s1.srt b/gradio/media_assets/data/s1.srt
new file mode 100644
index 0000000..c9d3854
--- /dev/null
+++ b/gradio/media_assets/data/s1.srt
@@ -0,0 +1,29 @@
+0
+0:00:00.000 --> 0:00:00.500
+A
+
+1
+0:00:00.500 --> 0:00:01.000
+B
+
+2
+0:00:01.000 --> 0:00:01.500
+C
+
+3
+0:00:01.500 --> 0:00:02.000
+D
+
+4
+0:00:02.000 --> 0:00:02.500
+E
+
+5
+0:00:02.500 --> 0:00:03.000
+F
+
+6
+0:00:03.000 --> 0:00:10.000
+This is multiple line test.
+Why did the tomato turn red? Because it saw the salad dressing.
+In Africa, every 60 seconds a minute passes
diff --git a/gradio/media_assets/data/s2.vtt b/gradio/media_assets/data/s2.vtt
new file mode 100644
index 0000000..4fa1878
--- /dev/null
+++ b/gradio/media_assets/data/s2.vtt
@@ -0,0 +1,16 @@
+WEBVTT
+
+0:00:00.000 --> 0:00:00.500
+一
+
+0:00:00.500 --> 0:00:01.000
+二
+
+0:00:01.000 --> 0:00:01.500
+三
+
+0:00:01.500 --> 0:00:02.000
+四
+
+0:00:02.000 --> 0:00:02.500
+中文測試
diff --git a/gradio/media_assets/data/s3.json b/gradio/media_assets/data/s3.json
new file mode 100644
index 0000000..6b1ca61
--- /dev/null
+++ b/gradio/media_assets/data/s3.json
@@ -0,0 +1,22 @@
+[
+ {
+ "text": "一",
+ "timestamp": [0.0, 0.5]
+ },
+ {
+ "text": "二",
+ "timestamp": [0.5, 1.0]
+ },
+ {
+ "text": "三",
+ "timestamp": [1.0, 1.5]
+ },
+ {
+ "text": "四",
+ "timestamp": [1.5, 2.0]
+ },
+ {
+ "text": "中文測試",
+ "timestamp": [2.0, 2.5]
+ }
+]
diff --git a/gradio/media_assets/data/sample.txt b/gradio/media_assets/data/sample.txt
new file mode 100644
index 0000000..11f15e4
--- /dev/null
+++ b/gradio/media_assets/data/sample.txt
@@ -0,0 +1 @@
+hello friends
\ No newline at end of file
diff --git a/gradio/media_assets/data/time.csv b/gradio/media_assets/data/time.csv
new file mode 100644
index 0000000..ddb2035
--- /dev/null
+++ b/gradio/media_assets/data/time.csv
@@ -0,0 +1,8 @@
+time,value,price
+1,1,4
+2,3,8
+3,6,12
+4,10,16
+5,15,20
+6,21,24
+7,28,28
\ No newline at end of file
diff --git a/gradio/media_assets/data/titanic.csv b/gradio/media_assets/data/titanic.csv
new file mode 100644
index 0000000..63b68ab
--- /dev/null
+++ b/gradio/media_assets/data/titanic.csv
@@ -0,0 +1,892 @@
+PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked
+1,0,3,"Braund, Mr. Owen Harris",male,22,1,0,A/5 21171,7.25,,S
+2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38,1,0,PC 17599,71.2833,C85,C
+3,1,3,"Heikkinen, Miss. Laina",female,26,0,0,STON/O2. 3101282,7.925,,S
+4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35,1,0,113803,53.1,C123,S
+5,0,3,"Allen, Mr. William Henry",male,35,0,0,373450,8.05,,S
+6,0,3,"Moran, Mr. James",male,,0,0,330877,8.4583,,Q
+7,0,1,"McCarthy, Mr. Timothy J",male,54,0,0,17463,51.8625,E46,S
+8,0,3,"Palsson, Master. Gosta Leonard",male,2,3,1,349909,21.075,,S
+9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27,0,2,347742,11.1333,,S
+10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14,1,0,237736,30.0708,,C
+11,1,3,"Sandstrom, Miss. Marguerite Rut",female,4,1,1,PP 9549,16.7,G6,S
+12,1,1,"Bonnell, Miss. Elizabeth",female,58,0,0,113783,26.55,C103,S
+13,0,3,"Saundercock, Mr. William Henry",male,20,0,0,A/5. 2151,8.05,,S
+14,0,3,"Andersson, Mr. Anders Johan",male,39,1,5,347082,31.275,,S
+15,0,3,"Vestrom, Miss. Hulda Amanda Adolfina",female,14,0,0,350406,7.8542,,S
+16,1,2,"Hewlett, Mrs. (Mary D Kingcome) ",female,55,0,0,248706,16,,S
+17,0,3,"Rice, Master. Eugene",male,2,4,1,382652,29.125,,Q
+18,1,2,"Williams, Mr. Charles Eugene",male,,0,0,244373,13,,S
+19,0,3,"Vander Planke, Mrs. Julius (Emelia Maria Vandemoortele)",female,31,1,0,345763,18,,S
+20,1,3,"Masselmani, Mrs. Fatima",female,,0,0,2649,7.225,,C
+21,0,2,"Fynney, Mr. Joseph J",male,35,0,0,239865,26,,S
+22,1,2,"Beesley, Mr. Lawrence",male,34,0,0,248698,13,D56,S
+23,1,3,"McGowan, Miss. Anna ""Annie""",female,15,0,0,330923,8.0292,,Q
+24,1,1,"Sloper, Mr. William Thompson",male,28,0,0,113788,35.5,A6,S
+25,0,3,"Palsson, Miss. Torborg Danira",female,8,3,1,349909,21.075,,S
+26,1,3,"Asplund, Mrs. Carl Oscar (Selma Augusta Emilia Johansson)",female,38,1,5,347077,31.3875,,S
+27,0,3,"Emir, Mr. Farred Chehab",male,,0,0,2631,7.225,,C
+28,0,1,"Fortune, Mr. Charles Alexander",male,19,3,2,19950,263,C23 C25 C27,S
+29,1,3,"O'Dwyer, Miss. Ellen ""Nellie""",female,,0,0,330959,7.8792,,Q
+30,0,3,"Todoroff, Mr. Lalio",male,,0,0,349216,7.8958,,S
+31,0,1,"Uruchurtu, Don. Manuel E",male,40,0,0,PC 17601,27.7208,,C
+32,1,1,"Spencer, Mrs. William Augustus (Marie Eugenie)",female,,1,0,PC 17569,146.5208,B78,C
+33,1,3,"Glynn, Miss. Mary Agatha",female,,0,0,335677,7.75,,Q
+34,0,2,"Wheadon, Mr. Edward H",male,66,0,0,C.A. 24579,10.5,,S
+35,0,1,"Meyer, Mr. Edgar Joseph",male,28,1,0,PC 17604,82.1708,,C
+36,0,1,"Holverson, Mr. Alexander Oskar",male,42,1,0,113789,52,,S
+37,1,3,"Mamee, Mr. Hanna",male,,0,0,2677,7.2292,,C
+38,0,3,"Cann, Mr. Ernest Charles",male,21,0,0,A./5. 2152,8.05,,S
+39,0,3,"Vander Planke, Miss. Augusta Maria",female,18,2,0,345764,18,,S
+40,1,3,"Nicola-Yarred, Miss. Jamila",female,14,1,0,2651,11.2417,,C
+41,0,3,"Ahlin, Mrs. Johan (Johanna Persdotter Larsson)",female,40,1,0,7546,9.475,,S
+42,0,2,"Turpin, Mrs. William John Robert (Dorothy Ann Wonnacott)",female,27,1,0,11668,21,,S
+43,0,3,"Kraeff, Mr. Theodor",male,,0,0,349253,7.8958,,C
+44,1,2,"Laroche, Miss. Simonne Marie Anne Andree",female,3,1,2,SC/Paris 2123,41.5792,,C
+45,1,3,"Devaney, Miss. Margaret Delia",female,19,0,0,330958,7.8792,,Q
+46,0,3,"Rogers, Mr. William John",male,,0,0,S.C./A.4. 23567,8.05,,S
+47,0,3,"Lennon, Mr. Denis",male,,1,0,370371,15.5,,Q
+48,1,3,"O'Driscoll, Miss. Bridget",female,,0,0,14311,7.75,,Q
+49,0,3,"Samaan, Mr. Youssef",male,,2,0,2662,21.6792,,C
+50,0,3,"Arnold-Franchi, Mrs. Josef (Josefine Franchi)",female,18,1,0,349237,17.8,,S
+51,0,3,"Panula, Master. Juha Niilo",male,7,4,1,3101295,39.6875,,S
+52,0,3,"Nosworthy, Mr. Richard Cater",male,21,0,0,A/4. 39886,7.8,,S
+53,1,1,"Harper, Mrs. Henry Sleeper (Myna Haxtun)",female,49,1,0,PC 17572,76.7292,D33,C
+54,1,2,"Faunthorpe, Mrs. Lizzie (Elizabeth Anne Wilkinson)",female,29,1,0,2926,26,,S
+55,0,1,"Ostby, Mr. Engelhart Cornelius",male,65,0,1,113509,61.9792,B30,C
+56,1,1,"Woolner, Mr. Hugh",male,,0,0,19947,35.5,C52,S
+57,1,2,"Rugg, Miss. Emily",female,21,0,0,C.A. 31026,10.5,,S
+58,0,3,"Novel, Mr. Mansouer",male,28.5,0,0,2697,7.2292,,C
+59,1,2,"West, Miss. Constance Mirium",female,5,1,2,C.A. 34651,27.75,,S
+60,0,3,"Goodwin, Master. William Frederick",male,11,5,2,CA 2144,46.9,,S
+61,0,3,"Sirayanian, Mr. Orsen",male,22,0,0,2669,7.2292,,C
+62,1,1,"Icard, Miss. Amelie",female,38,0,0,113572,80,B28,
+63,0,1,"Harris, Mr. Henry Birkhardt",male,45,1,0,36973,83.475,C83,S
+64,0,3,"Skoog, Master. Harald",male,4,3,2,347088,27.9,,S
+65,0,1,"Stewart, Mr. Albert A",male,,0,0,PC 17605,27.7208,,C
+66,1,3,"Moubarek, Master. Gerios",male,,1,1,2661,15.2458,,C
+67,1,2,"Nye, Mrs. (Elizabeth Ramell)",female,29,0,0,C.A. 29395,10.5,F33,S
+68,0,3,"Crease, Mr. Ernest James",male,19,0,0,S.P. 3464,8.1583,,S
+69,1,3,"Andersson, Miss. Erna Alexandra",female,17,4,2,3101281,7.925,,S
+70,0,3,"Kink, Mr. Vincenz",male,26,2,0,315151,8.6625,,S
+71,0,2,"Jenkin, Mr. Stephen Curnow",male,32,0,0,C.A. 33111,10.5,,S
+72,0,3,"Goodwin, Miss. Lillian Amy",female,16,5,2,CA 2144,46.9,,S
+73,0,2,"Hood, Mr. Ambrose Jr",male,21,0,0,S.O.C. 14879,73.5,,S
+74,0,3,"Chronopoulos, Mr. Apostolos",male,26,1,0,2680,14.4542,,C
+75,1,3,"Bing, Mr. Lee",male,32,0,0,1601,56.4958,,S
+76,0,3,"Moen, Mr. Sigurd Hansen",male,25,0,0,348123,7.65,F G73,S
+77,0,3,"Staneff, Mr. Ivan",male,,0,0,349208,7.8958,,S
+78,0,3,"Moutal, Mr. Rahamin Haim",male,,0,0,374746,8.05,,S
+79,1,2,"Caldwell, Master. Alden Gates",male,0.83,0,2,248738,29,,S
+80,1,3,"Dowdell, Miss. Elizabeth",female,30,0,0,364516,12.475,,S
+81,0,3,"Waelens, Mr. Achille",male,22,0,0,345767,9,,S
+82,1,3,"Sheerlinck, Mr. Jan Baptist",male,29,0,0,345779,9.5,,S
+83,1,3,"McDermott, Miss. Brigdet Delia",female,,0,0,330932,7.7875,,Q
+84,0,1,"Carrau, Mr. Francisco M",male,28,0,0,113059,47.1,,S
+85,1,2,"Ilett, Miss. Bertha",female,17,0,0,SO/C 14885,10.5,,S
+86,1,3,"Backstrom, Mrs. Karl Alfred (Maria Mathilda Gustafsson)",female,33,3,0,3101278,15.85,,S
+87,0,3,"Ford, Mr. William Neal",male,16,1,3,W./C. 6608,34.375,,S
+88,0,3,"Slocovski, Mr. Selman Francis",male,,0,0,SOTON/OQ 392086,8.05,,S
+89,1,1,"Fortune, Miss. Mabel Helen",female,23,3,2,19950,263,C23 C25 C27,S
+90,0,3,"Celotti, Mr. Francesco",male,24,0,0,343275,8.05,,S
+91,0,3,"Christmann, Mr. Emil",male,29,0,0,343276,8.05,,S
+92,0,3,"Andreasson, Mr. Paul Edvin",male,20,0,0,347466,7.8542,,S
+93,0,1,"Chaffee, Mr. Herbert Fuller",male,46,1,0,W.E.P. 5734,61.175,E31,S
+94,0,3,"Dean, Mr. Bertram Frank",male,26,1,2,C.A. 2315,20.575,,S
+95,0,3,"Coxon, Mr. Daniel",male,59,0,0,364500,7.25,,S
+96,0,3,"Shorney, Mr. Charles Joseph",male,,0,0,374910,8.05,,S
+97,0,1,"Goldschmidt, Mr. George B",male,71,0,0,PC 17754,34.6542,A5,C
+98,1,1,"Greenfield, Mr. William Bertram",male,23,0,1,PC 17759,63.3583,D10 D12,C
+99,1,2,"Doling, Mrs. John T (Ada Julia Bone)",female,34,0,1,231919,23,,S
+100,0,2,"Kantor, Mr. Sinai",male,34,1,0,244367,26,,S
+101,0,3,"Petranec, Miss. Matilda",female,28,0,0,349245,7.8958,,S
+102,0,3,"Petroff, Mr. Pastcho (""Pentcho"")",male,,0,0,349215,7.8958,,S
+103,0,1,"White, Mr. Richard Frasar",male,21,0,1,35281,77.2875,D26,S
+104,0,3,"Johansson, Mr. Gustaf Joel",male,33,0,0,7540,8.6542,,S
+105,0,3,"Gustafsson, Mr. Anders Vilhelm",male,37,2,0,3101276,7.925,,S
+106,0,3,"Mionoff, Mr. Stoytcho",male,28,0,0,349207,7.8958,,S
+107,1,3,"Salkjelsvik, Miss. Anna Kristine",female,21,0,0,343120,7.65,,S
+108,1,3,"Moss, Mr. Albert Johan",male,,0,0,312991,7.775,,S
+109,0,3,"Rekic, Mr. Tido",male,38,0,0,349249,7.8958,,S
+110,1,3,"Moran, Miss. Bertha",female,,1,0,371110,24.15,,Q
+111,0,1,"Porter, Mr. Walter Chamberlain",male,47,0,0,110465,52,C110,S
+112,0,3,"Zabour, Miss. Hileni",female,14.5,1,0,2665,14.4542,,C
+113,0,3,"Barton, Mr. David John",male,22,0,0,324669,8.05,,S
+114,0,3,"Jussila, Miss. Katriina",female,20,1,0,4136,9.825,,S
+115,0,3,"Attalah, Miss. Malake",female,17,0,0,2627,14.4583,,C
+116,0,3,"Pekoniemi, Mr. Edvard",male,21,0,0,STON/O 2. 3101294,7.925,,S
+117,0,3,"Connors, Mr. Patrick",male,70.5,0,0,370369,7.75,,Q
+118,0,2,"Turpin, Mr. William John Robert",male,29,1,0,11668,21,,S
+119,0,1,"Baxter, Mr. Quigg Edmond",male,24,0,1,PC 17558,247.5208,B58 B60,C
+120,0,3,"Andersson, Miss. Ellis Anna Maria",female,2,4,2,347082,31.275,,S
+121,0,2,"Hickman, Mr. Stanley George",male,21,2,0,S.O.C. 14879,73.5,,S
+122,0,3,"Moore, Mr. Leonard Charles",male,,0,0,A4. 54510,8.05,,S
+123,0,2,"Nasser, Mr. Nicholas",male,32.5,1,0,237736,30.0708,,C
+124,1,2,"Webber, Miss. Susan",female,32.5,0,0,27267,13,E101,S
+125,0,1,"White, Mr. Percival Wayland",male,54,0,1,35281,77.2875,D26,S
+126,1,3,"Nicola-Yarred, Master. Elias",male,12,1,0,2651,11.2417,,C
+127,0,3,"McMahon, Mr. Martin",male,,0,0,370372,7.75,,Q
+128,1,3,"Madsen, Mr. Fridtjof Arne",male,24,0,0,C 17369,7.1417,,S
+129,1,3,"Peter, Miss. Anna",female,,1,1,2668,22.3583,F E69,C
+130,0,3,"Ekstrom, Mr. Johan",male,45,0,0,347061,6.975,,S
+131,0,3,"Drazenoic, Mr. Jozef",male,33,0,0,349241,7.8958,,C
+132,0,3,"Coelho, Mr. Domingos Fernandeo",male,20,0,0,SOTON/O.Q. 3101307,7.05,,S
+133,0,3,"Robins, Mrs. Alexander A (Grace Charity Laury)",female,47,1,0,A/5. 3337,14.5,,S
+134,1,2,"Weisz, Mrs. Leopold (Mathilde Francoise Pede)",female,29,1,0,228414,26,,S
+135,0,2,"Sobey, Mr. Samuel James Hayden",male,25,0,0,C.A. 29178,13,,S
+136,0,2,"Richard, Mr. Emile",male,23,0,0,SC/PARIS 2133,15.0458,,C
+137,1,1,"Newsom, Miss. Helen Monypeny",female,19,0,2,11752,26.2833,D47,S
+138,0,1,"Futrelle, Mr. Jacques Heath",male,37,1,0,113803,53.1,C123,S
+139,0,3,"Osen, Mr. Olaf Elon",male,16,0,0,7534,9.2167,,S
+140,0,1,"Giglio, Mr. Victor",male,24,0,0,PC 17593,79.2,B86,C
+141,0,3,"Boulos, Mrs. Joseph (Sultana)",female,,0,2,2678,15.2458,,C
+142,1,3,"Nysten, Miss. Anna Sofia",female,22,0,0,347081,7.75,,S
+143,1,3,"Hakkarainen, Mrs. Pekka Pietari (Elin Matilda Dolck)",female,24,1,0,STON/O2. 3101279,15.85,,S
+144,0,3,"Burke, Mr. Jeremiah",male,19,0,0,365222,6.75,,Q
+145,0,2,"Andrew, Mr. Edgardo Samuel",male,18,0,0,231945,11.5,,S
+146,0,2,"Nicholls, Mr. Joseph Charles",male,19,1,1,C.A. 33112,36.75,,S
+147,1,3,"Andersson, Mr. August Edvard (""Wennerstrom"")",male,27,0,0,350043,7.7958,,S
+148,0,3,"Ford, Miss. Robina Maggie ""Ruby""",female,9,2,2,W./C. 6608,34.375,,S
+149,0,2,"Navratil, Mr. Michel (""Louis M Hoffman"")",male,36.5,0,2,230080,26,F2,S
+150,0,2,"Byles, Rev. Thomas Roussel Davids",male,42,0,0,244310,13,,S
+151,0,2,"Bateman, Rev. Robert James",male,51,0,0,S.O.P. 1166,12.525,,S
+152,1,1,"Pears, Mrs. Thomas (Edith Wearne)",female,22,1,0,113776,66.6,C2,S
+153,0,3,"Meo, Mr. Alfonzo",male,55.5,0,0,A.5. 11206,8.05,,S
+154,0,3,"van Billiard, Mr. Austin Blyler",male,40.5,0,2,A/5. 851,14.5,,S
+155,0,3,"Olsen, Mr. Ole Martin",male,,0,0,Fa 265302,7.3125,,S
+156,0,1,"Williams, Mr. Charles Duane",male,51,0,1,PC 17597,61.3792,,C
+157,1,3,"Gilnagh, Miss. Katherine ""Katie""",female,16,0,0,35851,7.7333,,Q
+158,0,3,"Corn, Mr. Harry",male,30,0,0,SOTON/OQ 392090,8.05,,S
+159,0,3,"Smiljanic, Mr. Mile",male,,0,0,315037,8.6625,,S
+160,0,3,"Sage, Master. Thomas Henry",male,,8,2,CA. 2343,69.55,,S
+161,0,3,"Cribb, Mr. John Hatfield",male,44,0,1,371362,16.1,,S
+162,1,2,"Watt, Mrs. James (Elizabeth ""Bessie"" Inglis Milne)",female,40,0,0,C.A. 33595,15.75,,S
+163,0,3,"Bengtsson, Mr. John Viktor",male,26,0,0,347068,7.775,,S
+164,0,3,"Calic, Mr. Jovo",male,17,0,0,315093,8.6625,,S
+165,0,3,"Panula, Master. Eino Viljami",male,1,4,1,3101295,39.6875,,S
+166,1,3,"Goldsmith, Master. Frank John William ""Frankie""",male,9,0,2,363291,20.525,,S
+167,1,1,"Chibnall, Mrs. (Edith Martha Bowerman)",female,,0,1,113505,55,E33,S
+168,0,3,"Skoog, Mrs. William (Anna Bernhardina Karlsson)",female,45,1,4,347088,27.9,,S
+169,0,1,"Baumann, Mr. John D",male,,0,0,PC 17318,25.925,,S
+170,0,3,"Ling, Mr. Lee",male,28,0,0,1601,56.4958,,S
+171,0,1,"Van der hoef, Mr. Wyckoff",male,61,0,0,111240,33.5,B19,S
+172,0,3,"Rice, Master. Arthur",male,4,4,1,382652,29.125,,Q
+173,1,3,"Johnson, Miss. Eleanor Ileen",female,1,1,1,347742,11.1333,,S
+174,0,3,"Sivola, Mr. Antti Wilhelm",male,21,0,0,STON/O 2. 3101280,7.925,,S
+175,0,1,"Smith, Mr. James Clinch",male,56,0,0,17764,30.6958,A7,C
+176,0,3,"Klasen, Mr. Klas Albin",male,18,1,1,350404,7.8542,,S
+177,0,3,"Lefebre, Master. Henry Forbes",male,,3,1,4133,25.4667,,S
+178,0,1,"Isham, Miss. Ann Elizabeth",female,50,0,0,PC 17595,28.7125,C49,C
+179,0,2,"Hale, Mr. Reginald",male,30,0,0,250653,13,,S
+180,0,3,"Leonard, Mr. Lionel",male,36,0,0,LINE,0,,S
+181,0,3,"Sage, Miss. Constance Gladys",female,,8,2,CA. 2343,69.55,,S
+182,0,2,"Pernot, Mr. Rene",male,,0,0,SC/PARIS 2131,15.05,,C
+183,0,3,"Asplund, Master. Clarence Gustaf Hugo",male,9,4,2,347077,31.3875,,S
+184,1,2,"Becker, Master. Richard F",male,1,2,1,230136,39,F4,S
+185,1,3,"Kink-Heilmann, Miss. Luise Gretchen",female,4,0,2,315153,22.025,,S
+186,0,1,"Rood, Mr. Hugh Roscoe",male,,0,0,113767,50,A32,S
+187,1,3,"O'Brien, Mrs. Thomas (Johanna ""Hannah"" Godfrey)",female,,1,0,370365,15.5,,Q
+188,1,1,"Romaine, Mr. Charles Hallace (""Mr C Rolmane"")",male,45,0,0,111428,26.55,,S
+189,0,3,"Bourke, Mr. John",male,40,1,1,364849,15.5,,Q
+190,0,3,"Turcin, Mr. Stjepan",male,36,0,0,349247,7.8958,,S
+191,1,2,"Pinsky, Mrs. (Rosa)",female,32,0,0,234604,13,,S
+192,0,2,"Carbines, Mr. William",male,19,0,0,28424,13,,S
+193,1,3,"Andersen-Jensen, Miss. Carla Christine Nielsine",female,19,1,0,350046,7.8542,,S
+194,1,2,"Navratil, Master. Michel M",male,3,1,1,230080,26,F2,S
+195,1,1,"Brown, Mrs. James Joseph (Margaret Tobin)",female,44,0,0,PC 17610,27.7208,B4,C
+196,1,1,"Lurette, Miss. Elise",female,58,0,0,PC 17569,146.5208,B80,C
+197,0,3,"Mernagh, Mr. Robert",male,,0,0,368703,7.75,,Q
+198,0,3,"Olsen, Mr. Karl Siegwart Andreas",male,42,0,1,4579,8.4042,,S
+199,1,3,"Madigan, Miss. Margaret ""Maggie""",female,,0,0,370370,7.75,,Q
+200,0,2,"Yrois, Miss. Henriette (""Mrs Harbeck"")",female,24,0,0,248747,13,,S
+201,0,3,"Vande Walle, Mr. Nestor Cyriel",male,28,0,0,345770,9.5,,S
+202,0,3,"Sage, Mr. Frederick",male,,8,2,CA. 2343,69.55,,S
+203,0,3,"Johanson, Mr. Jakob Alfred",male,34,0,0,3101264,6.4958,,S
+204,0,3,"Youseff, Mr. Gerious",male,45.5,0,0,2628,7.225,,C
+205,1,3,"Cohen, Mr. Gurshon ""Gus""",male,18,0,0,A/5 3540,8.05,,S
+206,0,3,"Strom, Miss. Telma Matilda",female,2,0,1,347054,10.4625,G6,S
+207,0,3,"Backstrom, Mr. Karl Alfred",male,32,1,0,3101278,15.85,,S
+208,1,3,"Albimona, Mr. Nassef Cassem",male,26,0,0,2699,18.7875,,C
+209,1,3,"Carr, Miss. Helen ""Ellen""",female,16,0,0,367231,7.75,,Q
+210,1,1,"Blank, Mr. Henry",male,40,0,0,112277,31,A31,C
+211,0,3,"Ali, Mr. Ahmed",male,24,0,0,SOTON/O.Q. 3101311,7.05,,S
+212,1,2,"Cameron, Miss. Clear Annie",female,35,0,0,F.C.C. 13528,21,,S
+213,0,3,"Perkin, Mr. John Henry",male,22,0,0,A/5 21174,7.25,,S
+214,0,2,"Givard, Mr. Hans Kristensen",male,30,0,0,250646,13,,S
+215,0,3,"Kiernan, Mr. Philip",male,,1,0,367229,7.75,,Q
+216,1,1,"Newell, Miss. Madeleine",female,31,1,0,35273,113.275,D36,C
+217,1,3,"Honkanen, Miss. Eliina",female,27,0,0,STON/O2. 3101283,7.925,,S
+218,0,2,"Jacobsohn, Mr. Sidney Samuel",male,42,1,0,243847,27,,S
+219,1,1,"Bazzani, Miss. Albina",female,32,0,0,11813,76.2917,D15,C
+220,0,2,"Harris, Mr. Walter",male,30,0,0,W/C 14208,10.5,,S
+221,1,3,"Sunderland, Mr. Victor Francis",male,16,0,0,SOTON/OQ 392089,8.05,,S
+222,0,2,"Bracken, Mr. James H",male,27,0,0,220367,13,,S
+223,0,3,"Green, Mr. George Henry",male,51,0,0,21440,8.05,,S
+224,0,3,"Nenkoff, Mr. Christo",male,,0,0,349234,7.8958,,S
+225,1,1,"Hoyt, Mr. Frederick Maxfield",male,38,1,0,19943,90,C93,S
+226,0,3,"Berglund, Mr. Karl Ivar Sven",male,22,0,0,PP 4348,9.35,,S
+227,1,2,"Mellors, Mr. William John",male,19,0,0,SW/PP 751,10.5,,S
+228,0,3,"Lovell, Mr. John Hall (""Henry"")",male,20.5,0,0,A/5 21173,7.25,,S
+229,0,2,"Fahlstrom, Mr. Arne Jonas",male,18,0,0,236171,13,,S
+230,0,3,"Lefebre, Miss. Mathilde",female,,3,1,4133,25.4667,,S
+231,1,1,"Harris, Mrs. Henry Birkhardt (Irene Wallach)",female,35,1,0,36973,83.475,C83,S
+232,0,3,"Larsson, Mr. Bengt Edvin",male,29,0,0,347067,7.775,,S
+233,0,2,"Sjostedt, Mr. Ernst Adolf",male,59,0,0,237442,13.5,,S
+234,1,3,"Asplund, Miss. Lillian Gertrud",female,5,4,2,347077,31.3875,,S
+235,0,2,"Leyson, Mr. Robert William Norman",male,24,0,0,C.A. 29566,10.5,,S
+236,0,3,"Harknett, Miss. Alice Phoebe",female,,0,0,W./C. 6609,7.55,,S
+237,0,2,"Hold, Mr. Stephen",male,44,1,0,26707,26,,S
+238,1,2,"Collyer, Miss. Marjorie ""Lottie""",female,8,0,2,C.A. 31921,26.25,,S
+239,0,2,"Pengelly, Mr. Frederick William",male,19,0,0,28665,10.5,,S
+240,0,2,"Hunt, Mr. George Henry",male,33,0,0,SCO/W 1585,12.275,,S
+241,0,3,"Zabour, Miss. Thamine",female,,1,0,2665,14.4542,,C
+242,1,3,"Murphy, Miss. Katherine ""Kate""",female,,1,0,367230,15.5,,Q
+243,0,2,"Coleridge, Mr. Reginald Charles",male,29,0,0,W./C. 14263,10.5,,S
+244,0,3,"Maenpaa, Mr. Matti Alexanteri",male,22,0,0,STON/O 2. 3101275,7.125,,S
+245,0,3,"Attalah, Mr. Sleiman",male,30,0,0,2694,7.225,,C
+246,0,1,"Minahan, Dr. William Edward",male,44,2,0,19928,90,C78,Q
+247,0,3,"Lindahl, Miss. Agda Thorilda Viktoria",female,25,0,0,347071,7.775,,S
+248,1,2,"Hamalainen, Mrs. William (Anna)",female,24,0,2,250649,14.5,,S
+249,1,1,"Beckwith, Mr. Richard Leonard",male,37,1,1,11751,52.5542,D35,S
+250,0,2,"Carter, Rev. Ernest Courtenay",male,54,1,0,244252,26,,S
+251,0,3,"Reed, Mr. James George",male,,0,0,362316,7.25,,S
+252,0,3,"Strom, Mrs. Wilhelm (Elna Matilda Persson)",female,29,1,1,347054,10.4625,G6,S
+253,0,1,"Stead, Mr. William Thomas",male,62,0,0,113514,26.55,C87,S
+254,0,3,"Lobb, Mr. William Arthur",male,30,1,0,A/5. 3336,16.1,,S
+255,0,3,"Rosblom, Mrs. Viktor (Helena Wilhelmina)",female,41,0,2,370129,20.2125,,S
+256,1,3,"Touma, Mrs. Darwis (Hanne Youssef Razi)",female,29,0,2,2650,15.2458,,C
+257,1,1,"Thorne, Mrs. Gertrude Maybelle",female,,0,0,PC 17585,79.2,,C
+258,1,1,"Cherry, Miss. Gladys",female,30,0,0,110152,86.5,B77,S
+259,1,1,"Ward, Miss. Anna",female,35,0,0,PC 17755,512.3292,,C
+260,1,2,"Parrish, Mrs. (Lutie Davis)",female,50,0,1,230433,26,,S
+261,0,3,"Smith, Mr. Thomas",male,,0,0,384461,7.75,,Q
+262,1,3,"Asplund, Master. Edvin Rojj Felix",male,3,4,2,347077,31.3875,,S
+263,0,1,"Taussig, Mr. Emil",male,52,1,1,110413,79.65,E67,S
+264,0,1,"Harrison, Mr. William",male,40,0,0,112059,0,B94,S
+265,0,3,"Henry, Miss. Delia",female,,0,0,382649,7.75,,Q
+266,0,2,"Reeves, Mr. David",male,36,0,0,C.A. 17248,10.5,,S
+267,0,3,"Panula, Mr. Ernesti Arvid",male,16,4,1,3101295,39.6875,,S
+268,1,3,"Persson, Mr. Ernst Ulrik",male,25,1,0,347083,7.775,,S
+269,1,1,"Graham, Mrs. William Thompson (Edith Junkins)",female,58,0,1,PC 17582,153.4625,C125,S
+270,1,1,"Bissette, Miss. Amelia",female,35,0,0,PC 17760,135.6333,C99,S
+271,0,1,"Cairns, Mr. Alexander",male,,0,0,113798,31,,S
+272,1,3,"Tornquist, Mr. William Henry",male,25,0,0,LINE,0,,S
+273,1,2,"Mellinger, Mrs. (Elizabeth Anne Maidment)",female,41,0,1,250644,19.5,,S
+274,0,1,"Natsch, Mr. Charles H",male,37,0,1,PC 17596,29.7,C118,C
+275,1,3,"Healy, Miss. Hanora ""Nora""",female,,0,0,370375,7.75,,Q
+276,1,1,"Andrews, Miss. Kornelia Theodosia",female,63,1,0,13502,77.9583,D7,S
+277,0,3,"Lindblom, Miss. Augusta Charlotta",female,45,0,0,347073,7.75,,S
+278,0,2,"Parkes, Mr. Francis ""Frank""",male,,0,0,239853,0,,S
+279,0,3,"Rice, Master. Eric",male,7,4,1,382652,29.125,,Q
+280,1,3,"Abbott, Mrs. Stanton (Rosa Hunt)",female,35,1,1,C.A. 2673,20.25,,S
+281,0,3,"Duane, Mr. Frank",male,65,0,0,336439,7.75,,Q
+282,0,3,"Olsson, Mr. Nils Johan Goransson",male,28,0,0,347464,7.8542,,S
+283,0,3,"de Pelsmaeker, Mr. Alfons",male,16,0,0,345778,9.5,,S
+284,1,3,"Dorking, Mr. Edward Arthur",male,19,0,0,A/5. 10482,8.05,,S
+285,0,1,"Smith, Mr. Richard William",male,,0,0,113056,26,A19,S
+286,0,3,"Stankovic, Mr. Ivan",male,33,0,0,349239,8.6625,,C
+287,1,3,"de Mulder, Mr. Theodore",male,30,0,0,345774,9.5,,S
+288,0,3,"Naidenoff, Mr. Penko",male,22,0,0,349206,7.8958,,S
+289,1,2,"Hosono, Mr. Masabumi",male,42,0,0,237798,13,,S
+290,1,3,"Connolly, Miss. Kate",female,22,0,0,370373,7.75,,Q
+291,1,1,"Barber, Miss. Ellen ""Nellie""",female,26,0,0,19877,78.85,,S
+292,1,1,"Bishop, Mrs. Dickinson H (Helen Walton)",female,19,1,0,11967,91.0792,B49,C
+293,0,2,"Levy, Mr. Rene Jacques",male,36,0,0,SC/Paris 2163,12.875,D,C
+294,0,3,"Haas, Miss. Aloisia",female,24,0,0,349236,8.85,,S
+295,0,3,"Mineff, Mr. Ivan",male,24,0,0,349233,7.8958,,S
+296,0,1,"Lewy, Mr. Ervin G",male,,0,0,PC 17612,27.7208,,C
+297,0,3,"Hanna, Mr. Mansour",male,23.5,0,0,2693,7.2292,,C
+298,0,1,"Allison, Miss. Helen Loraine",female,2,1,2,113781,151.55,C22 C26,S
+299,1,1,"Saalfeld, Mr. Adolphe",male,,0,0,19988,30.5,C106,S
+300,1,1,"Baxter, Mrs. James (Helene DeLaudeniere Chaput)",female,50,0,1,PC 17558,247.5208,B58 B60,C
+301,1,3,"Kelly, Miss. Anna Katherine ""Annie Kate""",female,,0,0,9234,7.75,,Q
+302,1,3,"McCoy, Mr. Bernard",male,,2,0,367226,23.25,,Q
+303,0,3,"Johnson, Mr. William Cahoone Jr",male,19,0,0,LINE,0,,S
+304,1,2,"Keane, Miss. Nora A",female,,0,0,226593,12.35,E101,Q
+305,0,3,"Williams, Mr. Howard Hugh ""Harry""",male,,0,0,A/5 2466,8.05,,S
+306,1,1,"Allison, Master. Hudson Trevor",male,0.92,1,2,113781,151.55,C22 C26,S
+307,1,1,"Fleming, Miss. Margaret",female,,0,0,17421,110.8833,,C
+308,1,1,"Penasco y Castellana, Mrs. Victor de Satode (Maria Josefa Perez de Soto y Vallejo)",female,17,1,0,PC 17758,108.9,C65,C
+309,0,2,"Abelson, Mr. Samuel",male,30,1,0,P/PP 3381,24,,C
+310,1,1,"Francatelli, Miss. Laura Mabel",female,30,0,0,PC 17485,56.9292,E36,C
+311,1,1,"Hays, Miss. Margaret Bechstein",female,24,0,0,11767,83.1583,C54,C
+312,1,1,"Ryerson, Miss. Emily Borie",female,18,2,2,PC 17608,262.375,B57 B59 B63 B66,C
+313,0,2,"Lahtinen, Mrs. William (Anna Sylfven)",female,26,1,1,250651,26,,S
+314,0,3,"Hendekovic, Mr. Ignjac",male,28,0,0,349243,7.8958,,S
+315,0,2,"Hart, Mr. Benjamin",male,43,1,1,F.C.C. 13529,26.25,,S
+316,1,3,"Nilsson, Miss. Helmina Josefina",female,26,0,0,347470,7.8542,,S
+317,1,2,"Kantor, Mrs. Sinai (Miriam Sternin)",female,24,1,0,244367,26,,S
+318,0,2,"Moraweck, Dr. Ernest",male,54,0,0,29011,14,,S
+319,1,1,"Wick, Miss. Mary Natalie",female,31,0,2,36928,164.8667,C7,S
+320,1,1,"Spedden, Mrs. Frederic Oakley (Margaretta Corning Stone)",female,40,1,1,16966,134.5,E34,C
+321,0,3,"Dennis, Mr. Samuel",male,22,0,0,A/5 21172,7.25,,S
+322,0,3,"Danoff, Mr. Yoto",male,27,0,0,349219,7.8958,,S
+323,1,2,"Slayter, Miss. Hilda Mary",female,30,0,0,234818,12.35,,Q
+324,1,2,"Caldwell, Mrs. Albert Francis (Sylvia Mae Harbaugh)",female,22,1,1,248738,29,,S
+325,0,3,"Sage, Mr. George John Jr",male,,8,2,CA. 2343,69.55,,S
+326,1,1,"Young, Miss. Marie Grice",female,36,0,0,PC 17760,135.6333,C32,C
+327,0,3,"Nysveen, Mr. Johan Hansen",male,61,0,0,345364,6.2375,,S
+328,1,2,"Ball, Mrs. (Ada E Hall)",female,36,0,0,28551,13,D,S
+329,1,3,"Goldsmith, Mrs. Frank John (Emily Alice Brown)",female,31,1,1,363291,20.525,,S
+330,1,1,"Hippach, Miss. Jean Gertrude",female,16,0,1,111361,57.9792,B18,C
+331,1,3,"McCoy, Miss. Agnes",female,,2,0,367226,23.25,,Q
+332,0,1,"Partner, Mr. Austen",male,45.5,0,0,113043,28.5,C124,S
+333,0,1,"Graham, Mr. George Edward",male,38,0,1,PC 17582,153.4625,C91,S
+334,0,3,"Vander Planke, Mr. Leo Edmondus",male,16,2,0,345764,18,,S
+335,1,1,"Frauenthal, Mrs. Henry William (Clara Heinsheimer)",female,,1,0,PC 17611,133.65,,S
+336,0,3,"Denkoff, Mr. Mitto",male,,0,0,349225,7.8958,,S
+337,0,1,"Pears, Mr. Thomas Clinton",male,29,1,0,113776,66.6,C2,S
+338,1,1,"Burns, Miss. Elizabeth Margaret",female,41,0,0,16966,134.5,E40,C
+339,1,3,"Dahl, Mr. Karl Edwart",male,45,0,0,7598,8.05,,S
+340,0,1,"Blackwell, Mr. Stephen Weart",male,45,0,0,113784,35.5,T,S
+341,1,2,"Navratil, Master. Edmond Roger",male,2,1,1,230080,26,F2,S
+342,1,1,"Fortune, Miss. Alice Elizabeth",female,24,3,2,19950,263,C23 C25 C27,S
+343,0,2,"Collander, Mr. Erik Gustaf",male,28,0,0,248740,13,,S
+344,0,2,"Sedgwick, Mr. Charles Frederick Waddington",male,25,0,0,244361,13,,S
+345,0,2,"Fox, Mr. Stanley Hubert",male,36,0,0,229236,13,,S
+346,1,2,"Brown, Miss. Amelia ""Mildred""",female,24,0,0,248733,13,F33,S
+347,1,2,"Smith, Miss. Marion Elsie",female,40,0,0,31418,13,,S
+348,1,3,"Davison, Mrs. Thomas Henry (Mary E Finck)",female,,1,0,386525,16.1,,S
+349,1,3,"Coutts, Master. William Loch ""William""",male,3,1,1,C.A. 37671,15.9,,S
+350,0,3,"Dimic, Mr. Jovan",male,42,0,0,315088,8.6625,,S
+351,0,3,"Odahl, Mr. Nils Martin",male,23,0,0,7267,9.225,,S
+352,0,1,"Williams-Lambert, Mr. Fletcher Fellows",male,,0,0,113510,35,C128,S
+353,0,3,"Elias, Mr. Tannous",male,15,1,1,2695,7.2292,,C
+354,0,3,"Arnold-Franchi, Mr. Josef",male,25,1,0,349237,17.8,,S
+355,0,3,"Yousif, Mr. Wazli",male,,0,0,2647,7.225,,C
+356,0,3,"Vanden Steen, Mr. Leo Peter",male,28,0,0,345783,9.5,,S
+357,1,1,"Bowerman, Miss. Elsie Edith",female,22,0,1,113505,55,E33,S
+358,0,2,"Funk, Miss. Annie Clemmer",female,38,0,0,237671,13,,S
+359,1,3,"McGovern, Miss. Mary",female,,0,0,330931,7.8792,,Q
+360,1,3,"Mockler, Miss. Helen Mary ""Ellie""",female,,0,0,330980,7.8792,,Q
+361,0,3,"Skoog, Mr. Wilhelm",male,40,1,4,347088,27.9,,S
+362,0,2,"del Carlo, Mr. Sebastiano",male,29,1,0,SC/PARIS 2167,27.7208,,C
+363,0,3,"Barbara, Mrs. (Catherine David)",female,45,0,1,2691,14.4542,,C
+364,0,3,"Asim, Mr. Adola",male,35,0,0,SOTON/O.Q. 3101310,7.05,,S
+365,0,3,"O'Brien, Mr. Thomas",male,,1,0,370365,15.5,,Q
+366,0,3,"Adahl, Mr. Mauritz Nils Martin",male,30,0,0,C 7076,7.25,,S
+367,1,1,"Warren, Mrs. Frank Manley (Anna Sophia Atkinson)",female,60,1,0,110813,75.25,D37,C
+368,1,3,"Moussa, Mrs. (Mantoura Boulos)",female,,0,0,2626,7.2292,,C
+369,1,3,"Jermyn, Miss. Annie",female,,0,0,14313,7.75,,Q
+370,1,1,"Aubart, Mme. Leontine Pauline",female,24,0,0,PC 17477,69.3,B35,C
+371,1,1,"Harder, Mr. George Achilles",male,25,1,0,11765,55.4417,E50,C
+372,0,3,"Wiklund, Mr. Jakob Alfred",male,18,1,0,3101267,6.4958,,S
+373,0,3,"Beavan, Mr. William Thomas",male,19,0,0,323951,8.05,,S
+374,0,1,"Ringhini, Mr. Sante",male,22,0,0,PC 17760,135.6333,,C
+375,0,3,"Palsson, Miss. Stina Viola",female,3,3,1,349909,21.075,,S
+376,1,1,"Meyer, Mrs. Edgar Joseph (Leila Saks)",female,,1,0,PC 17604,82.1708,,C
+377,1,3,"Landergren, Miss. Aurora Adelia",female,22,0,0,C 7077,7.25,,S
+378,0,1,"Widener, Mr. Harry Elkins",male,27,0,2,113503,211.5,C82,C
+379,0,3,"Betros, Mr. Tannous",male,20,0,0,2648,4.0125,,C
+380,0,3,"Gustafsson, Mr. Karl Gideon",male,19,0,0,347069,7.775,,S
+381,1,1,"Bidois, Miss. Rosalie",female,42,0,0,PC 17757,227.525,,C
+382,1,3,"Nakid, Miss. Maria (""Mary"")",female,1,0,2,2653,15.7417,,C
+383,0,3,"Tikkanen, Mr. Juho",male,32,0,0,STON/O 2. 3101293,7.925,,S
+384,1,1,"Holverson, Mrs. Alexander Oskar (Mary Aline Towner)",female,35,1,0,113789,52,,S
+385,0,3,"Plotcharsky, Mr. Vasil",male,,0,0,349227,7.8958,,S
+386,0,2,"Davies, Mr. Charles Henry",male,18,0,0,S.O.C. 14879,73.5,,S
+387,0,3,"Goodwin, Master. Sidney Leonard",male,1,5,2,CA 2144,46.9,,S
+388,1,2,"Buss, Miss. Kate",female,36,0,0,27849,13,,S
+389,0,3,"Sadlier, Mr. Matthew",male,,0,0,367655,7.7292,,Q
+390,1,2,"Lehmann, Miss. Bertha",female,17,0,0,SC 1748,12,,C
+391,1,1,"Carter, Mr. William Ernest",male,36,1,2,113760,120,B96 B98,S
+392,1,3,"Jansson, Mr. Carl Olof",male,21,0,0,350034,7.7958,,S
+393,0,3,"Gustafsson, Mr. Johan Birger",male,28,2,0,3101277,7.925,,S
+394,1,1,"Newell, Miss. Marjorie",female,23,1,0,35273,113.275,D36,C
+395,1,3,"Sandstrom, Mrs. Hjalmar (Agnes Charlotta Bengtsson)",female,24,0,2,PP 9549,16.7,G6,S
+396,0,3,"Johansson, Mr. Erik",male,22,0,0,350052,7.7958,,S
+397,0,3,"Olsson, Miss. Elina",female,31,0,0,350407,7.8542,,S
+398,0,2,"McKane, Mr. Peter David",male,46,0,0,28403,26,,S
+399,0,2,"Pain, Dr. Alfred",male,23,0,0,244278,10.5,,S
+400,1,2,"Trout, Mrs. William H (Jessie L)",female,28,0,0,240929,12.65,,S
+401,1,3,"Niskanen, Mr. Juha",male,39,0,0,STON/O 2. 3101289,7.925,,S
+402,0,3,"Adams, Mr. John",male,26,0,0,341826,8.05,,S
+403,0,3,"Jussila, Miss. Mari Aina",female,21,1,0,4137,9.825,,S
+404,0,3,"Hakkarainen, Mr. Pekka Pietari",male,28,1,0,STON/O2. 3101279,15.85,,S
+405,0,3,"Oreskovic, Miss. Marija",female,20,0,0,315096,8.6625,,S
+406,0,2,"Gale, Mr. Shadrach",male,34,1,0,28664,21,,S
+407,0,3,"Widegren, Mr. Carl/Charles Peter",male,51,0,0,347064,7.75,,S
+408,1,2,"Richards, Master. William Rowe",male,3,1,1,29106,18.75,,S
+409,0,3,"Birkeland, Mr. Hans Martin Monsen",male,21,0,0,312992,7.775,,S
+410,0,3,"Lefebre, Miss. Ida",female,,3,1,4133,25.4667,,S
+411,0,3,"Sdycoff, Mr. Todor",male,,0,0,349222,7.8958,,S
+412,0,3,"Hart, Mr. Henry",male,,0,0,394140,6.8583,,Q
+413,1,1,"Minahan, Miss. Daisy E",female,33,1,0,19928,90,C78,Q
+414,0,2,"Cunningham, Mr. Alfred Fleming",male,,0,0,239853,0,,S
+415,1,3,"Sundman, Mr. Johan Julian",male,44,0,0,STON/O 2. 3101269,7.925,,S
+416,0,3,"Meek, Mrs. Thomas (Annie Louise Rowley)",female,,0,0,343095,8.05,,S
+417,1,2,"Drew, Mrs. James Vivian (Lulu Thorne Christian)",female,34,1,1,28220,32.5,,S
+418,1,2,"Silven, Miss. Lyyli Karoliina",female,18,0,2,250652,13,,S
+419,0,2,"Matthews, Mr. William John",male,30,0,0,28228,13,,S
+420,0,3,"Van Impe, Miss. Catharina",female,10,0,2,345773,24.15,,S
+421,0,3,"Gheorgheff, Mr. Stanio",male,,0,0,349254,7.8958,,C
+422,0,3,"Charters, Mr. David",male,21,0,0,A/5. 13032,7.7333,,Q
+423,0,3,"Zimmerman, Mr. Leo",male,29,0,0,315082,7.875,,S
+424,0,3,"Danbom, Mrs. Ernst Gilbert (Anna Sigrid Maria Brogren)",female,28,1,1,347080,14.4,,S
+425,0,3,"Rosblom, Mr. Viktor Richard",male,18,1,1,370129,20.2125,,S
+426,0,3,"Wiseman, Mr. Phillippe",male,,0,0,A/4. 34244,7.25,,S
+427,1,2,"Clarke, Mrs. Charles V (Ada Maria Winfield)",female,28,1,0,2003,26,,S
+428,1,2,"Phillips, Miss. Kate Florence (""Mrs Kate Louise Phillips Marshall"")",female,19,0,0,250655,26,,S
+429,0,3,"Flynn, Mr. James",male,,0,0,364851,7.75,,Q
+430,1,3,"Pickard, Mr. Berk (Berk Trembisky)",male,32,0,0,SOTON/O.Q. 392078,8.05,E10,S
+431,1,1,"Bjornstrom-Steffansson, Mr. Mauritz Hakan",male,28,0,0,110564,26.55,C52,S
+432,1,3,"Thorneycroft, Mrs. Percival (Florence Kate White)",female,,1,0,376564,16.1,,S
+433,1,2,"Louch, Mrs. Charles Alexander (Alice Adelaide Slow)",female,42,1,0,SC/AH 3085,26,,S
+434,0,3,"Kallio, Mr. Nikolai Erland",male,17,0,0,STON/O 2. 3101274,7.125,,S
+435,0,1,"Silvey, Mr. William Baird",male,50,1,0,13507,55.9,E44,S
+436,1,1,"Carter, Miss. Lucile Polk",female,14,1,2,113760,120,B96 B98,S
+437,0,3,"Ford, Miss. Doolina Margaret ""Daisy""",female,21,2,2,W./C. 6608,34.375,,S
+438,1,2,"Richards, Mrs. Sidney (Emily Hocking)",female,24,2,3,29106,18.75,,S
+439,0,1,"Fortune, Mr. Mark",male,64,1,4,19950,263,C23 C25 C27,S
+440,0,2,"Kvillner, Mr. Johan Henrik Johannesson",male,31,0,0,C.A. 18723,10.5,,S
+441,1,2,"Hart, Mrs. Benjamin (Esther Ada Bloomfield)",female,45,1,1,F.C.C. 13529,26.25,,S
+442,0,3,"Hampe, Mr. Leon",male,20,0,0,345769,9.5,,S
+443,0,3,"Petterson, Mr. Johan Emil",male,25,1,0,347076,7.775,,S
+444,1,2,"Reynaldo, Ms. Encarnacion",female,28,0,0,230434,13,,S
+445,1,3,"Johannesen-Bratthammer, Mr. Bernt",male,,0,0,65306,8.1125,,S
+446,1,1,"Dodge, Master. Washington",male,4,0,2,33638,81.8583,A34,S
+447,1,2,"Mellinger, Miss. Madeleine Violet",female,13,0,1,250644,19.5,,S
+448,1,1,"Seward, Mr. Frederic Kimber",male,34,0,0,113794,26.55,,S
+449,1,3,"Baclini, Miss. Marie Catherine",female,5,2,1,2666,19.2583,,C
+450,1,1,"Peuchen, Major. Arthur Godfrey",male,52,0,0,113786,30.5,C104,S
+451,0,2,"West, Mr. Edwy Arthur",male,36,1,2,C.A. 34651,27.75,,S
+452,0,3,"Hagland, Mr. Ingvald Olai Olsen",male,,1,0,65303,19.9667,,S
+453,0,1,"Foreman, Mr. Benjamin Laventall",male,30,0,0,113051,27.75,C111,C
+454,1,1,"Goldenberg, Mr. Samuel L",male,49,1,0,17453,89.1042,C92,C
+455,0,3,"Peduzzi, Mr. Joseph",male,,0,0,A/5 2817,8.05,,S
+456,1,3,"Jalsevac, Mr. Ivan",male,29,0,0,349240,7.8958,,C
+457,0,1,"Millet, Mr. Francis Davis",male,65,0,0,13509,26.55,E38,S
+458,1,1,"Kenyon, Mrs. Frederick R (Marion)",female,,1,0,17464,51.8625,D21,S
+459,1,2,"Toomey, Miss. Ellen",female,50,0,0,F.C.C. 13531,10.5,,S
+460,0,3,"O'Connor, Mr. Maurice",male,,0,0,371060,7.75,,Q
+461,1,1,"Anderson, Mr. Harry",male,48,0,0,19952,26.55,E12,S
+462,0,3,"Morley, Mr. William",male,34,0,0,364506,8.05,,S
+463,0,1,"Gee, Mr. Arthur H",male,47,0,0,111320,38.5,E63,S
+464,0,2,"Milling, Mr. Jacob Christian",male,48,0,0,234360,13,,S
+465,0,3,"Maisner, Mr. Simon",male,,0,0,A/S 2816,8.05,,S
+466,0,3,"Goncalves, Mr. Manuel Estanslas",male,38,0,0,SOTON/O.Q. 3101306,7.05,,S
+467,0,2,"Campbell, Mr. William",male,,0,0,239853,0,,S
+468,0,1,"Smart, Mr. John Montgomery",male,56,0,0,113792,26.55,,S
+469,0,3,"Scanlan, Mr. James",male,,0,0,36209,7.725,,Q
+470,1,3,"Baclini, Miss. Helene Barbara",female,0.75,2,1,2666,19.2583,,C
+471,0,3,"Keefe, Mr. Arthur",male,,0,0,323592,7.25,,S
+472,0,3,"Cacic, Mr. Luka",male,38,0,0,315089,8.6625,,S
+473,1,2,"West, Mrs. Edwy Arthur (Ada Mary Worth)",female,33,1,2,C.A. 34651,27.75,,S
+474,1,2,"Jerwan, Mrs. Amin S (Marie Marthe Thuillard)",female,23,0,0,SC/AH Basle 541,13.7917,D,C
+475,0,3,"Strandberg, Miss. Ida Sofia",female,22,0,0,7553,9.8375,,S
+476,0,1,"Clifford, Mr. George Quincy",male,,0,0,110465,52,A14,S
+477,0,2,"Renouf, Mr. Peter Henry",male,34,1,0,31027,21,,S
+478,0,3,"Braund, Mr. Lewis Richard",male,29,1,0,3460,7.0458,,S
+479,0,3,"Karlsson, Mr. Nils August",male,22,0,0,350060,7.5208,,S
+480,1,3,"Hirvonen, Miss. Hildur E",female,2,0,1,3101298,12.2875,,S
+481,0,3,"Goodwin, Master. Harold Victor",male,9,5,2,CA 2144,46.9,,S
+482,0,2,"Frost, Mr. Anthony Wood ""Archie""",male,,0,0,239854,0,,S
+483,0,3,"Rouse, Mr. Richard Henry",male,50,0,0,A/5 3594,8.05,,S
+484,1,3,"Turkula, Mrs. (Hedwig)",female,63,0,0,4134,9.5875,,S
+485,1,1,"Bishop, Mr. Dickinson H",male,25,1,0,11967,91.0792,B49,C
+486,0,3,"Lefebre, Miss. Jeannie",female,,3,1,4133,25.4667,,S
+487,1,1,"Hoyt, Mrs. Frederick Maxfield (Jane Anne Forby)",female,35,1,0,19943,90,C93,S
+488,0,1,"Kent, Mr. Edward Austin",male,58,0,0,11771,29.7,B37,C
+489,0,3,"Somerton, Mr. Francis William",male,30,0,0,A.5. 18509,8.05,,S
+490,1,3,"Coutts, Master. Eden Leslie ""Neville""",male,9,1,1,C.A. 37671,15.9,,S
+491,0,3,"Hagland, Mr. Konrad Mathias Reiersen",male,,1,0,65304,19.9667,,S
+492,0,3,"Windelov, Mr. Einar",male,21,0,0,SOTON/OQ 3101317,7.25,,S
+493,0,1,"Molson, Mr. Harry Markland",male,55,0,0,113787,30.5,C30,S
+494,0,1,"Artagaveytia, Mr. Ramon",male,71,0,0,PC 17609,49.5042,,C
+495,0,3,"Stanley, Mr. Edward Roland",male,21,0,0,A/4 45380,8.05,,S
+496,0,3,"Yousseff, Mr. Gerious",male,,0,0,2627,14.4583,,C
+497,1,1,"Eustis, Miss. Elizabeth Mussey",female,54,1,0,36947,78.2667,D20,C
+498,0,3,"Shellard, Mr. Frederick William",male,,0,0,C.A. 6212,15.1,,S
+499,0,1,"Allison, Mrs. Hudson J C (Bessie Waldo Daniels)",female,25,1,2,113781,151.55,C22 C26,S
+500,0,3,"Svensson, Mr. Olof",male,24,0,0,350035,7.7958,,S
+501,0,3,"Calic, Mr. Petar",male,17,0,0,315086,8.6625,,S
+502,0,3,"Canavan, Miss. Mary",female,21,0,0,364846,7.75,,Q
+503,0,3,"O'Sullivan, Miss. Bridget Mary",female,,0,0,330909,7.6292,,Q
+504,0,3,"Laitinen, Miss. Kristina Sofia",female,37,0,0,4135,9.5875,,S
+505,1,1,"Maioni, Miss. Roberta",female,16,0,0,110152,86.5,B79,S
+506,0,1,"Penasco y Castellana, Mr. Victor de Satode",male,18,1,0,PC 17758,108.9,C65,C
+507,1,2,"Quick, Mrs. Frederick Charles (Jane Richards)",female,33,0,2,26360,26,,S
+508,1,1,"Bradley, Mr. George (""George Arthur Brayton"")",male,,0,0,111427,26.55,,S
+509,0,3,"Olsen, Mr. Henry Margido",male,28,0,0,C 4001,22.525,,S
+510,1,3,"Lang, Mr. Fang",male,26,0,0,1601,56.4958,,S
+511,1,3,"Daly, Mr. Eugene Patrick",male,29,0,0,382651,7.75,,Q
+512,0,3,"Webber, Mr. James",male,,0,0,SOTON/OQ 3101316,8.05,,S
+513,1,1,"McGough, Mr. James Robert",male,36,0,0,PC 17473,26.2875,E25,S
+514,1,1,"Rothschild, Mrs. Martin (Elizabeth L. Barrett)",female,54,1,0,PC 17603,59.4,,C
+515,0,3,"Coleff, Mr. Satio",male,24,0,0,349209,7.4958,,S
+516,0,1,"Walker, Mr. William Anderson",male,47,0,0,36967,34.0208,D46,S
+517,1,2,"Lemore, Mrs. (Amelia Milley)",female,34,0,0,C.A. 34260,10.5,F33,S
+518,0,3,"Ryan, Mr. Patrick",male,,0,0,371110,24.15,,Q
+519,1,2,"Angle, Mrs. William A (Florence ""Mary"" Agnes Hughes)",female,36,1,0,226875,26,,S
+520,0,3,"Pavlovic, Mr. Stefo",male,32,0,0,349242,7.8958,,S
+521,1,1,"Perreault, Miss. Anne",female,30,0,0,12749,93.5,B73,S
+522,0,3,"Vovk, Mr. Janko",male,22,0,0,349252,7.8958,,S
+523,0,3,"Lahoud, Mr. Sarkis",male,,0,0,2624,7.225,,C
+524,1,1,"Hippach, Mrs. Louis Albert (Ida Sophia Fischer)",female,44,0,1,111361,57.9792,B18,C
+525,0,3,"Kassem, Mr. Fared",male,,0,0,2700,7.2292,,C
+526,0,3,"Farrell, Mr. James",male,40.5,0,0,367232,7.75,,Q
+527,1,2,"Ridsdale, Miss. Lucy",female,50,0,0,W./C. 14258,10.5,,S
+528,0,1,"Farthing, Mr. John",male,,0,0,PC 17483,221.7792,C95,S
+529,0,3,"Salonen, Mr. Johan Werner",male,39,0,0,3101296,7.925,,S
+530,0,2,"Hocking, Mr. Richard George",male,23,2,1,29104,11.5,,S
+531,1,2,"Quick, Miss. Phyllis May",female,2,1,1,26360,26,,S
+532,0,3,"Toufik, Mr. Nakli",male,,0,0,2641,7.2292,,C
+533,0,3,"Elias, Mr. Joseph Jr",male,17,1,1,2690,7.2292,,C
+534,1,3,"Peter, Mrs. Catherine (Catherine Rizk)",female,,0,2,2668,22.3583,,C
+535,0,3,"Cacic, Miss. Marija",female,30,0,0,315084,8.6625,,S
+536,1,2,"Hart, Miss. Eva Miriam",female,7,0,2,F.C.C. 13529,26.25,,S
+537,0,1,"Butt, Major. Archibald Willingham",male,45,0,0,113050,26.55,B38,S
+538,1,1,"LeRoy, Miss. Bertha",female,30,0,0,PC 17761,106.425,,C
+539,0,3,"Risien, Mr. Samuel Beard",male,,0,0,364498,14.5,,S
+540,1,1,"Frolicher, Miss. Hedwig Margaritha",female,22,0,2,13568,49.5,B39,C
+541,1,1,"Crosby, Miss. Harriet R",female,36,0,2,WE/P 5735,71,B22,S
+542,0,3,"Andersson, Miss. Ingeborg Constanzia",female,9,4,2,347082,31.275,,S
+543,0,3,"Andersson, Miss. Sigrid Elisabeth",female,11,4,2,347082,31.275,,S
+544,1,2,"Beane, Mr. Edward",male,32,1,0,2908,26,,S
+545,0,1,"Douglas, Mr. Walter Donald",male,50,1,0,PC 17761,106.425,C86,C
+546,0,1,"Nicholson, Mr. Arthur Ernest",male,64,0,0,693,26,,S
+547,1,2,"Beane, Mrs. Edward (Ethel Clarke)",female,19,1,0,2908,26,,S
+548,1,2,"Padro y Manent, Mr. Julian",male,,0,0,SC/PARIS 2146,13.8625,,C
+549,0,3,"Goldsmith, Mr. Frank John",male,33,1,1,363291,20.525,,S
+550,1,2,"Davies, Master. John Morgan Jr",male,8,1,1,C.A. 33112,36.75,,S
+551,1,1,"Thayer, Mr. John Borland Jr",male,17,0,2,17421,110.8833,C70,C
+552,0,2,"Sharp, Mr. Percival James R",male,27,0,0,244358,26,,S
+553,0,3,"O'Brien, Mr. Timothy",male,,0,0,330979,7.8292,,Q
+554,1,3,"Leeni, Mr. Fahim (""Philip Zenni"")",male,22,0,0,2620,7.225,,C
+555,1,3,"Ohman, Miss. Velin",female,22,0,0,347085,7.775,,S
+556,0,1,"Wright, Mr. George",male,62,0,0,113807,26.55,,S
+557,1,1,"Duff Gordon, Lady. (Lucille Christiana Sutherland) (""Mrs Morgan"")",female,48,1,0,11755,39.6,A16,C
+558,0,1,"Robbins, Mr. Victor",male,,0,0,PC 17757,227.525,,C
+559,1,1,"Taussig, Mrs. Emil (Tillie Mandelbaum)",female,39,1,1,110413,79.65,E67,S
+560,1,3,"de Messemaeker, Mrs. Guillaume Joseph (Emma)",female,36,1,0,345572,17.4,,S
+561,0,3,"Morrow, Mr. Thomas Rowan",male,,0,0,372622,7.75,,Q
+562,0,3,"Sivic, Mr. Husein",male,40,0,0,349251,7.8958,,S
+563,0,2,"Norman, Mr. Robert Douglas",male,28,0,0,218629,13.5,,S
+564,0,3,"Simmons, Mr. John",male,,0,0,SOTON/OQ 392082,8.05,,S
+565,0,3,"Meanwell, Miss. (Marion Ogden)",female,,0,0,SOTON/O.Q. 392087,8.05,,S
+566,0,3,"Davies, Mr. Alfred J",male,24,2,0,A/4 48871,24.15,,S
+567,0,3,"Stoytcheff, Mr. Ilia",male,19,0,0,349205,7.8958,,S
+568,0,3,"Palsson, Mrs. Nils (Alma Cornelia Berglund)",female,29,0,4,349909,21.075,,S
+569,0,3,"Doharr, Mr. Tannous",male,,0,0,2686,7.2292,,C
+570,1,3,"Jonsson, Mr. Carl",male,32,0,0,350417,7.8542,,S
+571,1,2,"Harris, Mr. George",male,62,0,0,S.W./PP 752,10.5,,S
+572,1,1,"Appleton, Mrs. Edward Dale (Charlotte Lamson)",female,53,2,0,11769,51.4792,C101,S
+573,1,1,"Flynn, Mr. John Irwin (""Irving"")",male,36,0,0,PC 17474,26.3875,E25,S
+574,1,3,"Kelly, Miss. Mary",female,,0,0,14312,7.75,,Q
+575,0,3,"Rush, Mr. Alfred George John",male,16,0,0,A/4. 20589,8.05,,S
+576,0,3,"Patchett, Mr. George",male,19,0,0,358585,14.5,,S
+577,1,2,"Garside, Miss. Ethel",female,34,0,0,243880,13,,S
+578,1,1,"Silvey, Mrs. William Baird (Alice Munger)",female,39,1,0,13507,55.9,E44,S
+579,0,3,"Caram, Mrs. Joseph (Maria Elias)",female,,1,0,2689,14.4583,,C
+580,1,3,"Jussila, Mr. Eiriik",male,32,0,0,STON/O 2. 3101286,7.925,,S
+581,1,2,"Christy, Miss. Julie Rachel",female,25,1,1,237789,30,,S
+582,1,1,"Thayer, Mrs. John Borland (Marian Longstreth Morris)",female,39,1,1,17421,110.8833,C68,C
+583,0,2,"Downton, Mr. William James",male,54,0,0,28403,26,,S
+584,0,1,"Ross, Mr. John Hugo",male,36,0,0,13049,40.125,A10,C
+585,0,3,"Paulner, Mr. Uscher",male,,0,0,3411,8.7125,,C
+586,1,1,"Taussig, Miss. Ruth",female,18,0,2,110413,79.65,E68,S
+587,0,2,"Jarvis, Mr. John Denzil",male,47,0,0,237565,15,,S
+588,1,1,"Frolicher-Stehli, Mr. Maxmillian",male,60,1,1,13567,79.2,B41,C
+589,0,3,"Gilinski, Mr. Eliezer",male,22,0,0,14973,8.05,,S
+590,0,3,"Murdlin, Mr. Joseph",male,,0,0,A./5. 3235,8.05,,S
+591,0,3,"Rintamaki, Mr. Matti",male,35,0,0,STON/O 2. 3101273,7.125,,S
+592,1,1,"Stephenson, Mrs. Walter Bertram (Martha Eustis)",female,52,1,0,36947,78.2667,D20,C
+593,0,3,"Elsbury, Mr. William James",male,47,0,0,A/5 3902,7.25,,S
+594,0,3,"Bourke, Miss. Mary",female,,0,2,364848,7.75,,Q
+595,0,2,"Chapman, Mr. John Henry",male,37,1,0,SC/AH 29037,26,,S
+596,0,3,"Van Impe, Mr. Jean Baptiste",male,36,1,1,345773,24.15,,S
+597,1,2,"Leitch, Miss. Jessie Wills",female,,0,0,248727,33,,S
+598,0,3,"Johnson, Mr. Alfred",male,49,0,0,LINE,0,,S
+599,0,3,"Boulos, Mr. Hanna",male,,0,0,2664,7.225,,C
+600,1,1,"Duff Gordon, Sir. Cosmo Edmund (""Mr Morgan"")",male,49,1,0,PC 17485,56.9292,A20,C
+601,1,2,"Jacobsohn, Mrs. Sidney Samuel (Amy Frances Christy)",female,24,2,1,243847,27,,S
+602,0,3,"Slabenoff, Mr. Petco",male,,0,0,349214,7.8958,,S
+603,0,1,"Harrington, Mr. Charles H",male,,0,0,113796,42.4,,S
+604,0,3,"Torber, Mr. Ernst William",male,44,0,0,364511,8.05,,S
+605,1,1,"Homer, Mr. Harry (""Mr E Haven"")",male,35,0,0,111426,26.55,,C
+606,0,3,"Lindell, Mr. Edvard Bengtsson",male,36,1,0,349910,15.55,,S
+607,0,3,"Karaic, Mr. Milan",male,30,0,0,349246,7.8958,,S
+608,1,1,"Daniel, Mr. Robert Williams",male,27,0,0,113804,30.5,,S
+609,1,2,"Laroche, Mrs. Joseph (Juliette Marie Louise Lafargue)",female,22,1,2,SC/Paris 2123,41.5792,,C
+610,1,1,"Shutes, Miss. Elizabeth W",female,40,0,0,PC 17582,153.4625,C125,S
+611,0,3,"Andersson, Mrs. Anders Johan (Alfrida Konstantia Brogren)",female,39,1,5,347082,31.275,,S
+612,0,3,"Jardin, Mr. Jose Neto",male,,0,0,SOTON/O.Q. 3101305,7.05,,S
+613,1,3,"Murphy, Miss. Margaret Jane",female,,1,0,367230,15.5,,Q
+614,0,3,"Horgan, Mr. John",male,,0,0,370377,7.75,,Q
+615,0,3,"Brocklebank, Mr. William Alfred",male,35,0,0,364512,8.05,,S
+616,1,2,"Herman, Miss. Alice",female,24,1,2,220845,65,,S
+617,0,3,"Danbom, Mr. Ernst Gilbert",male,34,1,1,347080,14.4,,S
+618,0,3,"Lobb, Mrs. William Arthur (Cordelia K Stanlick)",female,26,1,0,A/5. 3336,16.1,,S
+619,1,2,"Becker, Miss. Marion Louise",female,4,2,1,230136,39,F4,S
+620,0,2,"Gavey, Mr. Lawrence",male,26,0,0,31028,10.5,,S
+621,0,3,"Yasbeck, Mr. Antoni",male,27,1,0,2659,14.4542,,C
+622,1,1,"Kimball, Mr. Edwin Nelson Jr",male,42,1,0,11753,52.5542,D19,S
+623,1,3,"Nakid, Mr. Sahid",male,20,1,1,2653,15.7417,,C
+624,0,3,"Hansen, Mr. Henry Damsgaard",male,21,0,0,350029,7.8542,,S
+625,0,3,"Bowen, Mr. David John ""Dai""",male,21,0,0,54636,16.1,,S
+626,0,1,"Sutton, Mr. Frederick",male,61,0,0,36963,32.3208,D50,S
+627,0,2,"Kirkland, Rev. Charles Leonard",male,57,0,0,219533,12.35,,Q
+628,1,1,"Longley, Miss. Gretchen Fiske",female,21,0,0,13502,77.9583,D9,S
+629,0,3,"Bostandyeff, Mr. Guentcho",male,26,0,0,349224,7.8958,,S
+630,0,3,"O'Connell, Mr. Patrick D",male,,0,0,334912,7.7333,,Q
+631,1,1,"Barkworth, Mr. Algernon Henry Wilson",male,80,0,0,27042,30,A23,S
+632,0,3,"Lundahl, Mr. Johan Svensson",male,51,0,0,347743,7.0542,,S
+633,1,1,"Stahelin-Maeglin, Dr. Max",male,32,0,0,13214,30.5,B50,C
+634,0,1,"Parr, Mr. William Henry Marsh",male,,0,0,112052,0,,S
+635,0,3,"Skoog, Miss. Mabel",female,9,3,2,347088,27.9,,S
+636,1,2,"Davis, Miss. Mary",female,28,0,0,237668,13,,S
+637,0,3,"Leinonen, Mr. Antti Gustaf",male,32,0,0,STON/O 2. 3101292,7.925,,S
+638,0,2,"Collyer, Mr. Harvey",male,31,1,1,C.A. 31921,26.25,,S
+639,0,3,"Panula, Mrs. Juha (Maria Emilia Ojala)",female,41,0,5,3101295,39.6875,,S
+640,0,3,"Thorneycroft, Mr. Percival",male,,1,0,376564,16.1,,S
+641,0,3,"Jensen, Mr. Hans Peder",male,20,0,0,350050,7.8542,,S
+642,1,1,"Sagesser, Mlle. Emma",female,24,0,0,PC 17477,69.3,B35,C
+643,0,3,"Skoog, Miss. Margit Elizabeth",female,2,3,2,347088,27.9,,S
+644,1,3,"Foo, Mr. Choong",male,,0,0,1601,56.4958,,S
+645,1,3,"Baclini, Miss. Eugenie",female,0.75,2,1,2666,19.2583,,C
+646,1,1,"Harper, Mr. Henry Sleeper",male,48,1,0,PC 17572,76.7292,D33,C
+647,0,3,"Cor, Mr. Liudevit",male,19,0,0,349231,7.8958,,S
+648,1,1,"Simonius-Blumer, Col. Oberst Alfons",male,56,0,0,13213,35.5,A26,C
+649,0,3,"Willey, Mr. Edward",male,,0,0,S.O./P.P. 751,7.55,,S
+650,1,3,"Stanley, Miss. Amy Zillah Elsie",female,23,0,0,CA. 2314,7.55,,S
+651,0,3,"Mitkoff, Mr. Mito",male,,0,0,349221,7.8958,,S
+652,1,2,"Doling, Miss. Elsie",female,18,0,1,231919,23,,S
+653,0,3,"Kalvik, Mr. Johannes Halvorsen",male,21,0,0,8475,8.4333,,S
+654,1,3,"O'Leary, Miss. Hanora ""Norah""",female,,0,0,330919,7.8292,,Q
+655,0,3,"Hegarty, Miss. Hanora ""Nora""",female,18,0,0,365226,6.75,,Q
+656,0,2,"Hickman, Mr. Leonard Mark",male,24,2,0,S.O.C. 14879,73.5,,S
+657,0,3,"Radeff, Mr. Alexander",male,,0,0,349223,7.8958,,S
+658,0,3,"Bourke, Mrs. John (Catherine)",female,32,1,1,364849,15.5,,Q
+659,0,2,"Eitemiller, Mr. George Floyd",male,23,0,0,29751,13,,S
+660,0,1,"Newell, Mr. Arthur Webster",male,58,0,2,35273,113.275,D48,C
+661,1,1,"Frauenthal, Dr. Henry William",male,50,2,0,PC 17611,133.65,,S
+662,0,3,"Badt, Mr. Mohamed",male,40,0,0,2623,7.225,,C
+663,0,1,"Colley, Mr. Edward Pomeroy",male,47,0,0,5727,25.5875,E58,S
+664,0,3,"Coleff, Mr. Peju",male,36,0,0,349210,7.4958,,S
+665,1,3,"Lindqvist, Mr. Eino William",male,20,1,0,STON/O 2. 3101285,7.925,,S
+666,0,2,"Hickman, Mr. Lewis",male,32,2,0,S.O.C. 14879,73.5,,S
+667,0,2,"Butler, Mr. Reginald Fenton",male,25,0,0,234686,13,,S
+668,0,3,"Rommetvedt, Mr. Knud Paust",male,,0,0,312993,7.775,,S
+669,0,3,"Cook, Mr. Jacob",male,43,0,0,A/5 3536,8.05,,S
+670,1,1,"Taylor, Mrs. Elmer Zebley (Juliet Cummins Wright)",female,,1,0,19996,52,C126,S
+671,1,2,"Brown, Mrs. Thomas William Solomon (Elizabeth Catherine Ford)",female,40,1,1,29750,39,,S
+672,0,1,"Davidson, Mr. Thornton",male,31,1,0,F.C. 12750,52,B71,S
+673,0,2,"Mitchell, Mr. Henry Michael",male,70,0,0,C.A. 24580,10.5,,S
+674,1,2,"Wilhelms, Mr. Charles",male,31,0,0,244270,13,,S
+675,0,2,"Watson, Mr. Ennis Hastings",male,,0,0,239856,0,,S
+676,0,3,"Edvardsson, Mr. Gustaf Hjalmar",male,18,0,0,349912,7.775,,S
+677,0,3,"Sawyer, Mr. Frederick Charles",male,24.5,0,0,342826,8.05,,S
+678,1,3,"Turja, Miss. Anna Sofia",female,18,0,0,4138,9.8417,,S
+679,0,3,"Goodwin, Mrs. Frederick (Augusta Tyler)",female,43,1,6,CA 2144,46.9,,S
+680,1,1,"Cardeza, Mr. Thomas Drake Martinez",male,36,0,1,PC 17755,512.3292,B51 B53 B55,C
+681,0,3,"Peters, Miss. Katie",female,,0,0,330935,8.1375,,Q
+682,1,1,"Hassab, Mr. Hammad",male,27,0,0,PC 17572,76.7292,D49,C
+683,0,3,"Olsvigen, Mr. Thor Anderson",male,20,0,0,6563,9.225,,S
+684,0,3,"Goodwin, Mr. Charles Edward",male,14,5,2,CA 2144,46.9,,S
+685,0,2,"Brown, Mr. Thomas William Solomon",male,60,1,1,29750,39,,S
+686,0,2,"Laroche, Mr. Joseph Philippe Lemercier",male,25,1,2,SC/Paris 2123,41.5792,,C
+687,0,3,"Panula, Mr. Jaako Arnold",male,14,4,1,3101295,39.6875,,S
+688,0,3,"Dakic, Mr. Branko",male,19,0,0,349228,10.1708,,S
+689,0,3,"Fischer, Mr. Eberhard Thelander",male,18,0,0,350036,7.7958,,S
+690,1,1,"Madill, Miss. Georgette Alexandra",female,15,0,1,24160,211.3375,B5,S
+691,1,1,"Dick, Mr. Albert Adrian",male,31,1,0,17474,57,B20,S
+692,1,3,"Karun, Miss. Manca",female,4,0,1,349256,13.4167,,C
+693,1,3,"Lam, Mr. Ali",male,,0,0,1601,56.4958,,S
+694,0,3,"Saad, Mr. Khalil",male,25,0,0,2672,7.225,,C
+695,0,1,"Weir, Col. John",male,60,0,0,113800,26.55,,S
+696,0,2,"Chapman, Mr. Charles Henry",male,52,0,0,248731,13.5,,S
+697,0,3,"Kelly, Mr. James",male,44,0,0,363592,8.05,,S
+698,1,3,"Mullens, Miss. Katherine ""Katie""",female,,0,0,35852,7.7333,,Q
+699,0,1,"Thayer, Mr. John Borland",male,49,1,1,17421,110.8833,C68,C
+700,0,3,"Humblen, Mr. Adolf Mathias Nicolai Olsen",male,42,0,0,348121,7.65,F G63,S
+701,1,1,"Astor, Mrs. John Jacob (Madeleine Talmadge Force)",female,18,1,0,PC 17757,227.525,C62 C64,C
+702,1,1,"Silverthorne, Mr. Spencer Victor",male,35,0,0,PC 17475,26.2875,E24,S
+703,0,3,"Barbara, Miss. Saiide",female,18,0,1,2691,14.4542,,C
+704,0,3,"Gallagher, Mr. Martin",male,25,0,0,36864,7.7417,,Q
+705,0,3,"Hansen, Mr. Henrik Juul",male,26,1,0,350025,7.8542,,S
+706,0,2,"Morley, Mr. Henry Samuel (""Mr Henry Marshall"")",male,39,0,0,250655,26,,S
+707,1,2,"Kelly, Mrs. Florence ""Fannie""",female,45,0,0,223596,13.5,,S
+708,1,1,"Calderhead, Mr. Edward Pennington",male,42,0,0,PC 17476,26.2875,E24,S
+709,1,1,"Cleaver, Miss. Alice",female,22,0,0,113781,151.55,,S
+710,1,3,"Moubarek, Master. Halim Gonios (""William George"")",male,,1,1,2661,15.2458,,C
+711,1,1,"Mayne, Mlle. Berthe Antonine (""Mrs de Villiers"")",female,24,0,0,PC 17482,49.5042,C90,C
+712,0,1,"Klaber, Mr. Herman",male,,0,0,113028,26.55,C124,S
+713,1,1,"Taylor, Mr. Elmer Zebley",male,48,1,0,19996,52,C126,S
+714,0,3,"Larsson, Mr. August Viktor",male,29,0,0,7545,9.4833,,S
+715,0,2,"Greenberg, Mr. Samuel",male,52,0,0,250647,13,,S
+716,0,3,"Soholt, Mr. Peter Andreas Lauritz Andersen",male,19,0,0,348124,7.65,F G73,S
+717,1,1,"Endres, Miss. Caroline Louise",female,38,0,0,PC 17757,227.525,C45,C
+718,1,2,"Troutt, Miss. Edwina Celia ""Winnie""",female,27,0,0,34218,10.5,E101,S
+719,0,3,"McEvoy, Mr. Michael",male,,0,0,36568,15.5,,Q
+720,0,3,"Johnson, Mr. Malkolm Joackim",male,33,0,0,347062,7.775,,S
+721,1,2,"Harper, Miss. Annie Jessie ""Nina""",female,6,0,1,248727,33,,S
+722,0,3,"Jensen, Mr. Svend Lauritz",male,17,1,0,350048,7.0542,,S
+723,0,2,"Gillespie, Mr. William Henry",male,34,0,0,12233,13,,S
+724,0,2,"Hodges, Mr. Henry Price",male,50,0,0,250643,13,,S
+725,1,1,"Chambers, Mr. Norman Campbell",male,27,1,0,113806,53.1,E8,S
+726,0,3,"Oreskovic, Mr. Luka",male,20,0,0,315094,8.6625,,S
+727,1,2,"Renouf, Mrs. Peter Henry (Lillian Jefferys)",female,30,3,0,31027,21,,S
+728,1,3,"Mannion, Miss. Margareth",female,,0,0,36866,7.7375,,Q
+729,0,2,"Bryhl, Mr. Kurt Arnold Gottfrid",male,25,1,0,236853,26,,S
+730,0,3,"Ilmakangas, Miss. Pieta Sofia",female,25,1,0,STON/O2. 3101271,7.925,,S
+731,1,1,"Allen, Miss. Elisabeth Walton",female,29,0,0,24160,211.3375,B5,S
+732,0,3,"Hassan, Mr. Houssein G N",male,11,0,0,2699,18.7875,,C
+733,0,2,"Knight, Mr. Robert J",male,,0,0,239855,0,,S
+734,0,2,"Berriman, Mr. William John",male,23,0,0,28425,13,,S
+735,0,2,"Troupiansky, Mr. Moses Aaron",male,23,0,0,233639,13,,S
+736,0,3,"Williams, Mr. Leslie",male,28.5,0,0,54636,16.1,,S
+737,0,3,"Ford, Mrs. Edward (Margaret Ann Watson)",female,48,1,3,W./C. 6608,34.375,,S
+738,1,1,"Lesurer, Mr. Gustave J",male,35,0,0,PC 17755,512.3292,B101,C
+739,0,3,"Ivanoff, Mr. Kanio",male,,0,0,349201,7.8958,,S
+740,0,3,"Nankoff, Mr. Minko",male,,0,0,349218,7.8958,,S
+741,1,1,"Hawksford, Mr. Walter James",male,,0,0,16988,30,D45,S
+742,0,1,"Cavendish, Mr. Tyrell William",male,36,1,0,19877,78.85,C46,S
+743,1,1,"Ryerson, Miss. Susan Parker ""Suzette""",female,21,2,2,PC 17608,262.375,B57 B59 B63 B66,C
+744,0,3,"McNamee, Mr. Neal",male,24,1,0,376566,16.1,,S
+745,1,3,"Stranden, Mr. Juho",male,31,0,0,STON/O 2. 3101288,7.925,,S
+746,0,1,"Crosby, Capt. Edward Gifford",male,70,1,1,WE/P 5735,71,B22,S
+747,0,3,"Abbott, Mr. Rossmore Edward",male,16,1,1,C.A. 2673,20.25,,S
+748,1,2,"Sinkkonen, Miss. Anna",female,30,0,0,250648,13,,S
+749,0,1,"Marvin, Mr. Daniel Warner",male,19,1,0,113773,53.1,D30,S
+750,0,3,"Connaghton, Mr. Michael",male,31,0,0,335097,7.75,,Q
+751,1,2,"Wells, Miss. Joan",female,4,1,1,29103,23,,S
+752,1,3,"Moor, Master. Meier",male,6,0,1,392096,12.475,E121,S
+753,0,3,"Vande Velde, Mr. Johannes Joseph",male,33,0,0,345780,9.5,,S
+754,0,3,"Jonkoff, Mr. Lalio",male,23,0,0,349204,7.8958,,S
+755,1,2,"Herman, Mrs. Samuel (Jane Laver)",female,48,1,2,220845,65,,S
+756,1,2,"Hamalainen, Master. Viljo",male,0.67,1,1,250649,14.5,,S
+757,0,3,"Carlsson, Mr. August Sigfrid",male,28,0,0,350042,7.7958,,S
+758,0,2,"Bailey, Mr. Percy Andrew",male,18,0,0,29108,11.5,,S
+759,0,3,"Theobald, Mr. Thomas Leonard",male,34,0,0,363294,8.05,,S
+760,1,1,"Rothes, the Countess. of (Lucy Noel Martha Dyer-Edwards)",female,33,0,0,110152,86.5,B77,S
+761,0,3,"Garfirth, Mr. John",male,,0,0,358585,14.5,,S
+762,0,3,"Nirva, Mr. Iisakki Antino Aijo",male,41,0,0,SOTON/O2 3101272,7.125,,S
+763,1,3,"Barah, Mr. Hanna Assi",male,20,0,0,2663,7.2292,,C
+764,1,1,"Carter, Mrs. William Ernest (Lucile Polk)",female,36,1,2,113760,120,B96 B98,S
+765,0,3,"Eklund, Mr. Hans Linus",male,16,0,0,347074,7.775,,S
+766,1,1,"Hogeboom, Mrs. John C (Anna Andrews)",female,51,1,0,13502,77.9583,D11,S
+767,0,1,"Brewe, Dr. Arthur Jackson",male,,0,0,112379,39.6,,C
+768,0,3,"Mangan, Miss. Mary",female,30.5,0,0,364850,7.75,,Q
+769,0,3,"Moran, Mr. Daniel J",male,,1,0,371110,24.15,,Q
+770,0,3,"Gronnestad, Mr. Daniel Danielsen",male,32,0,0,8471,8.3625,,S
+771,0,3,"Lievens, Mr. Rene Aime",male,24,0,0,345781,9.5,,S
+772,0,3,"Jensen, Mr. Niels Peder",male,48,0,0,350047,7.8542,,S
+773,0,2,"Mack, Mrs. (Mary)",female,57,0,0,S.O./P.P. 3,10.5,E77,S
+774,0,3,"Elias, Mr. Dibo",male,,0,0,2674,7.225,,C
+775,1,2,"Hocking, Mrs. Elizabeth (Eliza Needs)",female,54,1,3,29105,23,,S
+776,0,3,"Myhrman, Mr. Pehr Fabian Oliver Malkolm",male,18,0,0,347078,7.75,,S
+777,0,3,"Tobin, Mr. Roger",male,,0,0,383121,7.75,F38,Q
+778,1,3,"Emanuel, Miss. Virginia Ethel",female,5,0,0,364516,12.475,,S
+779,0,3,"Kilgannon, Mr. Thomas J",male,,0,0,36865,7.7375,,Q
+780,1,1,"Robert, Mrs. Edward Scott (Elisabeth Walton McMillan)",female,43,0,1,24160,211.3375,B3,S
+781,1,3,"Ayoub, Miss. Banoura",female,13,0,0,2687,7.2292,,C
+782,1,1,"Dick, Mrs. Albert Adrian (Vera Gillespie)",female,17,1,0,17474,57,B20,S
+783,0,1,"Long, Mr. Milton Clyde",male,29,0,0,113501,30,D6,S
+784,0,3,"Johnston, Mr. Andrew G",male,,1,2,W./C. 6607,23.45,,S
+785,0,3,"Ali, Mr. William",male,25,0,0,SOTON/O.Q. 3101312,7.05,,S
+786,0,3,"Harmer, Mr. Abraham (David Lishin)",male,25,0,0,374887,7.25,,S
+787,1,3,"Sjoblom, Miss. Anna Sofia",female,18,0,0,3101265,7.4958,,S
+788,0,3,"Rice, Master. George Hugh",male,8,4,1,382652,29.125,,Q
+789,1,3,"Dean, Master. Bertram Vere",male,1,1,2,C.A. 2315,20.575,,S
+790,0,1,"Guggenheim, Mr. Benjamin",male,46,0,0,PC 17593,79.2,B82 B84,C
+791,0,3,"Keane, Mr. Andrew ""Andy""",male,,0,0,12460,7.75,,Q
+792,0,2,"Gaskell, Mr. Alfred",male,16,0,0,239865,26,,S
+793,0,3,"Sage, Miss. Stella Anna",female,,8,2,CA. 2343,69.55,,S
+794,0,1,"Hoyt, Mr. William Fisher",male,,0,0,PC 17600,30.6958,,C
+795,0,3,"Dantcheff, Mr. Ristiu",male,25,0,0,349203,7.8958,,S
+796,0,2,"Otter, Mr. Richard",male,39,0,0,28213,13,,S
+797,1,1,"Leader, Dr. Alice (Farnham)",female,49,0,0,17465,25.9292,D17,S
+798,1,3,"Osman, Mrs. Mara",female,31,0,0,349244,8.6833,,S
+799,0,3,"Ibrahim Shawah, Mr. Yousseff",male,30,0,0,2685,7.2292,,C
+800,0,3,"Van Impe, Mrs. Jean Baptiste (Rosalie Paula Govaert)",female,30,1,1,345773,24.15,,S
+801,0,2,"Ponesell, Mr. Martin",male,34,0,0,250647,13,,S
+802,1,2,"Collyer, Mrs. Harvey (Charlotte Annie Tate)",female,31,1,1,C.A. 31921,26.25,,S
+803,1,1,"Carter, Master. William Thornton II",male,11,1,2,113760,120,B96 B98,S
+804,1,3,"Thomas, Master. Assad Alexander",male,0.42,0,1,2625,8.5167,,C
+805,1,3,"Hedman, Mr. Oskar Arvid",male,27,0,0,347089,6.975,,S
+806,0,3,"Johansson, Mr. Karl Johan",male,31,0,0,347063,7.775,,S
+807,0,1,"Andrews, Mr. Thomas Jr",male,39,0,0,112050,0,A36,S
+808,0,3,"Pettersson, Miss. Ellen Natalia",female,18,0,0,347087,7.775,,S
+809,0,2,"Meyer, Mr. August",male,39,0,0,248723,13,,S
+810,1,1,"Chambers, Mrs. Norman Campbell (Bertha Griggs)",female,33,1,0,113806,53.1,E8,S
+811,0,3,"Alexander, Mr. William",male,26,0,0,3474,7.8875,,S
+812,0,3,"Lester, Mr. James",male,39,0,0,A/4 48871,24.15,,S
+813,0,2,"Slemen, Mr. Richard James",male,35,0,0,28206,10.5,,S
+814,0,3,"Andersson, Miss. Ebba Iris Alfrida",female,6,4,2,347082,31.275,,S
+815,0,3,"Tomlin, Mr. Ernest Portage",male,30.5,0,0,364499,8.05,,S
+816,0,1,"Fry, Mr. Richard",male,,0,0,112058,0,B102,S
+817,0,3,"Heininen, Miss. Wendla Maria",female,23,0,0,STON/O2. 3101290,7.925,,S
+818,0,2,"Mallet, Mr. Albert",male,31,1,1,S.C./PARIS 2079,37.0042,,C
+819,0,3,"Holm, Mr. John Fredrik Alexander",male,43,0,0,C 7075,6.45,,S
+820,0,3,"Skoog, Master. Karl Thorsten",male,10,3,2,347088,27.9,,S
+821,1,1,"Hays, Mrs. Charles Melville (Clara Jennings Gregg)",female,52,1,1,12749,93.5,B69,S
+822,1,3,"Lulic, Mr. Nikola",male,27,0,0,315098,8.6625,,S
+823,0,1,"Reuchlin, Jonkheer. John George",male,38,0,0,19972,0,,S
+824,1,3,"Moor, Mrs. (Beila)",female,27,0,1,392096,12.475,E121,S
+825,0,3,"Panula, Master. Urho Abraham",male,2,4,1,3101295,39.6875,,S
+826,0,3,"Flynn, Mr. John",male,,0,0,368323,6.95,,Q
+827,0,3,"Lam, Mr. Len",male,,0,0,1601,56.4958,,S
+828,1,2,"Mallet, Master. Andre",male,1,0,2,S.C./PARIS 2079,37.0042,,C
+829,1,3,"McCormack, Mr. Thomas Joseph",male,,0,0,367228,7.75,,Q
+830,1,1,"Stone, Mrs. George Nelson (Martha Evelyn)",female,62,0,0,113572,80,B28,
+831,1,3,"Yasbeck, Mrs. Antoni (Selini Alexander)",female,15,1,0,2659,14.4542,,C
+832,1,2,"Richards, Master. George Sibley",male,0.83,1,1,29106,18.75,,S
+833,0,3,"Saad, Mr. Amin",male,,0,0,2671,7.2292,,C
+834,0,3,"Augustsson, Mr. Albert",male,23,0,0,347468,7.8542,,S
+835,0,3,"Allum, Mr. Owen George",male,18,0,0,2223,8.3,,S
+836,1,1,"Compton, Miss. Sara Rebecca",female,39,1,1,PC 17756,83.1583,E49,C
+837,0,3,"Pasic, Mr. Jakob",male,21,0,0,315097,8.6625,,S
+838,0,3,"Sirota, Mr. Maurice",male,,0,0,392092,8.05,,S
+839,1,3,"Chip, Mr. Chang",male,32,0,0,1601,56.4958,,S
+840,1,1,"Marechal, Mr. Pierre",male,,0,0,11774,29.7,C47,C
+841,0,3,"Alhomaki, Mr. Ilmari Rudolf",male,20,0,0,SOTON/O2 3101287,7.925,,S
+842,0,2,"Mudd, Mr. Thomas Charles",male,16,0,0,S.O./P.P. 3,10.5,,S
+843,1,1,"Serepeca, Miss. Augusta",female,30,0,0,113798,31,,C
+844,0,3,"Lemberopolous, Mr. Peter L",male,34.5,0,0,2683,6.4375,,C
+845,0,3,"Culumovic, Mr. Jeso",male,17,0,0,315090,8.6625,,S
+846,0,3,"Abbing, Mr. Anthony",male,42,0,0,C.A. 5547,7.55,,S
+847,0,3,"Sage, Mr. Douglas Bullen",male,,8,2,CA. 2343,69.55,,S
+848,0,3,"Markoff, Mr. Marin",male,35,0,0,349213,7.8958,,C
+849,0,2,"Harper, Rev. John",male,28,0,1,248727,33,,S
+850,1,1,"Goldenberg, Mrs. Samuel L (Edwiga Grabowska)",female,,1,0,17453,89.1042,C92,C
+851,0,3,"Andersson, Master. Sigvard Harald Elias",male,4,4,2,347082,31.275,,S
+852,0,3,"Svensson, Mr. Johan",male,74,0,0,347060,7.775,,S
+853,0,3,"Boulos, Miss. Nourelain",female,9,1,1,2678,15.2458,,C
+854,1,1,"Lines, Miss. Mary Conover",female,16,0,1,PC 17592,39.4,D28,S
+855,0,2,"Carter, Mrs. Ernest Courtenay (Lilian Hughes)",female,44,1,0,244252,26,,S
+856,1,3,"Aks, Mrs. Sam (Leah Rosen)",female,18,0,1,392091,9.35,,S
+857,1,1,"Wick, Mrs. George Dennick (Mary Hitchcock)",female,45,1,1,36928,164.8667,,S
+858,1,1,"Daly, Mr. Peter Denis ",male,51,0,0,113055,26.55,E17,S
+859,1,3,"Baclini, Mrs. Solomon (Latifa Qurban)",female,24,0,3,2666,19.2583,,C
+860,0,3,"Razi, Mr. Raihed",male,,0,0,2629,7.2292,,C
+861,0,3,"Hansen, Mr. Claus Peter",male,41,2,0,350026,14.1083,,S
+862,0,2,"Giles, Mr. Frederick Edward",male,21,1,0,28134,11.5,,S
+863,1,1,"Swift, Mrs. Frederick Joel (Margaret Welles Barron)",female,48,0,0,17466,25.9292,D17,S
+864,0,3,"Sage, Miss. Dorothy Edith ""Dolly""",female,,8,2,CA. 2343,69.55,,S
+865,0,2,"Gill, Mr. John William",male,24,0,0,233866,13,,S
+866,1,2,"Bystrom, Mrs. (Karolina)",female,42,0,0,236852,13,,S
+867,1,2,"Duran y More, Miss. Asuncion",female,27,1,0,SC/PARIS 2149,13.8583,,C
+868,0,1,"Roebling, Mr. Washington Augustus II",male,31,0,0,PC 17590,50.4958,A24,S
+869,0,3,"van Melkebeke, Mr. Philemon",male,,0,0,345777,9.5,,S
+870,1,3,"Johnson, Master. Harold Theodor",male,4,1,1,347742,11.1333,,S
+871,0,3,"Balkic, Mr. Cerin",male,26,0,0,349248,7.8958,,S
+872,1,1,"Beckwith, Mrs. Richard Leonard (Sallie Monypeny)",female,47,1,1,11751,52.5542,D35,S
+873,0,1,"Carlsson, Mr. Frans Olof",male,33,0,0,695,5,B51 B53 B55,S
+874,0,3,"Vander Cruyssen, Mr. Victor",male,47,0,0,345765,9,,S
+875,1,2,"Abelson, Mrs. Samuel (Hannah Wizosky)",female,28,1,0,P/PP 3381,24,,C
+876,1,3,"Najib, Miss. Adele Kiamie ""Jane""",female,15,0,0,2667,7.225,,C
+877,0,3,"Gustafsson, Mr. Alfred Ossian",male,20,0,0,7534,9.8458,,S
+878,0,3,"Petroff, Mr. Nedelio",male,19,0,0,349212,7.8958,,S
+879,0,3,"Laleff, Mr. Kristo",male,,0,0,349217,7.8958,,S
+880,1,1,"Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)",female,56,0,1,11767,83.1583,C50,C
+881,1,2,"Shelley, Mrs. William (Imanita Parrish Hall)",female,25,0,1,230433,26,,S
+882,0,3,"Markun, Mr. Johann",male,33,0,0,349257,7.8958,,S
+883,0,3,"Dahlberg, Miss. Gerda Ulrika",female,22,0,0,7552,10.5167,,S
+884,0,2,"Banfield, Mr. Frederick James",male,28,0,0,C.A./SOTON 34068,10.5,,S
+885,0,3,"Sutehall, Mr. Henry Jr",male,25,0,0,SOTON/OQ 392076,7.05,,S
+886,0,3,"Rice, Mrs. William (Margaret Norton)",female,39,0,5,382652,29.125,,Q
+887,0,2,"Montvila, Rev. Juozas",male,27,0,0,211536,13,,S
+888,1,1,"Graham, Miss. Margaret Edith",female,19,0,0,112053,30,B42,S
+889,0,3,"Johnston, Miss. Catherine Helen ""Carrie""",female,,1,2,W./C. 6607,23.45,,S
+890,1,1,"Behr, Mr. Karl Howell",male,26,0,0,111369,30,C148,C
+891,0,3,"Dooley, Mr. Patrick",male,32,0,0,370376,7.75,,Q
diff --git a/gradio/media_assets/images/avatar.png b/gradio/media_assets/images/avatar.png
new file mode 100644
index 0000000..8f1df71
Binary files /dev/null and b/gradio/media_assets/images/avatar.png differ
diff --git a/gradio/media_assets/images/bus.png b/gradio/media_assets/images/bus.png
new file mode 100644
index 0000000..855b404
Binary files /dev/null and b/gradio/media_assets/images/bus.png differ
diff --git a/gradio/media_assets/images/cheetah.jpg b/gradio/media_assets/images/cheetah.jpg
new file mode 100644
index 0000000..d1fde62
Binary files /dev/null and b/gradio/media_assets/images/cheetah.jpg differ
diff --git a/gradio/media_assets/images/cheetah1.jpg b/gradio/media_assets/images/cheetah1.jpg
new file mode 100644
index 0000000..c510ff3
Binary files /dev/null and b/gradio/media_assets/images/cheetah1.jpg differ
diff --git a/gradio/media_assets/images/groot.jpeg b/gradio/media_assets/images/groot.jpeg
new file mode 100644
index 0000000..06b192f
Binary files /dev/null and b/gradio/media_assets/images/groot.jpeg differ
diff --git a/gradio/media_assets/images/hf-logo_transpng.png b/gradio/media_assets/images/hf-logo_transpng.png
new file mode 100644
index 0000000..ccc7d9c
Binary files /dev/null and b/gradio/media_assets/images/hf-logo_transpng.png differ
diff --git a/gradio/media_assets/images/lion.jpg b/gradio/media_assets/images/lion.jpg
new file mode 100644
index 0000000..e9bf9f5
Binary files /dev/null and b/gradio/media_assets/images/lion.jpg differ
diff --git a/gradio/media_assets/images/logo.png b/gradio/media_assets/images/logo.png
new file mode 100644
index 0000000..8f1df71
Binary files /dev/null and b/gradio/media_assets/images/logo.png differ
diff --git a/gradio/media_assets/images/screenshot.gif b/gradio/media_assets/images/screenshot.gif
new file mode 100644
index 0000000..620ec8f
Binary files /dev/null and b/gradio/media_assets/images/screenshot.gif differ
diff --git a/gradio/media_assets/images/tower.jpg b/gradio/media_assets/images/tower.jpg
new file mode 100644
index 0000000..aec3fa9
Binary files /dev/null and b/gradio/media_assets/images/tower.jpg differ
diff --git a/gradio/media_assets/models3d/Bunny.obj b/gradio/media_assets/models3d/Bunny.obj
new file mode 100644
index 0000000..9baeb36
--- /dev/null
+++ b/gradio/media_assets/models3d/Bunny.obj
@@ -0,0 +1,7474 @@
+# OBJ file format with ext .obj
+# vertex count = 2503
+# face count = 4968
+v -3.4101800e-003 1.3031957e-001 2.1754370e-002
+v -8.1719160e-002 1.5250145e-001 2.9656090e-002
+v -3.0543480e-002 1.2477885e-001 1.0983400e-003
+v -2.4901590e-002 1.1211138e-001 3.7560240e-002
+v -1.8405680e-002 1.7843055e-001 -2.4219580e-002
+v 1.9067940e-002 1.2144925e-001 3.1968440e-002
+v 6.0412000e-003 1.2494359e-001 3.2652890e-002
+v -1.3469030e-002 1.6299355e-001 -1.2000020e-002
+v -3.4393240e-002 1.7236688e-001 -9.8213000e-004
+v -8.4314160e-002 1.0957263e-001 3.7097300e-003
+v -4.2233540e-002 1.7211574e-001 -4.1799800e-003
+v -6.3308390e-002 1.5660615e-001 -1.3838790e-002
+v -7.6903950e-002 1.6708033e-001 -2.6931360e-002
+v -7.2253920e-002 1.1539550e-001 5.1670300e-002
+v 1.2981330e-002 1.1366375e-001 3.8302950e-002
+v -3.7857280e-002 1.7010102e-001 1.4236000e-003
+v 4.8689400e-003 3.7962370e-002 4.5867630e-002
+v -5.7180550e-002 4.0918830e-002 4.6301340e-002
+v -4.5209070e-002 3.8839100e-002 4.4503770e-002
+v -3.3761490e-002 1.2617876e-001 1.7132300e-003
+v -5.0242270e-002 1.5773747e-001 9.3944500e-003
+v -2.1216950e-002 1.5887938e-001 -4.6923700e-003
+v -5.6472950e-002 1.5778406e-001 8.1786500e-003
+v -5.2802060e-002 4.1319860e-002 4.6169800e-002
+v -4.9960340e-002 4.3101950e-002 4.4462650e-002
+v -2.9748750e-002 3.6539860e-002 5.2493310e-002
+v -3.5438900e-003 4.2659770e-002 4.7541530e-002
+v 4.9304900e-003 4.1982660e-002 4.5723390e-002
+v -3.9088180e-002 1.6872020e-001 -1.1924680e-002
+v -5.6901000e-002 4.5437000e-002 4.3236960e-002
+v -4.1244880e-002 4.3098890e-002 4.2129560e-002
+v -2.6471980e-002 4.5034530e-002 5.1219460e-002
+v -2.1866970e-002 4.4022930e-002 5.3243800e-002
+v -3.6996250e-002 1.6899301e-001 1.3256300e-003
+v -6.7216590e-002 1.6171340e-001 -1.3733710e-002
+v 4.9760060e-002 7.0235220e-002 2.3732020e-002
+v -4.9186640e-002 4.6411230e-002 4.1170040e-002
+v -4.4590380e-002 4.3797990e-002 4.2685460e-002
+v -4.3686470e-002 4.7154500e-002 4.0286310e-002
+v -2.2491950e-002 4.6513620e-002 5.1885310e-002
+v -6.5174200e-003 4.5036200e-002 4.7502780e-002
+v 3.7699000e-004 4.4935790e-002 4.6519930e-002
+v 3.4023920e-002 1.1353879e-001 2.4595280e-002
+v -2.6467900e-002 1.8104250e-001 -8.0811700e-003
+v -1.7533470e-002 4.7964250e-002 4.8829630e-002
+v -7.0012600e-003 4.6416520e-002 4.7485540e-002
+v 5.9862300e-003 4.6689140e-002 4.9073620e-002
+v 9.1007200e-003 4.8474490e-002 4.9353190e-002
+v -3.5453700e-002 1.1244769e-001 3.5055410e-002
+v -7.5983200e-002 1.3820800e-001 4.9216580e-002
+v 3.4838440e-002 4.3153410e-002 2.8954310e-002
+v -5.2655550e-002 4.8494220e-002 3.8731190e-002
+v -4.7378940e-002 4.8456670e-002 3.9126790e-002
+v -3.8933750e-002 4.6364270e-002 4.0364780e-002
+v -2.6468940e-002 4.7816430e-002 4.9322590e-002
+v -2.2365790e-002 4.8073650e-002 5.0126500e-002
+v -1.3373430e-002 4.7892410e-002 4.7883850e-002
+v -1.2193490e-002 4.9470300e-002 4.9484490e-002
+v -6.3364000e-004 4.7193060e-002 4.9136900e-002
+v 2.0656800e-003 5.0104680e-002 5.2290220e-002
+v -2.2749270e-002 4.9883880e-002 4.6605520e-002
+v -1.8002080e-002 4.9917850e-002 4.6947970e-002
+v -7.8036800e-003 5.0169310e-002 5.0988650e-002
+v -2.6843800e-003 5.1247420e-002 5.3186790e-002
+v -6.3875650e-002 1.6140094e-001 -2.0064210e-002
+v 3.2434000e-002 4.5333970e-002 3.0316760e-002
+v -8.8064570e-002 1.2496764e-001 5.7412000e-004
+v -4.1503710e-002 1.6748512e-001 3.2765900e-003
+v -6.4457010e-002 1.5342891e-001 -5.1180400e-003
+v -3.4303190e-002 5.0520150e-002 3.8286020e-002
+v -2.2949400e-002 5.1020650e-002 4.3926450e-002
+v -1.4354710e-002 5.4428200e-002 5.0710310e-002
+v 1.3773100e-003 5.2302710e-002 5.3149010e-002
+v 3.6285000e-003 5.3198640e-002 5.3422710e-002
+v 8.0723800e-003 5.1574140e-002 5.1773560e-002
+v -7.2665890e-002 1.3005582e-001 5.1668200e-002
+v 3.7992780e-002 4.9793200e-002 3.1902020e-002
+v 3.8497260e-002 4.8062400e-002 3.1737450e-002
+v 2.1503510e-002 1.2563988e-001 2.1252620e-002
+v -7.6481330e-002 1.4827412e-001 -8.9376200e-003
+v -8.7240410e-002 1.1967213e-001 -1.7813000e-004
+v -4.3719960e-002 1.6822738e-001 2.3425000e-003
+v -4.0652200e-002 1.2266506e-001 2.6290300e-002
+v -4.6686180e-002 5.4570720e-002 3.7587370e-002
+v -4.4071750e-002 5.1058250e-002 3.8977810e-002
+v -3.8144110e-002 5.0599600e-002 3.9302160e-002
+v -1.9875770e-002 5.1607710e-002 4.6142000e-002
+v -1.6911250e-002 5.1843550e-002 4.8459320e-002
+v -1.6249190e-002 5.4292110e-002 5.0306940e-002
+v -1.0446540e-002 5.3685970e-002 5.1958610e-002
+v -4.3090900e-003 5.4467500e-002 5.3908250e-002
+v 7.8152700e-003 5.5050680e-002 5.2750250e-002
+v 3.7955090e-002 1.0488710e-001 -3.2031800e-003
+v -7.9003790e-002 1.2850550e-001 5.3149340e-002
+v -7.9778990e-002 1.3448894e-001 5.0990290e-002
+v -5.9129700e-002 1.5039712e-001 3.4489540e-002
+v -6.5691790e-002 1.4961818e-001 3.8160980e-002
+v -3.1951660e-002 1.2518394e-001 1.9400580e-002
+v -6.9372590e-002 1.6061775e-001 -9.1905000e-003
+v -4.5225500e-002 1.2935459e-001 2.0377520e-002
+v -4.1879110e-002 5.6164390e-002 3.9796700e-002
+v -3.0614840e-002 5.4412650e-002 3.6694290e-002
+v -2.4787600e-002 5.2606220e-002 4.0839760e-002
+v -2.1588860e-002 5.6836920e-002 4.5467040e-002
+v -2.4264000e-004 5.4536020e-002 5.4641200e-002
+v -8.0900510e-002 1.2558713e-001 5.2155370e-002
+v -2.9996210e-002 1.7811137e-001 -5.2358200e-003
+v 3.5515390e-002 5.0449570e-002 3.1439830e-002
+v 4.3315550e-002 5.2145550e-002 3.2492110e-002
+v -6.3938540e-002 1.5262699e-001 3.4481070e-002
+v -4.4489440e-002 6.1077710e-002 3.9545320e-002
+v -3.8979900e-002 5.7996270e-002 4.0151390e-002
+v -7.9087730e-002 1.7044488e-001 -4.1373170e-002
+v -4.6247300e-003 5.7759650e-002 5.3990710e-002
+v -1.4985500e-003 5.5925480e-002 5.4630800e-002
+v 5.1981700e-003 5.7017990e-002 5.3423530e-002
+v 3.0920000e-005 1.2315746e-001 3.4749660e-002
+v 3.3568300e-002 1.1523716e-001 2.1798410e-002
+v 3.8686300e-002 5.6450590e-002 3.1188930e-002
+v -3.4385780e-002 5.4096000e-002 3.8060290e-002
+v -8.5308300e-003 6.0159420e-002 5.5308950e-002
+v -4.4024000e-004 5.8343410e-002 5.4483410e-002
+v -9.1078730e-002 1.1506037e-001 4.0141810e-002
+v 4.0775480e-002 5.4557490e-002 3.2014740e-002
+v 4.5636880e-002 5.7402620e-002 3.1992220e-002
+v 2.0358850e-002 1.2448747e-001 2.5906340e-002
+v -1.4169700e-002 1.2767892e-001 1.3080500e-003
+v -1.1987590e-002 5.7493210e-002 5.2752420e-002
+v 3.2514500e-003 5.9828640e-002 5.5464300e-002
+v -1.2395240e-002 1.2264726e-001 3.3588280e-002
+v 1.3813780e-002 1.2322188e-001 3.2502590e-002
+v -7.7004310e-002 1.5521281e-001 2.4534770e-002
+v -2.8001360e-002 6.1075420e-002 3.7471210e-002
+v -8.5480000e-004 6.0593520e-002 5.5824810e-002
+v -3.8050200e-002 1.1527068e-001 3.3178540e-002
+v -1.6231340e-002 1.2382942e-001 2.9576990e-002
+v -2.5373550e-002 1.5840012e-001 -1.4801300e-003
+v -6.7818590e-002 1.5454353e-001 3.0233720e-002
+v -4.3082600e-003 6.1418570e-002 5.5688490e-002
+v -3.1958900e-003 1.1912518e-001 3.8349580e-002
+v -6.4292400e-003 1.2201090e-001 3.5740890e-002
+v 4.2312960e-002 5.9099150e-002 3.0848420e-002
+v 4.8510010e-002 6.1780760e-002 3.0347250e-002
+v 5.0412290e-002 6.0312610e-002 3.0245060e-002
+v -3.9185590e-002 6.3074530e-002 4.1382890e-002
+v -3.4448660e-002 6.0780500e-002 3.9543990e-002
+v -1.4746030e-002 6.5583910e-002 5.3730860e-002
+v 2.6645200e-003 6.2700010e-002 5.6525210e-002
+v -1.3991610e-002 1.1962575e-001 3.6251540e-002
+v 1.9659170e-002 1.1236219e-001 3.7545270e-002
+v -3.2597160e-002 1.7498725e-001 -2.5953100e-003
+v -2.1513900e-003 9.9437380e-002 4.9849750e-002
+v -5.6001390e-002 6.1830670e-002 2.7931150e-002
+v -5.4707260e-002 6.3461570e-002 3.1670590e-002
+v -5.1307940e-002 6.0521660e-002 3.1434930e-002
+v -4.1979320e-002 6.9629980e-002 4.1824930e-002
+v -3.0272490e-002 6.2474660e-002 3.7982220e-002
+v -1.1387860e-002 6.4742460e-002 5.4918000e-002
+v 6.9544900e-003 6.4700130e-002 5.5599150e-002
+v 4.3015090e-002 9.7690960e-002 1.0258300e-003
+v 4.0635900e-002 6.1574860e-002 2.9841250e-002
+v 4.6183560e-002 6.1910110e-002 3.0223400e-002
+v 3.7552960e-002 1.0685291e-001 2.6303470e-002
+v -7.8640730e-002 1.6387238e-001 -2.8387790e-002
+v -6.1996240e-002 1.4761484e-001 -4.3256800e-003
+v -5.7499800e-003 6.5488980e-002 5.6173390e-002
+v 2.5369000e-004 6.5741170e-002 5.6569260e-002
+v -2.0542550e-002 1.1979518e-001 3.3003670e-002
+v 4.3155900e-003 1.2782561e-001 2.8646880e-002
+v -4.6549580e-002 6.7652130e-002 3.9635790e-002
+v -1.7420580e-002 6.9659490e-002 5.4089530e-002
+v -1.5242190e-002 7.0909900e-002 5.5004790e-002
+v -1.0282890e-002 6.8926360e-002 5.5289610e-002
+v -1.1289000e-004 6.9288200e-002 5.6579790e-002
+v -3.6309330e-002 1.1876943e-001 3.0674020e-002
+v -7.0325800e-002 6.3367770e-002 1.9809180e-002
+v 4.3023100e-002 6.3795810e-002 2.8039210e-002
+v 4.2831110e-002 8.5556040e-002 2.7873760e-002
+v 1.6981600e-002 1.2715003e-001 2.2931490e-002
+v -4.2121490e-002 1.2825104e-001 1.0751500e-003
+v 1.6329230e-002 1.2251895e-001 3.1375390e-002
+v -8.1264160e-002 1.5381172e-001 2.5897830e-002
+v -3.2257870e-002 8.8192600e-002 -2.5130960e-002
+v -1.3774950e-002 7.0887950e-002 5.4695630e-002
+v 5.2929600e-003 6.8006030e-002 5.5670490e-002
+v 7.6962500e-003 7.2375600e-002 5.6062150e-002
+v 3.4830600e-003 1.2002635e-001 3.6911950e-002
+v 6.6532500e-003 1.1673563e-001 3.8716340e-002
+v 4.6086570e-002 6.6473930e-002 2.6808990e-002
+v 5.2327290e-002 6.4327070e-002 2.8281890e-002
+v -6.1897630e-002 1.2297065e-001 -8.7725500e-003
+v -6.3934700e-003 1.0524472e-001 -2.2841900e-002
+v -3.5218330e-002 6.8559830e-002 4.1381470e-002
+v -3.2689880e-002 6.7729720e-002 4.0124390e-002
+v -2.9245440e-002 6.9551520e-002 3.9369010e-002
+v -5.0024500e-003 6.9655000e-002 5.6892510e-002
+v 1.6573960e-002 1.1890153e-001 3.5042300e-002
+v -8.9385100e-002 9.9024040e-002 1.7521830e-002
+v 4.5719230e-002 6.9489400e-002 2.3549340e-002
+v 5.4537210e-002 6.8796720e-002 2.4517690e-002
+v -4.4989450e-002 7.1577330e-002 4.1929250e-002
+v -4.2439400e-003 1.2914902e-001 2.5829230e-002
+v -7.3880090e-002 1.2091638e-001 5.3395800e-002
+v -7.4033870e-002 1.4406894e-001 4.4994970e-002
+v 5.0400010e-002 6.7292480e-002 2.6851470e-002
+v -5.4056890e-002 1.5671602e-001 -2.4865900e-003
+v 2.6148110e-002 1.2014725e-001 2.7308010e-002
+v -1.0736490e-002 1.2990285e-001 1.0993790e-002
+v -4.5078840e-002 8.7261130e-002 -2.1865520e-002
+v -3.8340900e-002 6.8843770e-002 4.1846470e-002
+v -2.9255580e-002 7.5169210e-002 4.1186430e-002
+v -4.7311210e-002 1.6296037e-001 6.0740300e-003
+v -1.1866030e-002 7.3183750e-002 5.6250050e-002
+v -6.3734600e-003 7.2184340e-002 5.7972980e-002
+v -2.9935300e-003 7.2186440e-002 5.8167190e-002
+v -2.5781060e-002 9.3778180e-002 -2.8388220e-002
+v -1.6692560e-002 1.1568553e-001 3.7853150e-002
+v -8.4123410e-002 1.0832050e-001 2.4730980e-002
+v -7.4294080e-002 1.6356850e-001 -1.5534220e-002
+v -9.4297150e-002 1.2617744e-001 1.9224650e-002
+v -3.5207090e-002 1.2505219e-001 2.1635690e-002
+v -4.9495940e-002 7.3436340e-002 4.1673570e-002
+v -3.3064160e-002 7.6654840e-002 4.1277900e-002
+v -7.3157300e-003 7.3919590e-002 5.7971690e-002
+v 2.1850000e-005 7.3496040e-002 5.7696650e-002
+v 4.1934400e-003 7.2915170e-002 5.6298730e-002
+v -7.7256080e-002 1.4565854e-001 4.3122930e-002
+v 4.1073260e-002 8.8724320e-002 -9.7879400e-003
+v 3.7418710e-002 1.0850822e-001 3.3973000e-004
+v -5.5111380e-002 7.4687840e-002 4.1939740e-002
+v -4.2740230e-002 7.6995340e-002 4.2804080e-002
+v -6.8531190e-002 1.5630045e-001 2.0997710e-002
+v -9.9440200e-003 7.6343100e-002 5.7388560e-002
+v -3.2479200e-003 7.5710690e-002 5.8714640e-002
+v 1.3414380e-002 9.3073740e-002 5.1467750e-002
+v -7.3504440e-002 9.3883340e-002 -1.4751720e-002
+v -7.4471830e-002 1.3507476e-001 5.0688900e-002
+v -2.5851310e-002 1.2182948e-001 2.6079670e-002
+v -3.4022940e-002 1.7597076e-001 -3.7271600e-003
+v -7.5405850e-002 1.6839072e-001 -2.6792980e-002
+v -3.6658410e-002 7.5087300e-002 4.2006940e-002
+v -1.7795480e-002 7.7486190e-002 5.6087240e-002
+v -1.1378660e-002 7.9877150e-002 5.7698880e-002
+v -1.0415000e-004 7.6881950e-002 5.8190740e-002
+v 2.7381400e-003 7.9105680e-002 5.6719190e-002
+v 5.5681200e-003 7.6397140e-002 5.6327220e-002
+v -6.1895860e-002 1.5424247e-001 -1.9018600e-002
+v -7.2646960e-002 1.4098943e-001 4.6976640e-002
+v 1.5799740e-002 1.2901416e-001 1.3236870e-002
+v -1.1703420e-002 9.7355720e-002 5.1592080e-002
+v -5.8922160e-002 7.7545490e-002 4.2961390e-002
+v -5.3121320e-002 7.7912430e-002 4.3334920e-002
+v -5.0745740e-002 7.6148400e-002 4.3137630e-002
+v -4.7401820e-002 7.5550340e-002 4.2630140e-002
+v -4.5055620e-002 7.8796280e-002 4.2341310e-002
+v -3.9517650e-002 7.8127780e-002 4.2918620e-002
+v -1.5245570e-002 8.2940770e-002 5.6934590e-002
+v -1.4557790e-002 7.6582160e-002 5.6493250e-002
+v -5.9406000e-003 7.9038240e-002 5.7969830e-002
+v 3.7176540e-002 1.1064404e-001 1.8811330e-002
+v 2.3929700e-003 1.3162713e-001 1.1955100e-002
+v -9.3644210e-002 1.1789378e-001 1.8662080e-002
+v -6.3939810e-002 7.8621830e-002 4.2083520e-002
+v -4.5376460e-002 8.2383550e-002 4.3282120e-002
+v -3.6505460e-002 8.1152260e-002 4.3162320e-002
+v -3.3244340e-002 8.2266590e-002 4.1852180e-002
+v -3.0800650e-002 8.0068420e-002 4.1798070e-002
+v -2.0578500e-003 8.0998290e-002 5.7553840e-002
+v 8.1848100e-003 8.0756170e-002 5.5374510e-002
+v -1.2953370e-002 1.1593580e-001 3.8920230e-002
+v -7.8081470e-002 1.2351940e-001 5.2136990e-002
+v -2.6580930e-002 1.5567694e-001 -4.1963400e-003
+v -8.2471600e-002 1.1624130e-001 -2.3236300e-003
+v -2.7538480e-002 7.9964780e-002 4.7697210e-002
+v 1.2556400e-003 8.3845570e-002 5.7446440e-002
+v 6.1508300e-003 8.3406240e-002 5.6463500e-002
+v -6.2433240e-002 8.4035270e-002 4.4203120e-002
+v -5.9867170e-002 8.0540510e-002 4.3277090e-002
+v -5.5238340e-002 8.1999450e-002 4.4984770e-002
+v -5.4000400e-002 8.0568410e-002 4.4601460e-002
+v -5.0027020e-002 8.1311330e-002 4.4264180e-002
+v -4.1996120e-002 8.1083670e-002 4.2456150e-002
+v -3.9357940e-002 8.3631380e-002 4.3502350e-002
+v -8.6161480e-002 1.0838594e-001 1.8244920e-002
+v -8.6723010e-002 9.9917250e-002 3.5537100e-003
+v -2.2413700e-002 8.3283520e-002 5.5590700e-002
+v -1.6993180e-002 8.2555820e-002 5.7523880e-002
+v -1.2406010e-002 8.5222570e-002 5.7267780e-002
+v -7.4442100e-003 1.1693417e-001 3.9283850e-002
+v -2.1452000e-003 1.1143287e-001 4.2436620e-002
+v -7.5718220e-002 1.2522734e-001 5.3087330e-002
+v -7.7056660e-002 1.3193469e-001 5.2462430e-002
+v -6.1121040e-002 1.5569660e-001 2.2517050e-002
+v -3.7538540e-002 1.2744127e-001 1.5320870e-002
+v -2.0516700e-003 1.0093469e-001 4.5625920e-002
+v -6.4992150e-002 8.4550900e-002 4.4120060e-002
+v -5.7861950e-002 8.3944360e-002 4.4186030e-002
+v -4.5681080e-002 8.4988010e-002 4.4159500e-002
+v -3.5022640e-002 8.2888160e-002 4.2912760e-002
+v -2.9982010e-002 8.5402300e-002 4.3745080e-002
+v -8.8892260e-002 9.9209100e-002 9.5703200e-003
+v -1.9135300e-002 8.3474800e-002 5.7217390e-002
+v -8.3489710e-002 1.0724729e-001 7.5790000e-004
+v -7.0112800e-002 1.1790350e-001 5.2714160e-002
+v -3.5526320e-002 1.7595563e-001 -4.8676200e-003
+v -7.0831390e-002 1.2254425e-001 5.3274880e-002
+v 4.5133810e-002 9.3630690e-002 6.2336800e-003
+v -5.3616700e-002 8.5346850e-002 4.5332470e-002
+v -4.9000840e-002 8.6221680e-002 4.5352040e-002
+v -3.6744880e-002 8.6083690e-002 4.3612890e-002
+v -1.0872600e-002 8.8826770e-002 5.6665490e-002
+v -3.8450200e-003 8.4787810e-002 5.7197980e-002
+v -4.9020070e-002 1.1771293e-001 3.1581430e-002
+v -4.2914400e-002 1.1835991e-001 3.0645040e-002
+v -5.7684530e-002 1.5561695e-001 1.2983110e-002
+v -2.5411730e-002 1.2472533e-001 1.2886000e-004
+v 1.9012230e-002 1.2736197e-001 1.7786580e-002
+v -5.9498600e-002 8.8845470e-002 4.5109290e-002
+v -5.6931050e-002 8.8101500e-002 4.4692930e-002
+v 3.5765600e-003 1.3138981e-001 7.2086000e-003
+v -1.6683350e-002 8.7266690e-002 5.6741190e-002
+v -8.4980800e-003 8.3990470e-002 5.7605220e-002
+v 3.5078200e-003 8.6339520e-002 5.7048320e-002
+v -2.8398700e-002 1.8070650e-001 -7.8469500e-003
+v -7.6565830e-002 1.1674037e-001 5.1489350e-002
+v 1.7869430e-002 9.0898610e-002 4.8712940e-002
+v -4.0342100e-002 1.1669551e-001 3.2460200e-002
+v 5.9105700e-003 1.3140929e-001 1.6823750e-002
+v -8.5777550e-002 9.1701370e-002 -4.6970000e-005
+v -5.0372230e-002 8.8844660e-002 4.5188000e-002
+v -4.4434130e-002 8.7654530e-002 4.3477620e-002
+v -4.2056390e-002 8.6711520e-002 4.2534630e-002
+v -3.3058460e-002 8.6185500e-002 4.2560350e-002
+v -2.9241910e-002 9.0453360e-002 4.4236610e-002
+v -6.8964100e-003 8.4432910e-002 5.7168580e-002
+v -6.6210600e-003 9.0415250e-002 5.6879750e-002
+v -1.2439100e-003 8.9093200e-002 5.6552120e-002
+v 9.4076000e-003 9.0328050e-002 5.4214140e-002
+v 4.0194810e-002 1.0231597e-001 -2.0048600e-003
+v -8.6227130e-002 1.1466841e-001 2.2102000e-003
+v -8.9495490e-002 9.5632430e-002 1.4234810e-002
+v -6.7132160e-002 1.5709447e-001 -6.2032000e-003
+v -5.2935640e-002 9.0913520e-002 4.4568870e-002
+v -3.6744910e-002 8.8886950e-002 4.3312050e-002
+v -1.3626110e-002 8.9787930e-002 5.6674380e-002
+v 2.3337130e-002 1.2353449e-001 2.4874140e-002
+v -3.7053790e-002 1.2715094e-001 3.5474000e-004
+v -7.3696690e-002 1.5613015e-001 1.4359790e-002
+v -6.5592380e-002 9.1042400e-002 4.4092080e-002
+v -5.8997380e-002 9.2030670e-002 4.5335270e-002
+v -3.3238910e-002 8.8573580e-002 4.3697040e-002
+v -3.1834990e-002 9.0722970e-002 4.4173460e-002
+v -2.0022170e-002 8.8032110e-002 5.5589350e-002
+v -1.1213830e-002 9.2366370e-002 5.6105260e-002
+v 3.9108440e-002 1.0829072e-001 1.3142330e-002
+v 2.8675700e-002 1.1959600e-001 2.4545910e-002
+v -6.8940210e-002 1.5652777e-001 -1.9716000e-003
+v -6.2615110e-002 9.1126880e-002 4.5090730e-002
+v 3.0444560e-002 1.1886441e-001 2.0821750e-002
+v -1.5241090e-002 9.1821720e-002 5.5817230e-002
+v -5.6221700e-003 9.3235010e-002 5.5893630e-002
+v 4.7989900e-003 9.1654840e-002 5.4715170e-002
+v -6.8282400e-002 9.2376840e-002 4.2388730e-002
+v -5.5623730e-002 9.2187420e-002 4.5054970e-002
+v -5.1901030e-002 9.5457620e-002 4.3937650e-002
+v -4.8809030e-002 9.1083890e-002 4.4456690e-002
+v -4.5411560e-002 9.1002130e-002 4.3252770e-002
+v -4.4514550e-002 9.4860420e-002 4.2972490e-002
+v -3.9430320e-002 8.9597620e-002 4.3177890e-002
+v -3.5642240e-002 9.2617410e-002 4.4238490e-002
+v -1.2246000e-004 9.3201160e-002 5.5398380e-002
+v 9.5104600e-003 9.5483870e-002 5.0910600e-002
+v 2.1441660e-002 9.1354960e-002 4.8043360e-002
+v -8.9830300e-003 1.6926449e-001 -2.2683480e-002
+v -7.3019050e-002 1.5602104e-001 2.2419340e-002
+v -6.4760430e-002 1.5311588e-001 -2.0371200e-003
+v -6.9368510e-002 9.5242790e-002 4.2129000e-002
+v -6.0117140e-002 9.5552910e-002 4.4183820e-002
+v -2.9241690e-002 9.4290440e-002 4.4821190e-002
+v -2.6561430e-002 9.3289510e-002 4.4975420e-002
+v -1.4394030e-002 9.4587640e-002 5.3993500e-002
+v -8.8691600e-003 9.5400260e-002 5.4445980e-002
+v -1.2188700e-003 9.6201750e-002 5.3815910e-002
+v 4.0479000e-003 9.5817360e-002 5.2936770e-002
+v -4.6019400e-003 1.2428544e-001 3.3471960e-002
+v -7.8436460e-002 1.3928013e-001 4.8329360e-002
+v 1.0774610e-002 1.3079162e-001 1.4341740e-002
+v -5.6623730e-002 9.6322170e-002 4.3667910e-002
+v -3.6298870e-002 9.5695620e-002 4.3580310e-002
+v -2.4379930e-002 9.5866450e-002 4.4434530e-002
+v 1.0915500e-002 1.2633629e-001 2.9857020e-002
+v -5.8622700e-003 9.7350210e-002 5.2743650e-002
+v 1.6973450e-002 9.7106620e-002 4.7440920e-002
+v -6.7231980e-002 9.9173950e-002 4.1593880e-002
+v -5.4994210e-002 9.9640820e-002 4.2955230e-002
+v -4.8617990e-002 9.6452700e-002 4.4183060e-002
+v -5.5369000e-002 1.5442476e-001 1.6160650e-002
+v -9.4243550e-002 1.2207432e-001 2.3568470e-002
+v 1.3242990e-002 9.6738240e-002 4.8750160e-002
+v 2.0639290e-002 9.6602480e-002 4.6971000e-002
+v 7.3429700e-003 1.2098188e-001 3.5973430e-002
+v -1.3493870e-002 1.2882438e-001 5.9690700e-003
+v -2.0110640e-002 1.2504545e-001 2.3588310e-002
+v -6.9438450e-002 1.6479930e-001 -1.7218700e-002
+v -6.4028050e-002 9.7838670e-002 4.2565330e-002
+v -5.1996350e-002 9.9707850e-002 4.2716590e-002
+v -4.3990880e-002 9.9425460e-002 4.2383430e-002
+v -3.9738250e-002 1.0215357e-001 4.0574410e-002
+v -3.5931490e-002 9.9809950e-002 4.2335800e-002
+v -3.0867600e-002 9.6914680e-002 4.4651400e-002
+v -2.8342070e-002 9.7782680e-002 4.3761280e-002
+v -2.5622580e-002 9.8713420e-002 4.4210890e-002
+v -8.5236620e-002 1.1077356e-001 2.4537670e-002
+v 7.1936000e-003 9.8859470e-002 4.8419510e-002
+v 9.6509200e-003 1.0108782e-001 4.7373080e-002
+v 1.3487100e-002 1.0076420e-001 4.7454290e-002
+v 7.7389800e-003 1.3147500e-001 1.1682970e-002
+v 8.0905000e-004 1.1633319e-001 4.0167560e-002
+v -7.2652570e-002 1.6567918e-001 -1.8212480e-002
+v -5.6009400e-003 1.3076674e-001 1.0516060e-002
+v -2.6303720e-002 1.2518875e-001 1.7392980e-002
+v -4.7590430e-002 1.0081180e-001 4.2349150e-002
+v -4.1460830e-002 9.8544800e-002 4.1778620e-002
+v -3.3582070e-002 1.0383908e-001 4.0737990e-002
+v -2.2870240e-002 1.0284737e-001 4.3544750e-002
+v -2.2361970e-002 9.8207610e-002 4.4765940e-002
+v -1.8870510e-002 9.8973200e-002 4.4489280e-002
+v -7.1433690e-002 7.7573520e-002 3.8060760e-002
+v -7.3001150e-002 1.1826712e-001 5.3034590e-002
+v -6.8466430e-002 1.3498146e-001 -8.3359800e-003
+v -7.4683810e-002 1.0786100e-001 -9.0477100e-003
+v -6.4958960e-002 1.5852021e-001 -1.2595320e-002
+v -7.8931700e-002 1.5093057e-001 3.5151900e-002
+v -7.4113550e-002 9.9442520e-002 3.8337710e-002
+v -7.0456930e-002 1.0098777e-001 3.9794060e-002
+v -5.9058760e-002 1.0041260e-001 4.2725130e-002
+v -4.9187330e-002 1.0452012e-001 4.0301390e-002
+v -2.9151180e-002 1.0197369e-001 4.2633060e-002
+v -1.1599720e-002 1.0107813e-001 4.4191660e-002
+v 5.1450400e-003 1.0163906e-001 4.5423010e-002
+v -5.1495700e-002 1.0496738e-001 4.0347210e-002
+v -2.0218210e-002 1.0214391e-001 4.3701160e-002
+v 4.2515900e-003 1.0523743e-001 4.2563550e-002
+v 1.6832800e-002 1.0337487e-001 4.5287270e-002
+v -2.5661080e-002 1.2562669e-001 4.5537500e-003
+v -7.2141950e-002 1.0536685e-001 3.7523210e-002
+v -6.4984570e-002 1.0371550e-001 4.0647810e-002
+v -6.0652480e-002 1.0467197e-001 4.0906390e-002
+v -5.5308980e-002 1.0365394e-001 4.1516690e-002
+v -4.4243240e-002 1.0431726e-001 4.1339990e-002
+v -1.5513340e-002 1.0436131e-001 4.2919420e-002
+v -7.6323200e-003 1.0304531e-001 4.3710640e-002
+v -7.8046900e-003 1.0516619e-001 4.3825460e-002
+v 9.7163200e-003 1.0523506e-001 4.3603830e-002
+v 3.0300390e-002 1.1553645e-001 2.8685010e-002
+v -4.7496910e-002 1.0635662e-001 4.0165640e-002
+v -3.8978950e-002 1.0683037e-001 3.8247660e-002
+v -2.5869310e-002 1.0426705e-001 4.2207540e-002
+v -1.8057930e-002 1.0503919e-001 4.2802830e-002
+v -1.5180030e-002 1.0807750e-001 4.2350430e-002
+v -3.8981500e-003 1.0566175e-001 4.4047190e-002
+v 2.6820000e-005 1.0446731e-001 4.3775910e-002
+v 1.1978350e-002 1.0403629e-001 4.5396310e-002
+v 1.5004970e-002 1.0726898e-001 4.1811990e-002
+v 2.6488060e-002 1.2230287e-001 2.0398110e-002
+v -3.6225630e-002 1.0634244e-001 3.8644860e-002
+v -2.1126780e-002 1.0932290e-001 4.0715320e-002
+v -1.2819810e-002 1.0457100e-001 4.3465690e-002
+v 5.2847900e-003 1.0943666e-001 4.1674980e-002
+v 8.9403700e-003 1.0710645e-001 4.1243400e-002
+v -5.1839670e-002 1.6062039e-001 7.1421300e-003
+v -5.4201370e-002 1.1451730e-001 3.4843990e-002
+v 1.3226250e-002 1.2958070e-001 1.9689610e-002
+v -6.9382410e-002 1.0865787e-001 3.7507800e-002
+v -6.7691040e-002 1.0734145e-001 3.8018440e-002
+v -6.3782400e-002 1.1037270e-001 3.7579790e-002
+v -5.0749390e-002 1.0928682e-001 3.8297580e-002
+v -9.3936200e-003 1.0742813e-001 4.3454570e-002
+v 1.1760100e-003 1.0932531e-001 4.2662800e-002
+v 9.8020300e-003 1.1003994e-001 3.9945400e-002
+v 2.0131290e-002 1.0732778e-001 4.0323840e-002
+v -2.7872800e-003 1.0577531e-001 -2.2459030e-002
+v -5.4996890e-002 1.0774199e-001 3.9424590e-002
+v -4.5966740e-002 1.0905146e-001 3.8754110e-002
+v -4.2324540e-002 1.0737278e-001 3.9456440e-002
+v -3.2161240e-002 1.0896504e-001 3.8102720e-002
+v -3.0770180e-002 1.1597313e-001 3.2858800e-002
+v -1.1608610e-002 1.0983707e-001 4.2475330e-002
+v -2.9428320e-002 9.3166620e-002 -2.4931860e-002
+v -8.0043570e-002 9.2080160e-002 -9.4198200e-003
+v -4.9797430e-002 1.1342104e-001 3.5117920e-002
+v -4.3723850e-002 1.6191369e-001 5.7713400e-003
+v -5.7981740e-002 1.0943152e-001 3.7997640e-002
+v -4.1491180e-002 1.1224766e-001 3.5873450e-002
+v -2.4929830e-002 1.1592775e-001 3.4094730e-002
+v -2.0881690e-002 1.1409528e-001 3.7872990e-002
+v -7.5519700e-003 1.1183813e-001 4.2039690e-002
+v 3.7667200e-003 1.1240547e-001 4.1494710e-002
+v -6.2829620e-002 1.5189480e-001 -9.2373400e-003
+v -5.9195950e-002 1.1320797e-001 3.6234680e-002
+v -5.1079080e-002 9.3892810e-002 -2.1761690e-002
+v -7.3945370e-002 8.4374880e-002 -1.5154490e-002
+v -7.2146240e-002 1.3486431e-001 -7.7592200e-003
+v -1.9408870e-002 1.7041104e-001 -2.0994830e-002
+v -5.5530450e-002 1.4905531e-001 -1.9602100e-003
+v 1.6688460e-002 3.6976600e-002 4.3000600e-002
+v -5.2277330e-002 1.1775075e-001 3.3769460e-002
+v -6.9201380e-002 9.3039200e-002 -1.6486120e-002
+v 2.6579210e-002 1.1702438e-001 3.0867940e-002
+v -2.3574310e-002 3.7036910e-002 5.4144750e-002
+v -7.3775100e-003 3.8988430e-002 4.8929450e-002
+v 1.3234660e-002 3.8453060e-002 4.4501470e-002
+v 1.9487350e-002 4.0809290e-002 4.2641060e-002
+v -6.3953930e-002 1.4694729e-001 3.8484200e-002
+v -4.9579470e-002 3.6096540e-002 4.5955360e-002
+v -4.3323650e-002 3.6286400e-002 4.4042360e-002
+v -2.9047200e-002 1.2556338e-001 7.7617700e-003
+v -1.7343100e-003 3.9476800e-002 4.7262900e-002
+v -3.1358130e-002 1.5362199e-001 -4.6738900e-003
+v 2.5822000e-003 1.0747582e-001 -2.0606030e-002
+v -5.6802300e-002 1.4514674e-001 3.1740300e-002
+v -5.6464330e-002 3.7683110e-002 4.6819640e-002
+v -5.0964750e-002 3.8312290e-002 4.6286140e-002
+v -5.0980410e-002 1.3486613e-001 2.7585000e-002
+v -2.5647410e-002 3.8860730e-002 5.4161390e-002
+v -2.2542110e-002 4.0615780e-002 5.3986030e-002
+v -1.7618010e-002 3.8911170e-002 5.2403440e-002
+v -1.9711750e-002 1.6829145e-001 -1.3020960e-002
+v 2.3780070e-002 9.5222940e-002 4.6347330e-002
+v 1.4744290e-002 4.2716950e-002 4.4510310e-002
+v 2.1691360e-002 4.0161530e-002 4.0846450e-002
+v -6.4067240e-002 9.0172190e-002 -1.8855520e-002
+v 2.0319150e-002 1.0041961e-001 4.5760520e-002
+v -3.6425000e-002 9.3630690e-002 -2.3534630e-002
+v -1.4981170e-002 4.2571420e-002 5.1404530e-002
+v -5.7335340e-002 1.2340101e-001 4.0231470e-002
+v -5.4172560e-002 1.2337919e-001 3.7576440e-002
+v 2.2625210e-002 4.3621680e-002 4.0904580e-002
+v 2.8810520e-002 4.3352290e-002 3.2157720e-002
+v -4.2764160e-002 1.5727487e-001 5.2016200e-003
+v 9.2231900e-003 4.4125090e-002 4.5057440e-002
+v 1.5048210e-002 4.5755840e-002 4.3793870e-002
+v -6.3757290e-002 1.0251144e-001 -1.7484400e-002
+v -3.4070430e-002 1.6148975e-001 -1.3786960e-002
+v -8.2191500e-002 7.5610200e-002 1.6542620e-002
+v -6.6299420e-002 1.2337119e-001 5.0615920e-002
+v -1.5510100e-002 4.5283110e-002 5.0653040e-002
+v 1.8928020e-002 4.4249610e-002 4.3009830e-002
+v 2.5821800e-002 4.6326610e-002 3.8277230e-002
+v 2.7268700e-002 4.4547790e-002 3.6152520e-002
+v -4.5301340e-002 1.5695057e-001 7.2036900e-003
+v 2.3855760e-002 1.0616625e-001 3.9378080e-002
+v 2.1632670e-002 4.8127270e-002 4.0694430e-002
+v 4.3785360e-002 4.8803700e-002 3.1343420e-002
+v 4.8074790e-002 4.8969960e-002 2.8165490e-002
+v 5.2663090e-002 4.7673620e-002 2.1201270e-002
+v -5.2722450e-002 4.4722850e-002 4.4143250e-002
+v -3.0071610e-002 1.7258324e-001 -6.3597700e-003
+v -3.4508050e-002 1.5447469e-001 1.6504600e-003
+v 1.0629710e-002 4.6711810e-002 4.6472020e-002
+v 1.6743440e-002 4.8439000e-002 4.3678630e-002
+v 2.8827050e-002 9.2133370e-002 4.3920090e-002
+v -5.9937100e-002 1.2726188e-001 4.0771270e-002
+v -3.6752090e-002 1.5802075e-001 4.1862000e-003
+v -3.7885390e-002 1.6199719e-001 2.4686000e-004
+v -2.2047790e-002 1.8348586e-001 -1.2094990e-002
+v -2.4364620e-002 1.8096836e-001 -9.8312000e-003
+v -4.4882280e-002 1.5052959e-001 7.6451700e-003
+v 2.6996760e-002 5.1317780e-002 3.8752040e-002
+v 4.7735750e-002 5.2751040e-002 3.0797290e-002
+v 5.1703790e-002 4.8857380e-002 2.4147970e-002
+v -6.7504360e-002 1.1424088e-001 4.8036050e-002
+v -1.6257520e-002 1.6031250e-001 -9.6926000e-003
+v -6.3926300e-002 1.6792441e-001 -4.0730420e-002
+v -4.1665290e-002 1.4996141e-001 4.5405000e-003
+v -3.5203230e-002 1.6493551e-001 -2.6810000e-003
+v 4.1318770e-002 9.9496740e-002 2.4275750e-002
+v 1.4055220e-002 5.2523910e-002 4.8593880e-002
+v 1.9421220e-002 5.1321300e-002 4.4798910e-002
+v 2.3677990e-002 5.1474390e-002 4.1053270e-002
+v 3.4258130e-002 5.1930810e-002 3.2757880e-002
+v 5.5957340e-002 5.3147410e-002 2.3197720e-002
+v -3.9937960e-002 1.4922850e-001 1.6017200e-003
+v -4.6988800e-002 1.2600802e-001 2.6985500e-002
+v -2.7708370e-002 9.0081290e-002 -3.1911460e-002
+v 1.9204630e-002 5.5166510e-002 4.7722150e-002
+v 2.1886000e-002 5.3927560e-002 4.5102460e-002
+v 3.1286270e-002 5.2863840e-002 3.6913620e-002
+v 4.6661160e-002 5.4719230e-002 3.1976810e-002
+v 5.1823730e-002 5.3276700e-002 2.7927010e-002
+v -2.9264880e-002 1.6140418e-001 -2.1039500e-003
+v -6.8700770e-002 1.4463537e-001 4.3041630e-002
+v -5.6070060e-002 1.5000706e-001 2.9867640e-002
+v 4.4717850e-002 9.4802660e-002 1.2024710e-002
+v -4.1804090e-002 1.5582081e-001 6.4548200e-003
+v -6.8369340e-002 1.2289287e-001 5.2437860e-002
+v -6.4114810e-002 9.5509880e-002 -1.8114610e-002
+v -1.8383130e-002 1.8543664e-001 -1.7136370e-002
+v 1.1745400e-002 5.6678340e-002 5.1914060e-002
+v -5.9375360e-002 1.1998238e-001 4.0548240e-002
+v 5.9092080e-002 5.7956980e-002 2.0270120e-002
+v 4.3547740e-002 9.7389400e-002 1.7314650e-002
+v -2.6291780e-002 1.5963381e-001 -5.1845000e-004
+v 1.4904780e-002 5.6350380e-002 4.9522780e-002
+v 2.4286200e-002 5.4958580e-002 4.3086850e-002
+v 2.8952610e-002 5.6125250e-002 4.0388970e-002
+v -4.9507770e-002 1.2949500e-001 3.0259270e-002
+v 4.0824790e-002 9.5170220e-002 2.8657920e-002
+v 1.7774800e-002 5.8243780e-002 4.8864720e-002
+v 3.3573840e-002 5.8515260e-002 3.8310990e-002
+v 3.6385040e-002 5.6996480e-002 3.3601460e-002
+v -6.4205010e-002 1.2243894e-001 4.8008340e-002
+v -6.5424500e-002 1.4011279e-001 4.1308960e-002
+v 5.0801340e-002 5.7308080e-002 3.0001390e-002
+v 5.6671750e-002 5.6970820e-002 2.4291920e-002
+v -4.9349930e-002 1.4913519e-001 1.1274060e-002
+v -6.9760570e-002 1.3442855e-001 4.8265220e-002
+v 1.9537060e-002 6.0003780e-002 4.8576140e-002
+v 2.7013910e-002 5.9952790e-002 4.3454420e-002
+v 5.7679430e-002 6.1392970e-002 2.4201790e-002
+v -5.6916540e-002 1.2623512e-001 3.9426610e-002
+v 2.3469280e-002 1.1656262e-001 3.3537270e-002
+v -5.8298640e-002 1.3885500e-001 3.2937460e-002
+v 6.4598400e-003 6.0297430e-002 5.4780030e-002
+v 1.0406020e-002 5.9162400e-002 5.2484370e-002
+v 2.3183950e-002 5.8654360e-002 4.5871060e-002
+v 3.3040360e-002 6.1773840e-002 3.9781440e-002
+v -6.4348220e-002 1.2628088e-001 4.6650200e-002
+v -5.7031440e-002 1.1562007e-001 3.6494880e-002
+v 5.4451560e-002 5.8342890e-002 2.7653010e-002
+v -3.0134400e-002 1.7011322e-001 -7.3591600e-003
+v -3.7077100e-002 1.5986369e-001 1.6096500e-003
+v -5.6032760e-002 1.3731083e-001 3.1970590e-002
+v -6.7676470e-002 1.4150325e-001 4.3868140e-002
+v 9.9911700e-003 6.2735270e-002 5.4009240e-002
+v 1.4521510e-002 6.1382890e-002 5.0500900e-002
+v 3.0051740e-002 6.2169610e-002 4.1545810e-002
+v 3.7519170e-002 6.1062710e-002 3.4366020e-002
+v 5.3944010e-002 6.1391550e-002 2.8268530e-002
+v 5.9119900e-002 6.3128810e-002 2.1561830e-002
+v -2.4366390e-002 1.7693266e-001 -1.1719630e-002
+v -1.3253420e-002 1.6627152e-001 -1.4120370e-002
+v 3.9218740e-002 1.0669250e-001 2.0450190e-002
+v -1.7968980e-002 1.8078031e-001 -1.8103430e-002
+v 2.1902390e-002 6.0875970e-002 4.7282360e-002
+v 3.5341750e-002 6.1630030e-002 3.7606020e-002
+v -6.2145620e-002 1.3599775e-001 3.6700970e-002
+v 5.6820620e-002 6.3691150e-002 2.5286090e-002
+v -3.2800040e-002 1.5948699e-001 2.1962800e-003
+v 1.1212140e-002 6.6584120e-002 5.3982180e-002
+v 1.2919590e-002 6.4203580e-002 5.2441150e-002
+v 2.0126950e-002 6.3851330e-002 4.7919660e-002
+v 3.5971760e-002 6.6669610e-002 3.7781400e-002
+v 3.9906940e-002 6.4361260e-002 3.1686660e-002
+v -6.6702350e-002 1.3210600e-001 4.5480940e-002
+v -4.1601430e-002 1.5978000e-001 3.5374700e-003
+v 3.3044580e-002 1.0766252e-001 3.1916150e-002
+v 2.4672100e-002 6.3694500e-002 4.5204640e-002
+v 2.6108660e-002 6.8007640e-002 4.3902690e-002
+v 3.3363940e-002 6.7054760e-002 3.9729480e-002
+v 4.2915790e-002 6.6707700e-002 2.6994720e-002
+v 5.4714960e-002 6.4697160e-002 2.6979680e-002
+v -1.6530940e-002 1.6325000e-001 -9.2475200e-003
+v -1.7891600e-002 1.6113800e-001 -6.7072700e-003
+v 4.1118120e-002 9.7491260e-002 -3.9756700e-003
+v 2.3386770e-002 7.0075990e-002 4.7012620e-002
+v 3.8102900e-002 6.5678440e-002 3.5132520e-002
+v 1.0145240e-002 1.2221678e-001 3.4718950e-002
+v 5.8392410e-002 6.6741240e-002 2.1979460e-002
+v 3.8302050e-002 8.4549140e-002 -1.4478830e-002
+v 3.4126440e-002 9.7053980e-002 3.7590390e-002
+v -3.1355740e-002 1.5809888e-001 1.9128800e-003
+v -5.8259510e-002 1.4099493e-001 3.2440640e-002
+v -6.6817230e-002 1.1951525e-001 5.1490220e-002
+v -6.8090040e-002 1.1647050e-001 5.1151230e-002
+v 1.6568300e-002 6.6269890e-002 5.1009890e-002
+v 2.9362870e-002 6.6509780e-002 4.2289380e-002
+v 3.7027180e-002 9.3949630e-002 -1.1674040e-002
+v 5.6412730e-002 6.7659930e-002 2.3969320e-002
+v -6.1295740e-002 1.4519988e-001 3.7137830e-002
+v 8.3873000e-003 1.1336223e-001 3.9792610e-002
+v 1.1807030e-002 7.0920980e-002 5.4240490e-002
+v 2.9741730e-002 7.0647100e-002 4.1653890e-002
+v 3.6294410e-002 7.1220700e-002 3.7114610e-002
+v 3.9899680e-002 7.0294820e-002 3.2720020e-002
+v -6.2763130e-002 1.3778012e-001 3.6678590e-002
+v -1.5815440e-002 1.7504938e-001 -1.8654160e-002
+v -9.2268990e-002 1.1475156e-001 1.7017380e-002
+v -9.4964000e-004 1.0141111e-001 4.4290070e-002
+v -6.3712920e-002 1.1274250e-001 3.8006760e-002
+v -6.1096020e-002 1.1701650e-001 3.9654020e-002
+v 2.0991870e-002 6.9335450e-002 4.9003540e-002
+v 2.5658530e-002 7.0550460e-002 4.4539930e-002
+v 3.2978560e-002 7.3500690e-002 4.0486510e-002
+v 4.2156130e-002 6.9717580e-002 2.8318230e-002
+v -5.5516860e-002 1.2956070e-001 3.6598450e-002
+v -4.0802290e-002 1.6436059e-001 3.7448800e-003
+v -6.2546500e-003 1.0121650e-001 4.4322030e-002
+v -1.0986820e-002 1.6621199e-001 -1.6047550e-002
+v -3.0351420e-002 1.6448158e-001 -5.3291400e-003
+v 2.6110920e-002 1.0088990e-001 4.1733260e-002
+v -6.5599940e-002 1.1329504e-001 4.2318710e-002
+v 2.8814660e-002 9.6712680e-002 4.2257700e-002
+v 1.5263280e-002 7.1571940e-002 5.2717390e-002
+v 2.8982400e-002 7.4088480e-002 4.3447240e-002
+v 4.4872540e-002 7.5516710e-002 2.3155250e-002
+v -7.8225230e-002 1.4962481e-001 -2.5019400e-003
+v -4.6094940e-002 1.5296850e-001 9.0029700e-003
+v -5.2369030e-002 1.4682913e-001 1.8934650e-002
+v -2.1592100e-002 1.5763440e-001 -6.8623600e-003
+v 1.7176770e-002 7.3066230e-002 5.1826600e-002
+v 2.2687500e-002 7.5149180e-002 4.9312500e-002
+v 3.5472040e-002 7.3076670e-002 3.8482270e-002
+v -8.9480840e-002 1.3839976e-001 2.5061450e-002
+v -5.3216730e-002 1.3221978e-001 3.2978380e-002
+v -3.7776780e-002 1.5551947e-001 4.3700800e-003
+v -9.0549380e-002 1.3511875e-001 2.1680550e-002
+v -6.3366580e-002 1.3037076e-001 4.1669940e-002
+v 1.4074270e-002 7.6651720e-002 5.4221350e-002
+v 1.8109790e-002 7.5806590e-002 5.2488260e-002
+v 4.2209940e-002 7.8861480e-002 2.9187200e-002
+v -5.2115930e-002 1.4179906e-001 2.0510310e-002
+v 2.9063090e-002 1.1149602e-001 3.3805790e-002
+v -5.4731460e-002 1.4267229e-001 2.8980480e-002
+v 2.5903640e-002 7.5536040e-002 4.6416650e-002
+v 3.1298760e-002 7.5907440e-002 4.2699060e-002
+v 3.8446170e-002 7.5649430e-002 3.5050640e-002
+v 4.6351670e-002 7.4079520e-002 1.8354320e-002
+v -4.7656560e-002 1.3077525e-001 2.5523570e-002
+v -1.1447430e-002 1.7131059e-001 -1.9602980e-002
+v -3.6647240e-002 1.6640131e-001 -2.8167000e-004
+v -4.6653530e-002 1.5917824e-001 7.8019000e-003
+v -4.5569890e-002 1.4663612e-001 5.6514200e-003
+v 4.1438880e-002 9.2365100e-002 -7.4587000e-003
+v -6.4287420e-002 1.3463625e-001 3.9945640e-002
+v -6.1128890e-002 1.3178328e-001 3.8915910e-002
+v -4.7843540e-002 1.2215063e-001 2.8833160e-002
+v -4.9536830e-002 1.2491344e-001 3.1778440e-002
+v -7.1135380e-002 1.3817656e-001 4.7853960e-002
+v 1.0113870e-002 7.6468110e-002 5.5256790e-002
+v 1.7897450e-002 7.9516550e-002 5.2759530e-002
+v 2.1740850e-002 8.0250650e-002 5.0425390e-002
+v 2.5271590e-002 7.8724920e-002 4.8026570e-002
+v 3.0885040e-002 7.8999480e-002 4.3388770e-002
+v -6.2441930e-002 1.4084781e-001 3.6965840e-002
+v -6.2165060e-002 1.5666850e-001 -1.7837760e-002
+v 2.0657260e-002 1.0416830e-001 4.3004680e-002
+v -6.3602800e-002 1.1571453e-001 4.2572290e-002
+v 1.4424020e-002 8.0085500e-002 5.3755600e-002
+v 2.8779340e-002 8.2553250e-002 4.4527350e-002
+v 4.4450130e-002 8.1846900e-002 2.4552920e-002
+v 4.5541990e-002 8.3338380e-002 1.9700850e-002
+v -4.9665810e-002 1.2063801e-001 3.2163270e-002
+v -2.9177290e-002 1.7619959e-001 -5.6241100e-003
+v -5.8203130e-002 1.3270975e-001 3.6918680e-002
+v 3.8997050e-002 9.7088220e-002 -7.7799300e-003
+v -5.4725800e-002 1.2071262e-001 3.7451450e-002
+v 1.3189120e-002 8.4211180e-002 5.3065830e-002
+v -1.9926300e-002 1.6489742e-001 -9.9900200e-003
+v 2.0153130e-002 1.1849719e-001 3.4271250e-002
+v -5.5859940e-002 1.1774313e-001 3.7253480e-002
+v 1.8045260e-002 8.3623160e-002 5.1285840e-002
+v -6.3757130e-002 1.5912175e-001 -5.0155730e-002
+v -1.8527620e-002 1.7653197e-001 -1.7043540e-002
+v 2.8734400e-002 1.0360053e-001 3.8035240e-002
+v 4.1414010e-002 1.0284216e-001 1.6578920e-002
+v 2.4411730e-002 9.8016880e-002 4.4687400e-002
+v 2.0925180e-002 8.6311430e-002 4.9433120e-002
+v 3.0445010e-002 8.4959560e-002 4.3011090e-002
+v 3.3030090e-002 8.3781640e-002 4.1636930e-002
+v 3.6975090e-002 7.9876480e-002 3.7198390e-002
+v -7.7721460e-002 1.1355888e-001 4.8155990e-002
+v 2.9250000e-002 1.0651935e-001 3.6590330e-002
+v -5.3078180e-002 1.3754688e-001 2.8266470e-002
+v -6.2990590e-002 1.1999459e-001 4.5235530e-002
+v -6.5398320e-002 1.1751956e-001 4.8735570e-002
+v 3.3373910e-002 1.1227890e-001 2.7788130e-002
+v 3.8413590e-002 8.7489930e-002 3.5185850e-002
+v -6.1945930e-002 1.6479234e-001 -5.6647670e-002
+v -2.2876480e-002 1.7392813e-001 -1.3431140e-002
+v 4.3766230e-002 8.8390020e-002 -3.5708800e-003
+v 3.9291530e-002 1.0125969e-001 2.7550520e-002
+v 1.0936230e-002 8.6027290e-002 5.4732670e-002
+v 2.4108720e-002 8.4492600e-002 4.8292310e-002
+v 3.6758390e-002 9.9195470e-002 3.2837670e-002
+v -5.1941640e-002 1.2565987e-001 3.4587860e-002
+v -3.1582110e-002 1.6641850e-001 -5.7320000e-003
+v 7.6405900e-003 8.6427230e-002 5.6117850e-002
+v 1.6771020e-002 8.8644690e-002 5.0522960e-002
+v 3.4404610e-002 8.6932850e-002 4.0574270e-002
+v 3.6143820e-002 8.4439200e-002 3.7936930e-002
+v 4.1258830e-002 1.0361081e-001 2.6760600e-003
+v 2.4766140e-002 1.1081111e-001 3.6728360e-002
+v -2.2601590e-002 1.6250449e-001 -6.0717000e-003
+v -1.2893670e-002 1.7879041e-001 -2.2624750e-002
+v -2.4939150e-002 1.7031135e-001 -1.1329700e-002
+v -4.8468630e-002 1.4559606e-001 8.3661500e-003
+v 1.2534490e-002 8.9593930e-002 5.3394630e-002
+v 2.5872860e-002 8.8482290e-002 4.6655260e-002
+v 3.2756470e-002 8.8969130e-002 4.2215450e-002
+v -2.3343620e-002 1.6103450e-001 -3.1862400e-003
+v -9.2594970e-002 1.1943826e-001 2.6802950e-002
+v -7.4314840e-002 1.3761738e-001 -6.6698800e-003
+v -9.2499230e-002 1.2131500e-001 2.9256200e-002
+v -7.7378260e-002 1.5764266e-001 -1.4133650e-002
+v -9.2907340e-002 1.2307021e-001 3.6523230e-002
+v 2.8423340e-002 8.8011080e-002 4.4234200e-002
+v 3.5251680e-002 9.0836820e-002 3.9183920e-002
+v 1.5760560e-002 9.3203560e-002 4.9939310e-002
+v 3.8785530e-002 9.4954300e-002 3.2520220e-002
+v -6.1511220e-002 1.2373565e-001 4.3062680e-002
+v -6.8145120e-002 1.2748676e-001 5.0148970e-002
+v -2.0616710e-002 1.8237588e-001 -1.4299100e-002
+v 1.5137190e-002 1.1571495e-001 3.7031980e-002
+v -5.0718270e-002 1.5276300e-001 1.1816680e-002
+v 3.0168690e-002 1.0048686e-001 3.9404710e-002
+v -8.7426500e-002 9.5469530e-002 4.0312400e-003
+v -6.0010390e-002 1.4284463e-001 3.5449690e-002
+v -5.8603310e-002 1.4637237e-001 3.3808800e-002
+v 3.2411650e-002 9.3736150e-002 4.0890240e-002
+v -7.5917780e-002 1.4997690e-001 -1.6842050e-002
+v 1.8596570e-002 3.5293940e-002 -8.6782200e-003
+v 1.7209800e-002 3.5259400e-002 -1.4685160e-002
+v 4.4326540e-002 9.0818120e-002 2.2097520e-002
+v 3.8335910e-002 3.8830830e-002 3.0938100e-003
+v 2.2192920e-002 3.6775320e-002 -2.0919300e-003
+v 1.9636020e-002 3.8234010e-002 -1.2507670e-002
+v 2.3682120e-002 3.9762540e-002 3.7148760e-002
+v 4.6693280e-002 4.2465320e-002 6.5649500e-003
+v 2.1621110e-002 3.7657240e-002 -4.7021600e-003
+v 1.6638610e-002 3.8196090e-002 -1.9884930e-002
+v -9.0253980e-002 1.1366307e-001 3.7720210e-002
+v -9.0593870e-002 1.1373094e-001 1.0276770e-002
+v -6.2541690e-002 1.7679461e-001 -5.7821820e-002
+v -1.1091940e-002 1.7992082e-001 -2.5996430e-002
+v -6.2263130e-002 1.5219935e-001 -2.2578880e-002
+v -4.2276760e-002 9.4982570e-002 -2.2562420e-002
+v 4.3293410e-002 4.1864140e-002 2.0634400e-003
+v 4.3779590e-002 4.4530720e-002 -1.2622500e-003
+v 2.1696990e-002 4.0427270e-002 -9.4629500e-003
+v -1.1183700e-002 1.6450000e-001 -1.6151690e-002
+v -6.2372570e-002 1.5313041e-001 -2.8997120e-002
+v -9.2489300e-003 1.7725850e-001 -2.8270200e-002
+v 4.1477400e-002 8.5509410e-002 -9.1575000e-003
+v -8.1268710e-002 1.0879438e-001 2.9440660e-002
+v 4.9575680e-002 4.3815900e-002 1.4582960e-002
+v 5.2987960e-002 4.7747690e-002 5.0420000e-003
+v 2.1977540e-002 4.2855330e-002 -1.4536230e-002
+v 1.8505700e-002 3.8294100e-002 -1.7136500e-002
+v -3.5100500e-002 1.5203437e-001 -1.3279000e-004
+v 4.8749130e-002 4.5265000e-002 2.3023500e-003
+v 3.1912900e-002 9.9870060e-002 -1.4620980e-002
+v -1.4222520e-002 1.6167426e-001 -1.3349060e-002
+v -4.8663640e-002 1.3638523e-001 6.8063900e-003
+v -9.5837200e-003 1.7426102e-001 -2.8390760e-002
+v 5.2801850e-002 4.6539940e-002 1.0427720e-002
+v 5.1433800e-002 4.8485200e-002 1.0401000e-003
+v 2.3911240e-002 9.8021670e-002 -2.0807290e-002
+v 2.4567060e-002 4.4130110e-002 -1.0820840e-002
+v 2.0356810e-002 4.3662400e-002 -2.0456280e-002
+v -2.1882420e-002 1.1087418e-001 -1.9695320e-002
+v -5.3831800e-002 1.4981693e-001 2.5066610e-002
+v 5.4114210e-002 4.7773090e-002 1.7484000e-002
+v 5.6730570e-002 5.0515740e-002 1.0627080e-002
+v 4.5941820e-002 4.8138820e-002 -3.8715700e-003
+v -8.3817760e-002 1.1109094e-001 2.8524490e-002
+v 2.9207770e-002 4.7450250e-002 -8.5081800e-003
+v 2.8454920e-002 4.8067390e-002 -1.2847240e-002
+v 2.6637260e-002 4.7607100e-002 -1.6427740e-002
+v 2.2040110e-002 4.4992500e-002 -1.7528500e-002
+v 1.9120080e-002 4.7167750e-002 -2.2114680e-002
+v -1.5782200e-002 1.0072957e-001 -2.3724130e-002
+v -6.2514170e-002 1.7213119e-001 -5.2788100e-002
+v -6.2345600e-002 1.4745498e-001 -7.6600200e-003
+v 4.5598180e-002 8.8151720e-002 1.3124070e-002
+v -4.9422610e-002 1.4283525e-001 8.9728300e-003
+v -8.2761860e-002 1.1162341e-001 4.4221460e-002
+v -5.2166220e-002 1.5013661e-001 1.7448750e-002
+v -6.3616740e-002 1.4801371e-001 -2.0170260e-002
+v -5.1492690e-002 1.3796388e-001 2.3662180e-002
+v -6.1517580e-002 1.7517449e-001 -6.0631700e-002
+v 5.6524870e-002 5.0125660e-002 1.5564490e-002
+v 5.5257900e-002 5.1416260e-002 3.2062600e-003
+v 5.0318130e-002 5.2786370e-002 -3.4166300e-003
+v -6.2681950e-002 1.6744086e-001 -4.5713890e-002
+v 5.6520150e-002 5.1179900e-002 1.9940560e-002
+v 5.6907980e-002 5.1578130e-002 7.2538300e-003
+v 5.2854160e-002 5.1898670e-002 -6.2070000e-004
+v -3.8921140e-002 3.3767390e-002 -2.9042560e-002
+v 2.9740700e-002 5.0324690e-002 -1.3990860e-002
+v -6.8796190e-002 3.5117720e-002 -5.2067400e-003
+v 5.8826020e-002 5.5503780e-002 1.8647920e-002
+v -2.6160570e-002 1.2309988e-001 -4.4735500e-003
+v -5.3341960e-002 1.4401200e-001 2.4261390e-002
+v 5.8177390e-002 5.2821320e-002 1.5182420e-002
+v 5.9798140e-002 5.6840180e-002 1.3342730e-002
+v 5.4549870e-002 5.6044630e-002 -6.6158000e-004
+v 2.6775460e-002 5.1423450e-002 -2.0234060e-002
+v -8.6960400e-003 1.7291588e-001 -2.6708770e-002
+v -7.7039560e-002 7.1967020e-002 2.6405070e-002
+v -6.3069890e-002 1.5897471e-001 -4.2951850e-002
+v 3.5706690e-002 5.6083040e-002 -8.9993300e-003
+v 3.2600380e-002 5.3707520e-002 -1.1006150e-002
+v 2.9739960e-002 5.2538430e-002 -1.6224950e-002
+v 5.9238530e-002 5.6362780e-002 9.4530800e-003
+v 5.7421750e-002 5.6012210e-002 4.0245600e-003
+v 2.9062990e-002 5.5210580e-002 -1.8042060e-002
+v -1.7224410e-002 9.5214090e-002 -3.2085300e-002
+v -8.5911380e-002 1.0968787e-001 7.6582400e-003
+v 6.0594930e-002 6.1677210e-002 1.5591560e-002
+v 5.9531640e-002 6.0504600e-002 5.8397000e-003
+v 5.7306470e-002 5.9944620e-002 1.8886400e-003
+v 3.8829380e-002 5.9839830e-002 -6.4252500e-003
+v 3.0662770e-002 5.7300390e-002 -1.6518370e-002
+v -2.7762070e-002 1.2068537e-001 -9.0152900e-003
+v -8.8194590e-002 1.0314633e-001 1.7509020e-002
+v 6.0778800e-002 6.1646560e-002 1.0463990e-002
+v 3.5915080e-002 5.9916380e-002 -1.1966510e-002
+v 2.4251860e-002 5.6457470e-002 -2.4254800e-002
+v -6.1954390e-002 1.6865320e-001 -5.2621160e-002
+v -9.0557930e-002 1.1275994e-001 1.6141030e-002
+v -8.8469220e-002 1.1124294e-001 1.2679160e-002
+v 5.9558010e-002 6.3099260e-002 5.9471000e-003
+v 3.0940440e-002 6.0518080e-002 -1.8132720e-002
+v -9.3575750e-002 1.2474629e-001 2.6213300e-002
+v -9.3189820e-002 1.2019919e-001 3.7913720e-002
+v -9.2296100e-003 1.7314463e-001 -2.4197660e-002
+v -8.1739460e-002 7.6861340e-002 2.3313610e-002
+v -3.6992750e-002 1.5063932e-001 -2.0372300e-003
+v 6.0093570e-002 6.5693450e-002 1.8533320e-002
+v 5.9837240e-002 6.6423180e-002 8.5139400e-003
+v 4.0706180e-002 6.4475310e-002 -5.5920300e-003
+v 3.4745940e-002 6.3261340e-002 -1.4646740e-002
+v -6.1879660e-002 1.6000450e-001 -2.5806250e-002
+v -7.6537810e-002 1.5344875e-001 -1.2898750e-002
+v 3.8111070e-002 6.4811810e-002 -1.1142000e-002
+v 3.1909340e-002 6.4657050e-002 -1.8473410e-002
+v -8.3159350e-002 1.4674277e-001 3.0757900e-003
+v -8.7055900e-002 1.0562761e-001 9.7651100e-003
+v -7.1448330e-002 1.8105301e-001 -5.5478550e-002
+v -8.5632110e-002 1.2461094e-001 -2.7335800e-003
+v 6.0728970e-002 6.5806600e-002 1.3974830e-002
+v 3.9909650e-002 6.8171740e-002 -9.5698200e-003
+v 3.4981790e-002 6.7740790e-002 -1.5683210e-002
+v -9.1822030e-002 1.2747346e-001 3.6458650e-002
+v -6.2425420e-002 1.6366637e-001 -4.9667290e-002
+v -7.1168950e-002 1.4740156e-001 -2.7590940e-002
+v -5.0364760e-002 1.3715763e-001 1.9526100e-003
+v -5.0492650e-002 1.4159899e-001 1.6291740e-002
+v 5.9886670e-002 6.8513050e-002 1.6171610e-002
+v -6.1406990e-002 1.7268822e-001 -5.8265750e-002
+v 2.4990740e-002 6.5897320e-002 -2.3568270e-002
+v -7.4852750e-002 1.4993112e-001 -2.7752940e-002
+v -6.2225690e-002 6.0265200e-002 2.0449290e-002
+v -6.2001940e-002 3.6435020e-002 4.3918940e-002
+v 5.8374570e-002 7.1186410e-002 1.3072740e-002
+v -3.6125040e-002 1.2286688e-001 -8.2927900e-003
+v 2.9216510e-002 6.7850250e-002 -2.0418570e-002
+v -4.1681700e-002 1.2575112e-001 -7.0193300e-003
+v -7.4226550e-002 1.6437012e-001 -3.8240340e-002
+v -9.7845700e-003 1.6928488e-001 -2.4756660e-002
+v -8.9577950e-002 1.2078310e-001 3.5229100e-003
+v -6.2311930e-002 1.6371109e-001 -4.0623990e-002
+v 4.3514770e-002 9.1519890e-002 -2.6468100e-003
+v -4.8434350e-002 1.3754973e-001 1.3244980e-002
+v -8.9313160e-002 1.3653006e-001 3.0458750e-002
+v -7.4230190e-002 1.5652681e-001 -2.5167090e-002
+v 3.7378600e-002 7.3093410e-002 -1.2635370e-002
+v 2.6321810e-002 7.0240650e-002 -2.3878680e-002
+v -4.8023620e-002 1.4426649e-001 4.2498600e-003
+v -9.2019580e-002 1.1611534e-001 3.5842730e-002
+v -7.1305510e-002 7.3899020e-002 3.5969780e-002
+v -6.2059290e-002 1.5697807e-001 -3.3784580e-002
+v -9.7015300e-003 1.6738863e-001 -1.9360250e-002
+v 4.3342140e-002 7.1676120e-002 -2.2304600e-003
+v 4.1772460e-002 6.9568020e-002 -6.1596000e-003
+v 3.3505410e-002 7.2809860e-002 -1.7034800e-002
+v 2.9665000e-002 7.1506830e-002 -2.1282340e-002
+v -2.9460160e-002 1.5550263e-001 -1.1914700e-003
+v -8.6396440e-002 1.0479356e-001 5.9820600e-003
+v -5.4910700e-002 1.4662313e-001 2.8438970e-002
+v 4.4203810e-002 8.5204260e-002 -2.1170500e-003
+v 4.3264350e-002 7.5810540e-002 -3.8843900e-003
+v 1.3096990e-002 9.1126480e-002 -2.9269770e-002
+v -6.7069210e-002 9.1144610e-002 -1.7425950e-002
+v -9.0821680e-002 1.2276896e-001 6.0998500e-003
+v 4.5620000e-002 7.4684430e-002 2.6073900e-003
+v -9.3039800e-002 1.2026416e-001 1.1216820e-002
+v 4.4635590e-002 9.2794290e-002 1.7832070e-002
+v -1.1243390e-002 1.6457514e-001 -1.8240780e-002
+v 4.5511190e-002 8.6953050e-002 3.8865500e-003
+v 4.6252720e-002 7.7373870e-002 6.9140800e-003
+v 4.0281640e-002 7.2637130e-002 -9.2881000e-003
+v 4.3218200e-002 9.9486740e-002 5.0153300e-003
+v -5.1108270e-002 1.4520219e-001 1.4279480e-002
+v 4.4692980e-002 9.2688550e-002 2.2466700e-003
+v 4.3422540e-002 9.1860370e-002 2.4538450e-002
+v 4.0751360e-002 1.0554729e-001 7.5074100e-003
+v -8.5613030e-002 9.6277110e-002 -6.6514000e-004
+v 4.0721470e-002 7.8475530e-002 -8.2130000e-003
+v 3.5538080e-002 7.6062960e-002 -1.4434750e-002
+v -9.2736510e-002 1.2073095e-001 3.2692730e-002
+v -6.2278520e-002 1.5166598e-001 -1.4672730e-002
+v 4.4960220e-002 8.0942630e-002 6.1119000e-004
+v 3.7814740e-002 7.9698150e-002 -1.3289630e-002
+v 3.3864490e-002 7.8656690e-002 -1.7632490e-002
+v -9.1044280e-002 1.4199862e-001 2.1729630e-002
+v -7.4004450e-002 1.7818523e-001 -5.3916320e-002
+v -6.1768650e-002 1.6067957e-001 -3.4046350e-002
+v -4.9747450e-002 1.4112519e-001 5.2937500e-003
+v 4.1065440e-002 9.0460700e-002 2.9888620e-002
+v -7.2916360e-002 6.5057400e-002 1.8794620e-002
+v -9.0949690e-002 1.3895375e-001 1.7371130e-002
+v 4.2879050e-002 1.0093777e-001 9.4753200e-003
+v -7.2455480e-002 1.7610676e-001 -5.3535420e-002
+v -7.5862940e-002 1.5071299e-001 -9.0209000e-003
+v -8.5269820e-002 1.0267793e-001 1.3935600e-003
+v -7.7025570e-002 1.1396763e-001 -4.6168100e-003
+v 4.6280880e-002 7.8702020e-002 1.4786330e-002
+v 4.2106910e-002 8.1533160e-002 -6.6690900e-003
+v 3.6523880e-002 8.1991750e-002 -1.6229590e-002
+v -3.7420220e-002 4.5428500e-002 -2.4226790e-002
+v -8.5148910e-002 1.3965520e-001 2.4808500e-003
+v -6.3313300e-002 1.6503258e-001 -3.2895120e-002
+v -6.1591410e-002 1.5681572e-001 -2.5945630e-002
+v 4.5918540e-002 8.7036220e-002 8.4236300e-003
+v 4.4631140e-002 8.4178380e-002 8.2665000e-004
+v -4.4842870e-002 1.4629393e-001 1.7114800e-003
+v -6.4124180e-002 1.7953625e-001 -5.8730420e-002
+v -6.7070300e-002 1.8072682e-001 -5.6618620e-002
+v -6.4793760e-002 1.7885275e-001 -5.5883250e-002
+v -6.4371030e-002 1.7296209e-001 -4.9225660e-002
+v -7.0381530e-002 1.8071180e-001 -5.3172590e-002
+v -7.5269270e-002 1.5232949e-001 3.4374060e-002
+v -1.6273090e-002 1.2844514e-001 1.6683610e-002
+v -6.2116150e-002 1.5600787e-001 1.8034420e-002
+v -5.6010790e-002 1.5381662e-001 2.5369280e-002
+v -3.7277920e-002 1.7289068e-001 -8.6627000e-004
+v -7.4158700e-002 1.7987275e-001 -5.0794750e-002
+v -7.9039960e-002 1.5537445e-001 1.5141810e-002
+v -7.2505530e-002 1.5459529e-001 2.9588830e-002
+v -6.7738180e-002 1.7728865e-001 -5.0375960e-002
+v -7.5346900e-003 1.0021302e-001 4.7488700e-002
+v -5.9575620e-002 1.5472401e-001 2.6373250e-002
+v -7.7382710e-002 1.5346600e-001 3.0894990e-002
+v -8.1496670e-002 1.5473104e-001 1.9697340e-002
+v -7.2223320e-002 1.5896734e-001 -5.4242300e-003
+v -1.3708500e-002 1.8491150e-001 -2.5549550e-002
+v -4.3465340e-002 1.2451145e-001 2.2518890e-002
+v -6.9103650e-002 1.5559479e-001 1.6370800e-003
+v -7.3748080e-002 1.5539253e-001 2.3491700e-003
+v -6.8192410e-002 1.7439828e-001 -4.5365870e-002
+v -6.0052850e-002 1.5280350e-001 3.2887630e-002
+v -2.3459490e-002 1.2615386e-001 1.6613770e-002
+v -7.2777220e-002 1.7854465e-001 -4.8208800e-002
+v -7.6595580e-002 1.7753227e-001 -4.7118080e-002
+v 1.3906410e-002 1.2790838e-001 2.5110240e-002
+v -8.6367510e-002 1.0906537e-001 1.1980640e-002
+v -3.1358850e-002 1.2140977e-001 2.5971090e-002
+v -4.9104590e-002 1.3666879e-001 1.9314030e-002
+v -4.2930640e-002 1.2928436e-001 9.2700700e-003
+v -6.5320350e-002 1.5390322e-001 9.1386000e-004
+v -3.7606490e-002 1.2422605e-001 2.4313530e-002
+v 9.5078400e-003 1.3041865e-001 2.0715020e-002
+v -1.7976800e-003 1.3117283e-001 1.6360660e-002
+v 3.6231700e-003 1.3076791e-001 2.1168600e-002
+v -9.2674700e-002 1.1701945e-001 1.1889520e-002
+v -6.5739720e-002 1.5565338e-001 2.6017600e-002
+v -8.6561940e-002 1.4249188e-001 8.4326800e-003
+v -7.0731530e-002 1.5569959e-001 6.9058200e-003
+v -8.0840700e-003 1.3030537e-001 1.6872280e-002
+v -4.4286250e-002 1.2606625e-001 2.0795220e-002
+v -7.0222260e-002 1.5143521e-001 3.6718910e-002
+v -1.5210690e-002 1.8463639e-001 -2.2057240e-002
+v -1.7270750e-002 1.8699602e-001 -1.9977570e-002
+v -8.3560950e-002 1.5255943e-001 7.6806700e-003
+v -8.8130280e-002 9.7540510e-002 5.6788000e-003
+v -8.8399240e-002 1.3899000e-001 1.0640660e-002
+v -6.7780550e-002 1.5614453e-001 1.4276320e-002
+v -6.5864600e-003 1.2641717e-001 3.0226390e-002
+v -8.8746180e-002 1.3625578e-001 7.1477800e-003
+v -7.7206730e-002 1.5639950e-001 -1.8972540e-002
+v -9.3176480e-002 1.1821016e-001 2.3362360e-002
+v -2.3506850e-002 1.2672006e-001 1.0996900e-002
+v -6.6546650e-002 1.7171115e-001 -4.2127770e-002
+v -6.9136000e-002 1.7247836e-001 -3.9013330e-002
+v 5.7180270e-002 7.1107690e-002 8.0307600e-003
+v -7.5390870e-002 1.7952824e-001 -5.2402050e-002
+v -3.1828840e-002 1.2639115e-001 1.0013410e-002
+v -8.9888800e-003 1.2952269e-001 2.2026810e-002
+v 3.4325880e-002 1.1193312e-001 -2.2406500e-003
+v -8.1414950e-002 9.7100250e-002 -6.8745800e-003
+v -2.3298830e-002 1.8324307e-001 -1.7923000e-002
+v -6.1641660e-002 1.5582039e-001 1.1099820e-002
+v -8.8826450e-002 9.0483320e-002 2.1204700e-002
+v 5.8373130e-002 6.8067590e-002 5.7247600e-003
+v -4.3045630e-002 1.2785122e-001 1.6842260e-002
+v 3.0835720e-002 1.1554234e-001 -3.1785500e-003
+v -8.8631270e-002 9.4881200e-002 7.9337600e-003
+v -9.1715140e-002 1.1709957e-001 3.0809400e-002
+v -7.2083780e-002 1.7499844e-001 -4.1930320e-002
+v -6.9540630e-002 1.5308527e-001 3.3865720e-002
+v 6.0078690e-002 6.8129260e-002 1.1454500e-002
+v -4.0081060e-002 1.2628381e-001 1.9607250e-002
+v 3.2819930e-002 1.1655625e-001 4.4458600e-003
+v -7.2823220e-002 1.4510601e-001 -1.5654680e-002
+v -8.5270210e-002 1.0551770e-001 2.3290940e-002
+v -7.6051320e-002 1.1103825e-001 -6.2722100e-003
+v -8.6537730e-002 1.5154801e-001 2.5875370e-002
+v 5.5888480e-002 7.2579250e-002 1.0669650e-002
+v -5.4642360e-002 1.5522963e-001 1.2612400e-002
+v 3.6729960e-002 1.1116756e-001 3.8670600e-003
+v 3.1501870e-002 1.1725172e-001 1.6855100e-003
+v -7.8751550e-002 9.5240290e-002 -1.0600670e-002
+v -8.9408160e-002 1.4352815e-001 3.0924750e-002
+v -2.0891130e-002 1.8595338e-001 -1.5037360e-002
+v -7.0863560e-002 1.6136525e-001 -9.7324600e-003
+v -7.0919760e-002 1.7136688e-001 -3.2763750e-002
+v -3.0771290e-002 1.2564075e-001 1.6594770e-002
+v -5.4454180e-002 1.5297699e-001 2.2505190e-002
+v -1.5539500e-003 1.2754717e-001 2.9232870e-002
+v 2.9130550e-002 1.2027445e-001 6.1117500e-003
+v 2.5725940e-002 1.2122705e-001 -3.6150000e-005
+v -8.9318970e-002 9.9546980e-002 1.3418110e-002
+v -7.5429500e-002 1.7095605e-001 -3.2879890e-002
+v -2.8596020e-002 1.1901156e-001 2.9888170e-002
+v 2.1069780e-002 1.2497756e-001 1.0998100e-003
+v -9.2240760e-002 1.1816838e-001 4.1201730e-002
+v 2.4094600e-003 1.0016785e-001 4.6938070e-002
+v -5.6627620e-002 1.5270606e-001 2.9629030e-002
+v -5.7264800e-002 1.5506250e-001 1.9322430e-002
+v -3.6452070e-002 1.2199869e-001 2.7670650e-002
+v -7.4108160e-002 1.7355729e-001 -3.7986840e-002
+v 5.1537130e-002 7.3496690e-002 1.2698700e-002
+v -6.6096040e-002 1.5532529e-001 7.1561800e-003
+v 3.6102000e-002 1.1266103e-001 1.0491780e-002
+v 1.6715210e-002 1.2689851e-001 2.2331000e-004
+v -8.0767920e-002 1.4301400e-001 -1.5312800e-003
+v -9.1757600e-002 1.4334588e-001 1.7790710e-002
+v -8.6824940e-002 1.5280775e-001 1.5521450e-002
+v -6.5808100e-002 1.6764344e-001 -3.0558670e-002
+v -7.8217340e-002 1.6873975e-001 -3.3564250e-002
+v -7.2567060e-002 1.4753230e-001 4.1714090e-002
+v 5.8439960e-002 7.0200810e-002 1.7779620e-002
+v 5.6847560e-002 7.2017160e-002 1.7139380e-002
+v 5.4919390e-002 7.3161610e-002 1.5223590e-002
+v 4.7446900e-002 7.3691410e-002 1.2430020e-002
+v 1.2319360e-002 1.2903768e-001 1.3336200e-003
+v -7.9790640e-002 1.0351662e-001 -6.6275400e-003
+v -7.6655210e-002 1.5509766e-001 7.9686300e-003
+v 2.1747320e-002 1.2118456e-001 3.0878810e-002
+v -7.5260490e-002 1.4938613e-001 3.9175980e-002
+v -2.5919610e-002 1.8272826e-001 -1.3541090e-002
+v -6.7983790e-002 1.6974781e-001 -3.1627490e-002
+v 1.6831110e-002 1.2487146e-001 2.8425580e-002
+v 5.4016490e-002 7.2883850e-002 1.8678010e-002
+v 5.0522750e-002 7.3397910e-002 1.6166890e-002
+v -5.9582440e-002 1.5623338e-001 7.9209900e-003
+v 2.5343500e-002 1.2374750e-001 9.9818800e-003
+v 1.9262750e-002 1.2689390e-001 5.5552100e-003
+v -9.0758520e-002 1.4223375e-001 2.6008130e-002
+v -4.6548490e-002 1.3320769e-001 1.6889630e-002
+v -2.4106950e-002 1.8380887e-001 -1.1544760e-002
+v 8.6784400e-003 1.2894574e-001 2.6156880e-002
+v 2.4919200e-003 1.2983563e-001 2.4847110e-002
+v 5.7345150e-002 6.9482720e-002 2.1153510e-002
+v -8.5329840e-002 1.5339912e-001 2.0378290e-002
+v 3.2877320e-002 1.1691463e-001 9.2957500e-003
+v 2.4246630e-002 1.2377758e-001 4.8764500e-003
+v -4.7765650e-002 1.3301969e-001 2.2874020e-002
+v -6.3541830e-002 1.6332115e-001 -2.5912990e-002
+v -6.6605200e-002 1.6477375e-001 -2.0670760e-002
+v -6.8504220e-002 1.6732018e-001 -2.3959570e-002
+v -7.2759160e-002 1.6965906e-001 -2.7013420e-002
+v 4.8206850e-002 7.2698580e-002 1.6994630e-002
+v -2.7383180e-002 1.2324257e-001 2.1658860e-002
+v -4.5077500e-002 1.3124443e-001 1.1145770e-002
+v 2.9253150e-002 1.2057701e-001 1.2299330e-002
+v 1.3677610e-002 1.2967262e-001 6.9327400e-003
+v 8.4210900e-003 1.3090986e-001 6.2754400e-003
+v 9.6836000e-004 1.3064303e-001 2.5865900e-003
+v 3.0802000e-003 9.8307360e-002 5.0535640e-002
+v -5.2420170e-002 1.5310101e-001 1.2927370e-002
+v -7.0359720e-002 1.6906988e-001 -2.6144260e-002
+v 5.4359390e-002 7.1467260e-002 2.1381250e-002
+v 4.5161440e-002 7.1030380e-002 2.2530690e-002
+v 1.9320440e-002 1.2738348e-001 1.1296310e-002
+v -9.3281210e-002 1.2691094e-001 1.3505010e-002
+v -8.7405060e-002 1.0593990e-001 1.3645920e-002
+v -2.2851640e-002 9.0635040e-002 5.2280460e-002
+v -6.2099370e-002 1.5406697e-001 3.0837360e-002
+v -4.5851560e-002 1.2072981e-001 2.7665040e-002
+v 5.0781670e-002 7.2155170e-002 2.0680180e-002
+v -8.9607270e-002 1.3971105e-001 2.9308560e-002
+v -5.3323050e-002 1.5273520e-001 1.6213860e-002
+v -1.5227080e-002 1.2784878e-001 2.1545200e-002
+v 3.3663540e-002 1.1574212e-001 1.7181290e-002
+v 2.4000260e-002 1.2468761e-001 1.5517930e-002
+v -8.4166840e-002 9.7756820e-002 -3.2761900e-003
+v -3.6223590e-002 1.2777519e-001 9.8501500e-003
+v -3.9189580e-002 1.2828193e-001 5.0346300e-003
+v -3.3674050e-002 1.7774449e-001 -8.1799500e-003
+v -7.4488620e-002 1.5649443e-001 -2.5954600e-003
+v -4.6755620e-002 1.3284294e-001 8.1212800e-003
+v -8.4970410e-002 1.5322309e-001 1.2654460e-002
+v -1.0866210e-002 1.2691699e-001 2.7575440e-002
+v -3.1074000e-003 1.3072898e-001 5.6428500e-003
+v -8.8760540e-002 9.7037440e-002 2.1079040e-002
+v -6.4811320e-002 3.4530640e-002 1.5508440e-002
+v -6.4300260e-002 3.5086450e-002 2.4272050e-002
+v -6.6727020e-002 3.5895770e-002 3.3849430e-002
+v 1.9838510e-002 9.6518890e-002 -2.2785880e-002
+v -3.8670510e-002 1.6070199e-001 -1.2357760e-002
+v -7.6890090e-002 1.3041906e-001 -6.9570100e-003
+v -7.2539730e-002 3.5399270e-002 7.0298800e-003
+v -6.9209050e-002 3.5454810e-002 1.2042140e-002
+v -6.4160810e-002 3.5900770e-002 1.7687570e-002
+v -6.6804150e-002 3.7377740e-002 3.3296290e-002
+v -6.2928350e-002 3.9061660e-002 4.2707680e-002
+v -7.1752230e-002 3.6789350e-002 8.6966700e-003
+v -6.5171380e-002 3.7289500e-002 2.5953770e-002
+v -6.6392030e-002 3.7712350e-002 2.9621950e-002
+v -6.4558720e-002 3.9639900e-002 3.9411530e-002
+v -6.0145790e-002 4.1202050e-002 4.4293830e-002
+v -6.0318430e-002 3.8442990e-002 4.5245950e-002
+v -3.6756310e-002 8.8663360e-002 -2.3868800e-002
+v -3.9494750e-002 3.7551570e-002 4.2870900e-002
+v -7.2016030e-002 3.7572700e-002 3.9789400e-003
+v -7.1693630e-002 3.9461000e-002 6.0145000e-003
+v -7.1165950e-002 3.9366310e-002 8.1142100e-003
+v -6.9000300e-002 3.8467710e-002 1.0768900e-002
+v -6.7253420e-002 3.8142160e-002 1.3533960e-002
+v -6.1125670e-002 3.7790050e-002 1.9710900e-002
+v -3.9179680e-002 4.2406740e-002 4.1476070e-002
+v -3.5145960e-002 3.8585920e-002 4.7732690e-002
+v -2.8950940e-002 3.9285940e-002 5.3309090e-002
+v -1.8223900e-002 9.7494570e-002 4.6847940e-002
+v -6.6916260e-002 1.2278907e-001 -8.9077400e-003
+v -6.3754640e-002 3.8250120e-002 1.6593500e-002
+v -6.4415760e-002 4.1283840e-002 2.8243480e-002
+v -8.5856340e-002 9.7025390e-002 2.7414960e-002
+v -3.7501130e-002 4.0221900e-002 4.4296550e-002
+v -3.4333970e-002 4.0923630e-002 4.8425810e-002
+v -3.1172890e-002 4.0294330e-002 5.1312460e-002
+v -6.9997320e-002 4.2073080e-002 6.6897800e-003
+v -8.0379330e-002 9.7800660e-002 3.3645750e-002
+v -2.6273160e-002 7.7631160e-002 4.8356180e-002
+v -3.7501450e-002 4.2736690e-002 4.2988400e-002
+v -2.6177500e-002 4.2498930e-002 5.3315220e-002
+v -6.9637250e-002 4.1881270e-002 3.1825800e-003
+v -6.7156510e-002 4.1972860e-002 1.0240940e-002
+v -8.7405510e-002 1.0205209e-001 2.2020360e-002
+v -2.3944380e-002 7.8800140e-002 5.3534730e-002
+v -6.0902360e-002 4.3429500e-002 4.2678530e-002
+v -3.1217880e-002 4.3847510e-002 4.9780920e-002
+v -7.5729440e-002 1.0354026e-001 3.6070970e-002
+v -6.2425320e-002 4.1885720e-002 1.4646770e-002
+v -6.1051660e-002 4.4392230e-002 1.2421940e-002
+v 2.5855060e-002 8.9610660e-002 -2.2701840e-002
+v -7.7644960e-002 8.2214940e-002 3.5797660e-002
+v -6.0381270e-002 4.5921420e-002 4.0088740e-002
+v -2.4982010e-002 8.1777650e-002 5.3421060e-002
+v -3.4453850e-002 4.4563960e-002 4.5422990e-002
+v -2.9842910e-002 4.6782280e-002 4.7746920e-002
+v -1.5119580e-002 9.9930020e-002 4.4500270e-002
+v -6.7306470e-002 4.4176830e-002 7.5958300e-003
+v -5.7852990e-002 4.6444500e-002 1.1062610e-002
+v -5.1815260e-002 1.6392582e-001 1.7488800e-003
+v -5.5174130e-002 4.8383880e-002 3.8517780e-002
+v -7.8849150e-002 1.1867375e-001 5.0622870e-002
+v -2.7229070e-002 8.7991480e-002 4.7909730e-002
+v -7.5536880e-002 1.5977062e-001 -1.0438650e-002
+v -3.6151280e-002 4.6505140e-002 4.0740900e-002
+v -2.5439220e-002 9.0677870e-002 4.8852330e-002
+v -8.0050370e-002 1.1670406e-001 4.8762460e-002
+v -5.2513640e-002 4.7577880e-002 1.4858440e-002
+v -3.2043560e-002 5.0461830e-002 3.9341520e-002
+v -3.1487770e-002 4.6930210e-002 4.5253210e-002
+v -2.0321500e-002 9.3999570e-002 5.1588540e-002
+v -7.2145040e-002 9.1556450e-002 4.1494780e-002
+v -5.3644200e-002 4.9358170e-002 1.2201850e-002
+v -8.2403890e-002 1.2186563e-001 4.9365030e-002
+v -4.9754420e-002 4.9738300e-002 3.7037110e-002
+v -3.2332060e-002 4.8672840e-002 4.2523960e-002
+v -2.3122950e-002 9.4515900e-002 4.7358870e-002
+v -8.6347140e-002 9.1722090e-002 2.6811080e-002
+v -5.7713110e-002 4.8717820e-002 7.2765100e-003
+v -8.6970360e-002 8.8912090e-002 2.4879860e-002
+v -9.2237750e-002 1.2488519e-001 4.0786530e-002
+v -1.5862800e-002 9.7021620e-002 5.0139360e-002
+v -2.7720040e-002 5.0502090e-002 4.3340720e-002
+v -8.5918770e-002 1.4263412e-001 3.9849810e-002
+v -7.5097360e-002 9.0073560e-002 3.9581000e-002
+v -8.9430840e-002 1.4730552e-001 2.7694960e-002
+v -5.3288350e-002 5.1925760e-002 1.1730350e-002
+v -5.0168720e-002 5.3462260e-002 1.6255440e-002
+v -8.5986050e-002 1.4670902e-001 3.4827030e-002
+v -6.9937250e-002 8.6076860e-002 4.2175690e-002
+v -5.0399320e-002 5.1831330e-002 3.4037400e-002
+v -8.3298980e-002 1.4960772e-001 3.3740890e-002
+v -2.9174820e-002 5.2264530e-002 3.7637320e-002
+v -8.8763730e-002 1.1944938e-001 4.6560090e-002
+v -7.7693460e-002 1.7367969e-001 -4.1478670e-002
+v -8.3418140e-002 9.4127440e-002 3.0898450e-002
+v -5.6067510e-002 5.3470630e-002 7.3718200e-003
+v -7.8935630e-002 1.4817228e-001 3.9463070e-002
+v -6.7902770e-002 8.7817230e-002 4.3526990e-002
+v -4.4111240e-002 9.2883990e-002 -2.2373210e-002
+v -8.6605100e-002 1.3226807e-001 4.6783020e-002
+v -9.2654280e-002 1.2084025e-001 4.1629650e-002
+v -5.0887310e-002 5.2727900e-002 1.4455790e-002
+v -4.9763410e-002 5.6241200e-002 3.3624250e-002
+v -8.9771330e-002 1.2904861e-001 4.3022990e-002
+v -2.8054240e-002 5.4551030e-002 3.6786850e-002
+v -2.5867080e-002 5.6689210e-002 3.9182240e-002
+v -8.3702200e-002 1.2226381e-001 -3.7301400e-003
+v -8.1455470e-002 1.3012213e-001 5.2117660e-002
+v -5.1458550e-002 5.5878150e-002 1.5900350e-002
+v -7.8597700e-002 1.7441574e-001 -4.6607580e-002
+v -5.2909820e-002 5.7043070e-002 2.0988410e-002
+v -5.2978500e-002 5.9553770e-002 2.6211920e-002
+v -5.2130640e-002 5.6302970e-002 2.6672460e-002
+v -4.7714500e-002 6.1944520e-002 3.6705820e-002
+v -8.3539790e-002 8.1169560e-002 2.7014070e-002
+v -1.8340000e-002 5.7489970e-002 4.9763020e-002
+v -8.0069810e-002 9.0586130e-002 3.4593070e-002
+v -8.3812250e-002 8.6337700e-002 2.9223270e-002
+v -5.5436650e-002 5.9420250e-002 2.3018970e-002
+v -8.2227680e-002 1.4513771e-001 4.0600080e-002
+v -2.4187580e-002 7.2269150e-002 4.7681090e-002
+v -2.5353150e-002 6.2567200e-002 4.0642170e-002
+v -9.1132110e-002 1.2282100e-001 4.4115160e-002
+v -4.6076290e-002 1.6819719e-001 7.3744000e-004
+v -8.7829280e-002 1.4351461e-001 3.5707670e-002
+v -8.6990640e-002 1.3812326e-001 4.2316550e-002
+v -1.5715900e-002 6.0822970e-002 5.2365440e-002
+v -8.3803580e-002 1.2561100e-001 5.0440490e-002
+v -6.2786680e-002 1.1274190e-001 -1.3605440e-002
+v -8.1033840e-002 8.4698180e-002 3.3106400e-002
+v -8.8563540e-002 1.1624535e-001 4.5392840e-002
+v -2.0268380e-002 6.2266810e-002 4.8212120e-002
+v -1.2619630e-002 6.1635030e-002 5.4424080e-002
+v -7.0491190e-002 8.1818160e-002 4.0609890e-002
+v -8.3882520e-002 1.3331465e-001 4.9113540e-002
+v -5.6560350e-002 4.8355540e-002 3.6607050e-002
+v 9.9444900e-003 1.0919723e-001 -1.9472810e-002
+v -5.5928250e-002 3.5917310e-002 4.6376100e-002
+v -7.6003260e-002 1.6361344e-001 -1.8021110e-002
+v -8.3798850e-002 1.0290691e-001 2.8038330e-002
+v -8.8252110e-002 1.2692730e-001 4.6141300e-002
+v -7.9126720e-002 1.0619883e-001 3.2050700e-002
+v -8.8206230e-002 9.4485700e-002 2.3744010e-002
+v -8.9110330e-002 1.3851394e-001 3.7658780e-002
+v -1.9321360e-002 9.2123890e-002 5.3820650e-002
+v -5.8265630e-002 9.0926390e-002 -2.0948690e-002
+v -2.7046310e-002 6.7014450e-002 3.9672140e-002
+v -2.1416300e-002 1.7977662e-001 -2.1732520e-002
+v -7.8240000e-003 1.0924112e-001 -2.2185670e-002
+v -2.3988340e-002 8.5995590e-002 5.3716430e-002
+v -6.0483580e-002 1.5567975e-001 4.3343800e-003
+v -8.6389150e-002 1.2168475e-001 4.8412440e-002
+v -7.4084360e-002 1.4987744e-001 -3.2610050e-002
+v -2.0580600e-002 7.9572500e-002 5.6013880e-002
+v -8.3837500e-002 1.3927865e-001 4.4893850e-002
+v -2.2933960e-002 3.5632910e-002 5.2865490e-002
+v -8.6153620e-002 1.2735612e-001 4.8563960e-002
+v -6.5728590e-002 1.0709818e-001 -1.4317670e-002
+v -2.1481090e-002 7.4194460e-002 5.2857680e-002
+v -7.6423900e-002 1.5736285e-001 -9.0354600e-003
+v -7.7216010e-002 8.5594880e-002 3.7420770e-002
+v -8.4150830e-002 1.2955013e-001 5.0483700e-002
+v -8.1221440e-002 8.1003250e-002 3.1255840e-002
+v -8.1704000e-002 1.0167226e-001 3.0939660e-002
+v -8.6252730e-002 1.0106846e-001 2.5413770e-002
+v -8.0944970e-002 1.3903572e-001 4.7359080e-002
+v -7.8908350e-002 9.4830900e-002 3.5435500e-002
+v -7.3440160e-002 9.5412600e-002 4.0210650e-002
+v -5.2675780e-002 8.8220740e-002 -2.1886300e-002
+v -7.6440670e-002 7.7511060e-002 3.3748300e-002
+v -2.1791140e-002 1.0658035e-001 -2.2327000e-002
+v -8.8360940e-002 1.4996706e-001 2.6044170e-002
+v -2.4078870e-002 6.7906700e-002 4.5178370e-002
+v -2.0018090e-002 6.7569300e-002 5.1565340e-002
+v -8.3577750e-002 1.2052625e-001 4.9177500e-002
+v -1.4655950e-002 1.7456543e-001 -2.5972690e-002
+v -2.7395940e-002 8.4108300e-002 4.8745680e-002
+v -4.1933580e-002 8.8463400e-002 -2.2126350e-002
+v -3.1693900e-002 1.0261265e-001 -2.2352310e-002
+v -2.7890200e-002 1.0440703e-001 -2.2830920e-002
+v -7.3790400e-002 1.2016662e-001 -7.8851200e-003
+v -4.6124160e-002 1.0506369e-001 -2.0457580e-002
+v -2.7412650e-002 7.3269450e-002 4.2641380e-002
+v -4.5532880e-002 3.4736480e-002 -2.1363200e-002
+v -4.4993030e-002 3.9017010e-002 -2.1097830e-002
+v -4.6462610e-002 3.6800270e-002 -1.7778710e-002
+v -8.8366460e-002 1.1361863e-001 5.8227800e-003
+v 5.1746240e-002 7.2897250e-002 9.0647400e-003
+v -7.0385250e-002 3.7450300e-002 -9.3190000e-004
+v -6.0923170e-002 3.8621820e-002 2.2468850e-002
+v -7.7696720e-002 1.7027889e-001 -4.3117910e-002
+v -4.3793210e-002 1.6955506e-001 -7.3026400e-003
+v -7.7587180e-002 1.7717875e-001 -5.0221090e-002
+v -4.0541880e-002 3.8886010e-002 -2.7364950e-002
+v -4.4215850e-002 3.6131460e-002 -2.4252210e-002
+v -6.6634880e-002 4.0430310e-002 -5.0180700e-003
+v -6.9242120e-002 4.1474050e-002 1.9289000e-004
+v -7.5640690e-002 1.5930400e-001 -2.6908460e-002
+v -6.3087030e-002 3.9614170e-002 2.5181560e-002
+v -7.2303020e-002 1.5186699e-001 -4.1544310e-002
+v -4.1051490e-002 4.1528620e-002 -2.4061000e-002
+v -4.6990580e-002 3.8892380e-002 -1.4016920e-002
+v -8.9559690e-002 1.2851666e-001 4.5457500e-003
+v -7.6987340e-002 1.5369375e-001 -2.2970800e-003
+v -7.0121670e-002 1.6882633e-001 -5.1173650e-002
+v -6.4792610e-002 4.1724530e-002 3.1616900e-002
+v -4.2148060e-002 1.2409627e-001 -9.5602500e-003
+v -4.8069700e-002 1.2493027e-001 -8.4076400e-003
+v -4.2150480e-002 4.3343970e-002 -2.1508710e-002
+v -6.7315160e-002 4.4034000e-002 1.5741800e-003
+v -7.3386640e-002 1.5463418e-001 -2.9943830e-002
+v -5.5352770e-002 4.2936210e-002 1.9135490e-002
+v -6.0067770e-002 4.1419500e-002 2.2953280e-002
+v -6.5488460e-002 4.0937780e-002 3.5315470e-002
+v -8.0066400e-002 1.5039650e-001 6.0518000e-004
+v -4.4031300e-002 4.1949070e-002 -1.7993960e-002
+v -4.5186510e-002 4.2453420e-002 -1.4193620e-002
+v -8.3109430e-002 1.0265445e-001 -3.2933400e-003
+v -6.5472800e-002 4.5627570e-002 4.5575400e-003
+v -7.5427730e-002 1.5201213e-001 -1.4393690e-002
+v -5.4473420e-002 4.5937510e-002 2.3612600e-002
+v -6.2464100e-002 4.3722000e-002 2.8493310e-002
+v -6.2832600e-002 4.5182750e-002 3.4622890e-002
+v -6.3538130e-002 4.3524020e-002 3.7974010e-002
+v -6.0255260e-002 4.4749620e-002 -4.1316200e-003
+v -6.3242050e-002 4.5549700e-002 4.8428000e-004
+v -6.2249430e-002 4.6540050e-002 7.1903500e-003
+v -9.1003650e-002 1.4885725e-001 2.1507030e-002
+v -5.7094130e-002 4.5996540e-002 2.6865280e-002
+v -5.7276490e-002 4.7299580e-002 2.9889950e-002
+v -3.9519900e-002 1.7385855e-001 -7.5752600e-003
+v -8.9641110e-002 1.3841920e-001 3.4141800e-002
+v -9.2601430e-002 1.3018652e-001 2.5183580e-002
+v -9.2280860e-002 1.2762053e-001 2.9751670e-002
+v -3.3957310e-002 4.1025060e-002 -2.9660250e-002
+v -9.0199540e-002 1.1657506e-001 5.6754900e-003
+v -5.8515890e-002 4.7731310e-002 2.1246000e-004
+v -7.1723560e-002 1.4617438e-001 -2.1567820e-002
+v -5.2389820e-002 4.5449130e-002 1.7686300e-002
+v -5.9414350e-002 4.7277990e-002 3.4172420e-002
+v -5.7520620e-002 1.5877600e-001 4.1621200e-003
+v -8.0959140e-002 1.0926674e-001 -2.0189900e-003
+v -5.1904000e-002 4.6100060e-002 1.9421290e-002
+v -5.1830050e-002 4.8568730e-002 2.1647030e-002
+v -7.7650400e-002 1.5658012e-001 -1.6599150e-002
+v -3.7416450e-002 4.7682130e-002 -1.7147280e-002
+v -7.8876110e-002 1.5347012e-001 3.9875800e-003
+v -5.7635420e-002 5.0425540e-002 4.6108400e-003
+v -5.2625440e-002 5.0434620e-002 2.9046740e-002
+v -5.2998720e-002 4.9169020e-002 3.3967600e-002
+v -7.3502600e-002 1.6871934e-001 -4.4791800e-002
+v -5.4420720e-002 4.7836520e-002 -5.9186900e-003
+v -5.2312740e-002 5.1085350e-002 2.4485690e-002
+v -7.9129930e-002 1.6736568e-001 -3.5506230e-002
+v 9.4115700e-003 1.2350285e-001 -9.8291000e-003
+v -3.2715700e-002 1.0896631e-001 -1.8941410e-002
+v -3.1133380e-002 4.9607260e-002 -1.9406940e-002
+v 4.5997330e-002 6.9814450e-002 3.0143300e-003
+v 3.3525460e-002 1.0966209e-001 -6.9894800e-003
+v -5.5047160e-002 5.2767560e-002 -3.9461300e-003
+v -5.6897890e-002 4.9655570e-002 -1.5319000e-003
+v -5.0290500e-002 4.9098930e-002 1.7164780e-002
+v -5.0595170e-002 4.9923270e-002 1.9174130e-002
+v -5.1887420e-002 5.3324670e-002 2.8705560e-002
+v -6.7684480e-002 1.6533627e-001 -5.5466400e-002
+v -3.0271440e-002 5.2106080e-002 -1.7676140e-002
+v -9.1087300e-003 1.1141669e-001 -2.0543230e-002
+v -5.7069360e-002 5.4424380e-002 2.3395500e-003
+v -3.2748380e-002 1.7759875e-001 -1.1627470e-002
+v -2.9009580e-002 5.1265290e-002 -2.2175780e-002
+v -3.1383130e-002 5.1791310e-002 -1.3886800e-002
+v -5.5673960e-002 5.6983850e-002 -3.3510400e-003
+v -5.0916050e-002 5.3813610e-002 1.9753140e-002
+v -8.8875380e-002 1.5169443e-001 2.0086580e-002
+v -7.7153050e-002 1.7378676e-001 -4.7867620e-002
+v -7.8577770e-002 1.6420639e-001 -3.1825860e-002
+v -2.7545910e-002 5.4021570e-002 -2.5147390e-002
+v -5.4463660e-002 5.5357450e-002 1.0326840e-002
+v -8.7041410e-002 1.3058932e-001 9.1161000e-004
+v -9.0009340e-002 1.3278082e-001 5.9220600e-003
+v -9.2232620e-002 1.3195400e-001 1.5430650e-002
+v -4.8639980e-002 1.6472475e-001 -5.0591500e-003
+v -5.4066480e-002 5.9959350e-002 -7.5992200e-003
+v -5.7434090e-002 5.7683500e-002 8.7259700e-003
+v -8.6794730e-002 1.3850688e-001 4.5575900e-003
+v -9.2989530e-002 1.3092307e-001 1.9919290e-002
+v -9.1282030e-002 1.3311897e-001 2.4688630e-002
+v 2.1815020e-002 1.1770533e-001 -1.0015300e-002
+v -2.9647120e-002 5.8104260e-002 -2.1311320e-002
+v -3.1289530e-002 5.5208570e-002 -1.4387840e-002
+v -5.9002160e-002 5.9234620e-002 2.6140800e-003
+v -9.0241700e-002 1.3575994e-001 1.4149160e-002
+v -6.1569420e-002 1.7084875e-001 -6.1679170e-002
+v -6.6070180e-002 1.6557822e-001 -5.8644080e-002
+v -2.4539930e-002 1.8005865e-001 -1.8726950e-002
+v -1.6131750e-002 1.8298848e-001 -2.6037190e-002
+v -3.0809390e-002 5.6998040e-002 -1.7835020e-002
+v 1.0464280e-002 9.6180450e-002 -2.5898970e-002
+v -5.7491630e-002 5.9530160e-002 -1.0786100e-003
+v -8.9146460e-002 1.3650500e-001 2.5952780e-002
+v 4.3714500e-003 1.0391901e-001 -2.1515100e-002
+v -9.0377040e-002 1.3252490e-001 3.1082650e-002
+v -9.0795450e-002 1.3855232e-001 2.0562560e-002
+v -9.4237710e-002 1.2615419e-001 2.2201450e-002
+v -9.0336910e-002 1.3119830e-001 3.8138790e-002
+v -4.5082610e-002 1.2218447e-001 -1.1569430e-002
+v 1.1348010e-002 9.8243750e-002 -2.3024250e-002
+v -3.9227920e-002 9.9184630e-002 -2.1912720e-002
+v -6.5509530e-002 1.5857325e-001 -5.5600270e-002
+v -7.7409510e-002 1.6260515e-001 -2.0754580e-002
+v -4.8580010e-002 1.6689211e-001 -2.5256100e-003
+v -7.6922910e-002 1.5351394e-001 -9.0785600e-003
+v -6.7750580e-002 1.5734825e-001 -5.3982110e-002
+v 5.2906410e-002 6.5230450e-002 -5.1112000e-004
+v -2.9054820e-002 6.1084120e-002 -2.4918230e-002
+v -3.1066920e-002 6.5058860e-002 -2.2751080e-002
+v 2.4249720e-002 1.0266151e-001 -1.8313830e-002
+v -5.5473660e-002 1.6050213e-001 1.3763500e-003
+v -6.6642850e-002 1.6040875e-001 -5.6842680e-002
+v -7.8200320e-002 1.6073213e-001 -2.3999690e-002
+v -1.8320680e-002 1.1968625e-001 -1.1110660e-002
+v 2.1712970e-002 1.0956342e-001 -1.5081090e-002
+v -6.8382640e-002 1.5980248e-001 -5.4208800e-002
+v -2.5445620e-002 6.0208550e-002 -3.0864700e-002
+v -2.6540330e-002 6.5084000e-002 -3.1664870e-002
+v -2.8425710e-002 6.2199610e-002 -2.7938500e-002
+v -3.2605750e-002 6.1264600e-002 -1.5453010e-002
+v -7.0872290e-002 1.1611638e-001 -7.9563700e-003
+v -6.9780530e-002 1.5938570e-001 -4.9418240e-002
+v -3.0324870e-002 6.7694720e-002 -2.7654950e-002
+v -3.2977370e-002 6.6365180e-002 -1.8385530e-002
+v 1.3533490e-002 1.0255388e-001 -2.1579310e-002
+v 4.4408530e-002 6.9758860e-002 9.4765000e-004
+v -2.1999000e-003 1.1215881e-001 -1.9658660e-002
+v -7.2028500e-002 6.7046610e-002 -7.2256000e-004
+v -7.8699630e-002 1.7313910e-001 -4.2720470e-002
+v -8.3211970e-002 1.5072131e-001 4.2128500e-003
+v -8.7439060e-002 1.3374875e-001 2.3974700e-003
+v 2.6348020e-002 8.4562230e-002 -2.3151710e-002
+v -7.4901490e-002 7.0419350e-002 -2.2854300e-003
+v -5.4576350e-002 9.1562950e-002 -2.2098700e-002
+v -7.3242520e-002 1.5231332e-001 -3.5703520e-002
+v -7.4550960e-002 1.7218738e-001 -4.7551010e-002
+v -2.8680680e-002 6.8283500e-002 -3.0610160e-002
+v 1.7372900e-002 1.0246037e-001 -2.1487700e-002
+v -8.1257430e-002 7.3025200e-002 7.1020400e-003
+v -7.4982300e-002 1.5407794e-001 -1.8974470e-002
+v -9.1556500e-002 1.3196262e-001 1.0638150e-002
+v -8.2448000e-004 9.5165120e-002 -3.2056320e-002
+v -7.7618830e-002 7.3999130e-002 -5.3263500e-003
+v -7.9858790e-002 7.2755040e-002 3.0420200e-003
+v -8.1627470e-002 7.3470610e-002 1.1161690e-002
+v -7.3679290e-002 1.4785987e-001 -2.0236290e-002
+v -9.1309820e-002 1.4848588e-001 1.6270070e-002
+v -9.0850140e-002 1.4625613e-001 1.4809050e-002
+v -6.8543890e-002 1.7513008e-001 -5.7187900e-002
+v -2.7253960e-002 1.0747453e-001 -2.1279680e-002
+v 2.1443580e-002 1.2273826e-001 -2.9316700e-003
+v -7.9061200e-002 7.3724300e-002 -8.4521000e-004
+v -8.2063500e-002 7.5993670e-002 1.7615500e-003
+v -8.3736580e-002 7.6771840e-002 8.9586000e-003
+v -9.0205720e-002 1.4947775e-001 1.3035090e-002
+v 8.4818000e-004 1.1670025e-001 -1.7337090e-002
+v -7.4577550e-002 1.5164041e-001 -2.8647990e-002
+v -2.9087460e-002 7.2924630e-002 -3.3354470e-002
+v -3.1184020e-002 7.3989530e-002 -3.0339870e-002
+v -3.2606620e-002 7.1955620e-002 -2.4866580e-002
+v -8.0575990e-002 7.6607800e-002 -2.9879400e-003
+v -8.9491020e-002 1.4392581e-001 1.2488490e-002
+v -7.7388410e-002 1.4656426e-001 -4.3543000e-003
+v -7.2896160e-002 1.5834962e-001 -3.4109420e-002
+v 7.1346500e-003 1.1468229e-001 -1.8345640e-002
+v -3.4502610e-002 7.6130020e-002 -2.2373150e-002
+v -8.3890740e-002 8.0789530e-002 2.2951400e-003
+v -8.3740480e-002 7.7240270e-002 4.6673300e-003
+v -8.6204620e-002 8.0930750e-002 1.0535420e-002
+v -8.6061500e-002 7.9931100e-002 1.4440780e-002
+v -8.1542760e-002 7.7950660e-002 2.6727280e-002
+v 2.6666170e-002 1.1268609e-001 -1.0509540e-002
+v -7.6041430e-002 1.5663068e-001 -2.1420480e-002
+v -9.0012110e-002 1.5083344e-001 1.5752740e-002
+v -7.1156510e-002 1.6335125e-001 -4.5360530e-002
+v -3.3210960e-002 7.6873190e-002 -2.7708380e-002
+v -7.3263090e-002 7.9983830e-002 -1.3749940e-002
+v -7.9285950e-002 8.0048830e-002 -7.0125500e-003
+v -8.6034510e-002 8.2645720e-002 1.9542680e-002
+v -8.4335410e-002 8.0729950e-002 2.2180460e-002
+v -7.1351460e-002 1.5727092e-001 -4.2183090e-002
+v -7.3548450e-002 1.6120822e-001 -3.5288420e-002
+v 1.6732620e-002 1.0991230e-001 -1.7020040e-002
+v -3.0978770e-002 7.7020860e-002 -3.2816490e-002
+v -6.2359240e-002 1.7544824e-001 -6.1485990e-002
+v -1.7587870e-002 1.1491318e-001 -1.7205040e-002
+v -8.2354050e-002 8.0876320e-002 -2.4038900e-003
+v -7.8578910e-002 1.4050129e-001 -4.6031000e-003
+v -2.8931080e-002 7.9247620e-002 -3.5049800e-002
+v -3.1225710e-002 8.0413100e-002 -3.2182320e-002
+v -3.3258680e-002 7.9621670e-002 -2.7146060e-002
+v -4.4697400e-002 1.1791537e-001 -1.4725860e-002
+v -7.9723740e-002 8.4226660e-002 -8.7608600e-003
+v -8.5042160e-002 8.3817830e-002 -7.7640000e-005
+v -8.6776400e-002 8.4344860e-002 1.2419030e-002
+v -8.6674670e-002 8.2665010e-002 1.5174340e-002
+v -8.5106250e-002 8.5176580e-002 2.5679440e-002
+v -7.6975760e-002 8.2935940e-002 -1.1450630e-002
+v -8.2776390e-002 8.3430890e-002 -4.3687000e-003
+v -8.6180440e-002 8.2572150e-002 6.3639000e-003
+v -9.1160820e-002 1.4144362e-001 1.5673910e-002
+v -7.4638800e-002 1.4398484e-001 -7.1504600e-003
+v -8.3448500e-002 1.3393299e-001 -1.6873200e-003
+v -7.5804700e-002 1.5134475e-001 -1.9881200e-002
+v -7.4924140e-002 1.5273013e-001 -1.9397440e-002
+v -5.2314440e-002 1.2159646e-001 -1.0798060e-002
+v -3.0734050e-002 8.5427560e-002 -3.0506670e-002
+v -3.2590560e-002 8.1942660e-002 -2.9100210e-002
+v -8.6454830e-002 8.6940490e-002 9.1667000e-004
+v -1.2501820e-002 1.0634409e-001 -2.2360190e-002
+v -8.8585880e-002 1.4605869e-001 9.8780000e-003
+v -8.5609750e-002 1.4712513e-001 6.5981100e-003
+v -8.7511210e-002 1.5061504e-001 1.0152460e-002
+v -6.0113540e-002 3.5550440e-002 4.4907580e-002
+v -8.8284200e-002 8.6869110e-002 8.1029200e-003
+v -8.8812560e-002 8.7765490e-002 1.4226540e-002
+v -8.8001070e-002 8.6626430e-002 1.5466680e-002
+v -8.6991110e-002 8.6444700e-002 2.2420950e-002
+v -7.4609990e-002 1.4727815e-001 -1.4172380e-002
+v -3.4707910e-002 8.4035880e-002 -2.4302260e-002
+v -8.4964900e-002 8.9962540e-002 -3.0068000e-003
+v -8.8091450e-002 8.7741580e-002 4.8489900e-003
+v -9.1490470e-002 1.4543178e-001 2.2277220e-002
+v -9.4380420e-002 1.2183919e-001 1.7904340e-002
+v -2.9164530e-002 8.5393440e-002 -3.3666780e-002
+v -3.0557790e-002 8.8625920e-002 -2.7550670e-002
+v -7.7770550e-002 8.7844840e-002 -1.1694810e-002
+v -8.0728260e-002 8.8204150e-002 -7.8003100e-003
+v -8.3272540e-002 8.9476690e-002 -5.6502900e-003
+v -8.9398710e-002 8.9539000e-002 1.1645550e-002
+v -8.9698390e-002 1.3971257e-001 1.3774760e-002
+v -7.7134890e-002 1.5151225e-001 -5.5823000e-003
+v -5.1121410e-002 1.6374125e-001 -2.6640500e-003
+v -8.6442960e-002 1.2767438e-001 -1.4864100e-003
+v -6.9605590e-002 1.5490763e-001 -5.0188670e-002
+v -8.7265180e-002 9.2110030e-002 4.2059000e-003
+v -8.9086250e-002 9.2377120e-002 1.0569860e-002
+v -8.9612340e-002 9.1599880e-002 1.7812280e-002
+v -8.2732460e-002 1.4196856e-001 1.2529100e-003
+v -7.2618370e-002 1.4368135e-001 -1.0987100e-002
+v -7.7677230e-002 1.6610992e-001 -3.6777320e-002
+v -1.5078060e-002 9.3863440e-002 -3.4317310e-002
+v -7.1057280e-002 1.5476885e-001 -4.5778530e-002
+v -9.2331920e-002 1.2523886e-001 9.1589500e-003
+v -7.6046700e-002 9.1037250e-002 -1.3643150e-002
+v -8.2942810e-002 9.3291700e-002 -6.1856300e-003
+v -1.0411170e-002 9.4592340e-002 -3.3784850e-002
+v -2.9331140e-002 1.1476230e-001 -1.5844640e-002
+v -3.7218250e-002 1.1594244e-001 -1.5173050e-002
+v -1.2429920e-002 1.0286006e-001 -2.3822480e-002
+v 6.6509600e-003 8.8144500e-002 -3.2945810e-002
+v -6.4119900e-003 9.2876210e-002 -3.4817640e-002
+v 1.5800150e-002 1.1996558e-001 -1.1415630e-002
+v 2.9102740e-002 1.0247506e-001 -1.5768380e-002
+v 4.2080690e-002 6.3480630e-002 -2.5405300e-003
+v 2.8723120e-002 9.7943220e-002 -1.7497350e-002
+v -1.9987640e-002 1.0278313e-001 -2.3392920e-002
+v 3.3748350e-002 8.3644140e-002 -1.8630450e-002
+v -1.8685680e-002 1.8689625e-001 -2.0248700e-002
+v 6.4154900e-003 1.1790181e-001 -1.6282740e-002
+v 5.6305210e-002 6.7769910e-002 2.6525000e-003
+v -5.3608300e-003 1.1289400e-001 -1.9613290e-002
+v 4.5769430e-002 6.4628800e-002 -1.2166100e-003
+v -1.0090870e-002 9.8229650e-002 -2.7731360e-002
+v -6.0458520e-002 1.1755645e-001 -1.1354580e-002
+v 1.2933940e-002 1.1887250e-001 -1.3979370e-002
+v 1.5235680e-002 9.4977900e-002 -2.4437140e-002
+v -3.0892950e-002 4.7409030e-002 -2.4954000e-002
+v -1.7766190e-002 1.8572344e-001 -2.3049280e-002
+v -1.3034890e-002 1.1002855e-001 -2.0161170e-002
+v -7.1206550e-002 3.8608570e-002 7.7218000e-004
+v 1.7904800e-002 1.0627709e-001 -1.7729250e-002
+v -3.3623490e-002 1.1840428e-001 -1.1927480e-002
+v -4.9906840e-002 1.1788332e-001 -1.4402480e-002
+v -6.6878100e-003 1.1747209e-001 -1.5359280e-002
+v -1.5451470e-002 1.8597600e-001 -2.4795870e-002
+v -3.0603900e-002 3.8038460e-002 -3.0123840e-002
+v -1.3220270e-002 1.8397188e-001 -2.7519460e-002
+v -4.7859450e-002 1.1162729e-001 -1.7482120e-002
+v -1.3098990e-002 9.0776040e-002 -3.6659270e-002
+v -6.3117340e-002 1.5425437e-001 2.9730400e-003
+v -5.5139750e-002 1.1051601e-001 -1.7672740e-002
+v -1.1096770e-002 1.8202324e-001 -2.8042450e-002
+v -2.6568900e-002 3.4695830e-002 -2.9113750e-002
+v -6.6396600e-003 1.0222209e-001 -2.3519320e-002
+v -5.6996400e-002 1.5741713e-001 6.0244000e-004
+v 1.9076550e-002 9.1870620e-002 -2.4890230e-002
+v 1.3473090e-002 1.2429893e-001 -6.8361400e-003
+v -2.1730490e-002 9.8410960e-002 -2.4306850e-002
+v -1.7142170e-002 9.8057460e-002 -2.4924330e-002
+v -5.8698110e-002 1.5137318e-001 -6.5801000e-004
+v 3.5641100e-003 1.2764883e-001 -4.4672400e-003
+v -8.5369800e-003 9.9921220e-002 -2.4351070e-002
+v -1.2171980e-002 1.8125102e-001 -2.9061170e-002
+v -6.1113980e-002 1.5305212e-001 9.9983000e-004
+v -2.9570620e-002 1.1713871e-001 -1.3675530e-002
+v 3.0530110e-002 1.1221207e-001 -8.1860600e-003
+v -3.1714100e-002 3.5111530e-002 -3.0658990e-002
+v -1.3691130e-002 1.7914707e-001 -2.8126410e-002
+v 1.1620840e-002 1.1548972e-001 -1.6385680e-002
+v -6.1993570e-002 1.5028063e-001 -1.6297100e-003
+v 3.6684020e-002 1.0099570e-001 -9.8485900e-003
+v 4.8512670e-002 7.1798180e-002 6.0005000e-003
+v -4.6583000e-004 1.1983662e-001 -1.3610580e-002
+v 1.6747170e-002 9.0113950e-002 -2.7127190e-002
+v 6.9832400e-003 9.7730080e-002 -2.4800310e-002
+v -4.3226830e-002 4.6263570e-002 -1.1771730e-002
+v -8.3562500e-003 1.1373600e-001 -1.8239810e-002
+v -1.2354410e-002 1.1556773e-001 -1.6486930e-002
+v 4.6834470e-002 7.4354100e-002 1.0139500e-002
+v 2.5319170e-002 1.0931725e-001 -1.3579660e-002
+v -4.2459500e-002 1.1392482e-001 -1.6188050e-002
+v 5.7744640e-002 6.4158440e-002 2.6277600e-003
+v -5.9710530e-002 3.6535780e-002 -9.4949000e-003
+v -3.2078400e-003 1.0962100e-001 -2.1523850e-002
+v 2.7020740e-002 6.1345700e-002 -2.2292060e-002
+v 7.1030200e-003 1.0191162e-001 -2.1230990e-002
+v -3.8225680e-002 1.2465525e-001 -7.3257400e-003
+v 2.5941540e-002 1.1576352e-001 -8.2193900e-003
+v -6.1297960e-002 3.3900220e-002 -9.3216600e-003
+v -5.9466670e-002 1.4743956e-001 -1.8885400e-003
+v 1.0506610e-002 1.0087700e-001 -2.2109510e-002
+v 3.3081340e-002 1.0273382e-001 -1.2787210e-002
+v 1.2517840e-002 1.0475378e-001 -1.9915960e-002
+v 2.3087990e-002 9.3998720e-002 -2.2210680e-002
+v 3.1555430e-002 9.2484730e-002 -1.8204280e-002
+v 6.2723100e-003 9.9910370e-002 -2.2296890e-002
+v -4.0917240e-002 4.6121780e-002 -1.7942580e-002
+v 3.5407360e-002 9.8188850e-002 -1.2008970e-002
+v 9.4135900e-003 1.2121902e-001 -1.2937780e-002
+v 5.3735190e-002 7.2027350e-002 6.8010000e-003
+v 2.5620340e-002 1.1880719e-001 -5.0330800e-003
+v -3.8150260e-002 4.2466610e-002 -2.6893990e-002
+v -2.8212410e-002 1.1116862e-001 -1.8001930e-002
+v -6.0253590e-002 1.4339100e-001 -3.7906300e-003
+v 1.9016880e-002 1.0401450e-001 -1.9333120e-002
+v 7.5446700e-003 9.1682150e-002 -3.1643140e-002
+v -7.0760800e-003 1.2240119e-001 -1.1364410e-002
+v -1.9047500e-002 9.6562130e-002 -2.7579900e-002
+v -1.6953390e-002 1.0669256e-001 -2.2002990e-002
+v -6.7307000e-004 1.0119875e-001 -2.2857770e-002
+v -9.0179300e-003 1.2528031e-001 -7.7912000e-003
+v -6.8136180e-002 1.8006113e-001 -5.8816050e-002
+v -2.3600190e-002 1.1513818e-001 -1.5577390e-002
+v -5.9831220e-002 4.2842260e-002 -6.6469100e-003
+v 5.3124070e-002 5.9012380e-002 -2.8853800e-003
+v -3.6931840e-002 3.7107370e-002 -2.9714170e-002
+v -5.6215140e-002 1.4139213e-001 -2.8027300e-003
+v 3.6695880e-002 1.0372844e-001 -7.9621500e-003
+v -3.5885070e-002 1.2040038e-001 -1.0640470e-002
+v -9.3569500e-003 8.5423730e-002 -3.8112540e-002
+v -6.0127340e-002 1.2041391e-001 -9.3791100e-003
+v -3.9842790e-002 1.2156113e-001 -1.1570310e-002
+v 2.8322200e-002 1.0847957e-001 -1.2623390e-002
+v -1.8733500e-003 1.1593910e-001 -1.7169430e-002
+v 3.8648150e-002 9.0153340e-002 -1.2549680e-002
+v -1.7359200e-003 9.2244170e-002 -3.4310460e-002
+v 5.0000820e-002 6.1612070e-002 -3.4649900e-003
+v 5.5858960e-002 6.2910170e-002 6.9037000e-004
+v 2.0461520e-002 1.1515372e-001 -1.3103780e-002
+v -1.5165840e-002 1.1798075e-001 -1.4465520e-002
+v -7.0859540e-002 7.1510150e-002 3.3895100e-002
+v 2.2674030e-002 8.6606050e-002 -2.4925490e-002
+v 3.5358840e-002 8.7438890e-002 -1.7109050e-002
+v 1.8400920e-002 1.2145507e-001 -7.6804200e-003
+v -2.5425900e-002 4.1421010e-002 -2.9204830e-002
+v -8.2085100e-003 9.6777440e-002 -3.0809780e-002
+v -5.6810660e-002 3.3873940e-002 -1.1166310e-002
+v -3.4588640e-002 4.4744960e-002 -2.7122900e-002
+v -4.0251680e-002 1.1827531e-001 -1.3674080e-002
+v 1.6387020e-002 1.1402346e-001 -1.5496900e-002
+v 4.2635280e-002 6.0797460e-002 -3.4583700e-003
+v -5.0687200e-002 3.5935870e-002 -1.2380790e-002
+v 7.3446800e-003 9.4509570e-002 -2.9683220e-002
+v -1.9706700e-002 9.2917340e-002 -3.4636880e-002
+v -1.2083040e-002 1.2219229e-001 -9.7120900e-003
+v 4.8805930e-002 6.8457810e-002 1.6952900e-003
+v -3.0869700e-003 9.8402500e-002 -2.7403170e-002
+v -5.3198790e-002 1.3672896e-001 -1.6580500e-003
+v -4.7290060e-002 1.3055355e-001 1.6909100e-003
+v 4.4651700e-003 1.2044039e-001 -1.3931400e-002
+v -2.3850100e-003 1.2290534e-001 -1.0382460e-002
+v -2.4833330e-002 9.5858030e-002 -2.5162110e-002
+v -4.2296900e-002 3.6291920e-002 -2.7253600e-002
+v -5.4388260e-002 1.3404922e-001 -3.9920400e-003
+v -5.0539380e-002 1.3336659e-001 -1.0872200e-003
+v 2.6040300e-003 9.6942660e-002 -2.8407060e-002
+v -7.8163100e-003 1.2821209e-001 -1.9430400e-003
+v 6.5111700e-003 1.3002517e-001 9.2881000e-004
+v 3.4742860e-002 9.2274140e-002 -1.5654590e-002
+v -6.7787700e-002 1.8088887e-001 -5.8191050e-002
+v -3.3715410e-002 1.1151566e-001 -1.8078440e-002
+v 4.4630400e-003 1.2427294e-001 -9.4291400e-003
+v -2.3370170e-002 9.3392760e-002 -3.2031820e-002
+v -4.8982070e-002 1.2980647e-001 -1.3229400e-003
+v -7.8164000e-004 1.2822918e-001 -3.2490000e-003
+v 2.4960400e-003 8.9857600e-002 -3.3628450e-002
+v 7.4553300e-003 1.1196790e-001 -1.9554260e-002
+v 2.8791140e-002 9.1157340e-002 -2.0370210e-002
+v -5.3590150e-002 1.2437450e-001 -7.3470400e-003
+v -4.7743630e-002 1.2064432e-001 -1.2812990e-002
+v -1.9616230e-002 1.2109197e-001 -9.5487700e-003
+v -6.5047370e-002 1.7999148e-001 -5.9758600e-002
+v -5.1704160e-002 3.7620360e-002 -1.1763450e-002
+v -5.2124270e-002 1.2929832e-001 -4.1187000e-003
+v -4.5334450e-002 1.2891494e-001 1.5819100e-003
+v -3.0471200e-003 1.2919453e-001 -1.0688000e-003
+v 7.2129600e-003 1.2721957e-001 -5.2073700e-003
+v 1.1669320e-002 1.2720154e-001 -3.1850900e-003
+v 5.3056400e-002 6.9708830e-002 3.1291400e-003
+v -6.3021150e-002 1.7810951e-001 -6.0393570e-002
+v 2.8204800e-002 6.4391270e-002 -2.0698040e-002
+v 3.4400180e-002 1.0503000e-001 -1.0224920e-002
+v 3.0975190e-002 1.0790250e-001 -1.1058430e-002
+v -4.8984390e-002 1.1480518e-001 -1.5966690e-002
+v -3.2821710e-002 1.2300500e-001 -5.9088300e-003
+v -5.0792860e-002 1.2716487e-001 -4.8183200e-003
+v -3.5301670e-002 1.2547815e-001 -3.1542800e-003
+v 5.6455250e-002 6.9951490e-002 4.9191700e-003
+v -1.6240450e-002 1.2512177e-001 -3.6499700e-003
+v -1.6970400e-002 1.1119793e-001 -1.9586410e-002
+v -5.4088120e-002 3.9781210e-002 -1.0544680e-002
+v -3.4190490e-002 4.7514010e-002 -2.2301500e-002
+v 1.3699090e-002 9.3914220e-002 -2.6427690e-002
+v 8.8000000e-004 9.9234930e-002 -2.4355670e-002
+v -4.6459460e-002 1.2723953e-001 -4.8843300e-003
+v -4.1735500e-002 1.2687599e-001 -4.1742000e-003
+v -2.1000480e-002 1.2313643e-001 -6.1190100e-003
+v -1.2130450e-002 1.2572568e-001 -5.2007900e-003
+v -4.3822400e-003 1.2640753e-001 -6.9495200e-003
+v 1.4085700e-003 3.4781990e-002 -2.3265200e-002
+v -1.4846200e-002 3.5070930e-002 -2.6071900e-002
+v -2.1399500e-002 3.4795120e-002 -2.7958820e-002
+v 1.2009220e-002 3.5961900e-002 -2.1735750e-002
+v 3.8249200e-003 3.6129220e-002 -2.3878090e-002
+v -5.1139560e-002 9.6617580e-002 -2.2095120e-002
+v -5.4813320e-002 9.8102480e-002 -2.1425370e-002
+v -2.7597040e-002 1.6979824e-001 -1.8170420e-002
+v 1.3359870e-002 3.9377410e-002 -2.2496330e-002
+v 4.3919300e-003 3.8674430e-002 -2.4170290e-002
+v -6.8478200e-003 3.6444540e-002 -2.5177120e-002
+v -1.3280260e-002 3.7699590e-002 -2.6391810e-002
+v -4.7672760e-002 3.6116650e-002 -1.3301210e-002
+v -4.5590120e-002 1.0853826e-001 -1.8796680e-002
+v -5.0095670e-002 1.0990925e-001 -1.8504510e-002
+v -6.5766640e-002 3.6469550e-002 -7.2073000e-003
+v -2.3455840e-002 1.6824727e-001 -1.8822880e-002
+v -4.5918000e-003 3.8404570e-002 -2.5412870e-002
+v -2.4954130e-002 3.7441060e-002 -2.9152720e-002
+v 2.9007770e-002 3.7358220e-002 -2.7474000e-004
+v -7.9468800e-003 4.1489920e-002 -2.5911270e-002
+v -1.6803800e-002 3.9753810e-002 -2.7565350e-002
+v -6.5156150e-002 1.4034537e-001 -7.6848600e-003
+v -4.7080100e-002 4.0700690e-002 -1.1869830e-002
+v -6.8470630e-002 3.7477700e-002 -4.9557400e-003
+v 3.7326850e-002 4.0209510e-002 -8.5850000e-004
+v 3.5349870e-002 4.1257050e-002 -2.8075100e-003
+v 5.1820700e-003 4.1536320e-002 -2.4065670e-002
+v 1.8660660e-002 1.0030784e-001 -2.2127290e-002
+v -6.0510780e-002 1.0748450e-001 -1.7042300e-002
+v -6.2374340e-002 4.0146090e-002 -7.4040200e-003
+v 2.5456950e-002 3.9483890e-002 -4.0251400e-003
+v -2.2828000e-004 4.3394940e-002 -2.5124420e-002
+v -8.1088400e-003 4.3439060e-002 -2.6140070e-002
+v -1.7362450e-002 4.3237420e-002 -2.7665190e-002
+v -2.6416670e-002 4.4674020e-002 -2.8209740e-002
+v 3.8064500e-003 1.0944331e-001 -2.0203790e-002
+v -5.8232370e-002 9.5690400e-002 -2.0616030e-002
+v -6.6122370e-002 4.2341260e-002 -2.7538800e-003
+v -6.0959920e-002 9.4173040e-002 -1.9015670e-002
+v 3.1352250e-002 4.2649280e-002 -4.6745000e-003
+v -3.3540900e-002 3.6342620e-002 4.9089960e-002
+v 1.7252780e-002 4.4335610e-002 -2.3067190e-002
+v 1.0637660e-002 4.4161560e-002 -2.4926170e-002
+v 4.3843100e-003 4.5806710e-002 -2.6788990e-002
+v -8.2506400e-003 4.5148720e-002 -2.8441070e-002
+v -1.5748410e-002 4.5043860e-002 -2.7877790e-002
+v 2.8990330e-002 4.4697850e-002 -6.1863000e-003
+v 8.1686400e-003 4.5053030e-002 -2.5178740e-002
+v -9.6291000e-004 4.5378230e-002 -2.7308280e-002
+v -1.7033400e-003 4.7819200e-002 -2.9928930e-002
+v -3.1535830e-002 4.4740410e-002 -2.8079410e-002
+v -3.3619650e-002 1.5691468e-001 -1.1024870e-002
+v -5.0751180e-002 4.3109620e-002 -1.0018680e-002
+v 3.6890890e-002 4.7353200e-002 -6.1057100e-003
+v 2.4975630e-002 4.2644580e-002 -7.0169900e-003
+v 2.4562420e-002 4.8369560e-002 -1.9672760e-002
+v 1.3964040e-002 4.5579170e-002 -2.4706510e-002
+v 1.3376130e-002 4.8630300e-002 -2.6551500e-002
+v 3.7308900e-003 4.8127990e-002 -2.9025970e-002
+v -8.7947000e-003 4.7056850e-002 -2.9881630e-002
+v -1.3753770e-002 5.1865060e-002 -3.2243480e-002
+v -2.1200840e-002 4.6657090e-002 -2.7951320e-002
+v 3.9693540e-002 4.5658580e-002 -4.5274100e-003
+v 3.3627400e-002 4.8717730e-002 -6.3904600e-003
+v -6.5352120e-002 9.9294570e-002 -1.6820150e-002
+v 1.2868100e-003 5.0383670e-002 -3.0357440e-002
+v -8.1797500e-003 4.9845800e-002 -3.1071390e-002
+v -1.7184350e-002 4.8210500e-002 -2.9741930e-002
+v -2.6049450e-002 4.7692500e-002 -2.6149500e-002
+v -8.4747010e-002 1.1078350e-001 3.9488380e-002
+v -5.1316870e-002 4.8270690e-002 -7.9310500e-003
+v -8.2506510e-002 1.2765487e-001 -4.6796400e-003
+v 3.8663690e-002 5.1696670e-002 -6.6910200e-003
+v -7.5643160e-002 9.9440450e-002 -1.1927610e-002
+v 2.0284470e-002 5.1349190e-002 -2.4895380e-002
+v 5.9436000e-003 5.0976660e-002 -2.9119360e-002
+v -2.5528290e-002 5.1472710e-002 -2.6884680e-002
+v -3.5562670e-002 4.9399890e-002 -1.2865040e-002
+v -4.2818980e-002 1.6220182e-001 -1.0337510e-002
+v -6.5593600e-002 1.7665711e-001 -6.0504730e-002
+v -3.4151080e-002 1.7442797e-001 -1.3312550e-002
+v 4.3673180e-002 5.0162230e-002 -5.9843500e-003
+v -5.0342410e-002 1.5546197e-001 -5.1927700e-003
+v 2.5464180e-002 5.4029700e-002 -2.1691010e-002
+v 1.0149790e-002 4.9258540e-002 -2.7750590e-002
+v -2.2043190e-002 5.3612020e-002 -3.0135610e-002
+v -3.2875520e-002 5.1677630e-002 -1.0888650e-002
+v -3.7613820e-002 4.9534770e-002 -1.1626140e-002
+v -4.0750630e-002 4.9285110e-002 -1.1286200e-002
+v -4.6385170e-002 4.7490850e-002 -1.0085980e-002
+v 4.4473170e-002 5.3293010e-002 -6.3327900e-003
+v 3.3205620e-002 5.1020650e-002 -7.2382500e-003
+v 1.5678350e-002 5.1169270e-002 -2.6397810e-002
+v 6.8341700e-003 5.5010170e-002 -3.0561130e-002
+v 2.1424700e-003 5.5502800e-002 -3.1334400e-002
+v 5.9285000e-004 5.2867950e-002 -3.0513830e-002
+v -3.6481400e-003 5.1869000e-002 -3.1457940e-002
+v -9.4245600e-003 5.5399220e-002 -3.3653980e-002
+v -1.9302150e-002 5.8224770e-002 -3.3919700e-002
+v -6.1084270e-002 1.3386190e-001 -7.2248900e-003
+v -4.3309760e-002 5.5656840e-002 -1.1402110e-002
+v -6.1080540e-002 1.6833773e-001 -5.9192060e-002
+v 4.7574690e-002 5.2943630e-002 -5.1300300e-003
+v -3.7403030e-002 1.1150775e-001 -1.8243310e-002
+v 1.9972490e-002 5.4409710e-002 -2.7108230e-002
+v 5.3974800e-003 5.8382570e-002 -3.0903760e-002
+v -1.0603590e-002 5.3602910e-002 -3.3403350e-002
+v -3.4998290e-002 5.2331560e-002 -1.0347380e-002
+v -4.6471230e-002 5.1304340e-002 -9.8299800e-003
+v -6.7945360e-002 1.1493603e-001 -9.5107300e-003
+v -7.1048210e-002 1.5161088e-001 -4.4679270e-002
+v -5.8903800e-003 3.4790620e-002 -2.4224470e-002
+v 1.6842140e-002 5.5555670e-002 -2.8284560e-002
+v 1.0711040e-002 5.4687610e-002 -2.9767520e-002
+v -1.1826800e-003 5.9492420e-002 -3.3360920e-002
+v -5.2325900e-003 5.5688960e-002 -3.2840220e-002
+v -5.1705830e-002 5.2470760e-002 -7.4047200e-003
+v -5.2626360e-002 6.0043760e-002 -8.9566900e-003
+v -7.2598590e-002 9.7762720e-002 -1.4434510e-002
+v 4.4331260e-002 5.5818010e-002 -6.0362700e-003
+v 3.8463400e-002 5.4934820e-002 -6.1822500e-003
+v 3.8838620e-002 5.7808260e-002 -5.2584800e-003
+v -9.2015400e-003 5.9510130e-002 -3.4437110e-002
+v -3.5262560e-002 5.5284900e-002 -1.0545060e-002
+v -3.8336450e-002 5.4503540e-002 -1.0905320e-002
+v -1.7727540e-002 3.6289540e-002 5.2222250e-002
+v 5.0006490e-002 5.8095800e-002 -4.6211800e-003
+v 4.6133970e-002 5.9278810e-002 -4.7769600e-003
+v 1.5110300e-002 5.9819840e-002 -2.8645750e-002
+v 1.0312380e-002 5.7586530e-002 -2.9995250e-002
+v -6.1353400e-003 6.0256790e-002 -3.4695830e-002
+v -1.2318220e-002 5.9396390e-002 -3.5268510e-002
+v -1.4466910e-002 6.3136020e-002 -3.6865870e-002
+v -4.6650260e-002 5.9840950e-002 -1.2135840e-002
+v -5.6572080e-002 1.2480275e-001 -7.1885700e-003
+v -7.9237500e-002 1.2055419e-001 -5.6744800e-003
+v -7.9334790e-002 1.2560650e-001 -6.1175900e-003
+v 2.2340000e-002 5.8492230e-002 -2.6014120e-002
+v 7.6270400e-003 6.2098330e-002 -3.1135840e-002
+v 3.3101700e-003 6.0456840e-002 -3.2481070e-002
+v -1.6811880e-002 6.1275230e-002 -3.5929330e-002
+v -3.2491910e-002 5.7196350e-002 -1.2104730e-002
+v -3.4108240e-002 6.1466560e-002 -1.3053130e-002
+v -3.3896980e-002 5.7025330e-002 -1.1047570e-002
+v -3.8623580e-002 5.8303290e-002 -1.1505750e-002
+v -4.5008400e-002 6.2723940e-002 -1.3390450e-002
+v -5.6896010e-002 1.3398739e-001 -5.6270700e-003
+v -4.4853890e-002 1.5746031e-001 -8.6731600e-003
+v -7.8609550e-002 6.9656870e-002 1.1810740e-002
+v -2.3730020e-002 1.0186156e-001 -2.3836400e-002
+v -2.8122930e-002 9.9322390e-002 -2.3580130e-002
+v -5.0076720e-002 1.4997652e-001 -3.6419700e-003
+v -3.3048420e-002 9.5958590e-002 -2.3426460e-002
+v 1.9520390e-002 6.2064770e-002 -2.7292470e-002
+v -3.8864710e-002 1.0333987e-001 -2.0641400e-002
+v -4.8952940e-002 5.6281090e-002 -1.0220880e-002
+v -5.3993040e-002 1.4498656e-001 -1.1093400e-003
+v -4.5530560e-002 9.8510850e-002 -2.1729510e-002
+v -5.0910960e-002 1.0074570e-001 -2.1619430e-002
+v 2.3245830e-002 6.2792530e-002 -2.5047990e-002
+v 9.7412800e-003 6.3181400e-002 -3.1141370e-002
+v -8.6614000e-004 6.4559630e-002 -3.4490930e-002
+v -8.5264000e-003 6.4001730e-002 -3.5850480e-002
+v -4.8451500e-002 6.4794120e-002 -1.3029910e-002
+v -5.2325160e-002 1.0614813e-001 -1.9271240e-002
+v -5.5265350e-002 1.0216682e-001 -1.9897100e-002
+v -5.9042010e-002 9.9032210e-002 -1.9222950e-002
+v -5.7846760e-002 1.0433496e-001 -1.8525740e-002
+v -2.7113460e-002 1.7332156e-001 -1.8538890e-002
+v 2.2832000e-002 6.7082570e-002 -2.6297510e-002
+v 1.4519060e-002 6.4595540e-002 -2.9855690e-002
+v 1.1471330e-002 6.7581440e-002 -3.0901170e-002
+v -1.7739360e-002 6.6260830e-002 -3.7657310e-002
+v -6.5059750e-002 1.3452104e-001 -8.0899900e-003
+v -7.5829320e-002 1.4244605e-001 -5.8090000e-003
+v -4.1362350e-002 6.1637330e-002 -1.2813770e-002
+v -5.6147890e-002 6.1921550e-002 -5.7541100e-003
+v -6.2126110e-002 6.2845360e-002 -4.5202600e-003
+v -3.7292480e-002 1.6449057e-001 -1.3627050e-002
+v -1.9818920e-002 1.6509494e-001 -1.7608980e-002
+v 6.2881100e-003 6.5416350e-002 -3.2563040e-002
+v -5.9250500e-003 6.9515630e-002 -3.5933480e-002
+v -1.0538630e-002 6.7999180e-002 -3.6517060e-002
+v -3.5385700e-002 6.6817430e-002 -1.5434860e-002
+v -5.3994500e-002 6.4638700e-002 -9.3254900e-003
+v -6.3852310e-002 6.5572310e-002 -6.9393300e-003
+v -6.3920880e-002 1.2774242e-001 -8.5494600e-003
+v -2.6940700e-002 3.6184050e-002 5.3351850e-002
+v 1.9618650e-002 6.7007390e-002 -2.8356120e-002
+v 1.2275180e-002 6.9933940e-002 -3.1553160e-002
+v 5.4265100e-003 6.8247960e-002 -3.2730520e-002
+v -4.4084200e-003 6.6619200e-002 -3.4870250e-002
+v -2.1911350e-002 6.7144790e-002 -3.6535750e-002
+v -4.5643150e-002 1.5466949e-001 -7.2969400e-003
+v -5.1673460e-002 6.6850660e-002 -1.2120350e-002
+v -5.8105180e-002 6.6465950e-002 -1.0044340e-002
+v -5.6992260e-002 1.4311862e-001 -2.2403000e-003
+v -8.0651110e-002 1.3119854e-001 -4.4397800e-003
+v -5.6544310e-002 1.2850938e-001 -6.2014700e-003
+v 1.7758080e-002 7.0138540e-002 -2.9404680e-002
+v 6.4980500e-003 7.0791870e-002 -3.3525310e-002
+v 7.5831000e-004 7.0434460e-002 -3.4462560e-002
+v -1.3235950e-002 6.9292820e-002 -3.7917490e-002
+v -6.7390780e-002 1.1889688e-001 -8.7301400e-003
+v -3.8119520e-002 6.4162310e-002 -1.3829140e-002
+v 1.8527400e-003 1.1303356e-001 -1.9794270e-002
+v -7.5950810e-002 6.8170610e-002 1.8117970e-002
+v -1.0001990e-002 7.2671480e-002 -3.7661370e-002
+v -1.7976070e-002 7.0613770e-002 -3.8443880e-002
+v -2.3035990e-002 7.2778460e-002 -3.8072640e-002
+v -2.6120100e-002 7.1177480e-002 -3.5451530e-002
+v -6.8535420e-002 1.3929375e-001 -7.8046600e-003
+v -3.5263040e-002 7.1067650e-002 -1.8011860e-002
+v -4.1558180e-002 6.9774010e-002 -1.6774100e-002
+v -5.2831730e-002 7.0298920e-002 -1.4864960e-002
+v -6.6978850e-002 6.7638980e-002 -6.8094400e-003
+v -1.0244470e-002 1.7895826e-001 -2.9538870e-002
+v -7.5272650e-002 1.2680098e-001 -8.0241700e-003
+v -8.7359900e-002 1.1248315e-001 4.2049490e-002
+v 8.7503000e-003 7.4301560e-002 -3.3398210e-002
+v -6.4249520e-002 1.6045024e-001 -5.7041470e-002
+v -4.4354010e-002 7.3372220e-002 -1.7874430e-002
+v -4.5762580e-002 6.9445320e-002 -1.5928780e-002
+v -4.7957440e-002 7.2542990e-002 -1.6106990e-002
+v -5.7822630e-002 6.9538010e-002 -1.4416470e-002
+v -7.2071600e-002 7.1538150e-002 -7.4714400e-003
+v 2.5472930e-002 7.4094500e-002 -2.4938540e-002
+v 1.5719730e-002 7.3756350e-002 -2.9747770e-002
+v 4.8214000e-003 7.3763980e-002 -3.4552450e-002
+v -2.2528600e-003 7.3921320e-002 -3.5887190e-002
+v -7.3834900e-003 7.4799620e-002 -3.7223830e-002
+v -2.0225340e-002 7.7095190e-002 -3.9044290e-002
+v -3.4016180e-002 7.2101270e-002 -2.0823150e-002
+v -3.8493370e-002 7.2839870e-002 -1.7502230e-002
+v -6.4392550e-002 7.3116330e-002 -1.5335340e-002
+v -6.4480660e-002 7.0187350e-002 -1.2261750e-002
+v -2.3854330e-002 1.6164528e-001 -1.4504190e-002
+v 2.2104450e-002 7.2692600e-002 -2.6900140e-002
+v 1.5532370e-002 7.6586960e-002 -2.9606940e-002
+v 1.1574050e-002 7.4860570e-002 -3.1383860e-002
+v -1.4731560e-002 7.7640750e-002 -3.8490670e-002
+v -1.6018820e-002 7.4288800e-002 -3.8864420e-002
+v -5.1103620e-002 7.3071950e-002 -1.6243060e-002
+v -5.7989540e-002 7.4017880e-002 -1.7522320e-002
+v -6.9608380e-002 7.2322890e-002 -1.0934430e-002
+v -7.5996110e-002 1.1714132e-001 -6.5577200e-003
+v -3.7987660e-002 1.0751453e-001 -1.9975760e-002
+v 1.0696210e-002 7.9889200e-002 -3.2009580e-002
+v -5.3433400e-003 7.8264580e-002 -3.7476940e-002
+v -2.6081990e-002 7.6191290e-002 -3.6780200e-002
+v -3.9161040e-002 1.5718885e-001 -1.0580510e-002
+v -6.5609880e-002 7.5860010e-002 -1.6750060e-002
+v -7.0177600e-002 7.5663330e-002 -1.3839210e-002
+v -7.4291360e-002 7.4808360e-002 -9.3537900e-003
+v -6.3428890e-002 1.7185387e-001 -6.1412170e-002
+v 3.0684890e-002 7.5726870e-002 -2.0778090e-002
+v 1.9305010e-002 7.9017870e-002 -2.7743990e-002
+v -8.5992100e-003 7.9338730e-002 -3.7905180e-002
+v -2.3200110e-002 7.6568500e-002 -3.8386500e-002
+v -3.8117820e-002 7.6390120e-002 -1.8644360e-002
+v -4.4231130e-002 7.7664130e-002 -1.9026580e-002
+v -5.1025500e-002 7.5705070e-002 -1.8186900e-002
+v -7.0595130e-002 1.2994832e-001 -8.7629200e-003
+v 2.8147660e-002 7.8785370e-002 -2.2432450e-002
+v 7.6016000e-003 7.9435920e-002 -3.3714560e-002
+v 4.9502400e-003 7.8027250e-002 -3.4409750e-002
+v -1.5858350e-002 8.1165550e-002 -3.9185590e-002
+v -1.8502080e-002 8.3343870e-002 -3.9010720e-002
+v -7.9739350e-002 1.3606854e-001 -4.1482100e-003
+v -3.0980180e-002 1.6634656e-001 -1.6241160e-002
+v -3.5749800e-002 7.7248350e-002 -1.9374020e-002
+v -4.8944740e-002 7.9086360e-002 -1.9575700e-002
+v -5.5065860e-002 7.8089190e-002 -1.9755480e-002
+v 2.3706000e-002 8.0240410e-002 -2.5450120e-002
+v 1.2254110e-002 8.3456700e-002 -3.0771580e-002
+v 1.8549900e-003 8.4692790e-002 -3.4838500e-002
+v -2.0857000e-004 7.8941410e-002 -3.5782080e-002
+v -4.2710000e-004 8.2947370e-002 -3.6380660e-002
+v -4.4101600e-003 8.2794510e-002 -3.7467250e-002
+v -3.3202320e-002 1.0578320e-001 -2.0647590e-002
+v -3.9206970e-002 8.1536380e-002 -2.0571000e-002
+v -6.0355410e-002 7.9766610e-002 -1.9375540e-002
+v -4.1771830e-002 1.0396706e-001 -2.0832940e-002
+v -1.1204010e-002 8.2713320e-002 -3.8489610e-002
+v -2.3181500e-002 8.1686990e-002 -3.8329160e-002
+v -2.7233190e-002 8.0570950e-002 -3.6620670e-002
+v -3.5470180e-002 8.0196070e-002 -2.2325910e-002
+v -4.4864210e-002 8.1997900e-002 -2.0473520e-002
+v -5.0647890e-002 8.2309430e-002 -2.1365890e-002
+v -5.5522610e-002 8.1927600e-002 -2.1353790e-002
+v -8.8089610e-002 1.1135484e-001 1.8516150e-002
+v -7.2036080e-002 1.1107918e-001 4.5361400e-002
+v -3.3359780e-002 1.6986395e-001 -1.5448990e-002
+v -6.6839030e-002 6.2170510e-002 2.1576840e-002
+v 3.0730560e-002 8.1968990e-002 -2.0040460e-002
+v 1.6224320e-002 8.6480380e-002 -2.8952010e-002
+v -6.9855630e-002 1.0027892e-001 -1.4847830e-002
+v -6.3836170e-002 8.1704600e-002 -1.8908860e-002
+v -6.7914820e-002 8.0136290e-002 -1.7128200e-002
+v -4.5752080e-002 1.6340754e-001 -8.1780500e-003
+v 1.1727540e-002 8.8010780e-002 -3.0860110e-002
+v 7.3334800e-003 8.5270000e-002 -3.2829380e-002
+v -3.4356500e-003 8.7017890e-002 -3.6461000e-002
+v -2.6964110e-002 8.4512810e-002 -3.6361740e-002
+v -3.6553370e-002 8.5316190e-002 -2.2576200e-002
+v -3.8791090e-002 8.5232710e-002 -2.1917600e-002
+v -5.7676940e-002 8.6258340e-002 -2.1098320e-002
+v -6.2581810e-002 8.6394530e-002 -1.9169290e-002
+v -7.1395340e-002 1.2468846e-001 -8.5944200e-003
+v 1.4801570e-002 9.9040900e-002 -2.2842920e-002
+v -2.1162860e-002 1.7491852e-001 -2.1977110e-002
+v -1.4824250e-002 8.7288840e-002 -3.8317070e-002
+v -2.3285750e-002 8.9468030e-002 -3.6027250e-002
+v -5.1595650e-002 8.4422070e-002 -2.1600960e-002
+v -6.9481040e-002 8.5656460e-002 -1.7198420e-002
+v -7.0917210e-002 1.0754846e-001 -1.1496630e-002
+v 3.0145320e-002 8.6284000e-002 -2.0408140e-002
+v -5.5578110e-002 1.1567692e-001 -1.4645990e-002
+v -8.0981100e-003 8.9070080e-002 -3.6552200e-002
+v -8.1206310e-002 1.1205088e-001 -8.8299000e-004
+v -1.8772170e-002 8.9838040e-002 -3.6991710e-002
+v -2.1100420e-002 8.6587670e-002 -3.7849050e-002
+v -2.5809910e-002 8.8889590e-002 -3.5082250e-002
+v -4.8984800e-002 9.0731760e-002 -2.1817170e-002
+v -3.5874870e-002 3.4776000e-002 -3.0845200e-002
+v -3.3164390e-002 3.3606540e-002 -2.9721880e-002
+v -2.5964020e-002 3.3487000e-002 -2.6321120e-002
+v -1.6717530e-002 3.3611640e-002 -2.4625420e-002
+v -5.3486300e-003 3.3829010e-002 -2.2600430e-002
+v 6.4843500e-003 3.4293000e-002 -2.0854930e-002
+v 1.3950350e-002 3.4880000e-002 -1.8612870e-002
+v -4.2465980e-002 3.4189100e-002 -2.7260650e-002
+v -3.3241100e-002 3.3578760e-002 -2.6719450e-002
+v 6.2813500e-003 3.4165800e-002 -1.8764230e-002
+v -4.4265790e-002 3.3663660e-002 -2.1914420e-002
+v -2.3671460e-002 3.3630970e-002 -2.3217760e-002
+v -1.1558580e-002 3.3895430e-002 -2.1054260e-002
+v -2.0406400e-003 3.4053940e-002 -1.9331070e-002
+v 1.7323900e-003 3.4459660e-002 -1.6607870e-002
+v -2.7316070e-002 3.3910070e-002 -2.1353750e-002
+v -1.3371080e-002 3.4361580e-002 -1.9023720e-002
+v 9.5887300e-003 3.4207220e-002 -1.5424050e-002
+v -1.4981540e-002 3.5878180e-002 -1.7992380e-002
+v -2.3474300e-003 3.5903130e-002 -1.5929740e-002
+v 2.2544300e-003 3.6411540e-002 -1.4783970e-002
+v -3.5199130e-002 3.3835210e-002 -2.0508290e-002
+v -2.6075450e-002 3.5918600e-002 -1.9405170e-002
+v 8.2740600e-003 3.5645200e-002 -1.2648700e-002
+v 1.0473640e-002 3.4742600e-002 -1.1262870e-002
+v 1.4055380e-002 3.4483430e-002 -1.4495730e-002
+v -3.6970520e-002 3.5680360e-002 -1.5007790e-002
+v -2.4719500e-003 3.8408770e-002 -1.4159030e-002
+v -3.9481890e-002 3.3618220e-002 -2.3612470e-002
+v -4.1091510e-002 3.4006000e-002 -1.1997540e-002
+v -3.1589810e-002 3.5592330e-002 -1.9204150e-002
+v -2.0086310e-002 3.8064450e-002 -1.7220790e-002
+v -1.1113250e-002 3.8290290e-002 -1.5646360e-002
+v 4.4522600e-003 3.7705190e-002 -1.2957650e-002
+v 1.5870480e-002 3.4416230e-002 -2.9666500e-003
+v -4.7872000e-002 3.4136300e-002 -1.5418250e-002
+v -4.7521640e-002 3.3622720e-002 -1.2804590e-002
+v -3.3407340e-002 3.7577040e-002 -1.6158190e-002
+v -2.7851470e-002 3.8404330e-002 -1.7210420e-002
+v -8.5065300e-003 3.9028950e-002 -1.3000800e-002
+v 6.4552500e-003 3.8165190e-002 -1.0164860e-002
+v 7.4147100e-003 3.4659190e-002 -3.0116800e-003
+v 1.1966200e-002 3.4335400e-002 -5.9571300e-003
+v 2.0414820e-002 3.5567580e-002 -3.7806900e-003
+v -1.9288780e-002 3.8762570e-002 -1.4202620e-002
+v -1.1390100e-003 3.9176760e-002 -1.0381370e-002
+v 3.8149200e-003 3.9024470e-002 -8.0827300e-003
+v 7.5208200e-003 3.6733400e-002 -6.7614300e-003
+v 1.9968120e-002 3.4843990e-002 -1.8984900e-003
+v -4.5058400e-002 3.3600490e-002 -1.2527510e-002
+v -3.0754850e-002 3.8639810e-002 -1.4050770e-002
+v -5.1499810e-002 3.3729110e-002 -1.2082510e-002
+v -2.3756860e-002 3.8585750e-002 -1.1093270e-002
+v 3.9734700e-003 3.8208550e-002 -3.7963500e-003
+v 9.5485400e-003 3.4232620e-002 1.7162000e-003
+v 2.9086550e-002 3.5799990e-002 3.5630900e-003
+v -5.5965200e-002 3.3529910e-002 -9.1246200e-003
+v -1.9523510e-002 3.8505210e-002 -4.5434500e-003
+v 1.6363470e-002 3.4394790e-002 2.2948600e-003
+v 2.1324740e-002 3.4624040e-002 5.6444000e-003
+v -3.9670300e-002 3.6174000e-002 -7.3397700e-003
+v -1.4251730e-002 3.8648030e-002 -4.3030400e-003
+v 2.3262300e-003 3.5348200e-002 2.3246000e-003
+v 1.4014300e-002 3.5703800e-002 3.8878900e-003
+v 1.5322800e-002 3.6239700e-002 3.6628500e-003
+v 2.3753130e-002 3.4670710e-002 3.9885300e-003
+v 3.2369180e-002 3.5816010e-002 7.0246300e-003
+v -6.3715900e-002 3.3776930e-002 -8.0065600e-003
+v -6.4266880e-002 3.3562500e-002 -5.1253200e-003
+v -3.8066600e-002 3.8518600e-002 -7.3079600e-003
+v -9.4308800e-003 3.8887690e-002 -7.4848700e-003
+v 3.9677800e-003 3.4200210e-002 4.9754500e-003
+v 9.4292600e-003 3.6030400e-002 4.5275100e-003
+v 2.9859020e-002 3.4980130e-002 9.8349300e-003
+v -5.2730060e-002 3.3497900e-002 -1.8117500e-003
+v -4.1271000e-002 3.3855400e-002 -1.8800800e-003
+v -3.1105000e-003 3.8946190e-002 -2.7793900e-003
+v 6.2194100e-003 3.5134100e-002 6.5492800e-003
+v 2.0897900e-002 3.5937100e-002 8.7849000e-003
+v 3.5606010e-002 3.6526640e-002 9.8155300e-003
+v -6.7078340e-002 3.3840100e-002 -6.1688300e-003
+v -8.1140000e-004 3.7424170e-002 4.7721500e-003
+v 3.1492300e-003 3.4125310e-002 1.1762220e-002
+v 4.9172000e-003 3.3997100e-002 9.1666100e-003
+v 2.5130800e-002 3.4546910e-002 1.1012580e-002
+v 2.8248620e-002 3.5046370e-002 1.6016700e-002
+v -6.7032970e-002 6.5145960e-002 2.7292860e-002
+v -4.6380170e-002 3.3605230e-002 -8.9435000e-004
+v -3.3163400e-002 3.8195400e-002 -5.2520000e-004
+v -3.2074200e-002 3.8323400e-002 -4.2109000e-004
+v -2.1692690e-002 3.8266010e-002 4.5100800e-003
+v 2.3930750e-002 3.4816710e-002 1.7739160e-002
+v 4.2719120e-002 3.9977070e-002 8.9321600e-003
+v -5.8604080e-002 3.3462230e-002 -2.1667000e-004
+v -3.7314400e-002 3.3633000e-002 4.5724700e-003
+v -1.0423990e-002 3.8488570e-002 6.2292700e-003
+v -1.3896900e-003 3.8651360e-002 2.3966500e-003
+v -3.0845000e-004 3.5462480e-002 8.2607200e-003
+v -1.4089000e-003 3.6193080e-002 1.2944550e-002
+v 2.2252900e-002 3.6583300e-002 1.3979700e-002
+v -7.0961830e-002 3.4345730e-002 -7.8374000e-004
+v -6.9066180e-002 3.3717630e-002 -1.9761000e-004
+v -6.4825640e-002 3.3505860e-002 2.8222500e-003
+v -4.7059660e-002 3.3501860e-002 3.5646400e-003
+v -3.6953800e-003 3.8172780e-002 1.3046800e-002
+v 3.3475850e-002 3.6447340e-002 1.6266960e-002
+v 3.7249610e-002 3.7509920e-002 1.4815820e-002
+v -4.5675940e-002 3.3703640e-002 6.4300300e-003
+v -3.8639270e-002 3.3937310e-002 8.5506500e-003
+v -9.5064100e-003 3.8352640e-002 1.5570660e-002
+v 2.1499800e-002 3.5807100e-002 1.8169400e-002
+v 4.4876460e-002 4.1230990e-002 1.6008250e-002
+v -7.2474010e-002 3.6255930e-002 1.5532600e-003
+v -7.1498130e-002 3.4452970e-002 4.2026500e-003
+v -2.7790900e-002 3.8062900e-002 7.9376100e-003
+v -1.6556410e-002 3.8286470e-002 1.0215790e-002
+v 8.1043500e-003 3.4842900e-002 1.8134600e-002
+v 2.3589460e-002 3.5890600e-002 2.5337690e-002
+v 4.1261350e-002 4.0585070e-002 2.0751930e-002
+v -5.1350870e-002 3.3645700e-002 8.0329400e-003
+v -4.7104300e-002 3.5549500e-002 8.0803900e-003
+v -1.4103500e-003 3.6999940e-002 1.6982030e-002
+v 9.1714000e-004 3.4803380e-002 1.5634690e-002
+v 2.8887900e-003 3.4636250e-002 1.8849770e-002
+v 1.3279200e-002 3.4379500e-002 2.1423700e-002
+v 1.4322700e-002 3.4425500e-002 2.1593200e-002
+v 1.7490100e-002 3.4646300e-002 2.2040900e-002
+v 2.9868460e-002 3.6248820e-002 1.9872200e-002
+v -3.9222000e-002 3.6326200e-002 1.0789900e-002
+v -3.0307100e-002 3.3995400e-002 1.4706400e-002
+v 2.0081230e-002 3.5172700e-002 2.8018770e-002
+v 2.4989010e-002 3.8104580e-002 2.9429570e-002
+v 3.3584130e-002 3.8303930e-002 2.2928670e-002
+v 4.9015720e-002 4.4573630e-002 2.0659450e-002
+v -5.8225970e-002 6.6607310e-002 3.5050280e-002
+v -6.7330830e-002 3.3846440e-002 8.7266300e-003
+v -3.4692330e-002 3.3828710e-002 1.2438580e-002
+v -2.9803200e-002 3.4287000e-002 1.6353100e-002
+v 1.7023800e-003 3.6310890e-002 2.1179600e-002
+v 4.5137020e-002 4.4625440e-002 2.5516510e-002
+v -6.8876490e-002 1.1022176e-001 3.9004630e-002
+v -5.7680560e-002 3.3622690e-002 1.4040310e-002
+v -5.3210500e-002 3.3585300e-002 1.3987000e-002
+v -3.5711600e-002 3.5891600e-002 1.5502900e-002
+v -2.8861500e-002 3.5396700e-002 1.7350000e-002
+v -2.6580500e-002 3.7742600e-002 1.5705300e-002
+v -1.0974400e-003 3.8147840e-002 2.0427010e-002
+v 3.5047710e-002 4.0973940e-002 2.6970390e-002
+v -6.9685460e-002 3.4478780e-002 9.7984300e-003
+v -5.4019000e-002 3.3309900e-002 1.5848000e-002
+v 4.4816800e-003 3.7117830e-002 2.4755300e-002
+v 6.6605500e-003 3.5204730e-002 2.4315930e-002
+v 8.3833000e-003 3.4748700e-002 2.4057310e-002
+v 3.8883100e-002 4.1032980e-002 2.4976570e-002
+v -2.6441900e-003 3.8727070e-002 2.5131260e-002
+v 3.2222300e-003 3.8708440e-002 2.5898750e-002
+v 9.0016500e-003 3.6890930e-002 2.8482190e-002
+v 1.3196980e-002 3.4835790e-002 3.1630980e-002
+v 2.2291600e-002 3.7053310e-002 3.3101020e-002
+v 2.8948390e-002 3.9160020e-002 2.7234810e-002
+v -8.7773470e-002 1.1181412e-001 3.7144310e-002
+v -1.7870490e-002 3.8203890e-002 2.0243220e-002
+v 1.0087420e-002 3.7047690e-002 3.0822500e-002
+v 4.2296550e-002 4.5435770e-002 2.9040920e-002
+v -8.4341340e-002 1.1388013e-001 4.6513480e-002
+v -7.3795710e-002 1.0895629e-001 3.9217250e-002
+v -5.1243340e-002 6.4239200e-002 3.4258040e-002
+v -6.1777390e-002 3.4017860e-002 1.6900580e-002
+v -3.6665100e-002 3.5304200e-002 2.3032000e-002
+v -1.4930180e-002 3.8643510e-002 2.9378330e-002
+v -8.0894520e-002 1.0967225e-001 3.7910230e-002
+v -8.9822620e-002 1.1387199e-001 3.2845310e-002
+v -6.9655510e-002 6.8728370e-002 3.1127880e-002
+v -7.8449800e-002 1.0988832e-001 4.2517920e-002
+v -7.5824140e-002 1.0794900e-001 3.7128750e-002
+v -5.5740630e-002 3.4128050e-002 2.6674360e-002
+v -3.8279600e-002 3.5429000e-002 2.4380600e-002
+v -3.5283340e-002 3.4179780e-002 2.2744860e-002
+v -2.5798070e-002 3.7865000e-002 1.9981460e-002
+v 6.9064300e-003 3.9004270e-002 2.9548510e-002
+v 1.5448990e-002 3.4852440e-002 3.6984890e-002
+v 1.9128230e-002 3.5640640e-002 3.6642280e-002
+v -6.3664970e-002 6.6047840e-002 3.1828080e-002
+v 3.9604800e-002 4.4939530e-002 2.9992360e-002
+v -8.0294310e-002 7.1702430e-002 1.5995300e-002
+v -5.4185430e-002 6.7322700e-002 3.6935610e-002
+v -7.3110210e-002 1.4847168e-001 -2.8748470e-002
+v -5.8999980e-002 7.3751550e-002 4.1197080e-002
+v -5.9520730e-002 6.1040260e-002 -2.3753800e-003
+v -6.2791800e-002 3.4596760e-002 2.3505640e-002
+v -4.1895500e-002 3.3668300e-002 2.6940000e-002
+v 8.9808200e-003 3.7639400e-002 3.3900800e-002
+v 8.5287800e-003 3.4888000e-002 3.6265100e-002
+v -8.9803890e-002 1.1498106e-001 4.2771650e-002
+v -6.5545420e-002 7.4430370e-002 3.9168070e-002
+v -6.4644190e-002 6.1723230e-002 2.2552000e-004
+v 5.2496900e-003 3.9507100e-002 3.3271200e-002
+v 2.0250320e-002 3.7033170e-002 3.9327190e-002
+v -6.7006400e-002 6.3292870e-002 -1.7493900e-003
+v -6.4479770e-002 6.0651470e-002 4.2343200e-003
+v -5.7219630e-002 5.7000470e-002 4.9175800e-003
+v -7.4362810e-002 7.2437050e-002 3.1430040e-002
+v -6.2019000e-002 3.4343180e-002 3.1883280e-002
+v -4.6870820e-002 3.4444130e-002 3.0513130e-002
+v -2.0814280e-002 3.8400960e-002 2.7868430e-002
+v 1.6439350e-002 3.5635110e-002 4.1281040e-002
+v -6.9087160e-002 1.1205014e-001 4.5320060e-002
+v -7.1811570e-002 1.4861318e-001 -3.4639490e-002
+v -6.9538770e-002 6.3074750e-002 3.5758200e-003
+v -8.4863890e-002 7.8392100e-002 1.6462010e-002
+v -9.1188780e-002 1.1588893e-001 2.4705540e-002
+v -8.8827760e-002 1.1359169e-001 2.3873640e-002
+v -7.1302830e-002 1.1325363e-001 4.9444530e-002
+v -5.4876950e-002 7.0282330e-002 3.8828200e-002
+v -7.7208880e-002 1.0715887e-001 3.4738290e-002
+v -6.1241780e-002 5.9007440e-002 8.0916600e-003
+v -6.5885650e-002 3.5025080e-002 2.9416520e-002
+v -5.7889430e-002 3.4419570e-002 3.6265760e-002
+v -5.1847710e-002 3.4470270e-002 3.4635180e-002
+v -3.4834600e-002 3.4721400e-002 3.4578200e-002
+v -3.0984700e-002 3.8191900e-002 3.2390100e-002
+v -4.9613100e-003 3.9364900e-002 3.6702200e-002
+v 1.2224170e-002 3.5177480e-002 4.2620580e-002
+v -7.4898220e-002 1.1458863e-001 5.0776480e-002
+v -8.0469100e-002 1.1357963e-001 4.6643440e-002
+v -7.4107560e-002 6.9586030e-002 2.7264400e-002
+v -7.9002620e-002 7.6339320e-002 2.9248090e-002
+v -6.5297080e-002 3.4778970e-002 3.3744340e-002
+v -3.3656400e-002 3.4344100e-002 3.6914100e-002
+v 4.9318500e-003 3.4814800e-002 4.3462110e-002
+v 1.1347440e-002 3.6213020e-002 4.4652280e-002
+v -6.0569260e-002 7.1154540e-002 3.8653760e-002
+v -8.8979470e-002 1.1450869e-001 2.8446030e-002
+v -6.8543520e-002 6.1090480e-002 1.0557760e-002
+v -8.2710960e-002 1.1648975e-001 4.8518530e-002
+v -4.1913210e-002 3.4467720e-002 3.3200040e-002
+v -1.1289800e-002 3.9529200e-002 3.8844100e-002
+v -2.8261900e-003 3.4885340e-002 4.5611410e-002
+v -6.4561210e-002 5.9484140e-002 1.3061680e-002
+v -5.8581440e-002 5.7801460e-002 1.3429540e-002
+v -2.3320000e-002 3.9169500e-002 3.8473300e-002
+v -1.8159900e-002 3.9322300e-002 3.9402900e-002
+v -1.6471400e-002 3.4812800e-002 4.3684700e-002
+v 3.2906600e-003 3.5833470e-002 4.6024610e-002
+v -8.5229630e-002 1.1200712e-001 3.0416940e-002
+v -8.5644730e-002 1.1131719e-001 3.4234780e-002
+v -7.4530360e-002 6.6680690e-002 4.6953300e-003
+v -7.1112970e-002 6.2751470e-002 8.7995500e-003
+v -6.1149380e-002 5.8834410e-002 1.6539440e-002
+v -4.6912270e-002 3.4627180e-002 3.9739710e-002
+v -4.0760350e-002 3.4668230e-002 4.0492530e-002
+v -2.6323100e-002 3.4658000e-002 4.3473500e-002
+v -3.1836600e-003 3.6229910e-002 4.7873100e-002
+v -7.9940490e-002 1.0916678e-001 3.4119800e-002
+v -5.9712170e-002 6.3165280e-002 2.8789180e-002
+v -5.1176600e-002 6.8061880e-002 3.7398330e-002
+v -5.0126580e-002 7.0933150e-002 3.9481010e-002
+v -7.2790130e-002 6.4399880e-002 1.5205950e-002
+v -6.8511230e-002 6.1214650e-002 1.5354080e-002
+v -3.9343210e-002 3.5440180e-002 4.2492560e-002
+v -8.1305900e-003 3.5008350e-002 4.7502400e-002
+v -6.6080670e-002 7.0202740e-002 3.5552860e-002
+v -6.8602600e-002 1.4992277e-001 -4.0051350e-002
+v -7.1722100e-002 6.7023040e-002 2.4959750e-002
+v -7.5115010e-002 6.6557040e-002 1.0244090e-002
+v -6.5146650e-002 3.5945650e-002 3.9775080e-002
+v -3.6898600e-002 3.5924640e-002 4.4794170e-002
+v -9.4780400e-003 3.5977600e-002 4.9434210e-002
+v -8.5175960e-002 1.1706809e-001 4.8139420e-002
+v -6.3366400e-002 6.2790260e-002 2.5647610e-002
+v -6.6633330e-002 6.1001700e-002 1.8101240e-002
+v -5.8167590e-002 5.9985190e-002 2.2606060e-002
+v -6.4212210e-002 3.4992560e-002 3.9401920e-002
+v -5.3425790e-002 3.4560020e-002 4.2782420e-002
+v -1.8031490e-002 3.4859970e-002 4.9264760e-002
+v -1.1440410e-002 3.7640770e-002 5.0275730e-002
+v -7.5165320e-002 1.1154286e-001 4.6707180e-002
+v -7.7168390e-002 6.9826450e-002 5.0605600e-003
+v -7.2801360e-002 6.4382590e-002 1.2089080e-002
+v -7.8022000e-002 7.0995160e-002 2.1322150e-002
+v -6.1263370e-002 3.4690410e-002 4.1994900e-002
+v -5.4403750e-002 3.5007310e-002 4.4874590e-002
+v -4.5754280e-002 3.5206980e-002 4.3518120e-002
+v -3.3832440e-002 3.5168820e-002 4.6957890e-002
+v -2.8657630e-002 3.5083380e-002 5.0549440e-002
+v -1.5306440e-002 3.5246410e-002 5.0133810e-002
+v -6.5283650e-002 1.5592447e-001 -4.9865930e-002
+v -6.6467860e-002 1.4871539e-001 -3.1579300e-002
+v -6.2095980e-002 1.6388324e-001 -5.8385930e-002
+v -6.3274890e-002 1.5245731e-001 -3.2221730e-002
+v -4.3755720e-002 1.4773408e-001 -2.1433200e-003
+v -6.5696940e-002 1.4561631e-001 -1.8974710e-002
+v -6.6713650e-002 1.5358824e-001 -4.9097100e-002
+v -1.0482810e-002 1.6668287e-001 -2.1746090e-002
+v -6.2744510e-002 1.6397531e-001 -5.9398280e-002
+v -7.0413230e-002 1.4129200e-001 -8.4590800e-003
+v -6.1530380e-002 1.4037628e-001 -6.2734700e-003
+v -1.1452460e-002 1.7220633e-001 -2.6844980e-002
+v -6.3731140e-002 1.6577037e-001 -6.0103610e-002
+v -2.8218820e-002 1.5758144e-001 -1.0999490e-002
+v -1.8471270e-002 1.5967716e-001 -1.1169510e-002
+v -6.6700710e-002 1.5236775e-001 -4.5266390e-002
+v -4.9896410e-002 1.4670859e-001 -1.8614200e-003
+v -3.1449640e-002 1.5460463e-001 -7.6802300e-003
+v -6.7447660e-002 1.5507675e-001 -5.1594250e-002
+v -1.0906650e-002 1.7649301e-001 -2.9246300e-002
+v -7.2083600e-002 1.4965550e-001 -3.9265860e-002
+v -6.4230830e-002 1.4877806e-001 -2.5899710e-002
+v -6.3056640e-002 1.4341650e-001 -7.4907700e-003
+v -5.3043350e-002 1.4092550e-001 -4.7408000e-004
+v -3.9269410e-002 1.5205232e-001 -6.6203800e-003
+v -6.4796930e-002 1.5210615e-001 -3.6185520e-002
+v -6.4400320e-002 1.5834400e-001 -5.4256370e-002
+v -6.6178120e-002 1.4218350e-001 -9.3766300e-003
+v -6.7751430e-002 1.4605207e-001 -2.3333300e-002
+v -6.4731580e-002 1.5410067e-001 -4.0464820e-002
+v -2.4265590e-002 1.5687690e-001 -7.8509300e-003
+v -1.5723180e-002 1.6312344e-001 -1.6396570e-002
+v -7.0887660e-002 1.4404618e-001 -1.4908480e-002
+v -4.4341830e-002 1.5113809e-001 -5.6859800e-003
+v -6.2896810e-002 1.4694778e-001 -1.3098620e-002
+v -6.3755400e-002 1.4428875e-001 -1.1395730e-002
+v -6.8214560e-002 1.4390932e-001 -1.4984170e-002
+v -5.0271440e-002 1.4336563e-001 1.5153000e-003
+v -2.8535590e-002 1.6208479e-001 -1.4786030e-002
+v -6.5810700e-002 1.4359119e-001 -1.2585380e-002
+v -5.6179200e-002 1.3774406e-001 -4.0674300e-003
+v -6.8866880e-002 1.4723338e-001 -2.8739870e-002
+v -6.0965420e-002 1.7002113e-001 -6.0839390e-002
+v -1.3895490e-002 1.6787168e-001 -2.1897230e-002
+v -6.9413000e-002 1.5121847e-001 -4.4538540e-002
+v -5.5039800e-002 5.7309700e-002 1.6990900e-002
+f 1069 1647 1578
+f 1058 909 939
+f 421 1176 238
+f 1055 1101 1042
+f 238 1059 1126
+f 1254 30 1261
+f 1065 1071 1
+f 1037 1130 1120
+f 1570 2381 1585
+f 2434 2502 2473
+f 1632 1654 1646
+f 1144 1166 669
+f 1202 1440 305
+f 1071 1090 1
+f 1555 1570 1584
+f 1184 1174 404
+f 65 432 12
+f 1032 1085 574
+f 1789 2207 2223
+f 1154 1118 1184
+f 1141 1086 1154
+f 99 1117 342
+f 404 1174 419
+f 489 2000 1998
+f 1118 1174 1184
+f 1196 403 136
+f 1495 717 1490
+f 1804 402 1207
+f 2272 1398 891
+f 1100 1002 804
+f 1596 1595 2381
+f 208 420 1207
+f 402 208 1207
+f 1455 1935 1925
+f 1176 1059 238
+f 1150 1040 348
+f 1957 1537 2051
+f 1124 1189 939
+f 1804 1207 1823
+f 1381 1300 1109
+f 383 384 1182
+f 1085 1086 1141
+f 1040 1046 132
+f 220 1495 1188
+f 420 261 1207
+f 261 420 1065
+f 1055 1133 1101
+f 1054 421 403
+f 182 1109 2
+f 1181 1207 320
+f 545 1570 1561
+f 35 342 432
+f 1024 574 1141
+f 432 342 12
+f 1489 1081 1547
+f 1181 320 1805
+f 1516 1683 1507
+f 357 1117 1047
+f 1561 1570 1555
+f 1090 1196 1206
+f 1047 1203 1051
+f 1165 202 1121
+f 1099 341 301
+f 1174 240 419
+f 922 921 833
+f 1121 1080 385
+f 815 21 1183
+f 35 99 342
+f 1083 398 262
+f 106 94 1317
+f 94 292 1317
+f 292 95 1317
+f 940 1039 1033
+f 1300 1306 433
+f 21 212 471
+f 1120 1131 1037
+f 833 921 688
+f 1117 357 342
+f 106 271 94
+f 386 227 1375
+f 1130 1044 1053
+f 419 240 219
+f 1255 1244 32
+f 1557 1081 1489
+f 2062 2120 2109
+f 2034 2110 430
+f 23 315 1111
+f 291 94 271
+f 291 292 94
+f 50 386 95
+f 964 734 665
+f 1616 1585 1611
+f 445 1084 402
+f 574 1085 1141
+f 1654 341 1653
+f 220 1188 1640
+f 342 69 12
+f 417 261 328
+f 292 50 95
+f 204 227 386
+f 50 204 386
+f 1276 1471 1311
+f 1206 1196 136
+f 1033 1055 1042
+f 1037 1044 1130
+f 1180 320 417
+f 1121 202 1080
+f 325 203 271
+f 291 76 292
+f 292 237 50
+f 2159 1696 1767
+f 583 929 850
+f 1584 1585 1616
+f 1495 1490 1188
+f 1557 1489 1660
+f 1078 1069 1494
+f 1972 1992 1971
+f 183 1226 2000
+f 325 429 203
+f 292 76 237
+f 1152 227 1143
+f 1488 1412 1489
+f 1638 1646 1653
+f 1947 1869 2468
+f 203 306 291
+f 306 76 291
+f 237 248 50
+f 204 1143 227
+f 2395 14 429
+f 1502 881 2500
+f 1 1090 202
+f 1652 1653 1099
+f 2117 1863 2496
+f 50 248 204
+f 160 792 994
+f 884 888 857
+f 544 2117 2496
+f 1090 1206 202
+f 2463 879 2492
+f 429 306 203
+f 498 188 418
+f 865 884 857
+f 994 998 1014
+f 884 897 888
+f 1795 948 1802
+f 208 1035 1071
+f 1065 1 1066
+f 377 435 1377
+f 304 429 14
+f 304 306 429
+f 73 60 74
+f 248 592 204
+f 846 2264 829
+f 897 912 906
+f 1004 991 992
+f 1422 1421 1233
+f 980 10 303
+f 1058 922 909
+f 2436 2449 2418
+f 394 435 377
+f 435 475 446
+f 475 474 446
+f 336 337 361
+f 338 235 372
+f 624 148 129
+f 812 306 596
+f 1726 992 1019
+f 945 1514 1511
+f 1069 1627 1628
+f 1812 1823 1181
+f 1165 1121 169
+f 447 475 435
+f 2487 2458 901
+f 42 59 46
+f 401 7 187
+f 1010 970 797
+f 1513 220 1640
+f 2474 2491 2462
+f 594 307 1014
+f 398 1513 1640
+f 307 594 1026
+f 545 2381 1570
+f 403 421 238
+f 445 402 127
+f 1611 1631 1616
+f 1805 1180 1148
+f 394 447 435
+f 2341 2413 2376
+f 75 74 60
+f 541 47 42
+f 47 59 42
+f 541 42 28
+f 917 931 1103
+f 897 906 883
+f 2484 2068 779
+f 888 883 857
+f 261 1065 328
+f 363 1307 349
+f 377 363 394
+f 444 747 464
+f 323 338 362
+f 92 116 74
+f 592 634 97
+f 982 1027 1004
+f 1020 982 1004
+f 1084 1054 1035
+f 208 402 1084
+f 421 1119 1176
+f 1207 1181 1823
+f 1179 1187 1160
+f 263 296 1343
+f 1298 296 1307
+f 1307 296 349
+f 405 363 349
+f 405 394 363
+f 405 447 394
+f 362 372 384
+f 338 372 362
+f 983 1004 987
+f 122 134 139
+f 415 440 414
+f 75 92 74
+f 226 186 246
+f 796 787 700
+f 1119 1059 1176
+f 122 114 91
+f 624 129 116
+f 641 558 631
+f 1311 1318 1487
+f 100 1162 1170
+f 1653 341 1099
+f 1316 1983 273
+f 263 277 296
+f 296 358 349
+f 436 447 405
+f 109 554 570
+f 504 1385 2501
+f 115 122 91
+f 2068 2460 779
+f 43 777 163
+f 378 405 349
+f 358 378 349
+f 448 447 436
+f 448 476 447
+f 78 77 108
+f 75 60 47
+f 1764 2481 1795
+f 717 714 1512
+f 1490 717 1501
+f 238 1126 168
+f 1878 1866 826
+f 2025 2360 2367
+f 251 278 263
+f 278 277 263
+f 277 318 296
+f 296 318 358
+f 318 350 358
+f 378 436 405
+f 384 372 1182
+f 454 440 415
+f 987 1004 992
+f 493 476 448
+f 323 788 338
+f 403 238 136
+f 1565 1503 1474
+f 297 277 278
+f 297 318 277
+f 358 350 378
+f 378 388 436
+f 476 493 500
+f 73 105 60
+f 323 337 312
+f 953 1573 2358
+f 142 161 119
+f 454 443 440
+f 1862 1871 1405
+f 297 319 318
+f 560 47 541
+f 170 1323 111
+f 357 1047 1050
+f 1119 98 1059
+f 1838 1877 1900
+f 2359 230 251
+f 350 364 378
+f 449 448 436
+f 449 493 448
+f 185 186 226
+f 443 469 479
+f 874 165 2480
+f 463 444 464
+f 64 105 91
+f 1182 440 1129
+f 1958 1651 2502
+f 1238 2034 191
+f 251 279 278
+f 278 279 297
+f 364 388 378
+f 483 493 449
+f 134 148 139
+f 244 268 259
+f 910 942 930
+f 105 115 91
+f 24 30 18
+f 1132 487 1059
+f 1869 1947 2021
+f 2497 2494 2463
+f 2359 2385 230
+f 230 280 251
+f 251 280 279
+f 279 308 297
+f 297 308 319
+f 319 364 318
+f 364 350 318
+f 388 395 436
+f 436 395 449
+f 493 472 500
+f 122 129 134
+f 125 142 124
+f 373 400 393
+f 24 557 30
+f 2264 2278 2251
+f 1261 30 1269
+f 1730 1862 1877
+f 252 280 230
+f 343 364 319
+f 364 343 388
+f 63 64 91
+f 399 393 416
+f 416 444 463
+f 162 189 142
+f 768 373 326
+f 189 661 177
+f 189 199 661
+f 847 887 864
+f 533 747 444
+f 1744 1022 1418
+f 1170 524 729
+f 121 1342 128
+f 1236 1244 26
+f 280 281 279
+f 281 308 279
+f 343 319 308
+f 343 365 388
+f 388 365 395
+f 365 406 395
+f 406 449 395
+f 483 477 493
+f 477 491 472
+f 493 477 472
+f 78 109 77
+f 166 174 196
+f 481 150 814
+f 63 59 64
+f 326 373 393
+f 643 260 43
+f 230 253 252
+f 449 441 483
+f 441 477 483
+f 415 416 463
+f 226 246 245
+f 464 470 454
+f 323 362 337
+f 52 37 1283
+f 253 281 252
+f 281 280 252
+f 309 308 281
+f 330 343 308
+f 366 365 343
+f 441 449 406
+f 464 814 15
+f 883 906 887
+f 337 362 371
+f 479 498 290
+f 247 746 1003
+f 25 37 557
+f 640 930 669
+f 2486 2499 2459
+f 309 330 308
+f 343 330 366
+f 441 437 477
+f 290 498 418
+f 124 119 108
+f 77 124 108
+f 589 125 109
+f 570 589 109
+f 125 162 142
+f 1045 433 1034
+f 1207 261 320
+f 2004 2474 2495
+f 1215 1228 2285
+f 365 396 406
+f 396 422 406
+f 422 437 441
+f 406 422 441
+f 59 47 60
+f 51 78 66
+f 361 371 383
+f 196 215 214
+f 463 454 415
+f 27 41 535
+f 53 1283 37
+f 84 1299 1283
+f 1805 320 1180
+f 254 253 222
+f 254 281 253
+f 309 366 330
+f 396 365 366
+f 456 477 437
+f 484 491 477
+f 2480 2485 2493
+f 418 188 187
+f 53 85 1283
+f 85 84 1283
+f 420 1071 1065
+f 264 281 254
+f 298 309 281
+f 368 366 367
+f 368 396 366
+f 1639 1564 1139
+f 560 48 47
+f 82 471 212
+f 25 38 37
+f 202 1206 1080
+f 264 298 281
+f 298 331 309
+f 309 331 366
+f 331 367 366
+f 396 368 422
+f 422 456 437
+f 491 1192 313
+f 1699 2064 1710
+f 462 443 479
+f 371 362 384
+f 2502 2476 2464
+f 371 384 383
+f 21 732 212
+f 1571 1629 1627
+f 38 39 53
+f 37 38 53
+f 39 85 53
+f 1173 1184 404
+f 1006 2142 1674
+f 201 255 254
+f 255 264 254
+f 368 407 422
+f 450 456 422
+f 450 484 456
+f 456 484 477
+f 314 1192 491
+f 2027 2501 2489
+f 2475 2471 2488
+f 551 492 732
+f 464 481 814
+f 1081 1494 1547
+f 201 231 255
+f 407 450 422
+f 484 494 491
+f 494 327 491
+f 327 314 491
+f 876 797 995
+f 847 856 829
+f 125 143 162
+f 134 129 148
+f 1564 1571 1627
+f 417 320 261
+f 328 1065 1066
+f 170 156 201
+f 156 231 201
+f 231 282 255
+f 282 264 255
+f 450 485 484
+f 484 485 494
+f 2463 2486 2479
+f 159 185 167
+f 492 68 212
+f 732 492 212
+f 68 82 212
+f 1311 1471 1296
+f 101 156 111
+f 332 264 282
+f 332 298 264
+f 332 331 298
+f 331 332 367
+f 407 423 450
+f 450 423 485
+f 804 1002 1443
+f 2484 779 946
+f 689 443 462
+f 440 689 1129
+f 166 167 174
+f 38 31 39
+f 112 145 101
+f 101 145 156
+f 156 256 231
+f 332 423 368
+f 367 332 368
+f 368 423 407
+f 946 779 920
+f 1432 1261 1449
+f 461 478 453
+f 464 15 470
+f 31 54 39
+f 39 54 85
+f 86 101 85
+f 145 210 156
+f 282 283 332
+f 283 369 332
+f 369 423 332
+f 423 408 485
+f 854 876 965
+f 78 108 66
+f 440 443 689
+f 374 2465 961
+f 929 519 979
+f 54 86 85
+f 156 241 256
+f 256 282 231
+f 256 283 282
+f 389 423 369
+f 389 408 423
+f 408 457 485
+f 457 49 485
+f 485 49 494
+f 494 135 327
+f 175 83 314
+f 1167 1140 1483
+f 196 174 215
+f 697 16 68
+f 1038 82 16
+f 140 117 141
+f 1654 1653 1646
+f 1234 54 31
+f 86 112 101
+f 210 241 156
+f 923 917 911
+f 697 34 16
+f 145 193 210
+f 256 265 283
+f 265 310 283
+f 283 310 369
+f 310 344 369
+f 344 370 369
+f 370 389 369
+f 409 408 389
+f 409 466 408
+f 466 457 408
+f 466 49 457
+f 49 135 494
+f 174 225 215
+f 1014 766 602
+f 826 2220 2215
+f 1078 1494 1081
+f 1273 70 86
+f 120 112 86
+f 146 145 112
+f 146 193 145
+f 265 256 241
+f 223 265 241
+f 486 49 466
+f 175 327 135
+f 105 122 115
+f 480 15 681
+f 225 234 215
+f 731 34 697
+f 86 54 1273
+f 70 120 86
+f 193 241 210
+f 299 310 265
+f 310 333 344
+f 344 351 370
+f 424 466 409
+f 135 49 175
+f 214 215 234
+f 48 75 47
+f 34 9 1038
+f 16 34 1038
+f 203 291 271
+f 9 558 754
+f 1195 397 1120
+f 120 146 112
+f 146 194 193
+f 266 265 223
+f 266 299 265
+f 299 333 310
+f 333 351 344
+f 382 383 392
+f 399 416 415
+f 266 333 299
+f 351 352 370
+f 424 486 466
+f 487 175 49
+f 7 117 187
+f 1182 414 440
+f 41 42 46
+f 290 289 497
+f 2502 2464 2473
+f 372 399 414
+f 1570 1585 1584
+f 1066 1 1165
+f 1 202 1165
+f 120 70 102
+f 157 146 120
+f 194 223 193
+f 223 241 193
+f 352 379 370
+f 370 379 389
+f 410 409 389
+f 2478 1409 1958
+f 806 945 1002
+f 157 194 146
+f 267 266 223
+f 267 333 266
+f 379 410 389
+f 410 438 409
+f 438 424 409
+f 190 205 143
+f 337 371 361
+f 2215 830 826
+f 1631 1646 1638
+f 102 157 120
+f 157 195 194
+f 195 223 194
+f 195 211 223
+f 223 211 267
+f 267 300 333
+f 300 334 351
+f 333 300 351
+f 351 334 352
+f 410 411 438
+f 438 486 424
+f 487 49 486
+f 875 594 989
+f 108 581 66
+f 225 245 244
+f 312 336 335
+f 151 754 107
+f 274 1386 300
+f 352 334 379
+f 923 1729 1096
+f 244 245 268
+f 463 464 454
+f 414 399 415
+f 15 480 470
+f 1647 1069 1078
+f 909 922 833
+f 387 417 328
+f 133 157 102
+f 1314 133 102
+f 133 195 157
+f 1148 1179 1160
+f 1046 1167 182
+f 379 411 410
+f 792 339 229
+f 391 7 668
+f 185 226 174
+f 461 290 497
+f 2027 504 2501
+f 1196 1054 403
+f 728 1019 752
+f 2459 2483 2461
+f 1291 1264 55
+f 133 1356 195
+f 195 1356 211
+f 412 438 411
+f 4 486 438
+f 458 4 438
+f 4 487 486
+f 1720 1572 1771
+f 245 275 268
+f 1869 2021 2059
+f 235 399 372
+f 64 60 105
+f 836 2492 879
+f 1315 133 1314
+f 1331 1382 1356
+f 1310 926 1128
+f 7 1121 117
+f 119 161 611
+f 380 379 334
+f 379 380 411
+f 467 4 458
+f 495 487 4
+f 495 1126 487
+f 416 400 533
+f 479 469 498
+f 74 116 73
+f 478 461 497
+f 393 400 416
+f 61 1291 55
+f 505 1999 2474
+f 1999 2491 2474
+f 199 189 36
+f 1164 1165 169
+f 1179 387 249
+f 390 411 380
+f 411 390 412
+f 458 438 412
+f 495 168 1126
+f 480 469 470
+f 116 122 105
+f 418 187 140
+f 185 174 167
+f 166 148 167
+f 470 469 443
+f 40 55 32
+f 61 71 1291
+f 71 103 1291
+f 1184 1173 1154
+f 634 514 97
+f 425 458 412
+f 917 923 931
+f 2472 2489 853
+f 754 641 567
+f 44 567 1163
+f 454 470 443
+f 40 32 1249
+f 33 40 1249
+f 56 55 40
+f 56 61 55
+f 451 1265 439
+f 1180 417 1179
+f 1099 301 1077
+f 1189 1058 939
+f 1059 221 1132
+f 598 1074 1075
+f 412 426 425
+f 650 186 185
+f 234 244 259
+f 226 245 225
+f 1033 1042 1030
+f 2492 836 247
+f 7 169 1121
+f 1462 1322 1482
+f 425 467 458
+f 496 4 467
+f 1751 2468 2480
+f 290 418 140
+f 326 789 762
+f 142 177 161
+f 165 1751 2480
+f 87 103 71
+f 103 87 104
+f 1180 1179 1148
+f 417 387 1179
+f 2081 2060 2031
+f 1154 1173 1141
+f 181 131 197
+f 442 425 426
+f 614 144 143
+f 876 1010 797
+f 40 45 56
+f 56 45 61
+f 87 71 61
+f 1563 1437 1590
+f 1121 385 117
+f 1148 1160 1137
+f 1449 1459 1439
+f 1028 2462 929
+f 442 459 425
+f 459 467 425
+f 168 495 4
+f 496 168 4
+f 1763 1403 1444
+f 140 187 117
+f 244 234 225
+f 246 740 269
+f 372 414 1182
+f 40 547 45
+f 45 62 61
+f 62 87 61
+f 87 88 104
+f 1084 517 1054
+f 387 328 1064
+f 2467 2497 2485
+f 286 1363 302
+f 205 189 162
+f 290 140 289
+f 214 234 224
+f 393 399 809
+f 315 1131 397
+f 302 321 353
+f 1164 169 391
+f 427 459 442
+f 217 496 467
+f 217 168 496
+f 978 969 2074
+f 361 383 382
+f 269 276 245
+f 1440 11 305
+f 62 88 87
+f 328 1066 1064
+f 1066 1165 1164
+f 242 287 302
+f 1363 242 302
+f 287 321 302
+f 1179 249 1187
+f 983 1020 1004
+f 464 747 481
+f 788 323 276
+f 269 245 246
+f 88 89 1325
+f 171 172 242
+f 360 353 321
+f 360 1354 353
+f 1057 1064 1164
+f 2184 2188 2183
+f 460 459 451
+f 460 467 459
+f 149 168 217
+f 149 136 168
+f 116 129 122
+f 109 124 77
+f 159 167 148
+f 28 42 41
+f 57 88 62
+f 45 57 62
+f 1336 1325 89
+f 89 72 1336
+f 147 172 171
+f 172 258 242
+f 258 257 242
+f 257 287 242
+f 257 321 287
+f 345 360 321
+f 360 381 1354
+f 1069 938 1655
+f 387 473 249
+f 270 217 467
+f 130 136 149
+f 851 847 829
+f 983 987 975
+f 189 177 142
+f 88 72 89
+f 184 258 172
+f 257 288 321
+f 1265 451 459
+f 270 149 217
+f 226 225 174
+f 27 28 41
+f 109 125 124
+f 547 57 45
+f 57 58 88
+f 88 58 72
+f 2476 2484 2458
+f 147 184 172
+f 184 213 258
+f 258 243 257
+f 243 288 257
+f 345 321 288
+f 391 169 7
+f 468 460 451
+f 468 488 460
+f 270 467 460
+f 488 270 460
+f 1206 136 130
+f 481 793 150
+f 143 205 162
+f 142 119 124
+f 58 90 72
+f 90 128 72
+f 147 173 184
+f 173 213 184
+f 213 233 258
+f 258 233 243
+f 354 360 345
+f 354 381 360
+f 1026 991 307
+f 268 312 259
+f 1206 130 1080
+f 116 105 73
+f 139 148 166
+f 275 312 268
+f 188 401 187
+f 2479 2459 2461
+f 58 63 90
+f 1064 1066 1164
+f 1064 473 387
+f 288 311 345
+f 311 354 345
+f 996 994 307
+f 452 468 439
+f 452 478 468
+f 478 488 468
+f 141 130 149
+f 1564 1639 1563
+f 547 41 57
+f 2081 2107 2060
+f 382 381 354
+f 497 270 488
+f 289 149 270
+f 289 141 149
+f 114 122 139
+f 59 60 64
+f 275 323 312
+f 401 668 7
+f 41 46 57
+f 57 46 58
+f 1459 1345 1269
+f 1342 121 158
+f 166 173 158
+f 213 224 233
+f 233 259 243
+f 243 322 288
+f 322 311 288
+f 453 478 452
+f 497 289 270
+f 912 911 906
+f 276 323 275
+f 276 275 245
+f 46 63 58
+f 90 121 128
+f 173 214 213
+f 213 214 224
+f 259 322 243
+f 336 311 322
+f 336 354 311
+f 361 382 354
+f 1043 439 1290
+f 497 488 478
+f 385 130 141
+f 385 1080 130
+f 144 190 143
+f 535 41 547
+f 121 166 158
+f 335 336 322
+f 354 336 361
+f 2004 2481 1764
+f 698 439 1043
+f 289 140 141
+f 923 1096 931
+f 650 185 159
+f 46 59 63
+f 63 91 90
+f 90 114 121
+f 121 139 166
+f 173 196 214
+f 259 335 322
+f 2478 2502 2434
+f 312 337 336
+f 90 91 114
+f 114 139 121
+f 166 196 173
+f 224 234 233
+f 234 259 233
+f 259 312 335
+f 1124 916 1189
+f 542 541 530
+f 462 479 290
+f 269 783 276
+f 813 567 641
+f 276 783 788
+f 82 1038 1333
+f 816 701 703
+f 672 137 603
+f 625 635 624
+f 2457 2439 1973
+f 767 533 529
+f 2468 1869 2480
+f 662 190 639
+f 711 720 719
+f 630 639 614
+f 161 654 638
+f 781 991 982
+f 1227 31 516
+f 648 639 630
+f 630 614 590
+f 2098 544 1899
+f 578 579 586
+f 697 492 551
+f 529 533 400
+f 869 859 870
+f 1732 924 914
+f 1004 1027 991
+f 801 591 603
+f 636 676 651
+f 876 949 965
+f 2207 1789 1859
+f 76 739 237
+f 188 681 15
+f 578 604 599
+f 797 616 995
+f 510 2035 1365
+f 76 812 617
+f 617 739 76
+f 1468 93 1765
+f 596 546 812
+f 1457 1305 1477
+f 760 197 150
+f 671 773 765
+f 586 609 604
+f 591 700 632
+f 476 2312 474
+f 2084 2027 2489
+f 582 590 571
+f 1555 2449 1996
+f 674 546 596
+f 812 655 617
+f 161 177 661
+f 599 604 636
+f 700 787 576
+f 776 675 572
+f 776 674 675
+f 617 634 739
+f 591 632 649
+f 612 546 674
+f 617 655 634
+f 728 752 706
+f 571 2311 2305
+f 775 674 776
+f 775 612 674
+f 612 628 546
+f 546 628 812
+f 812 628 655
+f 620 630 615
+f 620 648 630
+f 667 653 646
+f 810 782 785
+f 150 197 814
+f 534 1517 2000
+f 702 572 2378
+f 748 776 572
+f 655 613 634
+f 911 917 905
+f 648 679 662
+f 727 771 713
+f 750 807 799
+f 639 190 144
+f 662 679 200
+f 702 748 572
+f 775 776 748
+f 628 718 655
+f 626 658 645
+f 791 778 790
+f 612 811 628
+f 613 514 634
+f 1380 1756 1673
+f 570 590 614
+f 720 741 719
+f 1074 795 835
+f 614 639 144
+f 612 775 811
+f 718 735 655
+f 655 735 613
+f 798 338 788
+f 636 652 676
+f 571 590 555
+f 528 730 687
+f 690 702 2312
+f 476 690 2312
+f 811 718 628
+f 721 778 727
+f 748 702 690
+f 735 686 613
+f 1517 2002 2127
+f 654 685 667
+f 569 588 606
+f 513 531 538
+f 538 549 548
+f 549 553 548
+f 550 588 549
+f 1903 869 870
+f 691 775 748
+f 691 600 775
+f 600 811 775
+f 811 563 718
+f 563 736 718
+f 718 736 735
+f 736 647 735
+f 735 647 686
+f 686 745 613
+f 745 514 613
+f 569 606 605
+f 654 667 638
+f 851 857 847
+f 588 569 549
+f 690 691 748
+f 680 514 745
+f 2127 2002 2094
+f 747 701 481
+f 400 373 529
+f 600 536 811
+f 536 563 811
+f 1306 227 1152
+f 522 24 18
+f 523 24 522
+f 865 857 851
+f 2031 2060 1540
+f 767 701 747
+f 618 652 609
+f 652 636 609
+f 573 22 710
+f 642 699 730
+f 1522 1518 2476
+f 500 629 691
+f 690 500 691
+f 691 629 600
+f 780 644 641
+f 579 578 561
+f 131 668 197
+f 197 668 814
+f 789 809 798
+f 622 760 150
+f 621 563 536
+f 673 745 686
+f 673 818 745
+f 818 680 745
+f 680 96 514
+f 2495 2462 1028
+f 1028 583 575
+f 663 794 664
+f 629 761 600
+f 761 757 600
+f 600 757 536
+f 621 696 563
+f 755 736 563
+f 696 755 563
+f 633 736 755
+f 633 647 736
+f 623 686 647
+f 633 623 647
+f 686 623 673
+f 819 680 818
+f 680 819 96
+f 1729 1677 1096
+f 2482 1899 2471
+f 537 536 757
+f 536 537 621
+f 673 819 818
+f 2428 222 230
+f 25 24 523
+f 25 557 24
+f 38 25 19
+f 710 22 272
+f 663 759 794
+f 1120 878 1195
+f 537 696 621
+f 696 633 755
+f 822 2215 2220
+f 97 96 1053
+f 750 784 743
+f 887 905 864
+f 768 784 373
+f 512 513 548
+f 573 664 22
+f 696 715 633
+f 673 521 819
+f 2454 2453 2445
+f 883 887 847
+f 306 812 76
+f 642 528 759
+f 798 809 235
+f 994 792 998
+f 587 626 586
+f 1900 1918 1937
+f 645 652 618
+f 537 786 696
+f 521 593 819
+f 515 19 523
+f 741 749 719
+f 789 326 809
+f 539 581 550
+f 657 777 723
+f 684 713 660
+f 692 712 720
+f 652 666 692
+f 507 761 629
+f 472 507 629
+f 507 757 761
+f 623 633 673
+f 724 521 673
+f 515 516 19
+f 304 675 674
+f 178 778 721
+f 947 1447 2358
+f 626 645 618
+f 586 626 618
+f 784 768 742
+f 753 537 757
+f 537 753 786
+f 724 981 521
+f 521 981 593
+f 979 559 850
+f 637 660 677
+f 787 631 576
+f 141 117 385
+f 809 399 235
+f 641 754 558
+f 542 553 561
+f 742 768 762
+f 444 416 533
+f 528 687 796
+f 813 598 566
+f 1490 1501 1557
+f 753 757 507
+f 786 715 696
+f 633 724 673
+f 2090 2062 2109
+f 646 653 660
+f 660 694 683
+f 677 660 683
+f 1872 839 838
+f 1224 18 30
+f 326 393 809
+f 799 529 373
+f 313 507 472
+f 715 774 633
+f 974 699 841
+f 703 820 816
+f 692 711 676
+f 1014 355 766
+f 875 752 1019
+f 627 646 660
+f 711 692 720
+f 652 692 676
+f 799 373 784
+f 813 566 567
+f 2462 2482 2475
+f 764 644 780
+f 1479 1924 1916
+f 753 738 786
+f 738 607 786
+f 786 607 715
+f 715 524 774
+f 633 774 724
+f 559 979 672
+f 758 798 783
+f 683 694 705
+f 820 703 562
+f 764 687 644
+f 744 743 725
+f 313 753 507
+f 607 524 715
+f 664 801 22
+f 646 627 610
+f 800 820 562
+f 750 769 807
+f 767 747 533
+f 578 586 604
+f 862 593 981
+f 688 2382 1083
+f 306 304 674
+f 738 584 607
+f 168 136 238
+f 773 552 765
+f 2473 2464 2458
+f 773 793 552
+f 626 619 658
+f 1007 1139 1013
+f 562 529 799
+f 744 750 743
+f 659 683 693
+f 677 683 659
+f 313 737 753
+f 753 737 738
+f 607 729 524
+f 27 518 28
+f 553 569 580
+f 657 163 777
+f 580 569 605
+f 789 798 758
+f 769 562 807
+f 820 671 816
+f 638 646 611
+f 1074 598 644
+f 750 799 784
+f 1931 907 898
+f 2483 2487 2461
+f 737 584 738
+f 1439 1438 1431
+f 2098 1213 544
+f 48 578 75
+f 796 631 787
+f 815 732 21
+f 581 588 550
+f 625 636 651
+f 778 1011 810
+f 693 705 725
+f 693 683 705
+f 236 1921 1966
+f 584 729 607
+f 2237 1866 2227
+f 530 541 28
+f 237 739 248
+f 512 530 28
+f 727 778 771
+f 684 727 713
+f 2237 2220 826
+f 542 561 560
+f 528 796 700
+f 808 785 671
+f 739 592 248
+f 895 905 896
+f 740 246 186
+f 272 137 979
+f 770 769 744
+f 712 742 720
+f 1213 2026 544
+f 1888 1235 2438
+f 555 554 2311
+f 737 313 1192
+f 1585 1612 1611
+f 695 721 685
+f 518 17 28
+f 769 770 562
+f 719 749 740
+f 648 669 679
+f 773 657 723
+f 606 637 619
+f 2072 2062 2042
+f 606 619 626
+f 549 569 553
+f 161 638 611
+f 910 917 942
+f 917 1103 942
+f 991 1026 992
+f 979 137 672
+f 785 163 657
+f 710 2488 2472
+f 611 581 119
+f 808 671 820
+f 1820 1900 1870
+f 759 700 591
+f 637 677 619
+f 2494 2490 2463
+f 671 765 816
+f 687 764 780
+f 1019 992 1026
+f 1726 1719 987
+f 713 771 694
+f 51 2355 78
+f 510 526 525
+f 525 526 1249
+f 526 33 1249
+f 2311 554 2335
+f 827 848 840
+f 603 591 649
+f 758 269 740
+f 1595 1612 1586
+f 1694 1048 1699
+f 682 740 186
+f 22 801 603
+f 555 570 554
+f 1053 110 97
+f 615 582 601
+f 814 668 188
+f 725 705 744
+f 528 700 759
+f 640 648 620
+f 703 701 562
+f 886 892 582
+f 631 731 576
+f 1087 1835 1747
+f 882 864 895
+f 956 950 1103
+f 1502 2500 2470
+f 205 190 200
+f 815 878 616
+f 616 878 995
+f 1183 878 815
+f 1601 1827 881
+f 527 535 526
+f 2184 2183 2175
+f 1142 1125 1133
+f 235 338 798
+f 160 339 792
+f 599 92 75
+f 598 1116 566
+f 631 558 731
+f 771 770 744
+f 730 528 642
+f 841 699 642
+f 668 401 188
+f 510 527 526
+f 749 758 740
+f 706 721 695
+f 694 726 705
+f 694 744 726
+f 906 911 905
+f 661 695 161
+f 708 815 616
+f 535 547 33
+f 794 759 591
+f 778 808 790
+f 269 758 783
+f 771 744 694
+f 800 808 820
+f 571 886 582
+f 854 948 1010
+f 906 905 887
+f 625 651 635
+f 2000 1226 534
+f 2140 1504 2016
+f 601 620 615
+f 620 601 640
+f 648 640 669
+f 698 452 439
+f 671 785 657
+f 1561 2356 545
+f 685 653 667
+f 685 727 684
+f 568 616 797
+f 708 732 815
+f 93 229 339
+f 865 851 839
+f 942 1103 950
+f 589 614 125
+f 606 610 627
+f 951 834 873
+f 92 599 625
+f 1878 830 1902
+f 2482 2098 1899
+f 568 708 616
+f 708 551 732
+f 2434 2487 2483
+f 160 964 665
+f 2316 2391 2309
+f 762 758 749
+f 570 614 589
+f 888 897 883
+f 2000 1517 1388
+f 685 721 727
+f 588 610 606
+f 653 685 684
+f 651 650 635
+f 760 1151 6
+f 793 622 150
+f 651 676 650
+f 744 769 750
+f 541 542 560
+f 476 500 690
+f 473 1064 1057
+f 561 578 560
+f 636 625 599
+f 876 995 949
+f 829 856 846
+f 682 704 740
+f 791 790 770
+f 2466 2500 2460
+f 579 587 586
+f 1352 1208 1095
+f 1684 1479 1916
+f 604 609 636
+f 751 721 706
+f 810 608 782
+f 672 603 649
+f 475 447 476
+f 794 591 801
+f 682 186 650
+f 808 800 790
+f 644 598 813
+f 704 719 740
+f 1011 608 810
+f 1192 584 737
+f 687 780 796
+f 2337 474 2312
+f 638 667 646
+f 706 1186 728
+f 733 575 568
+f 595 551 708
+f 595 540 551
+f 1308 501 1852
+f 665 339 160
+f 527 2447 535
+f 558 9 731
+f 723 793 773
+f 660 713 694
+f 693 725 666
+f 562 767 529
+f 550 538 531
+f 2267 2287 2233
+f 996 964 160
+f 2068 2470 2466
+f 704 711 719
+f 741 762 749
+f 605 606 626
+f 548 542 530
+f 995 878 709
+f 1898 1684 1916
+f 778 791 771
+f 782 163 785
+f 789 758 762
+f 857 883 847
+f 733 970 1028
+f 838 829 825
+f 2447 511 535
+f 22 603 137
+f 705 726 744
+f 605 587 580
+f 512 548 530
+f 743 784 742
+f 790 800 770
+f 778 810 808
+f 1014 998 355
+f 708 568 595
+f 656 697 551
+f 540 656 551
+f 143 125 614
+f 1000 1020 983
+f 778 178 1011
+f 676 704 682
+f 637 627 660
+f 606 627 637
+f 701 552 481
+f 808 810 785
+f 590 570 555
+f 716 595 568
+f 2355 2335 554
+f 912 1729 911
+f 1076 1456 1546
+f 697 68 492
+f 676 711 704
+f 839 851 838
+f 1028 575 733
+f 1020 844 982
+f 716 568 575
+f 844 781 982
+f 1238 2156 2034
+f 553 580 561
+f 580 579 561
+f 452 461 453
+f 560 578 48
+f 564 540 595
+f 632 656 540
+f 564 632 540
+f 75 578 599
+f 518 27 535
+f 511 518 535
+f 783 798 788
+f 642 759 663
+f 720 742 741
+f 605 626 587
+f 580 587 579
+f 725 712 666
+f 562 701 767
+f 1729 923 911
+f 712 743 742
+f 619 677 658
+f 161 695 654
+f 770 800 562
+f 2084 2489 2472
+f 575 559 716
+f 716 564 595
+f 654 695 685
+f 843 855 2064
+f 34 731 9
+f 527 510 1973
+f 723 622 793
+f 992 1726 987
+f 693 666 652
+f 2472 853 573
+f 624 159 148
+f 671 657 773
+f 681 188 498
+f 797 970 733
+f 565 656 632
+f 565 697 656
+f 565 731 697
+f 1949 951 920
+f 85 111 84
+f 662 200 190
+f 44 324 754
+f 33 547 40
+f 658 693 652
+f 658 652 645
+f 664 794 801
+f 666 712 692
+f 639 648 662
+f 611 646 610
+f 850 559 575
+f 1447 2490 1106
+f 1972 1955 1935
+f 582 615 590
+f 66 581 539
+f 780 641 631
+f 796 780 631
+f 1049 1192 83
+f 1348 13 1519
+f 799 807 562
+f 581 611 588
+f 687 795 644
+f 663 8 642
+f 1936 1972 1935
+f 650 676 682
+f 615 630 590
+f 730 795 687
+f 742 762 741
+f 548 553 542
+f 1048 1692 1074
+f 658 659 693
+f 37 52 30
+f 611 610 588
+f 649 632 564
+f 565 576 731
+f 2138 922 1058
+f 1204 854 965
+f 725 743 712
+f 644 813 641
+f 660 653 684
+f 771 791 770
+f 644 795 1074
+f 469 480 681
+f 559 672 564
+f 716 559 564
+f 672 649 564
+f 2161 1378 2171
+f 474 475 476
+f 816 765 701
+f 765 552 701
+f 513 538 548
+f 754 324 107
+f 609 586 618
+f 25 523 19
+f 677 659 658
+f 689 452 698
+f 1334 1115 1353
+f 700 565 632
+f 700 576 565
+f 481 552 793
+f 763 901 2458
+f 550 549 538
+f 781 964 996
+f 1596 1634 1595
+f 198 916 1124
+f 198 1124 341
+f 842 973 1025
+f 842 1025 836
+f 1009 1024 934
+f 573 710 2472
+f 1100 971 1002
+f 1501 1081 1557
+f 1225 1219 955
+f 413 2138 284
+f 955 1630 522
+f 341 1124 301
+f 2333 2376 2350
+f 1107 218 284
+f 398 925 1513
+f 1513 1442 1495
+f 1935 1455 1744
+f 1723 1935 1744
+f 825 1872 838
+f 1495 1442 1496
+f 963 1024 1009
+f 1511 1514 966
+f 1775 1729 912
+f 688 262 1067
+f 714 1007 1512
+f 919 1732 914
+f 2319 2331 2304
+f 2400 2407 2391
+f 1674 2164 1780
+f 843 927 899
+f 1660 988 1188
+f 1067 262 1640
+f 1381 1109 1483
+f 1437 1381 1483
+f 2495 1010 948
+f 1514 1289 1313
+f 899 374 961
+f 1438 1430 1422
+f 1634 1095 1632
+f 2487 973 2461
+f 1003 499 874
+f 849 848 827
+f 1430 1462 1453
+f 2496 2084 2471
+f 909 10 980
+f 730 927 835
+f 2031 1540 1536
+f 831 849 2178
+f 881 834 951
+f 1841 1722 1803
+f 1005 670 1020
+f 1021 670 1005
+f 1869 2059 2467
+f 903 902 1939
+f 2476 2502 1651
+f 853 8 573
+f 1850 831 2178
+f 934 746 247
+f 934 65 746
+f 301 285 1077
+f 968 944 977
+f 970 2495 1028
+f 974 2465 374
+f 899 927 374
+f 1882 1898 1916
+f 1613 1634 1596
+f 909 833 1396
+f 2492 247 1003
+f 919 914 1931
+f 1459 1299 1458
+f 1634 1632 1633
+f 844 670 228
+f 2494 2497 2467
+f 901 973 2487
+f 228 1772 734
+f 1701 1709 1666
+f 963 574 1024
+f 847 864 856
+f 1730 1736 2239
+f 870 859 848
+f 2074 2111 2103
+f 1140 1590 1483
+f 927 730 974
+f 2103 978 2074
+f 756 1745 1718
+f 848 859 840
+f 1296 1482 1320
+f 2331 51 66
+f 1067 988 962
+f 1396 833 1445
+f 1001 1005 1000
+f 901 1009 973
+f 1099 1077 817
+f 933 944 936
+f 952 958 1828
+f 988 1660 986
+f 833 1067 1445
+f 1067 1640 988
+f 218 413 284
+f 1843 180 347
+f 1846 1708 1798
+f 2469 2477 855
+f 1006 1021 1005
+f 381 382 250
+f 2369 828 531
+f 968 977 1001
+f 2460 1949 779
+f 1194 1441 1115
+f 1001 1000 968
+f 756 678 1745
+f 963 1009 901
+f 2471 2084 2472
+f 841 642 8
+f 982 991 1027
+f 670 844 1020
+f 1289 1514 945
+f 869 904 890
+f 1161 1115 1639
+f 823 2178 849
+f 746 12 499
+f 263 428 2366
+f 1685 1075 1692
+f 1002 926 806
+f 1799 1755 216
+f 944 968 993
+f 943 944 993
+f 31 38 19
+f 531 828 550
+f 1501 1078 1081
+f 1921 1149 431
+f 936 943 932
+f 1660 1489 1412
+f 301 980 285
+f 903 918 902
+f 869 890 868
+f 890 903 867
+f 1003 746 499
+f 951 1949 2500
+f 990 841 853
+f 1595 1634 1611
+f 374 927 974
+f 836 1025 247
+f 1653 1652 1638
+f 1303 1545 1142
+f 1616 1631 1638
+f 1629 1546 1628
+f 936 932 913
+f 513 506 531
+f 868 890 867
+f 2330 2369 2353
+f 924 918 914
+f 907 914 904
+f 1258 1421 1267
+f 301 939 980
+f 1472 1482 1296
+f 868 867 859
+f 472 491 313
+f 272 519 2488
+f 1471 1472 1296
+f 1025 934 247
+f 1634 1633 1611
+f 2176 1847 2177
+f 1310 1289 806
+f 924 933 918
+f 1969 1968 902
+f 2107 2128 2118
+f 1428 1436 1287
+f 1139 1564 1617
+f 2378 572 2384
+f 853 841 8
+f 2501 961 2465
+f 1221 1240 1408
+f 1069 1578 1627
+f 1006 1005 1001
+f 1617 1564 1578
+f 828 539 550
+f 1791 2168 2160
+f 1829 1718 1739
+f 1968 1939 902
+f 756 1718 665
+f 1998 2000 1388
+f 2451 545 2356
+f 178 997 1011
+f 1275 325 1270
+f 1709 872 1666
+f 2176 1959 1847
+f 944 943 936
+f 2424 518 511
+f 1445 1067 962
+f 2007 952 1828
+f 2052 2061 2081
+f 828 2303 539
+f 835 1699 1048
+f 1709 1706 872
+f 885 574 963
+f 1318 1296 1320
+f 859 867 1902
+f 1452 1448 1421
+f 943 993 976
+f 993 1000 983
+f 854 1010 876
+f 988 986 962
+f 2031 2052 2081
+f 924 1732 1828
+f 965 949 1060
+f 781 228 734
+f 1718 1765 665
+f 943 976 932
+f 1680 1794 1783
+f 1448 1471 1276
+f 1276 1267 1421
+f 1931 914 907
+f 991 781 996
+f 1276 1421 1448
+f 10 909 1396
+f 831 860 849
+f 1523 1762 1774
+f 924 1828 937
+f 307 994 1014
+f 946 963 901
+f 978 2103 977
+f 977 1006 1001
+f 1007 1161 1639
+f 1639 1294 1437
+f 885 1032 574
+f 1294 1381 1437
+f 733 568 797
+f 792 229 1112
+f 119 581 108
+f 843 835 927
+f 1889 860 831
+f 2211 2216 2204
+f 2400 2431 2422
+f 2103 1006 977
+f 840 1902 830
+f 827 840 830
+f 827 830 822
+f 1003 874 2492
+f 1432 1439 1431
+f 781 734 964
+f 1937 1936 1723
+f 918 913 902
+f 958 977 944
+f 1850 2178 2177
+f 1005 1020 1000
+f 991 996 307
+f 1396 1445 340
+f 2179 1763 889
+f 939 909 980
+f 1828 958 937
+f 978 977 958
+f 1590 1571 1563
+f 779 1949 920
+f 1551 1362 1573
+f 2103 2142 1006
+f 920 885 963
+f 946 920 963
+f 1584 1616 1583
+f 1453 1472 1452
+f 1647 1617 1578
+f 1578 1564 1627
+f 1628 938 1069
+f 869 868 859
+f 993 983 976
+f 912 1762 1775
+f 752 751 706
+f 1628 1546 938
+f 844 228 781
+f 840 859 1902
+f 898 907 904
+f 1025 973 1009
+f 663 664 573
+f 763 946 901
+f 898 904 869
+f 2172 889 1763
+f 1128 926 971
+f 860 848 849
+f 904 903 890
+f 2486 2459 2479
+f 577 782 608
+f 933 936 918
+f 2177 1847 1851
+f 665 1765 339
+f 937 958 944
+f 894 981 724
+f 968 1000 993
+f 2192 2195 2205
+f 1652 1099 817
+f 997 608 1011
+f 997 577 608
+f 577 163 782
+f 1112 998 792
+f 2177 1851 1850
+f 1257 1421 1258
+f 951 873 920
+f 822 830 2215
+f 1899 2496 2471
+f 1773 1668 1558
+f 904 914 903
+f 932 1671 913
+f 873 885 920
+f 1013 1617 1647
+f 873 1032 885
+f 894 862 981
+f 2469 855 961
+f 913 1671 1969
+f 2477 2064 855
+f 918 936 913
+f 860 870 848
+f 937 944 933
+f 1501 1013 1647
+f 824 178 751
+f 824 997 178
+f 824 577 997
+f 643 163 577
+f 863 856 882
+f 2128 2153 2134
+f 722 774 880
+f 722 894 774
+f 864 905 895
+f 850 575 583
+f 914 918 903
+f 924 937 933
+f 1501 717 1013
+f 1587 1324 928
+f 717 1512 1013
+f 602 577 824
+f 766 643 577
+f 894 709 862
+f 709 878 862
+f 976 975 932
+f 1324 1596 928
+f 880 524 1060
+f 2434 2459 2499
+f 1324 1613 1596
+f 752 824 751
+f 602 766 577
+f 1014 602 594
+f 1387 1226 2152
+f 2153 1387 2152
+f 669 930 950
+f 1710 1694 1699
+f 768 326 762
+f 582 892 601
+f 974 990 2465
+f 624 116 625
+f 835 795 730
+f 2458 2484 763
+f 989 602 824
+f 2064 2477 1710
+f 976 983 975
+f 949 722 880
+f 996 160 994
+f 2305 863 556
+f 556 863 886
+f 601 910 640
+f 2264 825 829
+f 989 824 752
+f 856 864 882
+f 1595 1586 2381
+f 1627 1629 1628
+f 2174 2180 2173
+f 2128 2134 2118
+f 137 272 22
+f 949 880 1060
+f 995 894 722
+f 894 995 709
+f 894 724 774
+f 886 895 892
+f 640 910 930
+f 871 870 860
+f 846 856 863
+f 1026 875 1019
+f 838 851 829
+f 1024 1171 934
+f 36 189 205
+f 863 882 886
+f 886 882 895
+f 875 1026 594
+f 52 1459 1269
+f 896 917 910
+f 1025 1009 934
+f 949 995 722
+f 2152 1226 1636
+f 895 896 892
+f 892 910 601
+f 942 950 930
+f 875 989 752
+f 594 602 989
+f 766 355 643
+f 355 260 643
+f 905 917 896
+f 965 1060 1162
+f 892 896 910
+f 1101 1052 1042
+f 1029 1031 834
+f 1101 1133 1118
+f 342 357 376
+f 516 515 2454
+f 1656 2494 2467
+f 1056 1303 1133
+f 1120 1130 862
+f 69 342 376
+f 1055 1056 1133
+f 499 69 165
+f 85 101 111
+f 1031 1032 834
+f 200 679 1166
+f 1031 1042 1032
+f 1171 65 934
+f 1822 1204 1177
+f 1096 956 1103
+f 514 96 97
+f 956 1145 1144
+f 1185 1166 1144
+f 1145 1185 1144
+f 1185 200 1166
+f 375 132 1041
+f 1153 1202 305
+f 32 1244 1249
+f 1096 1087 956
+f 554 78 2355
+f 1191 138 110
+f 65 35 432
+f 1087 1110 956
+f 1110 1146 956
+f 956 1146 1145
+f 1146 1156 1145
+f 1145 1156 1185
+f 950 956 1144
+f 2481 2495 948
+f 1156 1193 1185
+f 1050 1047 1051
+f 239 151 107
+f 1185 1193 36
+f 1747 1110 1087
+f 1134 1146 1110
+f 1146 1157 1156
+f 1156 1157 1193
+f 1041 1045 1034
+f 1397 1134 1110
+f 1157 1146 1134
+f 1157 1175 1193
+f 1193 199 36
+f 1090 1035 1196
+f 1456 1150 1051
+f 1175 199 1193
+f 1186 695 199
+f 1186 199 1175
+f 1175 1157 1134
+f 728 1186 1175
+f 197 760 6
+f 1130 593 862
+f 1167 1109 182
+f 1194 1115 1161
+f 2140 1928 1504
+f 921 922 2138
+f 1147 1134 1397
+f 1719 1147 1397
+f 1147 1175 1134
+f 1175 1147 728
+f 341 1654 1208
+f 754 151 9
+f 284 2138 1058
+f 1188 1557 1660
+f 1191 110 1053
+f 916 284 1189
+f 284 1058 1189
+f 2094 1465 2127
+f 1726 1019 1147
+f 1147 1019 728
+f 593 1130 96
+f 239 305 1038
+f 1036 1131 315
+f 397 1131 1120
+f 1053 96 1130
+f 2467 2485 1869
+f 517 1089 421
+f 834 1827 1029
+f 419 1047 1117
+f 1034 433 1306
+f 2239 1862 1730
+f 1453 1462 1472
+f 1408 1422 1399
+f 471 23 1111
+f 1205 1150 1456
+f 1205 1040 1150
+f 1131 1036 293
+f 293 1068 1044
+f 375 1041 138
+f 1205 1140 1046
+f 1040 1205 1046
+f 1140 1167 1046
+f 1104 1049 83
+f 1052 1085 1032
+f 1044 1068 1191
+f 1167 1483 1109
+f 208 1084 1035
+f 1040 132 375
+f 1834 20 3
+f 1050 1051 1070
+f 1133 1125 1174
+f 11 1440 1401
+f 420 208 1071
+f 1135 1079 1094
+f 1086 1101 1118
+f 1029 1030 1031
+f 1200 1061 294
+f 1191 1068 138
+f 1171 1141 65
+f 1141 1172 65
+f 1172 35 65
+f 1172 404 35
+f 404 99 35
+f 221 1104 1063
+f 802 398 1083
+f 20 1089 3
+f 2064 1699 835
+f 1042 1052 1032
+f 1433 1261 1432
+f 1323 2338 155
+f 1076 1205 1456
+f 1088 1402 1056
+f 1150 348 1070
+f 1200 1089 20
+f 1097 1162 100
+f 1032 873 834
+f 21 471 1111
+f 294 1097 1104
+f 1072 100 584
+f 1151 760 622
+f 132 1045 1041
+f 1050 1070 1135
+f 1088 1039 940
+f 650 159 635
+f 100 1170 729
+f 729 584 100
+f 1103 931 1096
+f 925 1443 1513
+f 138 1102 110
+f 1034 1306 1152
+f 1071 1035 1090
+f 100 1072 1097
+f 23 1158 315
+f 1068 375 138
+f 1586 1612 1585
+f 1819 1030 1029
+f 1041 1034 1102
+f 232 375 1068
+f 348 1079 1070
+f 1061 1097 294
+f 1513 1443 1442
+f 1200 294 1119
+f 376 1050 1062
+f 1094 1036 315
+f 1200 1119 1089
+f 1111 1183 21
+f 1044 1191 1053
+f 698 295 689
+f 1079 232 1036
+f 404 1117 99
+f 1495 1496 717
+f 1119 294 98
+f 3 1089 517
+f 1132 1063 83
+f 1132 83 175
+f 132 1046 182
+f 1111 1195 1183
+f 1131 1044 1037
+f 127 402 1804
+f 219 1272 1047
+f 1697 1135 1094
+f 2140 1854 2117
+f 1111 397 1195
+f 1177 1162 1097
+f 1061 1177 1097
+f 717 1509 714
+f 2 1300 433
+f 462 290 461
+f 98 294 221
+f 294 1104 221
+f 714 1161 1007
+f 1073 1152 1143
+f 1697 1094 1360
+f 1223 1423 1218
+f 836 2479 842
+f 1097 1072 1049
+f 348 1040 375
+f 3 517 316
+f 180 1061 1201
+f 348 375 232
+f 1432 1431 1415
+f 220 1513 1495
+f 1104 1097 1049
+f 306 674 596
+f 777 455 723
+f 2170 2151 1641
+f 1047 419 219
+f 1102 1034 1073
+f 1073 1034 1152
+f 1035 1054 1196
+f 1177 1204 1162
+f 746 65 12
+f 751 178 721
+f 1054 517 421
+f 1051 1150 1070
+f 1102 1073 110
+f 998 1136 355
+f 567 566 1163
+f 1111 315 397
+f 1048 1074 835
+f 1158 1094 315
+f 1374 1107 1252
+f 1112 1136 998
+f 472 629 500
+f 355 1136 260
+f 260 118 43
+f 1104 83 1063
+f 376 357 1050
+f 1463 1142 1545
+f 1036 232 293
+f 1030 1042 1031
+f 1079 348 232
+f 221 1063 1132
+f 1094 1079 1036
+f 1076 1629 1205
+f 1136 1197 260
+f 260 1197 118
+f 1204 965 1162
+f 293 232 1068
+f 1590 1205 1629
+f 1205 1590 1140
+f 250 382 392
+f 1296 1318 1311
+f 347 1201 20
+f 1201 1200 20
+f 132 182 1045
+f 1101 1086 1052
+f 1033 1039 1055
+f 138 1041 1102
+f 970 1010 2495
+f 455 777 43
+f 1992 1948 2023
+f 20 1834 347
+f 1072 584 1049
+f 584 1192 1049
+f 182 2 1045
+f 1163 324 44
+f 1360 1094 1158
+f 1450 1360 1158
+f 1091 1112 229
+f 509 723 455
+f 207 509 455
+f 1251 1257 1266
+f 1488 1489 1547
+f 2157 1541 1875
+f 305 107 324
+f 1045 2 433
+f 1070 1079 1135
+f 1136 1168 1197
+f 1197 359 118
+f 118 359 43
+f 359 356 43
+f 356 455 43
+f 356 207 455
+f 1240 1422 1408
+f 1163 1153 324
+f 1201 1061 1200
+f 1052 1086 1085
+f 1024 1141 1171
+f 1112 1105 1136
+f 1050 1135 1062
+f 1105 1168 1136
+f 1168 1178 1197
+f 1197 1178 359
+f 1173 404 1172
+f 465 356 359
+f 1174 1125 240
+f 1240 1431 1422
+f 1098 1113 1105
+f 1112 1098 1105
+f 1105 1178 1168
+f 1178 465 359
+f 1091 1098 1112
+f 1133 1174 1118
+f 98 221 1059
+f 487 1132 175
+f 980 1017 285
+f 465 207 356
+f 180 1201 347
+f 1060 524 1170
+f 445 127 316
+f 1431 1438 1422
+f 498 469 681
+f 940 1807 1759
+f 381 250 1290
+f 1113 1122 1105
+f 1105 1122 1178
+f 1151 509 207
+f 1236 2035 525
+f 1131 293 1044
+f 346 207 465
+f 346 1151 207
+f 1822 1796 1204
+f 1143 204 97
+f 123 1128 971
+f 2153 2152 2134
+f 126 1151 346
+f 517 445 316
+f 1450 1158 23
+f 1458 1462 1430
+f 1129 152 1182
+f 1122 1159 1178
+f 1178 1198 465
+f 79 346 465
+f 126 1155 1151
+f 1151 1155 6
+f 295 1129 689
+f 1073 1143 97
+f 1098 1123 1113
+f 1113 1123 1122
+f 1123 1169 1122
+f 1178 1159 1198
+f 1198 79 465
+f 392 383 152
+f 1822 1061 180
+f 116 92 625
+f 421 1089 1119
+f 1129 295 152
+f 110 1073 97
+f 1173 1172 1141
+f 1122 1169 1159
+f 79 126 346
+f 1155 181 6
+f 971 926 1002
+f 295 1043 152
+f 1039 1088 1056
+f 1428 1266 1436
+f 404 419 1117
+f 836 879 2479
+f 2464 2476 2458
+f 1198 317 79
+f 1124 939 301
+f 44 754 567
+f 1039 1056 1055
+f 1439 1459 1458
+f 1660 1412 986
+f 1169 1160 1159
+f 179 1155 126
+f 1155 131 181
+f 1061 1822 1177
+f 1153 305 324
+f 175 314 327
+f 1160 1187 1159
+f 1159 1187 1198
+f 1198 1187 317
+f 79 179 126
+f 1043 250 392
+f 152 1043 392
+f 96 819 593
+f 1123 1127 1169
+f 317 179 79
+f 1057 1155 179
+f 1155 391 131
+f 131 391 668
+f 2381 1586 1585
+f 12 69 499
+f 262 398 1640
+f 2107 2118 2060
+f 2130 2094 2002
+f 1187 249 317
+f 1155 1057 391
+f 1290 439 1265
+f 305 239 107
+f 1127 1160 1169
+f 317 473 179
+f 473 1057 179
+f 83 1192 314
+f 1043 1290 250
+f 1807 940 1030
+f 517 1084 445
+f 1057 1164 391
+f 2492 2480 2493
+f 163 643 43
+f 1056 1545 1303
+f 1069 1655 1023
+f 249 473 317
+f 1162 1060 1170
+f 1086 1118 1154
+f 82 68 16
+f 1989 1990 1536
+f 1633 1632 1611
+f 1487 2372 1305
+f 1494 1069 1023
+f 1137 1160 1127
+f 669 1166 679
+f 390 1285 426
+f 1955 1972 1971
+f 1219 1223 2437
+f 1254 1261 1223
+f 1319 1545 1056
+f 1320 1328 2443
+f 1261 1433 1223
+f 1219 1254 1223
+f 254 222 2428
+f 1237 1290 1265
+f 1284 1273 1263
+f 1277 1291 1301
+f 1314 102 1301
+f 1280 363 377
+f 1313 1353 1514
+f 468 451 439
+f 1918 1964 1956
+f 2026 29 2140
+f 1354 381 1279
+f 1224 30 1254
+f 147 158 173
+f 1247 1253 274
+f 1271 380 334
+f 2043 2072 2042
+f 274 300 267
+f 1356 1392 211
+f 13 240 1142
+f 1382 1330 1392
+f 1312 1323 155
+f 240 1125 1142
+f 2358 1573 1362
+f 1236 1249 1244
+f 1272 219 1348
+f 1271 1274 380
+f 191 2034 1982
+f 1992 2052 1990
+f 462 452 689
+f 2262 2286 2261
+f 183 489 1642
+f 2485 2480 1869
+f 84 111 1323
+f 1190 353 1354
+f 446 434 435
+f 1336 171 1341
+f 2021 430 2059
+f 862 878 1120
+f 1263 1273 1248
+f 1966 1921 2144
+f 1312 84 1323
+f 240 13 1348
+f 1359 1274 1271
+f 1392 1330 1247
+f 1520 1333 11
+f 1368 1253 1247
+f 1279 1285 1190
+f 2465 990 2489
+f 1272 1519 805
+f 1369 1272 805
+f 1317 95 1344
+f 1242 1248 1234
+f 1368 242 1363
+f 274 1262 1386
+f 532 597 1886
+f 2117 2026 2140
+f 1392 1247 274
+f 2162 508 985
+f 1964 1469 1965
+f 1315 104 1331
+f 1392 1356 1382
+f 128 1342 1336
+f 1285 427 426
+f 1219 1224 1254
+f 1320 1322 1321
+f 1320 1321 1328
+f 153 2443 1328
+f 1321 153 1328
+f 1235 1244 1243
+f 1225 1224 1219
+f 1359 353 1190
+f 1312 1473 1458
+f 1336 1342 147
+f 305 1333 1038
+f 1336 147 171
+f 516 31 19
+f 2479 2461 842
+f 1237 1265 427
+f 1263 1278 1284
+f 881 1827 834
+f 1237 427 1285
+f 1299 1312 1458
+f 1190 1285 1274
+f 1363 286 1253
+f 2330 2303 828
+f 427 442 426
+f 2493 2463 2492
+f 1285 380 1274
+f 522 18 1225
+f 2471 2472 2488
+f 2338 154 1321
+f 1423 1415 1218
+f 1225 18 1224
+f 1253 286 1262
+f 286 353 1359
+f 171 1368 1383
+f 1273 54 1234
+f 1973 2447 527
+f 1322 155 1321
+f 1203 1369 1413
+f 1307 363 1298
+f 1364 1375 1329
+f 1329 227 1306
+f 296 1298 1343
+f 947 2499 1447
+f 1203 1047 1272
+f 1098 1748 1123
+f 1519 1272 1348
+f 1277 70 1273
+f 1282 1337 1361
+f 286 302 353
+f 103 104 1315
+f 1377 435 434
+f 1449 1261 1345
+f 926 1310 806
+f 1263 1248 1242
+f 985 508 597
+f 1415 1222 1218
+f 88 1325 104
+f 170 111 156
+f 1384 1282 1361
+f 274 1253 1262
+f 1371 1317 1344
+f 1371 1366 1337
+f 1345 1459 1449
+f 171 1383 1341
+f 2438 1235 1227
+f 2134 1582 2118
+f 428 1260 1379
+f 1336 1341 1325
+f 1235 1242 1227
+f 1228 1687 2284
+f 1854 2140 2016
+f 1866 1887 1873
+f 1343 1298 1370
+f 1384 1361 2440
+f 171 242 1368
+f 1344 1309 1366
+f 1371 1344 1366
+f 1280 1377 1293
+f 200 1185 205
+f 1330 1383 1368
+f 1255 1264 1263
+f 543 1367 1876
+f 1343 1370 1260
+f 1293 1326 1370
+f 2440 1361 1302
+f 1282 1384 2406
+f 271 1337 1282
+f 170 2338 1323
+f 1528 1503 2470
+f 515 1347 2453
+f 1997 1705 1998
+f 2285 1228 2284
+f 1229 1250 1228
+f 1330 1368 1247
+f 1919 1619 2045
+f 1344 1364 1335
+f 1222 1240 1221
+f 1212 858 1741
+f 2388 1222 1221
+f 1528 2470 2068
+f 501 1308 2171
+f 1295 1311 1487
+f 2116 1619 1655
+f 1220 1229 1228
+f 8 663 573
+f 1343 1260 428
+f 1337 1366 1361
+f 1298 1280 1293
+f 1269 1345 1261
+f 1279 381 1290
+f 1230 1229 1220
+f 1230 1245 1229
+f 1245 1250 1229
+f 1227 1234 31
+f 1302 1361 1350
+f 1245 1266 1428
+f 1992 2023 2052
+f 2482 2471 2475
+f 452 462 461
+f 271 1282 1275
+f 1991 1989 1934
+f 1366 1309 1350
+f 1344 1335 1309
+f 730 699 974
+f 1374 1252 1208
+f 597 508 1912
+f 1363 1253 1368
+f 1386 1271 300
+f 1211 1218 1222
+f 1376 1377 434
+f 2399 2437 1211
+f 1284 1291 1277
+f 1230 1251 1245
+f 1251 1266 1245
+f 1317 1371 1337
+f 1288 1286 1095
+f 1095 1286 1352
+f 1241 1208 1352
+f 1241 1374 1208
+f 1284 1278 1291
+f 211 1392 267
+f 1344 1375 1364
+f 929 583 1028
+f 1361 1366 1350
+f 1115 1294 1639
+f 1291 103 1301
+f 1220 1231 1230
+f 1231 1251 1230
+f 1234 1248 1273
+f 1255 55 1264
+f 1360 1450 1702
+f 363 1280 1298
+f 1369 1203 1272
+f 1415 1240 1222
+f 1216 1231 1220
+f 1243 1263 1235
+f 1375 227 1329
+f 1264 1278 1263
+f 855 899 961
+f 1286 1241 1352
+f 2081 2128 2107
+f 1223 1433 1423
+f 1473 1312 155
+f 154 153 1321
+f 1377 1376 1293
+f 1392 274 267
+f 334 300 1271
+f 1955 1991 1934
+f 1613 1327 1288
+f 1327 1286 1288
+f 1349 1374 1241
+f 2370 2025 2367
+f 1315 1331 133
+f 434 446 1256
+f 1232 1251 1231
+f 1243 1244 1255
+f 1286 1304 1241
+f 1349 1107 1374
+f 1359 1271 1386
+f 1227 516 2431
+f 219 240 1348
+f 1270 271 1275
+f 1255 1263 1243
+f 2026 1926 29
+f 1683 2157 1212
+f 1326 1293 1376
+f 1255 32 55
+f 104 1325 1341
+f 519 2462 2475
+f 2154 2161 2137
+f 1376 434 1246
+f 1246 434 1256
+f 1257 1251 1232
+f 1262 1359 1386
+f 2195 2192 2186
+f 1308 534 1226
+f 2026 2117 544
+f 1327 1613 1324
+f 1327 1326 1286
+f 1286 1326 1304
+f 104 1341 1331
+f 774 524 880
+f 837 1517 534
+f 1127 1123 1567
+f 1279 1237 1285
+f 1297 1381 1294
+f 1217 1232 1216
+f 1142 1519 13
+f 1436 1267 1287
+f 1324 1372 1327
+f 1304 1246 1241
+f 1246 1349 1241
+f 1246 1373 1349
+f 286 1359 1262
+f 1382 1383 1330
+f 1284 1277 1273
+f 489 1998 1799
+f 1675 1116 1075
+f 106 1317 1337
+f 1311 1295 1281
+f 1292 1364 1329
+f 1335 1364 1292
+f 1334 1294 1115
+f 1334 1297 1294
+f 1300 1381 1297
+f 973 842 2461
+f 1217 1239 1232
+f 1232 1239 1257
+f 1258 1267 1436
+f 1359 1190 1274
+f 1862 1405 1877
+f 1372 1339 1327
+f 1339 1326 1327
+f 1373 1351 1349
+f 1276 1311 1281
+f 1256 2386 1351
+f 2 1109 1300
+f 482 1731 520
+f 803 1604 2022
+f 1223 1218 1211
+f 1341 1383 1382
+f 1298 1293 1370
+f 1190 1354 1279
+f 1324 2398 1372
+f 1714 1700 2173
+f 183 2000 489
+f 1701 1666 192
+f 1227 1242 1234
+f 1332 1289 1310
+f 1517 2005 2130
+f 1331 1341 1382
+f 525 1249 1236
+f 23 1268 1450
+f 1264 1291 1278
+f 1281 1287 1267
+f 1295 1305 1287
+f 1281 1295 1287
+f 1487 1305 1295
+f 1605 2097 2058
+f 1326 1376 1304
+f 1304 1376 1246
+f 1316 1919 1984
+f 2500 1949 2460
+f 1332 1313 1289
+f 2189 2181 2177
+f 1335 1334 1353
+f 1292 1297 1334
+f 1428 1250 1245
+f 969 958 952
+f 1217 1233 1239
+f 1233 1257 1239
+f 1876 1367 1338
+f 1379 1260 1372
+f 1372 1260 1339
+f 1128 1302 1310
+f 1310 1302 1332
+f 1335 1353 1313
+f 1292 1334 1335
+f 1297 1329 1300
+f 1279 1290 1237
+f 1301 103 1314
+f 70 1301 102
+f 23 1333 1268
+f 380 1285 390
+f 772 325 1275
+f 1314 103 1315
+f 2473 2458 2487
+f 1276 1281 1267
+f 1344 95 1375
+f 2053 1771 1572
+f 1246 1256 1373
+f 1373 1256 1351
+f 1340 1302 1128
+f 1350 1313 1332
+f 1329 1297 1292
+f 2434 2473 2487
+f 106 1337 271
+f 23 471 1333
+f 622 723 509
+f 1388 1517 2127
+f 1991 1990 1989
+f 183 1636 1226
+f 2133 1605 2151
+f 1260 1370 1339
+f 1339 1370 1326
+f 867 1894 1902
+f 390 426 412
+f 1235 1263 1242
+f 1399 1422 1233
+f 305 11 1333
+f 1300 1329 1306
+f 1302 1350 1332
+f 1350 1309 1313
+f 1309 1335 1313
+f 2470 2102 1502
+f 1787 1531 1599
+f 1724 1725 1691
+f 1827 1601 1927
+f 1678 1358 1476
+f 1823 1812 1846
+f 1805 1824 1708
+f 1746 1676 1797
+f 325 2395 429
+f 1835 1677 1826
+f 1507 1790 1722
+f 1526 1672 858
+f 158 147 1342
+f 1462 1473 1322
+f 1474 1414 1565
+f 1761 1900 1877
+f 940 1759 1008
+f 1565 1015 1008
+f 1924 1533 1933
+f 1878 826 830
+f 1565 1414 1015
+f 1402 1088 1008
+f 1538 1532 1651
+f 1015 1552 1008
+f 1538 1591 1474
+f 1532 1538 1474
+f 1474 1591 1414
+f 1484 1402 1008
+f 1552 1484 1008
+f 1414 1460 1015
+f 1015 1460 1552
+f 806 1289 945
+f 1597 1538 1659
+f 1484 1319 1402
+f 1056 1402 1319
+f 1538 1597 1591
+f 1591 960 1414
+f 1414 960 1460
+f 1925 1466 1455
+f 1552 1400 1484
+f 1484 1400 1319
+f 1400 113 1319
+f 1597 1580 1591
+f 1460 1400 1552
+f 1514 1441 966
+f 1597 1659 1409
+f 1657 113 1400
+f 1460 1657 1400
+f 1288 1095 1634
+f 1551 1597 1409
+f 1580 1598 1591
+f 1591 1598 960
+f 1536 1990 2031
+f 960 1657 1460
+f 1809 1746 1797
+f 1423 1433 1432
+f 2478 1362 1409
+f 1463 1545 113
+f 1657 1463 113
+f 1457 1287 1305
+f 1682 1716 1746
+f 1434 1761 1885
+f 1013 1139 1617
+f 2379 1362 2478
+f 1420 1597 1551
+f 1420 1580 1597
+f 1664 1808 1712
+f 2256 2250 2231
+f 1362 1551 1409
+f 2196 2214 2213
+f 1691 1725 1777
+f 1626 192 1666
+f 1534 1574 2058
+f 1574 1600 1605
+f 1600 1606 1605
+f 1606 1641 1605
+f 1573 1420 1551
+f 1657 1485 1463
+f 678 1806 1742
+f 1534 1553 1574
+f 1574 1575 1600
+f 1810 2170 585
+f 1623 1641 1606
+f 1407 1657 960
+f 1598 1407 960
+f 1485 1142 1463
+f 1716 1581 1676
+f 1738 1743 1733
+f 843 2064 835
+f 1539 1575 1574
+f 1553 1539 1574
+f 1575 1592 1600
+f 1592 1624 1606
+f 1600 1592 1606
+f 1642 585 1641
+f 1623 1642 1641
+f 1485 164 1142
+f 1738 1516 1743
+f 1809 1720 1798
+f 1533 1535 1534
+f 1592 1607 1624
+f 1624 1623 1606
+f 1163 566 1116
+f 1407 1485 1657
+f 1432 1449 1439
+f 1100 802 2382
+f 1743 1516 1722
+f 1746 1716 1676
+f 1535 1539 1534
+f 1534 1539 1553
+f 1642 1623 1624
+f 1095 1208 1654
+f 967 1407 1598
+f 1580 967 1598
+f 1809 1797 1720
+f 1924 1524 1535
+f 1533 1924 1535
+f 1539 1576 1575
+f 1642 216 585
+f 1407 1529 1485
+f 1485 1529 164
+f 1472 1462 1482
+f 1415 1431 1240
+f 966 1194 714
+f 383 1182 152
+f 474 2337 446
+f 1743 1841 1757
+f 1486 1524 1924
+f 1535 1525 1539
+f 1575 1576 1592
+f 1420 967 1580
+f 1288 1634 1613
+f 459 427 1265
+f 1404 2179 1393
+f 1404 1403 1800
+f 1404 1410 1403
+f 1410 1749 1403
+f 1349 1351 218
+f 1486 1498 1524
+f 1535 1524 1525
+f 1607 1636 1624
+f 183 1642 1624
+f 1636 183 1624
+f 1107 1349 218
+f 1351 845 218
+f 164 1519 1142
+f 845 413 218
+f 1525 1576 1539
+f 1576 1582 1592
+f 1592 2134 1607
+f 2134 1636 1607
+f 2147 1491 1401
+f 1407 1589 1529
+f 1529 1519 164
+f 1693 1763 1444
+f 1924 1479 1486
+f 1592 1582 2134
+f 499 165 874
+f 2176 1857 1959
+f 2327 2368 2326
+f 2358 821 953
+f 953 821 1573
+f 1824 1704 1464
+f 1731 1358 1678
+f 1394 1410 1404
+f 1394 1418 1410
+f 1466 1479 1839
+f 1486 1479 1498
+f 1498 1525 1524
+f 1576 2080 1582
+f 1785 1684 1898
+f 804 398 802
+f 804 925 398
+f 1447 1562 2358
+f 2358 1562 821
+f 821 1620 1573
+f 1620 1420 1573
+f 1420 1556 967
+f 1393 1394 1404
+f 1525 2080 1576
+f 1621 1420 1620
+f 1621 1556 1420
+f 967 1589 1407
+f 1505 5 1357
+f 1266 1258 1436
+f 1393 1395 1394
+f 2176 2175 1848
+f 1455 1466 1839
+f 1525 1540 2080
+f 1582 2080 2118
+f 1100 804 802
+f 1556 1589 967
+f 1589 1082 1529
+f 1093 1685 1357
+f 1504 1093 1357
+f 1425 1418 1394
+f 1475 1479 1466
+f 1479 1506 1498
+f 1789 1784 1730
+f 2501 2465 2489
+f 1438 1458 1430
+f 1462 1458 1473
+f 1454 805 1529
+f 1082 1454 1529
+f 1529 805 1519
+f 1425 1394 1395
+f 1425 1744 1418
+f 1479 1475 1506
+f 1540 2060 2080
+f 1556 1082 1589
+f 1443 945 1511
+f 1506 1536 1498
+f 1498 1536 1525
+f 1525 1536 1540
+f 1670 852 1672
+f 1998 1388 1389
+f 1511 966 1509
+f 1509 966 714
+f 1442 1443 1496
+f 1562 1635 821
+f 155 1322 1473
+f 1439 1458 1438
+f 1426 1425 1395
+f 1475 1499 1506
+f 1735 1588 1776
+f 2422 2454 2421
+f 1423 1432 1415
+f 1559 2101 2073
+f 845 866 413
+f 1429 1620 821
+f 1620 1429 1621
+f 1228 1250 1687
+f 1002 945 1443
+f 2382 802 1083
+f 1859 1411 1395
+f 1411 1426 1395
+f 1426 1744 1425
+f 1590 1437 1483
+f 1480 1475 1466
+f 1480 1499 1475
+f 1510 1733 1743
+f 1663 1696 1658
+f 1430 1453 1452
+f 1452 1472 1471
+f 1452 1471 1448
+f 1430 1452 1421
+f 1430 1421 1422
+f 1429 1082 1556
+f 1621 1429 1556
+f 1351 2386 845
+f 1126 1059 487
+f 1639 1437 1563
+f 1504 1928 1093
+f 1499 1536 1506
+f 1588 1770 1727
+f 1110 1747 1397
+f 1776 1588 1531
+f 1322 1320 1482
+f 1590 1629 1571
+f 1730 1877 1838
+f 1429 935 1082
+f 1082 935 1454
+f 804 1443 925
+f 1139 1007 1639
+f 1925 1480 1466
+f 1934 1989 1480
+f 1499 1989 1536
+f 1727 1526 1531
+f 1593 1614 502
+f 2455 2431 2400
+f 1755 1680 908
+f 1563 1571 1564
+f 1647 1078 1501
+f 2490 1635 1106
+f 1496 1511 717
+f 2454 2431 516
+f 1478 1153 1093
+f 1870 1426 1411
+f 1426 1723 1744
+f 962 986 1412
+f 717 1511 1509
+f 1825 1704 1824
+f 2225 2234 2253
+f 1490 1557 1188
+f 1635 80 821
+f 805 1454 935
+f 1186 706 695
+f 1194 1161 714
+f 1512 1007 1013
+f 592 97 204
+f 1258 1266 1257
+f 82 1333 471
+f 1694 1710 1505
+f 1643 490 1661
+f 1661 490 1114
+f 1518 2068 2484
+f 1750 1808 1664
+f 1656 1635 2490
+f 935 1521 805
+f 1546 1629 1076
+f 1301 70 1277
+f 966 1441 1194
+f 1148 1825 1824
+f 1614 1609 1643
+f 1114 1092 1921
+f 1770 1739 1670
+f 1631 1632 1646
+f 821 1016 1429
+f 1429 1016 935
+f 1632 1095 1654
+f 1083 262 688
+f 1724 1686 1725
+f 1644 490 1643
+f 1092 1149 1921
+f 3 893 1832
+f 988 1640 1188
+f 916 1107 284
+f 1656 80 1635
+f 1016 821 80
+f 1016 1521 935
+f 1478 1202 1153
+f 1401 1928 29
+f 1440 1478 1928
+f 1849 1700 1865
+f 1595 1611 1612
+f 1208 198 341
+f 1464 1704 1746
+f 2143 984 1721
+f 1848 1849 1868
+f 1662 1114 490
+f 1669 1787 1682
+f 1656 1618 80
+f 198 1208 916
+f 1440 1928 1401
+f 1521 1369 805
+f 1252 1107 916
+f 1745 678 1672
+f 1703 1779 1721
+f 1750 1465 1808
+f 1609 1644 1643
+f 1092 1114 1662
+f 1826 1523 1793
+f 2262 2261 2224
+f 1696 2166 1767
+f 1016 1648 1521
+f 1208 1252 916
+f 833 688 1067
+f 1794 1803 1558
+f 28 17 512
+f 1750 861 1566
+f 1594 1644 1609
+f 1644 1645 490
+f 490 1645 1662
+f 2229 2262 2224
+f 1602 861 1760
+f 1530 1777 1760
+f 872 1706 1673
+f 1696 1668 2166
+f 1708 1809 1798
+f 1581 1716 1814
+f 1709 1794 1680
+f 1233 1421 1257
+f 1724 1476 1686
+f 1469 1481 1965
+f 1965 1481 1492
+f 2073 1549 1559
+f 1594 1615 1644
+f 1799 1706 1755
+f 1725 1686 1837
+f 1720 1797 1572
+f 1618 2467 2022
+f 1618 1579 80
+f 1648 1016 80
+f 2134 2152 1636
+f 1611 1632 1631
+f 1761 1434 1470
+f 1559 1577 1594
+f 1603 1615 1594
+f 1615 1645 1644
+f 1637 1662 1645
+f 1662 1199 1092
+f 1199 1149 1092
+f 1451 1108 1149
+f 665 734 756
+f 1865 1700 1714
+f 1709 1841 1794
+f 1618 2022 1579
+f 1648 1413 1369
+f 1521 1648 1369
+f 1520 11 1401
+f 1446 1470 1434
+f 1798 1691 1754
+f 2063 1544 2073
+f 2073 1544 1549
+f 1594 1577 1603
+f 1615 1637 1645
+f 1637 1199 1662
+f 1427 1149 1199
+f 2167 1108 1451
+f 1997 1673 1705
+f 1706 1799 1705
+f 1841 1709 1757
+f 1604 1579 2022
+f 1579 707 80
+f 80 707 1648
+f 1520 1401 1491
+f 1649 1520 1491
+f 1435 1434 1885
+f 1470 1469 1461
+f 1481 1508 2024
+f 2370 1544 2063
+f 1549 1568 1559
+f 1559 1568 1577
+f 1603 1610 1615
+f 1615 1610 1637
+f 999 1199 1637
+f 1451 1149 1427
+f 1137 1825 1148
+f 1706 1705 1673
+f 1138 1604 2116
+f 1138 1579 1604
+f 1413 1648 707
+f 2360 2024 1508
+f 598 1075 1116
+f 229 93 1468
+f 1839 1479 1684
+f 2216 2229 2224
+f 1610 1625 1637
+f 329 999 1637
+f 1199 1017 1427
+f 1017 303 1427
+f 303 1451 1427
+f 1792 1754 1777
+f 2309 2391 2301
+f 1655 1138 2116
+f 1138 707 1579
+f 1649 1491 206
+f 1406 1885 1398
+f 1406 1419 1885
+f 1419 1435 1885
+f 1434 1435 1446
+f 1470 1481 1469
+f 1577 1583 1603
+f 999 1017 1199
+f 81 67 941
+f 67 1650 941
+f 1259 1815 2164
+f 1619 2116 2045
+f 1424 707 1138
+f 1702 1649 206
+f 1687 1406 1398
+f 1477 1481 1470
+f 1568 1569 1577
+f 1577 1569 1583
+f 1603 1583 1610
+f 1625 329 1637
+f 2167 340 273
+f 81 273 340
+f 81 962 67
+f 1547 1619 1488
+f 1830 1739 1770
+f 938 1424 1138
+f 1424 1413 707
+f 1527 1649 1702
+f 1527 1520 1649
+f 1527 1268 1520
+f 1250 1406 1687
+f 1441 1353 1115
+f 1203 1413 1051
+f 1250 1419 1406
+f 1477 2372 1481
+f 1481 2372 1508
+f 2449 1560 1568
+f 1549 2449 1568
+f 1568 1560 1569
+f 1569 1584 1583
+f 1652 329 1625
+f 329 817 999
+f 285 1017 999
+f 303 10 1451
+f 10 2167 1451
+f 1412 1650 67
+f 1412 1488 1650
+f 1547 1023 1619
+f 1023 1655 1619
+f 1655 938 1138
+f 1456 1413 1424
+f 1457 1470 1446
+f 1457 1477 1470
+f 329 1652 817
+f 10 340 2167
+f 938 1546 1424
+f 1546 1456 1424
+f 1259 1548 1779
+f 2052 2031 1990
+f 1440 1202 1478
+f 1428 1419 1250
+f 1428 1435 1419
+f 1428 1446 1435
+f 1934 1935 1955
+f 1560 1584 1569
+f 1610 1638 1625
+f 1638 1652 1625
+f 817 1077 999
+f 1077 285 999
+f 980 303 1017
+f 962 1412 67
+f 1494 1023 1547
+f 325 271 1270
+f 1443 1511 1496
+f 1450 1268 1527
+f 1514 1353 1441
+f 1287 1446 1428
+f 1446 1287 1457
+f 1305 2372 1477
+f 1992 1990 1991
+f 1992 1991 1971
+f 1971 1991 1955
+f 2449 1549 2418
+f 1583 1616 1610
+f 1610 1616 1638
+f 10 1396 340
+f 340 1445 81
+f 1445 962 81
+f 1790 984 1753
+f 984 2148 1753
+f 1588 1713 1770
+f 969 978 958
+f 1741 1779 1703
+f 1758 1846 1754
+f 1827 1819 1029
+f 1818 1530 1712
+f 1750 1566 2127
+f 2459 2434 2483
+f 1798 1720 1771
+f 1794 1841 1803
+f 216 1755 1810
+f 1098 1735 1748
+f 1735 1497 1748
+f 1502 2102 1601
+f 881 1502 1601
+f 1455 1839 1744
+f 1706 1709 1680
+f 1212 1741 1703
+f 1788 1969 1671
+f 1075 1074 1692
+f 951 2500 881
+f 2490 2486 2463
+f 1748 1497 1781
+f 1721 984 1840
+f 1815 1259 1741
+f 1626 1756 1837
+f 975 987 1542
+f 2230 2236 2235
+f 1772 678 734
+f 1542 1671 975
+f 1806 1772 1780
+f 678 1772 1806
+f 2218 2225 2268
+f 1828 1732 2007
+f 1526 1688 1531
+f 1752 1526 1554
+f 1844 1818 1712
+f 1823 1846 1804
+f 1781 1669 1704
+f 1721 1779 2143
+f 1770 1670 1526
+f 1497 1669 1781
+f 1098 1713 1735
+f 1742 1815 1741
+f 1526 858 1875
+f 1599 1531 1688
+f 1803 1790 1558
+f 1703 1721 1683
+f 1832 1766 957
+f 1542 1679 1671
+f 1679 1788 1671
+f 1927 1819 1827
+f 1718 1745 1739
+f 1684 1022 1839
+f 1459 1283 1299
+f 1022 1410 1418
+f 2368 2393 2326
+f 1669 1497 1776
+f 1875 858 1212
+f 1739 1745 852
+f 1964 1918 1461
+f 1356 133 1331
+f 1765 1829 1468
+f 858 1742 1741
+f 1006 1674 1021
+f 1723 1936 1935
+f 1468 1713 1098
+f 1724 1678 1476
+f 1680 1783 908
+f 1731 1543 520
+f 1683 1721 1840
+f 1467 1679 1542
+f 1812 1708 1846
+f 1679 1975 1788
+f 1713 1830 1770
+f 1803 1722 1790
+f 2301 2391 2349
+f 1713 1588 1735
+f 1836 1530 1818
+f 1837 1756 861
+f 886 571 556
+f 1181 1805 1812
+f 1706 1680 1755
+f 1677 1729 1775
+f 1776 1787 1669
+f 1526 1670 1672
+f 1727 1770 1526
+f 987 1467 1542
+f 1567 1704 1137
+f 1693 1865 1714
+f 897 1762 912
+f 1135 1697 1062
+f 1697 376 1062
+f 1543 1731 1678
+f 1793 1679 1467
+f 1777 1602 1760
+f 1846 1798 1754
+f 1835 1096 1677
+f 1033 1030 940
+f 1450 1527 1702
+f 1717 376 1697
+f 1711 1717 1697
+f 1717 165 376
+f 1840 984 1790
+f 1669 1746 1704
+f 1669 1682 1746
+f 2301 2349 2308
+f 1882 1444 1898
+f 1820 1789 1730
+f 861 1380 1566
+f 2301 2308 2266
+f 1771 1543 1691
+f 1958 1659 1651
+f 1697 1360 1711
+f 1711 1737 1717
+f 1717 1737 165
+f 1790 1753 1558
+f 1668 1696 1663
+f 1360 1702 1711
+f 1702 1707 1711
+f 1707 1737 1711
+f 1737 1751 165
+f 1444 1782 1693
+f 1716 1787 1599
+f 1744 1839 1022
+f 1898 1444 1785
+f 206 1707 1702
+f 1764 2468 1751
+f 316 1844 893
+f 893 1844 915
+f 1845 1804 1758
+f 1380 861 1756
+f 1780 670 1021
+f 1714 2172 1763
+f 1783 1558 1663
+f 1750 2127 1465
+f 1798 1771 1691
+f 1691 1543 1724
+f 1872 1910 839
+f 1737 2044 1751
+f 1751 2044 1764
+f 1757 1701 482
+f 1725 1602 1777
+f 1836 1845 1530
+f 2102 2470 1503
+f 2496 1899 544
+f 763 2484 946
+f 987 1719 1467
+f 1845 1758 1792
+f 1725 1837 1602
+f 1872 1866 1873
+f 1712 1530 1760
+f 489 1799 216
+f 1760 861 1750
+f 2068 2466 2460
+f 1696 2159 2168
+f 377 1377 1280
+f 1797 1676 1572
+f 1581 2053 1572
+f 1676 1581 1572
+f 1764 2498 2468
+f 2468 2498 1994
+f 1861 1695 1860
+f 2481 2004 2495
+f 1826 1677 1523
+f 1670 1739 852
+f 2234 2269 2253
+f 1724 1543 1678
+f 1658 2168 1791
+f 1397 1747 1719
+f 1696 2168 1658
+f 979 519 272
+f 1774 1975 1679
+f 975 1671 932
+f 1787 1716 1682
+f 1835 1826 1747
+f 2501 2469 961
+f 1810 908 1791
+f 1982 1768 191
+f 1137 1704 1825
+f 1804 1846 1758
+f 2004 2044 1737
+f 913 1969 902
+f 2498 1795 1801
+f 915 1844 1712
+f 1689 915 1712
+f 1740 1752 1541
+f 695 661 199
+f 1865 1693 1782
+f 1824 1464 1809
+f 1829 1765 1718
+f 1816 1768 1982
+f 1816 1622 1768
+f 1622 2165 1681
+f 1768 1622 1681
+f 670 1772 228
+f 1283 1459 52
+f 1785 1444 1749
+f 1675 1075 1685
+f 1567 1781 1704
+f 1858 1857 1848
+f 1526 1752 1688
+f 1791 2160 1810
+f 908 1658 1791
+f 1813 1773 1558
+f 1845 1792 1530
+f 69 376 165
+f 3 1832 1834
+f 1722 1516 1507
+f 1801 1821 1994
+f 1833 1982 2046
+f 1821 1833 2046
+f 1833 1816 1982
+f 1022 1785 1749
+f 2160 2170 1810
+f 1147 1719 1726
+f 1683 1840 1507
+f 1467 1719 1793
+f 1795 1802 1801
+f 1802 1811 1801
+f 1801 1811 1821
+f 1690 2165 1622
+f 1934 1480 1925
+f 229 1468 1091
+f 1780 2164 1742
+f 1672 1742 858
+f 1833 1417 1816
+f 1417 1622 1816
+f 1831 2165 1690
+f 1668 1663 1558
+f 1719 1747 1826
+f 1760 1750 1664
+f 1817 1690 1622
+f 1530 1792 1777
+f 948 1796 1802
+f 1796 1811 1802
+f 1515 1817 1622
+f 1695 1861 1831
+f 1783 1663 1658
+f 1749 1410 1022
+f 854 1796 948
+f 1811 1842 1833
+f 1821 1811 1833
+f 1833 1842 1417
+f 1622 1417 1515
+f 127 1804 1845
+f 1686 1626 1837
+f 1608 1690 1817
+f 1523 1775 1762
+f 127 1845 1836
+f 1812 1805 1708
+f 1523 1677 1775
+f 1780 1772 670
+f 1758 1754 1792
+f 1204 1796 854
+f 1822 1842 1811
+f 1608 1831 1690
+f 1822 1811 1796
+f 1842 1416 1417
+f 1417 1416 1515
+f 1515 1608 1817
+f 1728 1831 1608
+f 908 1783 1658
+f 127 1836 316
+f 1805 1148 1824
+f 852 1745 1672
+f 1478 1093 1928
+f 1822 1843 1842
+f 1843 959 1842
+f 1842 959 1416
+f 1728 1695 1831
+f 1728 1860 1695
+f 2346 446 2337
+f 1602 1837 861
+f 1087 1096 1835
+f 1708 1824 1809
+f 2004 1737 505
+f 1567 1748 1781
+f 520 1543 1883
+f 1760 1664 1712
+f 128 1336 72
+f 2053 1883 1543
+f 1822 180 1843
+f 1786 1608 1515
+f 929 2462 519
+f 512 2402 506
+f 1212 1703 1683
+f 1830 1829 1739
+f 2053 1543 1771
+f 1416 1769 1515
+f 1769 1786 1515
+f 1786 1728 1608
+f 1712 1808 1689
+f 1794 1558 1783
+f 1497 1735 1776
+f 1127 1567 1137
+f 1123 1748 1567
+f 36 205 1185
+f 959 1734 1416
+f 1738 1733 1541
+f 1774 1762 1974
+f 1752 1554 1541
+f 1752 1740 1688
+f 1526 1875 1554
+f 1468 1829 1830
+f 1755 908 1810
+f 1716 1599 1814
+f 1806 1780 1742
+f 2308 2349 2340
+f 1832 915 1689
+f 1713 1468 1830
+f 1814 1599 1346
+f 1832 1689 1766
+f 1022 1684 1785
+f 1093 1153 1116
+f 1672 678 1742
+f 1675 1685 1093
+f 1841 1743 1722
+f 1814 2053 1581
+f 1464 1746 1809
+f 2485 2497 2493
+f 1416 1734 1769
+f 1665 1728 1786
+f 1665 1951 1728
+f 1951 1860 1728
+f 1951 2094 1860
+f 1844 1836 1818
+f 316 1836 1844
+f 1776 1531 1787
+f 1719 1826 1793
+f 2147 1401 29
+f 2111 2121 1548
+f 1741 1259 1779
+f 1843 347 1834
+f 1843 1734 959
+f 1766 1769 1734
+f 957 1766 1734
+f 1766 1786 1769
+f 1766 1689 1786
+f 1689 1665 1786
+f 1754 1691 1777
+f 1507 1840 1790
+f 1761 1470 1461
+f 1523 1679 1793
+f 1091 1468 1098
+f 1820 1730 1838
+f 1843 1834 1734
+f 1808 1951 1665
+f 1588 1727 1531
+f 893 915 1832
+f 1523 1774 1679
+f 272 2488 710
+f 1093 1116 1675
+f 2340 2349 2348
+f 1832 1734 1834
+f 1832 957 1734
+f 1951 1808 2094
+f 1685 1692 1505
+f 1043 295 698
+f 2143 1779 2121
+f 1689 1808 1665
+f 1693 1714 1763
+f 1738 2157 1516
+f 1114 1921 236
+f 1268 1333 1520
+f 1149 1108 431
+f 508 2144 1912
+f 1957 1108 1537
+f 431 1108 1957
+f 1018 1108 2167
+f 1338 1957 1681
+f 2163 1957 1338
+f 1983 1390 2093
+f 30 557 37
+f 1714 2173 2172
+f 1983 1984 1390
+f 1984 2065 1390
+f 884 1762 897
+f 2065 1984 1214
+f 1950 1974 1762
+f 884 1950 1762
+f 2012 1698 1861
+f 1214 2116 803
+f 1950 1938 1974
+f 1938 1967 1974
+f 1900 1761 1461
+f 865 1929 884
+f 884 1929 1950
+f 2062 2071 2042
+f 919 1985 1732
+f 1593 502 2146
+f 1995 1213 2098
+f 1522 2476 1651
+f 2174 1849 2175
+f 1480 1989 1499
+f 1929 1938 1950
+f 1605 2058 1574
+f 2097 1605 2133
+f 1912 2014 1886
+f 2092 2082 2083
+f 206 1930 505
+f 2101 2100 2092
+f 2073 2101 2092
+f 839 1910 865
+f 1910 1901 1929
+f 865 1910 1929
+f 1967 1788 1975
+f 2073 2092 2063
+f 2101 1593 2100
+f 2015 1876 1698
+f 1853 1884 2014
+f 1831 1698 2165
+f 1316 273 81
+f 1901 1920 1929
+f 1929 1920 1938
+f 1920 1968 1967
+f 1938 1920 1967
+f 1849 2174 1700
+f 2173 1700 2174
+f 2062 2072 2091
+f 803 2467 2059
+f 2239 1736 2240
+f 1505 1357 1685
+f 1358 1686 1476
+f 1967 1968 1788
+f 1968 1969 1788
+f 2065 2110 2156
+f 2065 1214 2110
+f 2110 1214 503
+f 273 2093 1018
+f 273 1983 2093
+f 532 1886 2155
+f 2034 2021 1947
+f 216 1810 585
+f 1912 543 2014
+f 1390 2051 1537
+f 1872 1873 1910
+f 1984 2045 1214
+f 597 1912 1886
+f 1593 2146 2100
+f 2071 2062 2090
+f 2034 2046 1982
+f 2034 1947 2046
+f 1214 2045 2116
+f 1873 1887 1910
+f 1887 1901 1910
+f 1562 1447 1106
+f 2163 431 1957
+f 1948 1972 1936
+f 1972 1948 1992
+f 2014 2015 2013
+f 1853 2014 2013
+f 1550 1884 1853
+f 1947 2468 1994
+f 1355 1550 2154
+f 1355 1884 1550
+f 2081 2108 2128
+f 2024 1965 1492
+f 2024 2032 1965
+f 2116 1604 803
+f 1901 1911 1920
+f 1939 1968 1920
+f 1911 1939 1920
+f 872 1626 1666
+f 2062 2091 2120
+f 1819 1927 1759
+f 1021 1674 1780
+f 872 1673 1756
+f 1550 501 2171
+f 1378 1550 2171
+f 2146 2162 2145
+f 1358 482 192
+f 2109 2120 2119
+f 1866 1872 2227
+f 1391 2012 1860
+f 2136 2137 2161
+f 2162 1661 236
+f 1887 1894 1901
+f 1901 1894 1911
+f 505 1707 206
+f 2120 2137 2136
+f 2142 2164 1674
+f 1860 2012 1861
+f 1894 1939 1911
+f 2080 2060 2118
+f 2162 236 508
+f 2164 1815 1742
+f 1018 2093 1537
+f 2154 1378 2161
+f 2041 2098 2491
+f 2043 2042 2032
+f 1108 1018 1537
+f 1465 2094 1808
+f 502 1643 1661
+f 2467 1618 1656
+f 2119 2136 2135
+f 2119 2108 2071
+f 878 1183 1195
+f 2101 1594 1593
+f 2033 2370 2063
+f 2482 2491 2098
+f 1282 2406 1275
+f 2003 1948 1956
+f 2043 2032 2024
+f 2025 2043 2024
+f 2154 1550 1378
+f 1795 2498 1764
+f 2142 1548 2164
+f 2431 2454 2422
+f 1981 2011 1993
+f 2349 2391 2362
+f 502 2162 2146
+f 2025 2024 2360
+f 2129 2120 2091
+f 1732 1985 2007
+f 2171 1308 209
+f 1930 1995 2041
+f 1390 1238 2051
+f 1866 1878 1887
+f 1878 1894 1887
+f 1965 2032 2011
+f 874 2480 2492
+f 2071 2108 2069
+f 1358 1731 482
+f 430 2021 2034
+f 1965 2003 1964
+f 1855 1889 831
+f 1668 1773 2150
+f 1390 2156 1238
+f 898 869 1903
+f 2391 2407 2362
+f 2121 2111 2074
+f 1548 1259 2164
+f 2099 2129 2091
+f 1550 1853 501
+f 1853 1852 501
+f 952 2017 969
+f 2085 2121 2074
+f 2130 2006 1391
+f 2144 1367 543
+f 2100 2146 2099
+f 1545 1319 113
+f 1903 1922 898
+f 1922 1931 898
+f 585 2170 1641
+f 2007 2017 952
+f 2017 2074 969
+f 1558 1753 1813
+f 837 2005 1517
+f 2005 2006 2130
+f 1532 1474 1528
+f 2003 1981 1948
+f 2070 2071 2069
+f 1922 919 1931
+f 2017 2085 2074
+f 2085 2104 2121
+f 2100 2099 2082
+f 2156 2110 2034
+f 505 2474 2004
+f 1903 871 1922
+f 1922 1952 919
+f 919 1952 1985
+f 1985 2001 2007
+f 2001 2036 2017
+f 2007 2001 2017
+f 2017 2036 2085
+f 2036 2047 2085
+f 2047 2075 2085
+f 2075 2104 2085
+f 1948 1993 2023
+f 2400 2422 2407
+f 2011 2070 1993
+f 2033 2043 2025
+f 2012 2015 1698
+f 1876 1338 2165
+f 871 1940 1922
+f 1985 1976 2001
+f 2121 2104 2143
+f 1051 1413 1456
+f 2358 1362 2379
+f 1859 1789 1870
+f 2090 2109 2071
+f 1405 1398 1885
+f 1886 1884 1355
+f 1922 1960 1952
+f 1952 1960 1985
+f 1960 1976 1985
+f 1956 1948 1936
+f 2135 209 2128
+f 2157 1875 1212
+f 2160 2168 2169
+f 1900 1461 1918
+f 2001 2018 2036
+f 2075 2086 2104
+f 2111 2142 2103
+f 1937 1956 1936
+f 2023 2070 2061
+f 2135 2128 2108
+f 2042 2071 2011
+f 2138 413 2383
+f 2033 2072 2043
+f 1922 1940 1960
+f 2070 2069 2061
+f 2069 2108 2061
+f 2108 2119 2135
+f 1855 1904 1889
+f 1889 1904 871
+f 871 1904 1940
+f 1976 2018 2001
+f 2036 2018 2047
+f 2122 2143 2104
+f 216 1642 489
+f 2148 984 2143
+f 1975 1974 1967
+f 2157 1683 1516
+f 1614 1593 1594
+f 2269 2270 2276
+f 1926 2147 29
+f 2082 2091 2072
+f 430 503 2059
+f 1904 1905 1940
+f 1940 1961 1960
+f 1961 1976 1960
+f 2087 2086 2075
+f 2065 2156 1390
+f 1820 1838 1900
+f 534 1308 837
+f 2167 273 1018
+f 831 1850 1855
+f 2019 2037 2018
+f 2018 2037 2047
+f 2037 2075 2047
+f 2086 2095 2104
+f 2095 2122 2104
+f 2122 2148 2143
+f 1926 1213 1995
+f 1405 1885 1761
+f 2006 2013 2012
+f 2211 2233 2216
+f 1855 1890 1904
+f 1904 1895 1905
+f 1905 1932 1940
+f 1961 1977 1976
+f 1976 1986 2018
+f 2484 2476 1518
+f 1870 1411 1859
+f 1548 2142 2111
+f 1904 1890 1895
+f 1895 1932 1905
+f 1940 1932 1961
+f 1976 1977 1986
+f 1986 2008 2018
+f 2018 2008 2019
+f 2087 2075 2037
+f 2087 2095 2086
+f 2094 1391 1860
+f 1852 1853 2006
+f 1853 2013 2006
+f 929 979 850
+f 1855 1874 1890
+f 2008 2028 2019
+f 1993 2070 2023
+f 1705 1799 1998
+f 1491 2147 206
+f 1851 1856 1855
+f 1895 1890 1874
+f 2038 2019 2028
+f 2038 2048 2037
+f 2019 2038 2037
+f 2048 2067 2087
+f 2037 2048 2087
+f 2087 2067 2095
+f 2095 2149 2122
+f 2149 2148 2122
+f 1308 2005 837
+f 209 1308 1387
+f 1601 2102 1927
+f 254 170 201
+f 1800 1403 1763
+f 1510 1346 1740
+f 870 871 1903
+f 1919 1650 1619
+f 2148 1667 1753
+f 1932 1923 1961
+f 1977 1953 1986
+f 2067 2112 2095
+f 2112 2149 2095
+f 2148 2149 1667
+f 2422 2421 2407
+f 1926 2026 1213
+f 1912 2144 543
+f 2128 1387 2153
+f 1733 1510 1740
+f 990 853 2489
+f 503 1214 803
+f 1921 431 2163
+f 2146 2145 2129
+f 2144 1921 2163
+f 1855 1856 1874
+f 1895 1923 1932
+f 1923 1941 1961
+f 1961 1941 1977
+f 2048 2076 2067
+f 2076 2113 2067
+f 2067 2113 2112
+f 1723 1900 1937
+f 1870 1900 1723
+f 1367 2163 1338
+f 520 1346 1510
+f 1698 1831 1861
+f 1984 1919 2045
+f 1895 1891 1923
+f 2008 1986 2028
+f 1948 1981 1993
+f 1883 1346 520
+f 1883 1814 1346
+f 1930 206 2147
+f 2499 2486 1447
+f 1891 1906 1923
+f 1923 1953 1941
+f 1953 1977 1941
+f 1953 1987 1986
+f 2113 2123 2112
+f 2123 2149 2112
+f 1387 1308 1226
+f 1599 1688 1346
+f 2093 1390 1537
+f 2003 2011 1981
+f 1987 2028 1986
+f 2038 2049 2048
+f 2048 2049 2076
+f 1813 1667 2149
+f 2123 1813 2149
+f 1461 1469 1964
+f 1757 1510 1743
+f 505 1930 1999
+f 2223 1784 1789
+f 1532 1522 1651
+f 1906 1913 1923
+f 1913 1943 1923
+f 1943 1942 1923
+f 1923 1942 1953
+f 1942 1987 1953
+f 1308 1852 2005
+f 2053 1814 1883
+f 1733 1740 1541
+f 2154 1886 1355
+f 1503 1528 1474
+f 1874 1879 1895
+f 1895 1879 1891
+f 2076 2124 2113
+f 2113 2124 2123
+f 1896 1891 1879
+f 1891 1896 1906
+f 1942 1962 1987
+f 1962 2009 2028
+f 1987 1962 2028
+f 2009 2038 2028
+f 2109 2119 2071
+f 1918 1956 1937
+f 1851 1864 1856
+f 1896 1897 1906
+f 1906 1897 1913
+f 1943 1962 1942
+f 2049 2077 2076
+f 2124 2125 2123
+f 1930 2147 1926
+f 1902 1894 1878
+f 482 1510 1757
+f 2129 2137 2120
+f 503 803 2059
+f 1847 1857 1851
+f 1851 1857 1864
+f 2039 2038 2009
+f 2038 2039 2049
+f 2076 2077 2124
+f 2150 1813 2123
+f 482 520 1510
+f 1994 1821 2046
+f 2044 2004 1764
+f 1864 1867 1856
+f 1867 1874 1856
+f 1897 1944 1913
+f 1943 1944 1962
+f 2124 2126 2125
+f 2150 2123 2125
+f 2099 2146 2129
+f 2041 1995 2098
+f 1605 1641 2151
+f 1847 1959 1857
+f 1874 1867 1879
+f 1913 1944 1943
+f 1944 1963 1962
+f 2077 2096 2124
+f 2096 2126 2124
+f 2126 2150 2125
+f 941 1650 1919
+f 2135 2136 209
+f 1884 1886 2014
+f 2049 2029 2077
+f 1388 2127 1389
+f 1389 2127 1566
+f 1930 1926 1995
+f 941 1919 1316
+f 2110 503 430
+f 1867 1880 1879
+f 1879 1880 1896
+f 1897 1907 1944
+f 1963 1978 1962
+f 1962 1978 2009
+f 2039 2029 2049
+f 2077 2078 2096
+f 822 823 827
+f 2166 1668 2150
+f 81 941 1316
+f 2204 2216 2203
+f 2011 2071 2070
+f 1880 1892 1896
+f 1892 1907 1897
+f 1896 1892 1897
+f 1907 1914 1944
+f 1978 2010 2009
+f 2010 2039 2009
+f 1688 1740 1346
+f 1789 1820 1870
+f 2130 1391 2094
+f 1944 1945 1963
+f 2029 2078 2077
+f 1767 2150 2126
+f 1767 2166 2150
+f 803 2022 2467
+f 1503 1927 2102
+f 1914 1954 1944
+f 1944 1954 1945
+f 1963 1970 1978
+f 2078 2105 2096
+f 2105 2126 2096
+f 1965 2011 2003
+f 192 1626 1358
+f 2101 1559 1594
+f 1930 2041 1999
+f 1698 1876 2165
+f 1398 1871 891
+f 2165 1338 1681
+f 1970 2010 1978
+f 2010 2030 2029
+f 2039 2010 2029
+f 2030 2055 2078
+f 2029 2030 2078
+f 1849 1848 2175
+f 1871 1862 891
+f 543 2015 2014
+f 1857 1858 1864
+f 1864 1858 1867
+f 1963 1945 1970
+f 2055 2088 2078
+f 2078 2088 2105
+f 2105 2131 2126
+f 2126 2131 1767
+f 2063 2083 2033
+f 2161 2171 209
+f 2032 2042 2011
+f 1813 2150 1773
+f 1914 1908 1954
+f 1970 1979 2010
+f 2088 2131 2105
+f 2015 543 1876
+f 1694 1692 1048
+f 1395 2207 1859
+f 1395 1393 2207
+f 1730 1784 1736
+f 2500 2466 2470
+f 1709 1701 1757
+f 1945 1979 1970
+f 2030 2050 2055
+f 2350 2317 2286
+f 2154 2155 1886
+f 871 860 1889
+f 2161 209 2136
+f 2497 2463 2493
+f 2190 2204 2203
+f 1800 2179 1404
+f 2477 2469 1385
+f 1385 1715 2477
+f 2128 209 1387
+f 1858 1868 1867
+f 1867 1881 1880
+f 1893 1892 1880
+f 1881 1893 1880
+f 1893 1907 1892
+f 1907 1908 1914
+f 1954 1979 1945
+f 1979 1980 2010
+f 2131 2159 1767
+f 1765 93 339
+f 1761 1877 1405
+f 523 1347 515
+f 1541 2157 1738
+f 2144 2163 1367
+f 1380 1389 1566
+f 2317 2392 2316
+f 1994 2498 1801
+f 1867 1868 1881
+f 1980 2050 2030
+f 2010 1980 2030
+f 2050 2089 2055
+f 2055 2089 2088
+f 2088 2114 2131
+f 1538 1651 1659
+f 2145 2155 2129
+f 2140 29 1928
+f 2370 2033 2025
+f 2252 2239 2240
+f 2239 2252 1862
+f 2392 2391 2316
+f 2469 2501 1385
+f 2477 1715 1710
+f 502 1614 1643
+f 2438 1227 2431
+f 1915 1907 1893
+f 1915 1908 1907
+f 1954 1908 1979
+f 1908 1988 1979
+f 1979 1988 1980
+f 2114 2159 2131
+f 2155 2154 2129
+f 508 1966 2144
+f 872 1756 1626
+f 1710 1715 1505
+f 236 1966 508
+f 2272 2284 1398
+f 2325 2355 2319
+f 1548 2121 1779
+f 1532 1528 1522
+f 1980 2056 2050
+f 2050 2056 2089
+f 2013 2015 2012
+f 1964 2003 1956
+f 2006 2012 1391
+f 1565 1927 1503
+f 2244 2243 2226
+f 5 1715 1385
+f 1858 1848 1868
+f 1915 1946 1908
+f 1946 1988 1908
+f 1980 2020 2056
+f 2115 2159 2114
+f 2092 2083 2063
+f 1398 2284 1687
+f 2162 2155 2145
+f 519 2475 2488
+f 2158 5 1385
+f 5 1505 1715
+f 1692 1694 1505
+f 1988 2020 1980
+f 2115 2169 2159
+f 2169 2168 2159
+f 2083 2082 2072
+f 1316 1984 1983
+f 1488 1619 1650
+f 2083 2072 2033
+f 2361 1210 1233
+f 1933 1946 1915
+f 2056 2079 2089
+f 2088 2115 2114
+f 2099 2091 2082
+f 2162 532 2155
+f 1852 2006 2005
+f 2023 2061 2052
+f 2176 2184 2175
+f 2162 985 532
+f 1909 1893 1881
+f 1909 1915 1893
+f 1988 2040 2020
+f 2040 2056 2020
+f 2089 2079 2088
+f 2088 2079 2115
+f 1782 1444 1882
+f 1216 1215 2320
+f 867 1939 1894
+f 867 903 1939
+f 1372 2398 1379
+f 1863 504 2027
+f 2158 1385 504
+f 1868 1782 1881
+f 1909 1933 1915
+f 2040 1988 1946
+f 1481 2024 1492
+f 2120 2136 2119
+f 1522 1528 1518
+f 1871 1398 1405
+f 1221 1408 1399
+f 1357 5 2158
+f 2179 1800 1763
+f 1868 1865 1782
+f 1882 1881 1782
+f 1882 1909 1881
+f 2040 2057 2056
+f 2106 2079 2056
+f 2057 2106 2056
+f 2106 2132 2079
+f 2132 2115 2079
+f 2115 2132 2169
+f 532 985 597
+f 2092 2100 2082
+f 1210 1221 1399
+f 1399 1233 1210
+f 2130 2002 1517
+f 1849 1865 1868
+f 1933 2040 1946
+f 52 1269 30
+f 1667 1813 1753
+f 1997 1380 1673
+f 940 1008 1088
+f 1947 1994 2046
+f 1882 1916 1909
+f 1924 1933 1909
+f 1533 2040 1933
+f 1533 1534 2040
+f 2058 2040 1534
+f 2058 2057 2040
+f 1238 191 1768
+f 1997 1389 1380
+f 1875 1541 1554
+f 1854 504 1863
+f 1854 2158 504
+f 2396 1275 2406
+f 2426 2443 153
+f 1916 1924 1909
+f 1925 1935 1934
+f 1870 1723 1426
+f 2058 2097 2057
+f 2097 2106 2057
+f 2132 2151 2169
+f 2151 2160 2169
+f 1106 1635 1562
+f 1957 1768 1681
+f 1957 2051 1768
+f 526 535 33
+f 1614 1594 1609
+f 2233 2229 2216
+f 2496 2027 2084
+f 2496 1863 2027
+f 2117 1854 1863
+f 2016 2158 1854
+f 2016 1504 1357
+f 2158 2016 1357
+f 1114 236 1661
+f 2129 2154 2137
+f 2133 2106 2097
+f 2491 1999 2041
+f 2051 1238 1768
+f 2061 2108 2081
+f 2189 2195 2186
+f 2348 2349 2362
+f 1701 192 482
+f 505 1737 1707
+f 2133 2132 2106
+f 2132 2133 2151
+f 2151 2170 2160
+f 502 1661 2162
+f 1998 1389 1997
+f 2297 2352 2329
+f 2352 2364 2329
+f 2394 2414 2364
+f 2352 2394 2364
+f 2402 512 2415
+f 2255 2254 2243
+f 2446 1365 2456
+f 2271 2282 2298
+f 846 2283 2264
+f 2293 2310 2318
+f 2254 2295 2294
+f 2283 2290 2278
+f 2270 2294 2293
+f 2423 2455 2400
+f 2281 2287 2267
+f 2190 2191 2204
+f 2271 2263 2282
+f 2334 2329 2364
+f 2424 2432 2409
+f 2282 2263 2298
+f 1409 1659 1958
+f 2263 2302 2298
+f 2297 2329 2296
+f 1256 446 2346
+f 1958 2502 2478
+f 2437 2399 2444
+f 263 2366 2359
+f 849 827 823
+f 2311 2325 2290
+f 2499 2379 2434
+f 2446 2456 2423
+f 947 2358 2379
+f 2499 947 2379
+f 2205 2195 2212
+f 2245 2237 2227
+f 2245 2256 2237
+f 2256 2263 2271
+f 556 571 2305
+f 1528 2068 1518
+f 2424 2439 2432
+f 2302 2352 2297
+f 1866 2237 826
+f 2248 2242 2211
+f 2334 2364 2363
+f 2235 2244 2226
+f 2255 2295 2254
+f 2329 2324 2296
+f 2439 2447 1973
+f 2329 2334 2324
+f 2409 2432 2414
+f 2293 2318 2276
+f 866 2425 2416
+f 1487 1493 2372
+f 2237 2231 2230
+f 2415 512 17
+f 2035 1236 26
+f 921 2138 688
+f 2491 2482 2462
+f 6 181 197
+f 2481 948 1795
+f 2138 2383 2382
+f 2377 2394 2352
+f 2377 506 2394
+f 2394 506 2402
+f 2401 2402 2415
+f 2394 2402 2401
+f 2318 2326 2276
+f 2439 2457 2432
+f 2298 2302 2297
+f 2244 2249 2243
+f 2404 1100 2382
+f 2238 2245 2227
+f 2245 2257 2256
+f 2257 2263 2256
+f 2324 2334 2328
+f 2257 2289 2263
+f 2289 2302 2263
+f 2236 2231 2250
+f 2138 2382 688
+f 2383 2404 2382
+f 1100 2404 2343
+f 2353 2352 2302
+f 2353 2377 2352
+f 2237 2230 2220
+f 2335 2355 2325
+f 2308 2340 2315
+f 2253 2269 2276
+f 2311 2335 2325
+f 2439 2424 511
+f 2268 2267 2248
+f 2383 413 2404
+f 123 971 832
+f 2234 2243 2269
+f 2225 2213 2234
+f 2219 2213 2225
+f 2195 2196 2212
+f 1544 2418 1549
+f 413 866 2404
+f 2404 866 2416
+f 2416 2417 2404
+f 2404 2417 2343
+f 2415 2409 2401
+f 2196 2219 2212
+f 2268 2248 2218
+f 2206 2214 2197
+f 2417 2332 2343
+f 2343 2332 832
+f 2330 2302 2289
+f 2330 2353 2302
+f 2453 2454 515
+f 2218 2248 2217
+f 2218 2217 2205
+f 2276 2281 2268
+f 2178 2197 2177
+f 2197 2189 2177
+f 2332 2066 832
+f 832 2066 123
+f 2231 2236 2230
+f 669 950 1144
+f 2217 2211 2199
+f 1216 1209 1217
+f 2066 2365 123
+f 2230 2226 2214
+f 2290 2325 2304
+f 2325 2319 2304
+f 2217 2248 2211
+f 2191 2192 2199
+f 510 525 2035
+f 2417 1917 2332
+f 2332 1917 2066
+f 2408 2413 2341
+f 2248 2267 2242
+f 2326 2333 2281
+f 1340 2365 2066
+f 2440 1302 1340
+f 2226 2230 2235
+f 1153 1163 1116
+f 2431 2455 2438
+f 2416 2425 2417
+f 2495 2474 2462
+f 2290 2304 2277
+f 825 2227 1872
+f 151 239 1038
+f 9 151 1038
+f 545 928 2381
+f 2440 2406 1384
+f 928 1596 2381
+f 2186 2188 2185
+f 2456 26 1888
+f 2287 2333 2262
+f 2425 2342 2417
+f 2342 1917 2417
+f 1917 877 2066
+f 2336 1340 2066
+f 2336 2440 1340
+f 2328 2351 2327
+f 825 2238 2227
+f 2351 2368 2327
+f 1222 2388 1211
+f 678 756 734
+f 428 263 1343
+f 2188 2191 2190
+f 2341 2376 2333
+f 2066 877 2336
+f 2290 2277 2278
+f 739 634 592
+f 675 304 14
+f 2384 675 14
+f 2199 2211 2204
+f 2191 2199 2204
+f 2322 2318 2310
+f 2287 2262 2233
+f 2185 2188 2184
+f 2386 2425 845
+f 2384 572 675
+f 1128 123 2365
+f 832 971 2343
+f 2188 2186 2191
+f 2185 2184 2176
+f 2345 1917 2342
+f 2345 877 1917
+f 2336 2406 2440
+f 971 1100 2343
+f 2299 2289 2257
+f 2299 2303 2289
+f 2249 2255 2243
+f 506 513 512
+f 2437 955 1219
+f 1587 2398 1324
+f 877 2396 2336
+f 2336 2396 2406
+f 2463 2479 879
+f 2376 2412 2350
+f 2281 2267 2268
+f 2303 2330 2289
+f 624 635 159
+f 1996 2356 1561
+f 2449 2436 1996
+f 2356 2054 2451
+f 928 2398 1587
+f 2333 2350 2262
+f 2035 26 2456
+f 2346 2342 2425
+f 2346 2345 2342
+f 1544 2380 2418
+f 2412 2392 2350
+f 622 509 1151
+f 2436 2054 1996
+f 545 2451 928
+f 2326 2341 2333
+f 2346 2425 2386
+f 1365 2035 2456
+f 2369 2377 2353
+f 2369 506 2377
+f 2451 900 928
+f 900 2398 928
+f 1235 1888 1244
+f 2337 2345 2346
+f 877 772 2396
+f 772 1275 2396
+f 2432 2446 2414
+f 2294 2295 2310
+f 2369 2330 828
+f 2418 2419 2436
+f 2450 2429 2436
+f 2436 2429 2054
+f 2490 2494 1656
+f 1321 155 2338
+f 1256 2346 2386
+f 2448 877 2345
+f 877 2448 772
+f 2446 2423 2414
+f 2351 2334 2363
+f 2243 2254 2269
+f 2380 2419 2418
+f 2419 2450 2436
+f 2283 2278 2264
+f 822 2197 823
+f 1008 1759 1565
+f 2448 2345 2337
+f 2270 2293 2276
+f 2323 2324 2328
+f 2429 1012 2054
+f 2226 2243 2213
+f 2395 325 772
+f 2370 2367 2380
+f 2054 2435 2451
+f 2435 2397 2451
+f 2451 2397 900
+f 1774 1974 1975
+f 2305 2290 2283
+f 846 2305 2283
+f 2320 1215 2285
+f 2139 2448 2337
+f 2448 2395 772
+f 1232 1231 1216
+f 2272 2285 2284
+f 2367 2371 2380
+f 2371 2405 2380
+f 2380 2405 2419
+f 2419 2429 2450
+f 2429 176 1012
+f 2397 2373 900
+f 2373 2398 900
+f 2373 1379 2398
+f 2372 1500 1508
+f 1133 1303 1142
+f 2252 2273 2272
+f 891 2252 2272
+f 2419 2405 2429
+f 2405 2430 2429
+f 2429 2430 176
+f 2189 2186 2181
+f 2212 2219 2218
+f 2312 2139 2337
+f 2139 2384 2448
+f 2448 2384 2395
+f 899 855 843
+f 2272 2273 2285
+f 2331 2303 2299
+f 176 2435 2054
+f 1012 176 2054
+f 2177 2185 2176
+f 2218 2219 2225
+f 1216 1220 1215
+f 2378 2139 2312
+f 2384 14 2395
+f 2324 2295 2255
+f 2240 2273 2252
+f 2371 2387 2405
+f 2410 2430 2405
+f 2430 2442 176
+f 2435 2344 2397
+f 2397 2344 2373
+f 2456 1888 2455
+f 2242 2267 2233
+f 2233 2262 2229
+f 2378 2384 2139
+f 2323 2310 2295
+f 2323 2322 2310
+f 2240 2274 2273
+f 974 841 990
+f 2490 1447 2486
+f 2387 2410 2405
+f 2442 2141 176
+f 2344 1778 2373
+f 972 1379 2373
+f 1778 972 2373
+f 1379 972 428
+f 1211 2437 1223
+f 1228 1215 1220
+f 702 2378 2312
+f 17 518 2415
+f 1888 26 1244
+f 2324 2323 2295
+f 2305 2311 2290
+f 2307 2285 2273
+f 2274 2307 2273
+f 2307 2320 2285
+f 2369 531 506
+f 2435 2258 2344
+f 2296 2324 2288
+f 1233 1217 2361
+f 2360 2371 2367
+f 2410 2442 2430
+f 176 2141 2258
+f 176 2258 2435
+f 539 2331 66
+f 2350 2392 2317
+f 2268 2225 2253
+f 1508 1500 2371
+f 2360 1508 2371
+f 2371 1500 2387
+f 972 2366 428
+f 1626 1686 1358
+f 1759 1807 1819
+f 2277 2257 2245
+f 2277 2299 2257
+f 1784 2228 1736
+f 2265 2240 1736
+f 2228 2265 1736
+f 2265 2274 2240
+f 1209 2320 2307
+f 2320 1209 1216
+f 1555 1584 1560
+f 2387 1500 2372
+f 2410 2420 2442
+f 2433 972 1778
+f 2433 2366 972
+f 955 522 1225
+f 2339 2307 2274
+f 2372 1493 2387
+f 2411 2420 2410
+f 2420 954 2442
+f 2442 954 2141
+f 2344 2433 1778
+f 2205 2212 2218
+f 2328 2334 2351
+f 2394 2401 2414
+f 2250 2256 2271
+f 2339 1209 2307
+f 2328 2322 2323
+f 866 845 2425
+f 3 316 893
+f 2387 2411 2410
+f 2441 2141 954
+f 2141 2441 2258
+f 2354 2433 2344
+f 2254 2294 2270
+f 2269 2254 2270
+f 863 2305 846
+f 2441 2354 2258
+f 2258 2354 2344
+f 2319 2355 51
+f 2223 2228 1784
+f 1493 2411 2387
+f 1560 2449 1555
+f 2288 2324 2255
+f 825 2251 2238
+f 2251 2245 2238
+f 1299 84 1312
+f 2246 2265 2228
+f 2313 2274 2265
+f 2313 2339 2274
+f 2251 2277 2245
+f 2319 51 2331
+f 891 1862 2252
+f 2443 954 2420
+f 2443 2441 954
+f 511 2447 2439
+f 2242 2233 2211
+f 188 15 814
+f 2443 2426 2441
+f 2426 2354 2441
+f 2306 2403 2433
+f 2433 2403 2366
+f 539 2303 2331
+f 2246 2228 2223
+f 1030 1819 1807
+f 2354 2306 2433
+f 2413 2412 2376
+f 2438 2455 1888
+f 1848 1857 2176
+f 2207 2208 2223
+f 2208 2246 2223
+f 1209 2339 1217
+f 2339 2361 1217
+f 1221 1210 2388
+f 554 109 78
+f 386 1375 95
+f 2327 2326 2318
+f 2179 2182 1393
+f 2182 2208 1393
+f 1393 2208 2207
+f 2361 2399 2388
+f 2388 2399 1211
+f 2306 2354 2426
+f 2403 2359 2366
+f 2214 2226 2213
+f 2268 2253 2276
+f 889 2200 2179
+f 2200 2182 2179
+f 2200 2221 2182
+f 2221 2208 2182
+f 2314 2265 2246
+f 2314 2313 2265
+f 2339 2374 2361
+f 2478 2434 2379
+f 2205 2217 2199
+f 2208 2259 2246
+f 2259 2275 2246
+f 2314 2321 2313
+f 2313 2347 2339
+f 2347 2374 2339
+f 2374 2399 2361
+f 153 154 2426
+f 154 2306 2426
+f 2385 2359 2403
+f 2221 2259 2208
+f 2306 2357 2403
+f 2357 2385 2403
+f 2237 2256 2231
+f 2172 2180 889
+f 2180 2200 889
+f 2200 2201 2221
+f 2246 2291 2314
+f 2374 2444 2399
+f 571 555 2311
+f 2192 2205 2199
+f 2173 2180 2172
+f 2279 2246 2275
+f 2279 2291 2246
+f 2292 2314 2291
+f 2321 2362 2313
+f 2362 2347 2313
+f 2347 2389 2374
+f 2444 955 2437
+f 2292 2291 2279
+f 2452 2444 2374
+f 2054 2356 1996
+f 2338 2306 154
+f 2186 2192 2191
+f 2193 2201 2200
+f 2259 2221 2201
+f 2247 2259 2201
+f 2452 955 2444
+f 2278 2277 2251
+f 2338 2357 2306
+f 2181 2186 2185
+f 2276 2326 2281
+f 2432 2457 2446
+f 2198 2201 2193
+f 2198 2232 2201
+f 2232 2247 2201
+f 2389 2452 2374
+f 2452 1630 955
+f 1403 1749 1444
+f 1555 1996 1561
+f 2357 2427 2385
+f 2385 2428 230
+f 2409 2415 2424
+f 2304 2331 2299
+f 2193 2200 2180
+f 2445 2452 2389
+f 1565 1759 1927
+f 2380 1544 2370
+f 2338 2427 2357
+f 2427 2428 2385
+f 230 222 253
+f 2202 2198 2193
+f 2202 2209 2198
+f 2209 2241 2198
+f 2241 2232 2198
+f 2266 2275 2259
+f 2365 1340 1128
+f 2415 518 2424
+f 2338 170 2427
+f 170 2428 2427
+f 2181 2185 2177
+f 2196 2195 2189
+f 2183 2193 2180
+f 2453 1630 2452
+f 2197 2214 2189
+f 2401 2409 2414
+f 822 2220 2197
+f 1210 2361 2388
+f 2187 2193 2183
+f 2187 2202 2193
+f 2266 2279 2275
+f 2279 2300 2292
+f 2375 2347 2362
+f 2375 2390 2347
+f 2390 2389 2347
+f 2453 2452 2445
+f 1347 1630 2453
+f 1630 1347 522
+f 2220 2206 2197
+f 2262 2350 2286
+f 170 254 2428
+f 2457 1973 2446
+f 1973 1365 2446
+f 2174 2183 2180
+f 2194 2202 2187
+f 2222 2241 2209
+f 2222 2260 2241
+f 2266 2259 2247
+f 2390 2445 2389
+f 2264 2251 825
+f 2363 2368 2351
+f 2326 2393 2341
+f 1855 1850 1851
+f 2210 2209 2202
+f 2210 2222 2209
+f 2261 2260 2222
+f 2280 2279 2266
+f 2280 2300 2279
+f 251 263 2359
+f 2277 2304 2299
+f 2220 2230 2206
+f 2202 2194 2210
+f 2213 2243 2234
+f 2328 2327 2322
+f 2294 2310 2293
+f 2214 2196 2189
+f 2196 2213 2219
+f 2224 2222 2210
+f 2421 2390 2375
+f 2206 2230 2214
+f 2194 2203 2210
+f 2224 2261 2222
+f 2421 2445 2390
+f 2322 2327 2318
+f 2393 2408 2341
+f 1365 1973 510
+f 2216 2210 2203
+f 2216 2224 2210
+f 2266 2308 2280
+f 2280 2308 2300
+f 2407 2421 2375
+f 2175 2183 2174
+f 2194 2190 2203
+f 2454 2445 2421
+f 522 1347 523
+f 2456 2455 2423
+f 823 2197 2178
+f 2281 2333 2287
+f 2188 2187 2183
+f 2188 2190 2194
+f 2187 2188 2194
+f 2308 2315 2300
+f 2407 2375 2362
+f 2443 2420 2503
+f 2420 2411 2503
+f 2411 1493 2503
+f 1493 1487 2503
+f 1487 1318 2503
+f 1318 1320 2503
+f 1320 2443 2503
diff --git a/gradio/media_assets/models3d/Duck.glb b/gradio/media_assets/models3d/Duck.glb
new file mode 100644
index 0000000..217170d
Binary files /dev/null and b/gradio/media_assets/models3d/Duck.glb differ
diff --git a/gradio/media_assets/models3d/Fox.gltf b/gradio/media_assets/models3d/Fox.gltf
new file mode 100644
index 0000000..ff3115c
--- /dev/null
+++ b/gradio/media_assets/models3d/Fox.gltf
@@ -0,0 +1,1777 @@
+{
+ "asset": {
+ "copyright": "CC-BY 4.0 Model by PixelMannen https://opengameart.org/content/fox-and-shiba and @tomkranis https://sketchfab.com/3d-models/low-poly-fox-by-pixelmannen-animated-371dea88d7e04a76af5763f2a36866bc and @AsoboStudio with @scurest https://github.com/KhronosGroup/glTF-Sample-Models/pull/150#issuecomment-406300118",
+ "version": "2.0"
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "componentType": 5126,
+ "count": 1728,
+ "type": "VEC3",
+ "byteOffset": 0,
+ "min": [
+ -12.592718124389648,
+ -0.12174476683139801,
+ -88.09500122070312
+ ],
+ "max": [
+ 12.592718124389648,
+ 78.90718841552734,
+ 66.62486267089844
+ ]
+ },
+ {
+ "bufferView": 1,
+ "componentType": 5126,
+ "count": 1728,
+ "type": "VEC2",
+ "byteOffset": 0
+ },
+ {
+ "bufferView": 1,
+ "componentType": 5123,
+ "count": 1728,
+ "type": "VEC4",
+ "byteOffset": 13824
+ },
+ {
+ "bufferView": 2,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 1728,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 3,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 24,
+ "type": "MAT4"
+ },
+ {
+ "bufferView": 4,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 83,
+ "type": "SCALAR",
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 3.4166667461395264
+ ]
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 1328,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 2656,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 3984,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 5312,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 6640,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 7968,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 9296,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 10624,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 11952,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 13280,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 14608,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 15936,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 17264,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 18592,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 19920,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 21248,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 22576,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 23904,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 6,
+ "byteOffset": 0,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 25232,
+ "componentType": 5126,
+ "count": 83,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 4,
+ "byteOffset": 332,
+ "componentType": 5126,
+ "count": 18,
+ "type": "SCALAR",
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 0.7083333134651184
+ ]
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 26560,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 26848,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 27136,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 27424,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 27712,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 28000,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 28288,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 28576,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 28864,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 29152,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 29440,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 29728,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 30016,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 30304,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 30592,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 30880,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 31168,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 31456,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 31744,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 6,
+ "byteOffset": 996,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 32032,
+ "componentType": 5126,
+ "count": 18,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 4,
+ "byteOffset": 404,
+ "componentType": 5126,
+ "count": 25,
+ "type": "SCALAR",
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.1583333015441895
+ ]
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 32320,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 32720,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 33120,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 33520,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 33920,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 34320,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 34720,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 35120,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 35520,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 35920,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 36320,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 36720,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 37120,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 37520,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 37920,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 38320,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 38720,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 39120,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 39520,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ },
+ {
+ "bufferView": 6,
+ "byteOffset": 1212,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC3"
+ },
+ {
+ "bufferView": 5,
+ "byteOffset": 39920,
+ "componentType": 5126,
+ "count": 25,
+ "type": "VEC4"
+ }
+ ],
+ "animations": [
+ {
+ "channels": [
+ {
+ "sampler": 0,
+ "target": {
+ "node": 8,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 1,
+ "target": {
+ "node": 7,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 2,
+ "target": {
+ "node": 11,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 3,
+ "target": {
+ "node": 10,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 4,
+ "target": {
+ "node": 9,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 5,
+ "target": {
+ "node": 14,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 6,
+ "target": {
+ "node": 13,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 7,
+ "target": {
+ "node": 12,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 8,
+ "target": {
+ "node": 6,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 9,
+ "target": {
+ "node": 5,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 10,
+ "target": {
+ "node": 17,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 11,
+ "target": {
+ "node": 16,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 12,
+ "target": {
+ "node": 15,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 13,
+ "target": {
+ "node": 20,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 14,
+ "target": {
+ "node": 19,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 15,
+ "target": {
+ "node": 18,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 16,
+ "target": {
+ "node": 24,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 17,
+ "target": {
+ "node": 23,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 18,
+ "target": {
+ "node": 22,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 19,
+ "target": {
+ "node": 4,
+ "path": "translation"
+ }
+ },
+ {
+ "sampler": 20,
+ "target": {
+ "node": 4,
+ "path": "rotation"
+ }
+ }
+ ],
+ "samplers": [
+ {
+ "input": 5,
+ "output": 6
+ },
+ {
+ "input": 5,
+ "output": 7
+ },
+ {
+ "input": 5,
+ "output": 8
+ },
+ {
+ "input": 5,
+ "output": 9
+ },
+ {
+ "input": 5,
+ "output": 10
+ },
+ {
+ "input": 5,
+ "output": 11
+ },
+ {
+ "input": 5,
+ "output": 12
+ },
+ {
+ "input": 5,
+ "output": 13
+ },
+ {
+ "input": 5,
+ "output": 14
+ },
+ {
+ "input": 5,
+ "output": 15
+ },
+ {
+ "input": 5,
+ "output": 16
+ },
+ {
+ "input": 5,
+ "output": 17
+ },
+ {
+ "input": 5,
+ "output": 18
+ },
+ {
+ "input": 5,
+ "output": 19
+ },
+ {
+ "input": 5,
+ "output": 20
+ },
+ {
+ "input": 5,
+ "output": 21
+ },
+ {
+ "input": 5,
+ "output": 22
+ },
+ {
+ "input": 5,
+ "output": 23
+ },
+ {
+ "input": 5,
+ "output": 24
+ },
+ {
+ "input": 5,
+ "output": 25
+ },
+ {
+ "input": 5,
+ "output": 26
+ }
+ ],
+ "name": "Survey"
+ },
+ {
+ "channels": [
+ {
+ "sampler": 0,
+ "target": {
+ "node": 8,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 1,
+ "target": {
+ "node": 7,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 2,
+ "target": {
+ "node": 11,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 3,
+ "target": {
+ "node": 10,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 4,
+ "target": {
+ "node": 9,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 5,
+ "target": {
+ "node": 14,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 6,
+ "target": {
+ "node": 13,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 7,
+ "target": {
+ "node": 12,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 8,
+ "target": {
+ "node": 6,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 9,
+ "target": {
+ "node": 5,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 10,
+ "target": {
+ "node": 17,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 11,
+ "target": {
+ "node": 16,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 12,
+ "target": {
+ "node": 15,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 13,
+ "target": {
+ "node": 20,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 14,
+ "target": {
+ "node": 19,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 15,
+ "target": {
+ "node": 18,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 16,
+ "target": {
+ "node": 24,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 17,
+ "target": {
+ "node": 23,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 18,
+ "target": {
+ "node": 22,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 19,
+ "target": {
+ "node": 4,
+ "path": "translation"
+ }
+ },
+ {
+ "sampler": 20,
+ "target": {
+ "node": 4,
+ "path": "rotation"
+ }
+ }
+ ],
+ "samplers": [
+ {
+ "input": 27,
+ "output": 28
+ },
+ {
+ "input": 27,
+ "output": 29
+ },
+ {
+ "input": 27,
+ "output": 30
+ },
+ {
+ "input": 27,
+ "output": 31
+ },
+ {
+ "input": 27,
+ "output": 32
+ },
+ {
+ "input": 27,
+ "output": 33
+ },
+ {
+ "input": 27,
+ "output": 34
+ },
+ {
+ "input": 27,
+ "output": 35
+ },
+ {
+ "input": 27,
+ "output": 36
+ },
+ {
+ "input": 27,
+ "output": 37
+ },
+ {
+ "input": 27,
+ "output": 38
+ },
+ {
+ "input": 27,
+ "output": 39
+ },
+ {
+ "input": 27,
+ "output": 40
+ },
+ {
+ "input": 27,
+ "output": 41
+ },
+ {
+ "input": 27,
+ "output": 42
+ },
+ {
+ "input": 27,
+ "output": 43
+ },
+ {
+ "input": 27,
+ "output": 44
+ },
+ {
+ "input": 27,
+ "output": 45
+ },
+ {
+ "input": 27,
+ "output": 46
+ },
+ {
+ "input": 27,
+ "output": 47
+ },
+ {
+ "input": 27,
+ "output": 48
+ }
+ ],
+ "name": "Walk"
+ },
+ {
+ "channels": [
+ {
+ "sampler": 0,
+ "target": {
+ "node": 8,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 1,
+ "target": {
+ "node": 7,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 2,
+ "target": {
+ "node": 11,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 3,
+ "target": {
+ "node": 10,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 4,
+ "target": {
+ "node": 9,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 5,
+ "target": {
+ "node": 14,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 6,
+ "target": {
+ "node": 13,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 7,
+ "target": {
+ "node": 12,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 8,
+ "target": {
+ "node": 6,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 9,
+ "target": {
+ "node": 5,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 10,
+ "target": {
+ "node": 17,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 11,
+ "target": {
+ "node": 16,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 12,
+ "target": {
+ "node": 15,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 13,
+ "target": {
+ "node": 20,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 14,
+ "target": {
+ "node": 19,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 15,
+ "target": {
+ "node": 18,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 16,
+ "target": {
+ "node": 24,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 17,
+ "target": {
+ "node": 23,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 18,
+ "target": {
+ "node": 22,
+ "path": "rotation"
+ }
+ },
+ {
+ "sampler": 19,
+ "target": {
+ "node": 4,
+ "path": "translation"
+ }
+ },
+ {
+ "sampler": 20,
+ "target": {
+ "node": 4,
+ "path": "rotation"
+ }
+ }
+ ],
+ "samplers": [
+ {
+ "input": 49,
+ "output": 50
+ },
+ {
+ "input": 49,
+ "output": 51
+ },
+ {
+ "input": 49,
+ "output": 52
+ },
+ {
+ "input": 49,
+ "output": 53
+ },
+ {
+ "input": 49,
+ "output": 54
+ },
+ {
+ "input": 49,
+ "output": 55
+ },
+ {
+ "input": 49,
+ "output": 56
+ },
+ {
+ "input": 49,
+ "output": 57
+ },
+ {
+ "input": 49,
+ "output": 58
+ },
+ {
+ "input": 49,
+ "output": 59
+ },
+ {
+ "input": 49,
+ "output": 60
+ },
+ {
+ "input": 49,
+ "output": 61
+ },
+ {
+ "input": 49,
+ "output": 62
+ },
+ {
+ "input": 49,
+ "output": 63
+ },
+ {
+ "input": 49,
+ "output": 64
+ },
+ {
+ "input": 49,
+ "output": 65
+ },
+ {
+ "input": 49,
+ "output": 66
+ },
+ {
+ "input": 49,
+ "output": 67
+ },
+ {
+ "input": 49,
+ "output": 68
+ },
+ {
+ "input": 49,
+ "output": 69
+ },
+ {
+ "input": 49,
+ "output": 70
+ }
+ ],
+ "name": "Run"
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteOffset": 0,
+ "byteLength": 20736,
+ "byteStride": 12
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 20736,
+ "byteLength": 27648,
+ "byteStride": 8
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 48384,
+ "byteLength": 27648,
+ "byteStride": 16
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 76032,
+ "byteLength": 1536
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 77568,
+ "byteLength": 504,
+ "byteStride": 4
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 78072,
+ "byteLength": 40320,
+ "byteStride": 16
+ },
+ {
+ "buffer": 0,
+ "byteOffset": 118392,
+ "byteLength": 1512,
+ "byteStride": 12
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "data:application/octet-stream;base64,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AgACAAoABAACAAIAAgAQAAIAAgACABAAAgACAAIADQACAAIABAAFAAcACgAKAAQAAgACAAoAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAKAAIAAgACAAUABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgAQAAIAAgADAAIAAgACAAIAEAARAAIABAAFAAcACgAEAAoABwACAAoABAACAAIAAgANAAIAAgACABAAAgACAAIADQACAAIAAwACAAIAAgADAAIAAgACAAIAEAACAAIAAgAQABEAAgADAAIAAgACAAIAAgACAAIACgACAAIAAgAKAAQAAgACAAoAAgACAAIADwACAAIAAgAPAA4AAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIAAgAUAAIAAgACAA0AAgACAAIAEAAUAAIACgACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAEAAUABwAKAAoAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABwACAAIAAgAHAAgAAgACAAcAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgAUAAIAAgACAA0AAgACAAIAFAACAAIAAwACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgACABQAAgACAAMAAgACAAIAAgAQABQAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACABQAAgACAAIAEAAUAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIABwACAAIAAgAIAAcAAgACAAcACAACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwAOAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIAAwACAAIAAgAHAAQAAgACAAMAFAACAAIAAgAUAAIAAgACAA0AAgACAAIAFAACAAIABAAFAAcACgAHAAIAAgACAAcABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAFAAQAAgACAAcAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgAUAAIAAgACABQAFQACAAMAAgACAAIABAAFAAcACgAHAAQAAgACAAQACgAHAAIAAgANAAIAAgACAA0AAgACAAIAFAACAAIAAwACAAIAAgACABQAAgACAAMAAgACAAIAAgAUABUAAgACABQAAgACAAIAAgACAAIABwACAAIAAgAHAAIAAgACAAcABAACAAIADwACAAIAAgAPAAIAAgACAA8ADgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIADwACAAIAAgAPAAIAAgACAA8AAgACAAIABwACAAIAAgAEAAUABwAKAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAcAAgACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIACgACAAIAAgAKAAQAAgACAAoAAgACAAIACgACAAIAAgALAAoAAgACAAoACwACAAIACgALAAIAAgAKAAIAAgACAAoAAgACAAIABwACAAIAAgAHAAQAAgACAAcAAgACAAIABwACAAIAAgAHAAIAAgACAAcAAgACAAIACgALAAIAAgAKAAIAAgACAAoABAACAAIACgAEAAIAAgAEAAoABwACAAoACwACAAIABwAIAAIAAgAEAAoABwACAAcABAACAAIABwAEAAIAAgAHAAIAAgACAAcACAACAAIABwACAAIAAgAHAAIAAgACAAcACAACAAIABwAIAAIAAgAIAAcAAgACAAcAAgACAAIAAwAUAAIAAgACABQAAgACAAIAFAACAAIAAgAUAAIAAgADAAIAAgACAAMAFAACAAIABAAKAAcAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAEAAoABwACAAcABAACAAIABwAEAAIAAgADAAIAAgACAAMAAgACAAIAAwAQAAIAAgADAAIAAgACAAIAEAACAAIAAgAQAAIAAgACABAAAgACAAMAEAACAAIABAAKAAcAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAoABAACAAIACgAEAAIAAgAEAAoABwACAAMAAgACAAIAAwACAAIAAgACAAIAAgACAAIAAgACAAIAAwACAAIAAgACAAIAAgACAAIAAgACAAIAAwACAAIAAgACABQAFQACAAIAAgACAAIAAgAQAAIAAgACABAAEQACAAIAAgACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABQAEAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIAAgANAAIAAgACAA0AAgACAA0ADgACAAIADQAOAAIAAgACAA0AAgACAA0ADgACAAIACgAEAAIAAgADABAAAgACAAMAEAACAAIAAwAQAAIAAgAKAAQAAgACAAoABAACAAIAAwACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgACAAIAAgACAAMAAgACAAIAAgANAAIAAgACABAAAgACAAIADQACAAIAAgACAAIAAgACABAAAgACAAIAEAACAAIAAgAQAAIAAgACABAAAgACAAIADQACAAIACgAEAAIAAgAKAAQAAgACAAoACwACAAIABAACAAIAAgAEAAIAAgACAAUABAACAAIABAACAAIAAgAEAAIAAgACAAUABAACAAIABgACAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgANAAIAAgACAA0AAgACAA0ADgACAAIAAgAQAAIAAgACABAAAgACABEAEAACAAIADQAOAAIAAgANAA4AAgACAA8ADgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgACAAIAAgACAAIAAgACAAIADQACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgAQAAIAAgACABAAAgACABAAEQACAAIAAgAQAAIAAgACABAAAgACABEAEAACAAIAAgAQAAIAAgACABAAAgACABEAEAACAAIAAgAQAAIAAgACABAAAgACABEAEAACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQAQAAIAAgACABAAEQACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQAQAAIAAgARABAAAgACABEAAgACAAIAEAARAAIAAgARABAAAgACABEAAgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIACgALAAIAAgAKAAsAAgACAAsAAgACAAIACwAKAAIAAgAKAAsAAgACAAsAAgACAAIABwACAAIAAgAHAAIAAgACAAcAAgACAAIABwACAAIAAgAHAAIAAgACAAcAAgACAAIABAAKAAcAAgAKAAQAAgACAAoACwACAAIACgALAAIAAgAKAAsAAgACAAsAAgACAAIADQAOAAIAAgACAA0AAgACAA0ADgACAAIADQAOAAIAAgAPAA4AAgACAA0ADgACAAIAAgANAAIAAgACAA0AAgACAA0ADgACAAIADQAOAAIAAgANAA4AAgACAAIADQACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIADwAOAAIAAgAPAA4AAgACAA8AAgACAAIAEQAQAAIAAgACABAAAgACAAIAEAARAAIAAwACAAIAAgACAAIAAgACAAIAAgACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgACAAIAAgACAAIAEAACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACABAAAgACAAIADQACAAIABQAEAAIAAgAEAAIAAgACAAUABAACAAIABQAEAAIAAgAEAAIAAgACAAUABAACAAIACgALAAIAAgAKAAQAAgACAAoACwACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIACgALAAIAAgAKAAQAAgACAAoACwACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIAAwAQAAIAAgADAAIAAgACAAoABAACAAIADQAOAAIAAgACAA0AAgACAAIADQACAAIAEAARAAIAAgACABAAAgACABEAEAACAAIADwAOAAIAAgANAA4AAgACAA8ADgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACAAIAAgACAAIADQACAAIAEQAQAAIAAgACABAAAgACABAAEQACAAIAAwACAAIAAgADAAIAAgACAAIAAgACAAIAEQAQAAIAAgACABAAAgACABEAEAACAAIAEQAQAAIAAgACABAAAgACABEAEAACAAIAEQAQAAIAAgACABAAAgACABEAEAACAAIAEQASAAIAAgACABAAEQACABEAEgACAAIACgALAAIAAgAEAAoABwACAAoACwACAAIACwACAAIAAgAKAAsAAgACAAsACgACAAIADwAOAAIAAgANAA4AAgACAA8ADgACAAIADwAOAAIAAgANAA4AAgACAA8ADgACAAIADwAOAAIAAgANAA4AAgACAA8ADgACAAIADwAOAAIAAgANAA4AAgACAA0ADgACAAIADwACAAIAAgAPAA4AAgACAA8AAgACAAIADwACAAIAAgAPAA4AAgACAA8AAgACAAIADwACAAIAAgAPAA4AAgACAA8AAgACAAIAAgANAAIAAgANAA4AAgACAAIADQACAAIADQAOAAIAAgANAA4AAgACAAIADQACAAIABwAEAAIAAgADABQAAgACAAMAFAACAAIAAwAUAAIAAgAHAAQAAgACAAcABAACAAIAAgACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAIAAgACAAIABAACAAIAAgAHAAIAAgACAAcAAgACAAIABwACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACAA0AAgACAAIAFAACAAIAAgACAAIAAgACABQAAgACAAIAFAACAAIAAgAUAAIAAgACAA0AAgACAAIAFAACAAIABwAEAAIAAgAHAAgAAgACAAcABAACAAIABAACAAIAAgAFAAQAAgACAAQAAgACAAIABAACAAIAAgAFAAQAAgACAAQAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABAACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAKAAIAAgACAAQAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAGAAIAAgACAAUABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABQAEAAIAAgAGAAIAAgACAAUABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAAgANAAIAAgANAA4AAgACAAIADQACAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIADQAOAAIAAgAPAA4AAgACAA0ADgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgACAAIAAgACAA0AAgACAAIAAgACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgAUAAIAAgAUABUAAgACAAIAFAACAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIAAgAUAAIAAgAVABQAAgACAAIAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAUAAIAAgAVABYAAgACAAIAFAAVAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAUAAIAAgAVAAIAAgACABUAFAACAAIAFAAVAAIAAgAVAAIAAgACABUAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIABwAIAAIAAgAIAAIAAgACAAcACAACAAIACAAHAAIAAgAIAAIAAgACAAcACAACAAIABAAKAAcAAgAHAAgAAgACAAcABAACAAIABwAIAAIAAgAIAAIAAgACAAcACAACAAIADQAOAAIAAgAPAA4AAgACAA0ADgACAAIADQAOAAIAAgAPAA4AAgACAA0ADgACAAIADQAOAAIAAgAPAA4AAgACAA8ADgACAAIADwAOAAIAAgANAA4AAgACAA0ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIADwAOAAIAAgAPAAIAAgACAA8ADgACAAIAFQAUAAIAAgACABQAFQACAAIAFAACAAIAAwACAAIAAgACAAIAAgACAAIAAgACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgACABQAAgACAAIAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACAA0AAgACAAIAFAACAAIABQAEAAIAAgAFAAQAAgACAAQAAgACAAIABQAEAAIAAgAEAAIAAgACAAUABAACAAIABwAIAAIAAgAHAAgAAgACAAcABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABwAIAAIAAgAHAAgAAgACAAcABAACAAIABwACAAIAAgAHAAIAAgACAAcAAgACAAIAAwAUAAIAAgAHAAQAAgACAAMAAgACAAIADQAOAAIAAgACAA0AAgACAAIADQACAAIAFAAVAAIAAgAVABQAAgACAAIAFAACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIAAgANAAIAAgACAA0AAgACAAIAAgACAAIAFQAUAAIAAgAUABUAAgACAAIAFAACAAIAAwACAAIAAgACAAIAAgACAAMAAgACAAIAFQAUAAIAAgAVABQAAgACAAIAFAACAAIAFQAUAAIAAgAVABQAAgACAAIAFAACAAIAFQAUAAIAAgAVABQAAgACAAIAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIABwAIAAIAAgAEAAoABwACAAcACAACAAIACAACAAIAAgAIAAcAAgACAAcACAACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIADQAOAAIAAgANAA4AAgACAA8ADgACAAIADwAOAAIAAgAPAA4AAgACAA0ADgACAAIADwACAAIAAgAPAAIAAgACAA8ADgACAAIADwACAAIAAgAPAAIAAgACAA8ADgACAAIADwACAAIAAgAPAAIAAgACAA8ADgACAAIAEQASAAIAAgARABIAAgACAAIAEAARAAIAAgAQABEAAgACABAAAgACABEAEgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAEAAIAAgACAAQAAgACAAIACgAEAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAEAAIAAgACAAoABAACAAIABwAEAAIAAgAHAAgAAgACAAcAAgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAHAAQAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAQAAgACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIABwAIAAIAAgAIAAcAAgACAAcAAgACAAIABwACAAIAAgAHAAIAAgACAAcACAACAAIABwAIAAIAAgAHAAgAAgACAAcAAgACAAIACgACAAIAAgAKAAsAAgACAAoACwACAAIACgALAAIAAgAKAAIAAgACAAoAAgACAAIACgACAAIAAgAKAAsAAgACAAsACgACAAIACwAKAAIAAgAKAAIAAgACAAoAAgACAAIABwACAAIAAgAHAAIAAgACAAgABwACAAIACAAHAAIAAgAHAAgAAgACAAcAAgACAAIACwAKAAIAAgAKAAsAAgACAAoACwACAAIACAAHAAIAAgAHAAgAAgACAAcACAACAAIACgALAAIAAgAKAAIAAgACAAsACgACAAIACgACAAIAAgAKAAsAAgACAAoAAgACAAIACgACAAIAAgAKAAIAAgACAAoAAgACAAIABAACAAIAAgAEAAIAAgACAAoAAgACAAIACgACAAIAAgAKAAIAAgACAAQAAgACAAIACgACAAIAAgAKAAQAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAoAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABwACAAIAAgAHAAIAAgACAAcACAACAAIABwAIAAIAAgAHAAIAAgACAAcAAgACAAIACgAEAAIAAgAKAAIAAgACAAoACwACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAcABAACAAIABwAEAAIAAgAHAAIAAgACAAQAAgACAAIAEQASAAIAAgARABIAAgACABEAEAACAAIAEQAQAAIAAgARABAAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACAAIAEAACAAIAAgAQAAIAAgARABAAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEAACAAIAEQAQAAIAAgARABAAAgACABEAEgACAAIAFQAWAAIAAgAVABQAAgACABUAFAACAAIAFQAUAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABQAAgACABUAFAACAAIAFQAUAAIAAgAVABYAAgACABUAFgACAAIAFQAUAAIAAgAVABYAAgACABQAFQACAAIAEQAQAAIAAgAQABEAAgACABEAEgACAAIAFQAWAAIAAgACABQAAgACAAIAFAAVAAIAAgAUABUAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABQAAgACAAIAFAACAAIAAgAUAAIAAgAVABYAAgACABUAFgACAAIAAgAUABUAAgAVABYAAgACABUAFgACAAIAAgANAAIAAgANAA4AAgACAA0ADgACAAIADQAOAAIAAgACAA0AAgACAAIADQACAAIAAgANAAIAAgANAA4AAgACAA0ADgACAAIAAgANAAIAAgANAA4AAgACAA0ADgACAAIAAgANAAIAAgANAA4AAgACAA0ADgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAQAAgACAAIABAACAAIAAgAEAAIAAgACAAMAAgACAAIAAwACAAIAAgADAAIAAgACAAIAEAACAAIAAgAQAAIAAgACAAIAAgACAAMAAgACAAIAAwACAAIAAgADABAAAgACAAIAEAACAAIAAgAQAAIAAgACABAAAgACAAMAAgACAAIAAgAQAAIAAgADAAIAAgACAAMAAgACAAIAAgAQAAIAAgADABAAAgACAAMAEAACAAIAAgAUAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgAEAAIAAgACAAQAAgACAAIABAACAAIAAgADAAIAAgACAAMAAgACAAIAAwACAAIAAgACAAIAAgACAAIAFAACAAIAAgAUAAIAAgADAAIAAgACAAMAAgACAAIAAgAUAAIAAgADABQAAgACAAMAFAACAAIAAwACAAIAAgACABQAAgACAAIAFAACAAIAAgAUAAIAAgADABQAAgACAAMAAgACAAIABAACAAIAAgAHAAIAAgACAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAQAAgACAAIABQAEAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAoAAgACAAIACgACAAIAAgAKAAIAAgACAAUABAACAAIABQAEAAIAAgAHAAIAAgACAAcAAgACAAIABwACAAIAAgAFAAQAAgACAAUABAACAAIABQAEAAIAAgAFAAQAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAUABAACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIABgACAAIAAgAFAAQAAgACAAUABAACAAIAEQASAAIAAgARAAIAAgACABIAEQACAAIAEQASAAIAAgARABIAAgACABMAEgACAAIAEQASAAIAAgASABEAAgACABIAEwACAAIAEwASAAIAAgASABMAAgACABMAAgACAAIAEQASAAIAAgARABIAAgACABMAEgACAAIAEgARAAIAAgARABIAAgACABIAEwACAAIAEQASAAIAAgARABIAAgACABIAEwACAAIAEQASAAIAAgARABIAAgACABIAEwACAAIAEwACAAIAAgATABIAAgACABMAAgACAAIAEwACAAIAAgATAAIAAgACABMAAgACAAIAEwASAAIAAgATAAIAAgACABMAAgACAAIAEwACAAIAAgASABMAAgACABMAAgACAAIAEgATAAIAAgASABMAAgACABMAAgACAAIAEgATAAIAAgASABMAAgACABMAAgACAAIACwACAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgAMAAsAAgACAAsAAgACAAIACwACAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgALAAoAAgACAAsAAgACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIADAACAAIAAgAMAAIAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwAAgACAAIAEQASAAIAAgARABAAAgACABEAEgACAAIAEQACAAIAAgARABAAAgACABEAEgACAAIAEQASAAIAAgARABAAAgACABEAEgACAAIAEgARAAIAAgARAAIAAgACABEAEgACAAIAEQASAAIAAgAQABEAAgACABEAAgACAAIAEgATAAIAAgARABIAAgACABMAEgACAAIAEgATAAIAAgASABEAAgACABIAEwACAAIAEwASAAIAAgARABIAAgACABMAEgACAAIAEgATAAIAAgASABEAAgACABIAEwACAAIAEwASAAIAAgARABIAAgACABIAEwACAAIAEwACAAIAAgATABIAAgACABMAEgACAAIAEwACAAIAAgATAAIAAgACABMAAgACAAIAEwASAAIAAgATAAIAAgACABMAAgACAAIAEwACAAIAAgATAAIAAgACABMAAgACAAIAEwACAAIAAgASABMAAgACABMAEgACAAIAEwACAAIAAgASABMAAgACABMAAgACAAIACwACAAIAAgAKAAsAAgACAAsAAgACAAIADAALAAIAAgALAAIAAgACAAwACwACAAIACwACAAIAAgALAAoAAgACAAsAAgACAAIACwACAAIAAgALAAoAAgACAAsAAgACAAIACwACAAIAAgAKAAsAAgACAAsAAgACAAIADAALAAIAAgALAAIAAgACAAwACwACAAIADAALAAIAAgALAAIAAgACAAwACwACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIADAALAAIAAgAMAAsAAgACAAwACwACAAIADAACAAIAAgAMAAIAAgACAAwAAgACAAIADAACAAIAAgAMAAIAAgACAAwAAgACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIADAACAAIAAgAMAAsAAgACAAwAAgACAAIAFQAWAAIAAgAWABUAAgACABUAAgACAAIAFQAWAAIAAgAXABYAAgACABUAFgACAAIAFQAWAAIAAgAWABcAAgACABYAFQACAAIAFwAWAAIAAgAXAAIAAgACABYAFwACAAIAFQAWAAIAAgAXABYAAgACABUAFgACAAIAFgAVAAIAAgAWABcAAgACABUAFgACAAIAFQAWAAIAAgAWABcAAgACABUAFgACAAIAFQAWAAIAAgAWABcAAgACABUAFgACAAIAFwACAAIAAgAXAAIAAgACABcAFgACAAIAFwACAAIAAgAXAAIAAgACABcAAgACAAIAFwAWAAIAAgAXAAIAAgACABcAAgACAAIAFwACAAIAAgAXAAIAAgACABYAFwACAAIAFgAXAAIAAgAXAAIAAgACABYAFwACAAIAFgAXAAIAAgAXAAIAAgACABYAFwACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAJAAgAAgACAAgAAgACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACQACAAIAAgAJAAIAAgACAAkAAgACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAIAAgACAAkACAACAAIAFQAWAAIAAgAVABYAAgACABUAFAACAAIAFQACAAIAAgAVABYAAgACABUAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFAACAAIAFgAVAAIAAgAVABYAAgACABUAAgACAAIAFQAWAAIAAgAVAAIAAgACABQAFQACAAIAFgAXAAIAAgAXABYAAgACABUAFgACAAIAFgAXAAIAAgAWABcAAgACABYAFQACAAIAFwAWAAIAAgAXABYAAgACABUAFgACAAIAFgAXAAIAAgAWABcAAgACABYAFQACAAIAFwAWAAIAAgAWABcAAgACABUAFgACAAIAFwACAAIAAgAXABYAAgACABcAFgACAAIAFwACAAIAAgAXAAIAAgACABcAAgACAAIAFwAWAAIAAgAXAAIAAgACABcAAgACAAIAFwACAAIAAgAXAAIAAgACABcAAgACAAIAFwACAAIAAgAXABYAAgACABYAFwACAAIAFwACAAIAAgAXAAIAAgACABYAFwACAAIACAACAAIAAgAIAAIAAgACAAcACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAIAAIAAgACAAgABwACAAIACAACAAIAAgAIAAIAAgACAAgABwACAAIACAACAAIAAgAIAAIAAgACAAcACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAgAAgACAAkACAACAAIACQACAAIAAgAJAAIAAgACAAkAAgACAAIACQACAAIAAgAJAAIAAgACAAkAAgACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACQACAAIAAgAJAAIAAgACAAkACAACAAIACwACAAIAAgAMAAsAAgACAAwACwACAAIADAALAAIAAgALAAIAAgACAAsAAgACAAIACAACAAIAAgAIAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACAACAAIAAgAIAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIACwACAAIAAgALAAIAAgACAAsACgACAAIACwAKAAIAAgAKAAsAAgACAAsAAgACAAIACwACAAIAAgAMAAsAAgACAAwACwACAAIADAALAAIAAgALAAIAAgACAAsAAgACAAIACwACAAIAAgAMAAsAAgACAAwACwACAAIADAALAAIAAgALAAIAAgACAAsAAgACAAIACAACAAIAAgAHAAgAAgACAAcACAACAAIABwAIAAIAAgAIAAIAAgACAAgAAgACAAIACAACAAIAAgAIAAcAAgACAAcACAACAAIABwAIAAIAAgAIAAIAAgACAAgAAgACAAIACwACAAIAAgALAAIAAgACAAoACwACAAIACgALAAIAAgAKAAsAAgACAAsAAgACAAIACwACAAIAAgALAAIAAgACAAoACwACAAIACgALAAIAAgALAAoAAgACAAsAAgACAAIACAACAAIAAgAHAAgAAgACAAgABwACAAIACAAHAAIAAgAIAAIAAgACAAgAAgACAAIACAACAAIAAgAIAAIAAgACAAkACAACAAIACQAIAAIAAgAJAAgAAgACAAgAAgACAAIADAALAAIAAgALAAIAAgACAAsAAgACAAIACwAKAAIAAgALAAIAAgACAAsAAgACAAIACAAHAAIAAgAIAAIAAgACAAgAAgACAAIACAAHAAIAAgAIAAIAAgACAAgAAgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAFQAWAAIAAgAVABYAAgACABUAFAACAAIAFQAWAAIAAgAVABYAAgACABUAFgACAAIAEQASAAIAAgARABAAAgACABEAEgACAAIAEQASAAIAAgARABIAAgACABEAEgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIABgACAAIAAgAGAAIAAgACAAYAAgACAAIAmpkZP83MzD4AAAAAAAAAANo/Mz+amRk+AmcZPgAAAAAK12M/rkfhPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA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uB6FPgAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAACtdjP65H4T0AAAAAAAAAAArXYz+uR+E9AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAAD8AAAA/AAAAAAAAAACZmRk/zszMPgAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAAAAAD8AAAA/AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAA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AAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAGZmZj/NzMw9AAAAAAAAAABmZmY/zczMPQAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAwvUoP3sUrj4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAFyPQj+PwnU+AAAAAAAAAADC9Sg/exSuPgAAAAAAAAAAwvUoP3sUrj4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAEA/AACAPgAAAAAAAAAAAABAPwAAgD4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAADC9Sg/exSuPgAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAGZmZj/NzMw9AAAAAAAAAABmZmY/zczMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAQD8AAIA+AAAAAAAAAAAAAAA/AAAAPwAAAAAAAAAAAAAAPwAAAD8AAAAAAAAAAGZmZj/NzMw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAMzNzP83MTD0AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAMzNzP83MTD0AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAzM3M/zcxMPQAAAAAAAAAAMzNzP83MTD0AAAAAAAAAADMzcz/NzEw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAA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AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAABmZmY/zczMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAABmZmY/zczMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAABmZmY/zczMPQAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAGZmZj/NzMw9AAAAAAAAAABmZmY/zczMPQAAAAAAAAAAZmZmP83MzD0AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAABmZmY/zczMPQAAAAAAAAAAZmZmP83MzD0AAAAAAAAAAGZmZj/NzMw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAABmZmY/zczMPQAAAAAAAAAAZmZmP83MzD0AAAAAAAAAAGZmZj/NzMw9AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAUrkc/sEdhPgAAAAAAAAAAFK5HP7BHYT4AAAAAAAAAAM3MTD/NzEw+AAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAGZmZj/NzMw9AAAAAAAAAABmZmY/zczMPQAAAAAAAAAAZmZmP83MzD0AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAGZmZj/NzMw9AAAAAAAAAABmZmY/zczMPQAAAAAAAAAAZmZmP83MzD0AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAM3MTD/NzEw+AAAAAAAAAADNzEw/zcxMPgAAAAAAAAAAzcxMP83MTD4AAAAAAAAAAAAAgD8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAAAAAAA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HAsyvz3BAr4ZCzI/Z8ECPhwLMr87wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9mwQI+HAsyvzrBAr4ZCzI/ZsECPhwLMr86wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/Z8ECPhwLMr87wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/aMECPhwLMr88wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9owQI+HAsyvzzBAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/Z8ECPhwLMr87wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9mwQI+HAsyvzrBAr4ZCzI/acECPhwLMr89wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9nwQI+HAsyvzvBAr4ZCzI/ZsECPhwLMr86wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9owQI+HAsyvzzBAr4ZCzI/ZsECPhwLMr86wQK+GQsyP2bBAj4cCzK/OsECvhkLMj9mwQI+HAsyvzrBAr4ZCzI/ZsECPhwLMr86wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9pwQI+HAsyvz3BAr4ZCzI/acECPhwLMr89wQK+GQsyP2nBAj4cCzK/PcECvhkLMj9nwQI+HAsyvzvBAr4ZCzI/Z8ECPhwLMr87wQK+GQsyP2nBAj4cCzK/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",
+ "byteLength": 119904
+ }
+ ],
+ "images": [
+ {
+ "uri": "data:image/png;base64,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",
+ "mimeType": "image/png"
+ }
+ ],
+ "materials": [
+ {
+ "name": "fox_material",
+ "pbrMetallicRoughness": {
+ "baseColorTexture": {
+ "index": 0
+ },
+ "metallicFactor": 0,
+ "roughnessFactor": 0.58
+ }
+ }
+ ],
+ "meshes": [
+ {
+ "name": "fox1",
+ "primitives": [
+ {
+ "attributes": {
+ "POSITION": 0,
+ "TEXCOORD_0": 1,
+ "JOINTS_0": 2,
+ "WEIGHTS_0": 3
+ },
+ "material": 0
+ }
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "children": [
+ 1,
+ 2
+ ],
+ "name": "root"
+ },
+ {
+ "name": "fox",
+ "mesh": 0,
+ "skin": 0
+ },
+ {
+ "children": [
+ 3
+ ],
+ "name": "_rootJoint"
+ },
+ {
+ "children": [
+ 4
+ ],
+ "name": "b_Root_00",
+ "rotation": [
+ -0.7071080924875391,
+ 0.0,
+ 0.0,
+ 0.7071054698831242
+ ]
+ },
+ {
+ "children": [
+ 5,
+ 15,
+ 18,
+ 22
+ ],
+ "name": "b_Hip_01",
+ "rotation": [
+ 0.12769094176175547,
+ -0.6954820192393762,
+ -0.12769022650601444,
+ 0.695481840425441
+ ],
+ "translation": [
+ 0,
+ 26.748403549194336,
+ 42.93817138671875
+ ]
+ },
+ {
+ "children": [
+ 6
+ ],
+ "name": "b_Spine01_02",
+ "rotation": [
+ 0.0,
+ 0.0,
+ -0.5904157638238317,
+ 0.8070992664030376
+ ],
+ "translation": [
+ 12.850601196289062,
+ 0,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 7,
+ 9,
+ 12
+ ],
+ "name": "b_Spine02_03",
+ "rotation": [
+ 0.0,
+ 0.0,
+ 0.017411952404281082,
+ 0.9998484004655261
+ ],
+ "translation": [
+ 21.65575408935547,
+ -0.000118255615234375,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 8
+ ],
+ "name": "b_Neck_04",
+ "rotation": [
+ 0.0,
+ 0.0,
+ 0.30337914028264346,
+ 0.9528699267168443
+ ],
+ "translation": [
+ 25.64914321899414,
+ 0,
+ 0
+ ]
+ },
+ {
+ "name": "b_Head_05",
+ "rotation": [
+ 0.0,
+ 0.0,
+ -0.4002854151487349,
+ 0.9163905206947555
+ ],
+ "translation": [
+ 13.376960754394531,
+ 0,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 10
+ ],
+ "name": "b_RightUpperArm_06",
+ "rotation": [
+ 0.0004673273262011562,
+ -0.0004461484692255928,
+ -0.7121792881110691,
+ 0.7019973248825985
+ ],
+ "translation": [
+ 18.677913665771484,
+ -4.297340393066406,
+ 6.9675750732421875
+ ]
+ },
+ {
+ "children": [
+ 11
+ ],
+ "name": "b_RightForeArm_07",
+ "rotation": [
+ 0.0,
+ 0.0,
+ 0.03712589977348744,
+ 0.9993105961441663
+ ],
+ "translation": [
+ 23.04512596130371,
+ 0,
+ 0
+ ]
+ },
+ {
+ "name": "b_RightHand_08",
+ "rotation": [
+ -0.012037406914797018,
+ -0.00782221012465276,
+ 0.4605623277185148,
+ 0.8875112709988741
+ ],
+ "translation": [
+ 19.350055694580078,
+ -0.14598655700683594,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 13
+ ],
+ "name": "b_LeftUpperArm_09",
+ "rotation": [
+ 0.0004972619220940174,
+ -0.0008821923166442875,
+ -0.7120874929914663,
+ 0.7020900061903927
+ ],
+ "translation": [
+ 18.67791748046875,
+ -4.297344207763672,
+ -6.967987060546875
+ ]
+ },
+ {
+ "children": [
+ 14
+ ],
+ "name": "b_LeftForeArm_010",
+ "rotation": [
+ 0.0,
+ 0.0,
+ 0.03712589977348744,
+ 0.9993105961441663
+ ],
+ "translation": [
+ 23.045124053955078,
+ 0,
+ 0
+ ]
+ },
+ {
+ "name": "b_LeftHand_011",
+ "rotation": [
+ 0.01651791440721507,
+ 0.014013739873997781,
+ 0.46007557521271,
+ 0.8876154790736099
+ ],
+ "translation": [
+ 19.350051879882812,
+ -0.14599037170410156,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 16
+ ],
+ "name": "b_Tail01_012",
+ "rotation": [
+ 0.0,
+ 0.0,
+ 0.9818928940656295,
+ 0.1894369145214904
+ ],
+ "translation": [
+ 4.2603759765625,
+ 15.958770751953125,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 17
+ ],
+ "name": "b_Tail02_013",
+ "rotation": [
+ 0.0,
+ 0.0,
+ -0.0696171663387466,
+ 0.9975737818081244
+ ],
+ "translation": [
+ 12.411918640136719,
+ 0,
+ 0
+ ]
+ },
+ {
+ "name": "b_Tail03_014",
+ "rotation": [
+ 0.0,
+ 0.0,
+ -0.05383274484207684,
+ 0.9985499664927979
+ ],
+ "translation": [
+ 24.24032211303711,
+ 0,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 19
+ ],
+ "name": "b_LeftLeg01_015",
+ "rotation": [
+ 0.0,
+ -0.0001717522536559936,
+ 0.9700158834020681,
+ -0.2430414706359161
+ ],
+ "translation": [
+ 4.813770294189453,
+ 5.154018402099609,
+ -6.968006134033203
+ ]
+ },
+ {
+ "children": [
+ 20
+ ],
+ "name": "b_LeftLeg02_016",
+ "rotation": [
+ 0.0,
+ 0.0,
+ -0.36804378855511655,
+ 0.9298084586117706
+ ],
+ "translation": [
+ 18.944175720214844,
+ 0,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 21
+ ],
+ "name": "b_LeftFoot01_017",
+ "rotation": [
+ 0.0002484105929664666,
+ 0.0,
+ 0.4584841122585099,
+ 0.888702569535333
+ ],
+ "translation": [
+ 17.942811965942383,
+ 0,
+ 0
+ ]
+ },
+ {
+ "name": "b_LeftFoot02_018",
+ "rotation": [
+ 0.0,
+ 0.0,
+ 0.5472882949090243,
+ 0.8369441571906533
+ ],
+ "translation": [
+ 15.779938697814941,
+ 0,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 23
+ ],
+ "name": "b_RightLeg01_019",
+ "rotation": [
+ 0.0,
+ 0.0,
+ 0.9699585942054535,
+ -0.24327006705918533
+ ],
+ "translation": [
+ 4.813777923583984,
+ 5.154026031494141,
+ 6.967563629150391
+ ]
+ },
+ {
+ "children": [
+ 24
+ ],
+ "name": "b_RightLeg02_020",
+ "rotation": [
+ 0.0,
+ 0.0,
+ -0.36804381432052885,
+ 0.9298084484131106
+ ],
+ "translation": [
+ 18.944183349609375,
+ 0,
+ 0
+ ]
+ },
+ {
+ "children": [
+ 25
+ ],
+ "name": "b_RightFoot01_021",
+ "rotation": [
+ -0.00015345455876803163,
+ 0.0,
+ 0.4579093746168346,
+ 0.888998864504178
+ ],
+ "translation": [
+ 17.94281005859375,
+ 0,
+ 0
+ ]
+ },
+ {
+ "name": "b_RightFoot02_022",
+ "rotation": [
+ 0.0,
+ 0.0,
+ 0.5472882949090243,
+ 0.8369441571906533
+ ],
+ "translation": [
+ 15.779935836791992,
+ 0,
+ 0
+ ]
+ }
+ ],
+ "samplers": [
+ {
+ "magFilter": 9729,
+ "minFilter": 9987
+ }
+ ],
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "skins": [
+ {
+ "inverseBindMatrices": 4,
+ "joints": [
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 23,
+ 24,
+ 25
+ ],
+ "skeleton": 2
+ }
+ ],
+ "textures": [
+ {
+ "sampler": 0,
+ "source": 0
+ }
+ ]
+}
diff --git a/gradio/media_assets/models3d/face.obj b/gradio/media_assets/models3d/face.obj
new file mode 100644
index 0000000..a2e82ee
--- /dev/null
+++ b/gradio/media_assets/models3d/face.obj
@@ -0,0 +1,2471 @@
+####
+#
+# OBJ File Generated by Meshlab
+#
+####
+# Object face.obj
+#
+# Vertices: 845
+# Faces: 1610
+#
+####
+v 54.126293 -49.502399 71.230705 0.384314 0.254902 0.200000
+v 51.424591 -54.578999 75.431709 0.337255 0.227451 0.172549
+v 44.556496 -61.237099 65.307907 0.384314 0.254902 0.192157
+v 18.416197 -75.205498 35.343601 0.529412 0.376471 0.298039
+v 13.523697 -77.731102 34.633598 0.513725 0.364706 0.290196
+v 11.454597 -74.201599 29.775103 0.639216 0.454902 0.372549
+v 5.855427 -77.386299 30.315100 0.603922 0.435294 0.356863
+v -0.378855 -77.287903 29.743700 0.611765 0.443137 0.360784
+v 6.812867 -79.041397 33.545300 0.505882 0.368627 0.298039
+v 21.881296 -70.810898 34.982002 0.588235 0.415686 0.329412
+v 17.653797 -69.034798 30.263802 0.674510 0.478431 0.388235
+v 39.436996 -66.435501 64.968597 0.164706 0.109804 0.082353
+v 33.808598 -70.338699 57.717205 0.364706 0.247059 0.184314
+v 31.909595 -68.409302 47.974705 0.439216 0.301961 0.227451
+v 38.451797 -64.744904 55.703503 0.427451 0.294118 0.219608
+v 28.802397 -72.939499 50.408905 0.372549 0.254902 0.192157
+v 22.955397 -75.486603 43.228905 0.400000 0.274510 0.215686
+v 26.511995 -70.622101 41.049904 0.494118 0.341176 0.262745
+v 24.465696 -76.217796 49.972805 0.345098 0.239216 0.184314
+v 15.571197 -78.986099 40.602001 0.396078 0.278431 0.219608
+v 17.437696 -79.466103 47.124001 0.352941 0.247059 0.196078
+v 28.846695 -74.018898 56.477203 0.164706 0.113725 0.086275
+v 9.321696 -81.938202 48.001999 0.172549 0.129412 0.105882
+v -0.898453 -82.647102 47.089699 0.172549 0.129412 0.105882
+v -0.725759 -82.110100 43.572498 0.400000 0.301961 0.254902
+v 18.763496 -79.316498 51.133400 0.172549 0.117647 0.094118
+v 8.731546 -81.557503 44.441002 0.392157 0.286275 0.235294
+v 27.614496 -65.079002 38.432205 0.596078 0.419608 0.321569
+v 33.274998 -61.490501 43.398605 0.568627 0.400000 0.301961
+v 38.784996 -60.440899 50.047802 0.513725 0.356863 0.266667
+v 42.862595 -58.651798 55.019802 0.490196 0.337255 0.250980
+v 7.726847 -80.575996 38.638500 0.415686 0.301961 0.247059
+v -0.490965 -80.930901 37.912300 0.427451 0.317647 0.262745
+v -0.419731 -79.354401 33.152100 0.513725 0.376471 0.305882
+v -0.332592 -73.782600 27.050400 0.705882 0.505882 0.415686
+v -0.212143 -66.515099 24.546000 0.815686 0.584314 0.501961
+v 11.557498 -67.907898 26.621702 0.749020 0.529412 0.443137
+v 23.830296 -66.563599 34.771904 0.631373 0.447059 0.352941
+v 35.915897 105.133003 38.176804 0.219608 0.152941 0.117647
+v 36.548496 101.879997 36.786003 0.454902 0.309804 0.235294
+v 27.391598 102.010002 31.503502 0.278431 0.188235 0.141176
+v 59.787598 -0.163902 47.832703 0.611765 0.403922 0.321569
+v 50.721996 -9.559180 38.382904 0.721569 0.490196 0.403922
+v 52.439697 -0.358719 38.911705 0.741176 0.501961 0.419608
+v 29.889196 -56.737301 37.395603 0.662745 0.470588 0.360784
+v 26.097795 -54.826500 33.666504 0.694118 0.486275 0.372549
+v 26.293499 -49.727501 32.217205 0.717647 0.501961 0.388235
+v 25.434498 -44.035900 30.291403 0.729412 0.509804 0.400000
+v 31.436096 -44.876701 34.064003 0.737255 0.533333 0.411765
+v 32.500095 -51.078300 37.202503 0.690196 0.490196 0.376471
+v 21.003098 -62.278099 31.444601 0.705882 0.501961 0.400000
+v 26.550097 -59.714100 35.467205 0.666667 0.474510 0.364706
+v 44.267796 -52.222500 50.556103 0.552941 0.384314 0.290196
+v 44.155598 -45.754700 45.234005 0.607843 0.423529 0.325490
+v 39.424698 -54.441200 45.685303 0.588235 0.411765 0.309804
+v 42.849094 -40.630600 40.559803 0.662745 0.462745 0.364706
+v 38.941998 -42.900600 37.800304 0.713725 0.505882 0.400000
+v 52.563396 -19.698900 43.829704 0.650980 0.443137 0.356863
+v 46.654697 -16.837099 36.168205 0.749020 0.517647 0.427451
+v 33.321995 -35.720200 31.409004 0.745098 0.541176 0.427451
+v 32.657997 -40.731899 33.174603 0.752941 0.549020 0.427451
+v 35.472797 -44.317001 36.017105 0.737255 0.533333 0.415686
+v 27.102999 -39.309502 30.560902 0.713725 0.494118 0.388235
+v 21.412498 -21.051500 22.568802 0.733333 0.509804 0.415686
+v 23.988897 -17.175900 24.872402 0.803922 0.549020 0.447059
+v 27.558098 -24.650700 25.424603 0.745098 0.533333 0.443137
+v 27.926598 -35.123001 29.129301 0.701961 0.494118 0.384314
+v 24.306997 -32.355598 25.602802 0.674510 0.478431 0.384314
+v 26.352098 -28.906099 25.178001 0.725490 0.537255 0.439216
+v 22.071098 -41.536701 27.588202 0.670588 0.447059 0.368627
+v 20.085999 -45.811401 26.494501 0.701961 0.486275 0.384314
+v 19.001799 -42.619900 24.806602 0.662745 0.431373 0.364706
+v 28.976398 -21.374100 26.378202 0.788235 0.556863 0.454902
+v 26.341497 -16.211300 25.941402 0.839216 0.584314 0.474510
+v 30.814697 -16.946400 27.290703 0.866667 0.611765 0.501961
+v 15.764998 -20.247000 19.382002 0.698039 0.478431 0.392157
+v 16.598598 -15.898800 21.267502 0.737255 0.486275 0.400000
+v 22.444199 -3.466390 25.169203 0.752941 0.505882 0.415686
+v 21.588697 -7.433040 25.304302 0.760784 0.513725 0.423529
+v 18.939598 -4.099130 23.521101 0.556863 0.321569 0.270588
+v 26.227198 -11.129200 26.175901 0.894118 0.639216 0.533333
+v 23.958097 -13.787900 25.571701 0.843137 0.584314 0.474510
+v 3.233079 -28.114599 12.996900 0.807843 0.517647 0.474510
+v 3.691189 -24.150000 13.560400 0.788235 0.545098 0.450980
+v 7.239779 -23.905701 14.043200 0.760784 0.533333 0.439216
+v 3.876879 -20.045601 14.070800 0.803922 0.580392 0.490196
+v 9.195189 -19.025499 15.793001 0.705882 0.498039 0.411765
+v 3.141149 -17.198000 13.771500 0.807843 0.584314 0.498039
+v 4.931409 -13.967200 13.240000 0.737255 0.509804 0.435294
+v 30.555098 -31.658199 28.330402 0.737255 0.537255 0.439216
+v 36.921795 -39.106701 34.709404 0.749020 0.541176 0.427451
+v 37.097694 -31.720600 32.396603 0.737255 0.521569 0.415686
+v 32.560898 -26.616501 28.413202 0.749020 0.533333 0.435294
+v 10.314199 -9.869830 13.888401 0.564706 0.352941 0.301961
+v 3.716749 -10.896600 9.270600 0.549020 0.333333 0.278431
+v 6.998729 -8.745080 9.093191 0.458824 0.274510 0.247059
+v 11.017399 -7.650950 11.325301 0.407843 0.235294 0.196078
+v 13.094099 -8.179750 13.619401 0.596078 0.372549 0.305882
+v 14.387198 -9.345590 16.544502 0.682353 0.423529 0.349020
+v 14.674398 -10.957500 19.197702 0.619608 0.352941 0.286275
+v 12.273198 -13.638600 18.082401 0.670588 0.415686 0.349020
+v 15.706099 -12.324600 21.310001 0.654902 0.400000 0.341176
+v 0.160743 -11.927500 9.164860 0.631373 0.415686 0.345098
+v 16.748499 -0.088195 17.466702 0.486275 0.262745 0.211765
+v 14.135499 2.999720 13.703901 0.498039 0.278431 0.223529
+v 15.227698 3.521270 17.552402 0.509804 0.286275 0.227451
+v 11.515499 5.295930 9.697881 0.517647 0.298039 0.235294
+v 12.664099 6.361880 14.138000 0.529412 0.305882 0.243137
+v 17.526098 -1.031370 20.210901 0.478431 0.250980 0.203922
+v 18.114098 -7.909870 23.033703 0.556863 0.313725 0.266667
+v 10.384699 8.630760 10.439901 0.541176 0.321569 0.250980
+v 17.828499 0.413114 21.808401 0.541176 0.305882 0.250980
+v 17.929298 -3.542410 20.437902 0.482353 0.247059 0.203922
+v 17.031097 -10.684200 22.274002 0.619608 0.368627 0.313725
+v 0.287526 -2.020300 -3.337250 0.925490 0.694118 0.627451
+v 3.060050 -2.048090 -2.924980 0.894118 0.650980 0.584314
+v 3.007730 -4.729930 -2.346410 0.870588 0.611765 0.537255
+v 5.864850 -1.934710 -1.552539 0.835294 0.592157 0.525490
+v 5.417400 -4.893230 -0.945915 0.788235 0.533333 0.458824
+v 8.198860 -5.353820 2.653151 0.627451 0.403922 0.329412
+v 5.432270 -7.772490 2.088590 0.639216 0.411765 0.337255
+v 6.036009 -8.309330 5.213161 0.458824 0.270588 0.223529
+v 8.222100 -6.896640 5.293671 0.498039 0.305882 0.247059
+v 5.315060 7.954630 2.505800 0.662745 0.400000 0.321569
+v 5.391600 3.642640 -0.798212 0.729412 0.454902 0.372549
+v 3.235490 4.316670 -1.382350 0.764706 0.482353 0.396078
+v 5.431940 1.019950 -1.779530 0.788235 0.521569 0.443137
+v 3.012540 0.964488 -2.729010 0.847059 0.572549 0.494118
+v 3.567950 -7.050370 -0.455861 0.772549 0.517647 0.439216
+v 0.254061 -9.470920 1.815530 0.694118 0.466667 0.388235
+v 3.185970 -9.021570 1.994320 0.686275 0.458824 0.380392
+v 3.347260 -9.952950 5.307401 0.568627 0.360784 0.294118
+v 11.644599 -4.814710 6.525851 0.666667 0.439216 0.360784
+v 10.245199 -6.916610 8.546041 0.431373 0.254902 0.207843
+v 14.322199 -5.140650 10.096601 0.682353 0.439216 0.360784
+v 13.032799 -7.158690 11.593801 0.564706 0.352941 0.286275
+v 16.428497 -5.656990 13.658102 0.650980 0.396078 0.321569
+v 15.041399 -7.855270 14.466301 0.674510 0.423529 0.345098
+v 17.493698 -6.043210 17.118502 0.572549 0.313725 0.254902
+v 16.415197 -8.234700 17.133102 0.619608 0.356863 0.290196
+v 0.234891 4.747230 -1.584250 0.788235 0.498039 0.407843
+v 7.301090 1.952910 -0.069825 0.709804 0.443137 0.364706
+v 9.047320 -0.970925 1.557191 0.690196 0.443137 0.368627
+v 9.465290 3.177850 3.547511 0.596078 0.352941 0.282353
+v 17.660997 -6.569220 20.282202 0.505882 0.258824 0.215686
+v 16.557198 -9.033780 19.871101 0.552941 0.290196 0.235294
+v 20.394499 -10.617700 24.778902 0.776471 0.517647 0.427451
+v 19.250498 -13.611500 23.636002 0.780392 0.513725 0.423529
+v 11.990899 1.078080 6.089581 0.576471 0.337255 0.266667
+v 12.157999 -1.685630 5.705281 0.647059 0.403922 0.329412
+v 8.839929 9.251840 7.807421 0.568627 0.341176 0.262745
+v 8.096309 14.803000 11.047301 0.600000 0.368627 0.290196
+v 11.552299 11.023700 14.935801 0.584314 0.360784 0.278431
+v 8.089608 23.007601 17.162300 0.607843 0.376471 0.290196
+v 12.042598 18.267500 20.407701 0.627451 0.396078 0.305882
+v 21.344799 3.444300 24.364801 0.760784 0.525490 0.435294
+v 10.378798 43.596600 19.092800 0.760784 0.545098 0.439216
+v 11.674199 50.650799 15.859901 0.796078 0.596078 0.509804
+v 54.439495 58.174599 40.267605 0.525490 0.352941 0.262745
+v 56.231796 64.326599 44.901005 0.525490 0.349020 0.258824
+v 20.091297 46.555901 20.626602 0.615686 0.435294 0.352941
+v 12.458998 40.488800 23.149502 0.682353 0.470588 0.364706
+v 11.517699 58.712601 14.732701 0.894118 0.666667 0.576471
+v 20.941198 69.476898 17.756903 0.811765 0.568627 0.458824
+v 28.594099 70.756203 20.988201 0.768627 0.537255 0.427451
+v 36.831398 72.183800 26.344303 0.705882 0.490196 0.380392
+v 44.649097 71.571800 32.518906 0.650980 0.447059 0.337255
+v 44.312595 65.052498 30.244703 0.650980 0.450980 0.349020
+v 51.160194 68.888397 38.786102 0.580392 0.392157 0.290196
+v 50.121696 63.216999 35.800903 0.592157 0.400000 0.301961
+v 29.676498 38.193501 27.577202 0.278431 0.203922 0.172549
+v 32.242195 35.520000 28.930403 0.176471 0.149020 0.133333
+v 36.059795 38.007198 28.101904 0.337255 0.254902 0.223529
+v 29.195498 28.762300 30.062502 0.537255 0.407843 0.376471
+v 35.387295 30.454100 30.872303 0.494118 0.427451 0.419608
+v 40.619995 36.623299 30.930103 0.376471 0.290196 0.262745
+v 40.689995 30.856199 33.447403 0.560784 0.435294 0.415686
+v 46.166798 34.721901 35.938004 0.427451 0.274510 0.219608
+v 24.791199 28.750900 30.347101 0.631373 0.439216 0.380392
+v 23.558298 30.911301 30.865202 0.654902 0.525490 0.490196
+v 23.990099 35.138500 30.007702 0.588235 0.525490 0.505882
+v 18.925999 31.543200 29.964102 0.607843 0.411765 0.345098
+v 22.978798 22.609301 29.898302 0.698039 0.478431 0.415686
+v 44.176495 24.024099 36.367702 0.666667 0.462745 0.368627
+v 48.844097 28.759600 40.696102 0.568627 0.372549 0.290196
+v 29.935898 20.776899 30.510502 0.764706 0.541176 0.462745
+v 37.725395 21.069901 32.600403 0.768627 0.552941 0.450980
+v 43.673397 41.639000 29.921003 0.639216 0.447059 0.364706
+v 30.249598 43.423698 24.863802 0.698039 0.498039 0.396078
+v 21.971298 38.089298 28.511002 0.552941 0.376471 0.305882
+v 18.851599 37.250599 29.058102 0.600000 0.403922 0.317647
+v 26.141699 38.694302 27.701101 0.415686 0.305882 0.258824
+v 24.366098 42.716400 25.250301 0.670588 0.470588 0.364706
+v 33.047497 40.919201 25.876904 0.588235 0.415686 0.341176
+v 38.202995 45.220299 25.513803 0.741176 0.541176 0.447059
+v 37.181396 41.326099 26.587204 0.592157 0.415686 0.341176
+v 40.767696 39.773300 28.946104 0.533333 0.368627 0.305882
+v 17.566698 40.536701 25.680403 0.682353 0.478431 0.372549
+v 12.599898 34.387402 26.919203 0.635294 0.439216 0.356863
+v 9.244298 36.241699 22.858601 0.670588 0.454902 0.349020
+v 42.106094 27.525101 34.601604 0.588235 0.396078 0.337255
+v 9.833448 28.316700 23.639999 0.592157 0.392157 0.298039
+v 18.639698 24.671700 29.695803 0.650980 0.447059 0.376471
+v 17.292599 20.688499 27.482101 0.654902 0.439216 0.341176
+v 13.333098 24.688499 26.033302 0.607843 0.407843 0.305882
+v 22.784698 17.533800 28.735502 0.721569 0.498039 0.407843
+v 30.601099 15.688600 30.290201 0.780392 0.560784 0.466667
+v 41.169495 16.181299 33.723103 0.792157 0.564706 0.458824
+v 47.120296 20.681999 37.970406 0.709804 0.501961 0.400000
+v 51.931698 25.841200 43.142902 0.639216 0.443137 0.341176
+v 52.340096 32.697899 43.622303 0.576471 0.384314 0.298039
+v 55.356895 32.597599 46.411205 0.619608 0.439216 0.341176
+v 53.222797 37.287899 42.089905 0.568627 0.392157 0.301961
+v 29.678698 24.733801 30.073902 0.717647 0.501961 0.431373
+v 36.489197 25.085699 31.599504 0.698039 0.498039 0.427451
+v 7.209608 -47.131500 19.961300 0.694118 0.474510 0.392157
+v 14.446698 -46.892300 22.922901 0.701961 0.486275 0.392157
+v 14.611198 -50.406300 25.080801 0.741176 0.521569 0.407843
+v 59.776695 32.152901 51.172405 0.615686 0.435294 0.333333
+v 55.757698 22.840000 45.449802 0.650980 0.447059 0.341176
+v 50.009796 17.205099 38.838005 0.725490 0.501961 0.396078
+v 41.713795 8.508140 32.452805 0.839216 0.580392 0.486275
+v 57.126797 40.041698 45.909004 0.552941 0.380392 0.294118
+v 60.104397 43.092098 50.342705 0.509804 0.345098 0.262745
+v 54.672398 42.407398 40.515404 0.556863 0.380392 0.305882
+v 56.906197 46.631001 42.925304 0.462745 0.305882 0.239216
+v 51.822495 43.786900 36.258904 0.596078 0.415686 0.337255
+v 54.271397 50.864399 37.835804 0.419608 0.282353 0.223529
+v 47.592297 46.765598 30.486204 0.627451 0.454902 0.384314
+v 49.821495 53.293598 31.830803 0.427451 0.298039 0.243137
+v 42.391796 49.445400 25.742804 0.607843 0.454902 0.388235
+v 44.034695 54.943100 26.506004 0.443137 0.317647 0.266667
+v 36.751995 50.112900 22.968603 0.545098 0.407843 0.349020
+v 37.753296 55.541100 22.525805 0.490196 0.364706 0.313725
+v 28.692497 48.042801 21.970001 0.600000 0.435294 0.356863
+v 31.105598 56.180599 19.828001 0.596078 0.450980 0.392157
+v 36.435097 81.925400 28.781404 0.682353 0.470588 0.364706
+v 45.338295 80.322098 35.563904 0.603922 0.411765 0.305882
+v 24.592999 80.095299 21.969002 0.772549 0.541176 0.427451
+v 11.213599 69.043297 15.977401 0.835294 0.588235 0.474510
+v 11.825998 81.413200 18.962801 0.803922 0.564706 0.447059
+v 21.538898 53.003700 17.177603 0.588235 0.439216 0.380392
+v -0.179365 43.542702 17.002100 0.898039 0.666667 0.564706
+v 5.625159 37.419102 18.996799 0.803922 0.576471 0.466667
+v 4.198629 23.955999 12.407200 0.686275 0.435294 0.345098
+v 14.535299 -2.114900 9.443011 0.631373 0.380392 0.309804
+v 15.685598 8.142320 20.454601 0.658824 0.427451 0.337255
+v 30.598497 1.488610 27.147802 0.839216 0.576471 0.490196
+v 29.978397 -6.397050 26.571701 0.894118 0.635294 0.545098
+v 41.297398 -1.405200 31.022003 0.854902 0.596078 0.509804
+v 36.111397 -6.540340 28.005903 0.882353 0.623529 0.537255
+v 44.276794 -9.991890 32.788902 0.811765 0.568627 0.478431
+v 36.580196 -12.147500 28.853804 0.874510 0.623529 0.525490
+v 23.545498 26.550501 30.193302 0.670588 0.454902 0.392157
+v 7.536978 -50.700600 22.940800 0.737255 0.513725 0.407843
+v 0.049966 -50.745201 22.357901 0.713725 0.490196 0.403922
+v -0.059187 -47.707500 19.745001 0.662745 0.450980 0.376471
+v 13.614799 -61.189201 26.770203 0.772549 0.549020 0.447059
+v 7.402448 -54.763500 24.320801 0.792157 0.549020 0.443137
+v 5.676798 -60.044102 24.120001 0.815686 0.568627 0.478431
+v 0.007680 -58.305000 23.528999 0.823529 0.568627 0.478431
+v 14.550098 -55.003899 26.621601 0.780392 0.549020 0.431373
+v 20.750698 -49.841400 28.390602 0.729412 0.505882 0.396078
+v 14.341599 -43.669201 21.142101 0.690196 0.443137 0.392157
+v 9.106318 -44.220402 18.280300 0.749020 0.454902 0.427451
+v 4.624769 -44.514198 17.213200 0.768627 0.458824 0.439216
+v 13.494699 1.203290 9.375581 0.541176 0.305882 0.247059
+v 17.587399 -3.090150 17.170603 0.521569 0.282353 0.231373
+v 15.390899 0.616895 13.529201 0.525490 0.290196 0.239216
+v 16.498499 -2.610490 13.351402 0.580392 0.333333 0.270588
+v 0.114789 -17.205601 13.556900 0.803922 0.576471 0.490196
+v 0.213039 -10.739900 5.333460 0.631373 0.419608 0.345098
+v 0.155695 9.698840 1.605760 0.737255 0.458824 0.372549
+v 63.699497 35.875702 57.276005 0.541176 0.364706 0.274510
+v 62.952995 26.737499 54.915302 0.588235 0.400000 0.301961
+v 62.913197 46.593300 56.027405 0.486275 0.321569 0.239216
+v 66.067696 49.149700 63.298607 0.435294 0.282353 0.207843
+v 67.025894 36.883900 64.329010 0.478431 0.313725 0.235294
+v 66.947090 22.464199 62.047707 0.505882 0.325490 0.243137
+v 62.186794 10.766600 50.973103 0.576471 0.376471 0.290196
+v 68.964989 11.545400 68.556404 0.415686 0.266667 0.200000
+v 64.644791 3.438430 56.958504 0.498039 0.317647 0.243137
+v 7.315678 30.263700 20.278500 0.635294 0.407843 0.309804
+v 16.478199 14.480500 23.730602 0.674510 0.435294 0.341176
+v 22.498598 11.176800 26.488401 0.752941 0.505882 0.411765
+v 30.975899 9.046680 28.818901 0.800000 0.545098 0.447059
+v 59.705097 18.493999 48.250504 0.619608 0.415686 0.317647
+v 53.774796 10.607700 40.580803 0.741176 0.505882 0.411765
+v 4.194539 31.168200 16.627800 0.733333 0.498039 0.388235
+v -0.049233 31.606899 15.159500 0.800000 0.552941 0.435294
+v 60.551994 58.758701 51.876305 0.482353 0.313725 0.231373
+v 64.570694 60.979000 60.018402 0.403922 0.266667 0.196078
+v 58.582897 51.884102 46.769802 0.482353 0.317647 0.239216
+v 58.461395 70.455803 49.594505 0.466667 0.313725 0.231373
+v 52.436497 76.538399 42.343403 0.533333 0.356863 0.262745
+v -0.208444 49.913601 15.256100 0.917647 0.698039 0.611765
+v -0.124523 58.086498 14.326200 0.905882 0.674510 0.584314
+v 0.274219 81.382401 18.191799 0.819608 0.576471 0.458824
+v 0.481933 93.290398 21.740499 0.725490 0.498039 0.384314
+v 12.405998 93.661400 22.874903 0.713725 0.486275 0.376471
+v 24.880598 93.906502 26.578302 0.658824 0.447059 0.341176
+v 36.480595 93.078102 32.634003 0.576471 0.388235 0.294118
+v 46.004696 90.203003 39.649605 0.505882 0.345098 0.258824
+v 53.584496 85.144096 46.494904 0.450980 0.305882 0.223529
+v 62.545998 71.367798 56.403603 0.364706 0.250980 0.188235
+v 59.255596 78.845100 52.443905 0.400000 0.274510 0.203922
+v 61.191795 85.050301 57.241005 0.333333 0.235294 0.180392
+v 65.452393 75.230003 61.807304 0.286275 0.207843 0.160784
+v 68.381790 63.713902 67.643311 0.266667 0.188235 0.149020
+v 69.324196 50.443100 71.447411 0.341176 0.227451 0.172549
+v 70.128990 37.903400 72.923409 0.360784 0.239216 0.180392
+v 70.487595 24.768299 72.393105 0.396078 0.250980 0.192157
+v 10.803699 -24.072800 15.435301 0.713725 0.501961 0.407843
+v 15.684198 -24.840200 18.155502 0.698039 0.494118 0.403922
+v 14.923498 -28.631399 17.459002 0.729412 0.498039 0.431373
+v 19.121199 -30.573601 20.995502 0.694118 0.478431 0.403922
+v 21.119598 -26.506599 21.583101 0.701961 0.509804 0.423529
+v 36.499996 -22.712601 30.213703 0.811765 0.572549 0.466667
+v 40.448395 -36.176701 36.400604 0.725490 0.509804 0.407843
+v 44.144596 -32.603699 38.731205 0.705882 0.490196 0.392157
+v 38.536495 -47.817600 40.317505 0.666667 0.474510 0.364706
+v 46.546295 -37.311100 43.387104 0.635294 0.439216 0.349020
+v 50.373398 -34.235699 47.127705 0.611765 0.423529 0.333333
+v 49.724796 -40.818298 50.185303 0.588235 0.403922 0.317647
+v 49.255295 -27.889099 42.907104 0.658824 0.454902 0.360784
+v 44.257797 -23.479799 35.807602 0.752941 0.525490 0.427451
+v 40.645798 -27.765900 34.063705 0.764706 0.537255 0.431373
+v 40.754898 -19.093399 32.012905 0.815686 0.576471 0.478431
+v 10.416199 -27.973200 14.961201 0.756863 0.498039 0.443137
+v 0.075350 -28.306400 12.898400 0.803922 0.521569 0.466667
+v 0.092755 -24.430799 13.505000 0.772549 0.529412 0.443137
+v 0.110785 -20.513000 13.908800 0.807843 0.588235 0.501961
+v 0.085609 -54.512001 23.610901 0.784314 0.537255 0.443137
+v 20.909399 -54.946800 30.047201 0.729412 0.509804 0.392157
+v 35.645298 -55.715099 42.223003 0.623529 0.439216 0.337255
+v 49.814896 -48.565498 56.933704 0.517647 0.356863 0.274510
+v 47.977795 -55.414200 62.039703 0.443137 0.301961 0.231373
+v 55.688496 -28.994600 53.447903 0.560784 0.380392 0.301961
+v 55.983391 -43.000198 65.692207 0.450980 0.305882 0.239216
+v 56.362797 -36.061699 59.322903 0.517647 0.349020 0.274510
+v 57.892796 -19.573200 52.540302 0.560784 0.376471 0.294118
+v 70.365593 -2.993800 89.379608 0.321569 0.211765 0.164706
+v 71.177193 8.937180 81.271606 0.321569 0.211765 0.164706
+v 72.558990 10.622100 91.867805 0.298039 0.200000 0.156863
+v 66.125992 -21.471800 81.920410 0.341176 0.223529 0.168627
+v 67.528091 -10.306600 77.117210 0.364706 0.239216 0.180392
+v 61.701691 -27.660801 67.740509 0.443137 0.294118 0.227451
+v 68.337189 -0.022244 71.967606 0.380392 0.243137 0.188235
+v 63.424397 -15.896200 64.452606 0.443137 0.290196 0.219608
+v 68.125694 -16.648800 92.619408 0.301961 0.200000 0.156863
+v 63.768192 -32.212200 86.796310 0.305882 0.203922 0.156863
+v 61.167294 -36.372501 75.628105 0.376471 0.250980 0.192157
+v 59.626293 -41.868698 80.668411 0.333333 0.223529 0.172549
+v 64.162796 -6.066620 60.428703 0.454902 0.294118 0.223529
+v 57.393696 -11.239100 47.991703 0.611765 0.411765 0.329412
+v 72.524994 24.387699 82.025009 0.282353 0.188235 0.145098
+v 73.856789 35.723499 88.540108 0.105882 0.074510 0.062745
+v 73.683792 23.761801 91.300209 0.243137 0.164706 0.129412
+v 74.501190 30.125500 94.839706 0.094118 0.062745 0.050980
+v 74.034592 22.480499 96.303810 0.141176 0.098039 0.078431
+v 72.831192 7.859240 98.770004 0.149020 0.101961 0.082353
+v 69.296593 -13.425300 99.241905 0.305882 0.200000 0.160784
+v 71.395790 -3.606020 100.302010 0.325490 0.215686 0.172549
+v 71.129791 -7.143250 103.242004 0.188235 0.125490 0.101961
+v 68.107590 -18.211399 101.004005 0.133333 0.086275 0.070588
+v 72.363594 -0.137348 102.838005 0.176471 0.117647 0.094118
+v 46.213596 99.739601 44.501404 0.388235 0.266667 0.200000
+v 54.685898 92.834099 51.120205 0.376471 0.258824 0.192157
+v 55.596695 97.970802 54.777103 0.294118 0.207843 0.160784
+v 59.781796 92.442200 58.018204 0.125490 0.090196 0.066667
+v 50.586895 101.808998 50.572002 0.172549 0.121569 0.094118
+v 43.422997 104.478996 44.205505 0.184314 0.125490 0.098039
+v 72.007690 49.799099 78.898109 0.250980 0.176471 0.137255
+v 73.057594 49.127201 82.269409 0.094118 0.066667 0.054902
+v 49.949097 58.855202 33.782906 0.564706 0.388235 0.301961
+v 44.336697 60.042801 28.311203 0.603922 0.419608 0.337255
+v 37.959198 59.937000 23.450504 0.674510 0.482353 0.400000
+v 4.464459 16.161301 7.486950 0.682353 0.423529 0.341176
+v 0.094371 16.649401 6.087460 0.745098 0.466667 0.376471
+v 0.014745 24.425800 10.934700 0.745098 0.482353 0.384314
+v 71.478493 58.516998 75.568504 0.105882 0.074510 0.058824
+v 68.260490 72.263603 66.620407 0.137255 0.101961 0.082353
+v 70.731094 66.735497 71.686806 0.109804 0.082353 0.066667
+v 72.507889 38.181301 81.341507 0.254902 0.176471 0.137255
+v 64.500496 82.174896 61.573006 0.149020 0.109804 0.082353
+v 55.767296 99.280998 55.781605 0.145098 0.101961 0.078431
+v 12.876298 100.102997 25.744701 0.317647 0.215686 0.164706
+v 0.286601 -4.793040 -2.721520 0.894118 0.643137 0.568627
+v 64.638489 -29.722000 98.167007 0.121569 0.082353 0.062745
+v 58.149193 -45.228298 86.664307 0.290196 0.196078 0.152941
+v 62.356594 -36.605999 93.365707 0.274510 0.184314 0.141176
+v 60.716892 -40.186600 93.595604 0.113725 0.078431 0.058824
+v 56.109493 -48.650299 86.931511 0.141176 0.098039 0.074510
+v 49.385094 -57.278099 77.067108 0.160784 0.109804 0.082353
+v 66.355392 -24.387800 96.374504 0.270588 0.184314 0.141176
+v 37.257298 34.215500 30.609003 0.415686 0.392157 0.388235
+v 39.520298 33.790501 31.976805 0.529412 0.494118 0.490196
+v 27.428999 33.649700 29.901602 0.372549 0.333333 0.309804
+v 23.755198 -35.746101 28.257301 0.482353 0.278431 0.235294
+v 3.793149 -35.116299 16.927900 0.600000 0.278431 0.294118
+v 3.855958 -38.129200 17.080799 0.756863 0.376471 0.407843
+v -0.023884 -38.223301 16.909401 0.749020 0.368627 0.400000
+v 8.071998 -35.035198 18.102501 0.588235 0.266667 0.286275
+v 8.431998 -37.966801 18.149799 0.717647 0.352941 0.384314
+v 13.239598 -35.231899 20.689901 0.545098 0.262745 0.274510
+v 13.372499 -37.816101 20.728703 0.631373 0.317647 0.337255
+v 17.666399 -35.273201 23.658503 0.447059 0.227451 0.227451
+v 17.640099 -37.751598 24.088802 0.556863 0.282353 0.286275
+v 21.365198 -34.941399 26.083202 0.478431 0.266667 0.231373
+v 21.738897 -38.277302 27.387302 0.564706 0.325490 0.298039
+v 21.377197 -34.903000 26.050001 0.490196 0.278431 0.239216
+v -0.125513 -44.742699 17.141100 0.749020 0.454902 0.419608
+v 14.317698 -31.063400 17.972801 0.662745 0.403922 0.364706
+v 6.533319 -27.864201 13.520501 0.788235 0.505882 0.458824
+v 17.674997 -35.227402 23.629501 0.427451 0.219608 0.211765
+v 18.364597 -33.324001 22.496202 0.580392 0.341176 0.301961
+v 13.245798 -35.168499 20.655602 0.537255 0.258824 0.270588
+v 13.862498 -33.057598 19.452902 0.596078 0.333333 0.313725
+v 8.065038 -34.963299 18.048201 0.580392 0.270588 0.282353
+v 9.277578 -32.971199 17.116100 0.631373 0.349020 0.337255
+v 3.799159 -35.048901 16.841700 0.600000 0.278431 0.294118
+v 5.629269 -33.251099 16.003500 0.643137 0.349020 0.341176
+v 2.758119 -33.165501 15.103900 0.650980 0.345098 0.341176
+v 0.038323 -35.330101 16.649599 0.396078 0.176471 0.192157
+v 0.067189 -33.274799 14.942100 0.662745 0.345098 0.352941
+v 2.880549 -31.155199 13.597900 0.701961 0.388235 0.380392
+v 5.936328 -30.985800 14.264301 0.694118 0.396078 0.380392
+v 4.098669 -41.269699 16.459999 0.811765 0.458824 0.486275
+v 8.892108 -41.014599 17.645000 0.760784 0.427451 0.450980
+v 9.781240 -30.931801 15.664701 0.682353 0.400000 0.376471
+v 13.905798 -40.587799 20.457802 0.658824 0.360784 0.364706
+v 18.077698 -39.992001 24.048601 0.603922 0.341176 0.325490
+v 0.069898 -31.269300 13.405600 0.725490 0.411765 0.407843
+v 37.322296 65.013603 24.625504 0.709804 0.498039 0.396078
+v 29.652199 63.115200 19.714302 0.780392 0.560784 0.466667
+v 21.287199 60.616798 16.368002 0.847059 0.623529 0.533333
+v 0.127883 -14.200400 12.254200 0.741176 0.509804 0.435294
+v 0.278404 1.040420 -3.071150 0.870588 0.596078 0.513725
+v -0.116712 37.803699 17.313200 0.882353 0.650980 0.533333
+v 0.279360 -7.378550 -0.992634 0.819608 0.560784 0.478431
+v 0.046502 68.758400 15.460200 0.854902 0.607843 0.494118
+v 0.599951 99.454498 24.243299 0.349020 0.235294 0.180392
+v 0.031221 -35.395699 16.746401 0.458824 0.203922 0.219608
+v -0.106864 -41.376099 16.290199 0.800000 0.447059 0.474510
+v -53.841106 -48.983299 71.426689 0.407843 0.274510 0.215686
+v -51.275307 -53.996498 75.538391 0.368627 0.247059 0.192157
+v -44.535206 -60.645199 65.560791 0.411765 0.274510 0.211765
+v -18.750103 -74.796997 35.786999 0.529412 0.372549 0.298039
+v -14.085703 -77.391403 34.978100 0.509804 0.364706 0.290196
+v -11.957403 -73.963402 30.096098 0.635294 0.450980 0.368627
+v -6.566503 -77.262100 30.449800 0.596078 0.431373 0.349020
+v -7.564453 -78.871101 33.695099 0.501961 0.360784 0.290196
+v -22.016403 -70.478699 35.529198 0.588235 0.415686 0.329412
+v -17.863401 -68.837303 30.791098 0.674510 0.478431 0.388235
+v -39.540703 -65.852203 65.266602 0.180392 0.121569 0.090196
+v -33.939205 -69.790604 58.118694 0.388235 0.262745 0.200000
+v -31.925804 -67.925903 48.449497 0.458824 0.317647 0.239216
+v -38.444904 -64.204102 56.125298 0.450980 0.305882 0.231373
+v -28.939505 -72.407097 50.816597 0.392157 0.270588 0.203922
+v -23.188004 -74.982597 43.634598 0.415686 0.290196 0.223529
+v -26.559404 -70.188103 41.544697 0.505882 0.352941 0.270588
+v -24.695004 -75.705597 50.416195 0.364706 0.250980 0.192157
+v -16.072004 -78.604698 40.981701 0.403922 0.282353 0.223529
+v -17.844904 -79.063103 47.658199 0.360784 0.250980 0.196078
+v -29.056807 -73.493698 56.917194 0.176471 0.117647 0.090196
+v -10.376904 -81.684601 48.519402 0.168627 0.121569 0.101961
+v -19.158804 -78.889801 51.744801 0.176471 0.121569 0.094118
+v -9.669264 -81.344200 44.934101 0.376471 0.274510 0.223529
+v -27.571703 -64.815102 39.005997 0.603922 0.423529 0.329412
+v -33.198803 -61.133202 43.968197 0.576471 0.403922 0.305882
+v -38.691704 -59.988998 50.588497 0.525490 0.364706 0.274510
+v -42.734406 -58.145699 55.489697 0.501961 0.341176 0.258824
+v -8.533234 -80.387604 38.906601 0.407843 0.294118 0.239216
+v -11.915502 -67.795502 26.995598 0.745098 0.529412 0.443137
+v -23.853804 -66.357597 35.367195 0.635294 0.447059 0.352941
+v -34.301003 104.745003 38.021896 0.207843 0.141176 0.109804
+v -35.090004 101.537003 36.596096 0.427451 0.282353 0.215686
+v -26.006802 101.713997 31.525198 0.270588 0.176471 0.133333
+v -59.540703 -0.172551 48.112595 0.619608 0.403922 0.325490
+v -50.470104 -9.587020 38.842495 0.729412 0.490196 0.411765
+v -52.236305 -0.444375 39.310497 0.745098 0.501961 0.419608
+v -29.805405 -56.547798 37.997498 0.666667 0.474510 0.364706
+v -26.022703 -54.744801 34.242695 0.694118 0.490196 0.376471
+v -26.218403 -49.663700 32.767998 0.717647 0.498039 0.388235
+v -25.394102 -44.041801 30.750198 0.729412 0.505882 0.396078
+v -31.448503 -44.790001 34.639797 0.737255 0.525490 0.403922
+v -32.419804 -50.899601 37.823597 0.690196 0.490196 0.376471
+v -21.044302 -62.170700 31.980398 0.705882 0.498039 0.400000
+v -26.501503 -59.568001 36.049995 0.670588 0.474510 0.368627
+v -44.034306 -51.844898 51.117294 0.560784 0.384314 0.294118
+v -43.882004 -45.489799 45.821396 0.611765 0.419608 0.329412
+v -39.261204 -54.099098 46.284496 0.592157 0.411765 0.309804
+v -42.625404 -40.450100 41.156296 0.662745 0.458824 0.360784
+v -38.801704 -42.761002 38.411594 0.713725 0.501961 0.396078
+v -52.231606 -19.653799 44.279896 0.658824 0.443137 0.356863
+v -46.410305 -16.803400 36.662895 0.756863 0.517647 0.427451
+v -33.261505 -35.711102 31.876297 0.741176 0.537255 0.423529
+v -32.671204 -40.674500 33.693596 0.749020 0.545098 0.423529
+v -35.453705 -44.188400 36.643997 0.733333 0.529412 0.411765
+v -27.096502 -39.330601 30.955198 0.713725 0.498039 0.384314
+v -21.293402 -21.040701 22.753899 0.729412 0.498039 0.407843
+v -23.889301 -17.153200 25.090498 0.796078 0.537255 0.431373
+v -27.442001 -24.677700 25.678598 0.741176 0.529412 0.435294
+v -27.894802 -35.143299 29.500797 0.698039 0.494118 0.384314
+v -24.245901 -32.390999 25.918598 0.666667 0.470588 0.380392
+v -26.250002 -28.942699 25.473698 0.721569 0.529412 0.431373
+v -22.010002 -41.590900 27.971397 0.670588 0.450980 0.364706
+v -20.023802 -45.808498 26.881199 0.705882 0.486275 0.384314
+v -18.923801 -42.662899 25.164799 0.666667 0.435294 0.364706
+v -28.864202 -21.376200 26.642797 0.784314 0.545098 0.443137
+v -26.229301 -16.210501 26.192198 0.827451 0.568627 0.454902
+v -30.681902 -16.933201 27.620897 0.858824 0.603922 0.490196
+v -15.621302 -20.221901 19.470098 0.690196 0.470588 0.380392
+v -16.465702 -15.840100 21.345999 0.729412 0.474510 0.384314
+v -22.337402 -3.399040 25.406399 0.741176 0.494118 0.396078
+v -21.458002 -7.348170 25.506098 0.721569 0.474510 0.380392
+v -18.854101 -4.005630 23.601898 0.533333 0.301961 0.250980
+v -26.091501 -11.087000 26.476297 0.878431 0.623529 0.513725
+v -23.841803 -13.780800 25.798899 0.831373 0.572549 0.458824
+v -3.088731 -28.148701 13.002100 0.811765 0.521569 0.478431
+v -3.510681 -24.156099 13.543200 0.788235 0.541176 0.450980
+v -7.061972 -23.912500 14.032299 0.764706 0.529412 0.435294
+v -3.687661 -20.046000 14.031400 0.807843 0.584314 0.494118
+v -9.043321 -19.022600 15.774699 0.705882 0.494118 0.403922
+v -2.931611 -17.201000 13.721800 0.807843 0.584314 0.494118
+v -4.743171 -14.022100 13.188100 0.737255 0.505882 0.431373
+v -30.459501 -31.690300 28.703999 0.729412 0.537255 0.435294
+v -36.833103 -39.026501 35.269897 0.749020 0.537255 0.423529
+v -36.962105 -31.691099 32.883995 0.741176 0.517647 0.411765
+v -32.433002 -26.637400 28.764599 0.749020 0.529412 0.427451
+v -10.186301 -10.045900 13.821699 0.560784 0.341176 0.290196
+v -3.445531 -10.998200 9.252610 0.556863 0.337255 0.278431
+v -6.762481 -8.908630 9.055569 0.494118 0.294118 0.258824
+v -10.854001 -7.895510 11.215299 0.427451 0.250980 0.211765
+v -12.950301 -8.438200 13.560799 0.588235 0.368627 0.305882
+v -14.310402 -9.532510 16.465097 0.674510 0.411765 0.333333
+v -14.624502 -11.047600 19.142298 0.615686 0.345098 0.282353
+v -12.154902 -13.660100 18.041199 0.662745 0.403922 0.337255
+v -15.637602 -12.306800 21.315199 0.635294 0.376471 0.313725
+v -16.678001 0.018672 17.475899 0.454902 0.239216 0.188235
+v -14.077101 3.077540 13.715799 0.470588 0.254902 0.203922
+v -15.178302 3.616140 17.563799 0.490196 0.270588 0.215686
+v -11.450801 5.402890 9.661229 0.498039 0.278431 0.219608
+v -12.646701 6.457730 14.109399 0.513725 0.294118 0.231373
+v -17.444702 -0.924688 20.193298 0.450980 0.227451 0.184314
+v -18.059002 -7.877630 23.064198 0.529412 0.286275 0.243137
+v -10.368801 8.722010 10.384299 0.521569 0.301961 0.235294
+v -17.744202 0.517129 21.854698 0.521569 0.294118 0.239216
+v -17.907402 -3.455530 20.442198 0.458824 0.231373 0.188235
+v -16.974201 -10.677600 22.292898 0.596078 0.341176 0.286275
+v -2.506120 -2.028510 -3.080180 0.913725 0.674510 0.607843
+v -2.469770 -4.768590 -2.486050 0.886275 0.623529 0.552941
+v -5.439900 -1.886710 -1.785390 0.847059 0.584314 0.517647
+v -4.991440 -4.960640 -1.139691 0.803922 0.541176 0.462745
+v -7.917010 -5.451350 2.487329 0.647059 0.415686 0.341176
+v -5.044560 -7.893640 1.983989 0.650980 0.415686 0.349020
+v -5.685171 -8.434240 5.155859 0.486275 0.290196 0.243137
+v -7.915720 -7.024910 5.173709 0.537255 0.337255 0.282353
+v -5.069190 8.041960 2.428190 0.662745 0.396078 0.317647
+v -5.017550 3.745630 -0.921486 0.737255 0.454902 0.372549
+v -2.789870 4.372350 -1.462200 0.772549 0.486275 0.396078
+v -4.999330 1.117930 -1.968431 0.792157 0.509804 0.427451
+v -2.481880 1.025610 -2.840810 0.854902 0.576471 0.494118
+v -3.088550 -7.134860 -0.568535 0.784314 0.529412 0.447059
+v -2.741080 -9.109190 1.948480 0.694118 0.462745 0.384314
+v -2.959080 -10.054000 5.285800 0.576471 0.360784 0.298039
+v -11.491201 -4.921930 6.367449 0.674510 0.447059 0.372549
+v -10.020901 -7.086960 8.412829 0.474510 0.290196 0.243137
+v -14.259301 -5.239610 9.939149 0.690196 0.443137 0.368627
+v -12.878401 -7.387150 11.456599 0.564706 0.352941 0.290196
+v -16.464901 -5.716880 13.556098 0.647059 0.388235 0.317647
+v -14.983801 -8.065290 14.371499 0.670588 0.415686 0.337255
+v -17.583902 -6.038910 17.100498 0.560784 0.305882 0.247059
+v -16.457602 -8.372230 17.068798 0.623529 0.352941 0.286275
+v -6.973730 2.091660 -0.242824 0.705882 0.435294 0.356863
+v -8.818600 -0.891477 1.383239 0.690196 0.439216 0.364706
+v -9.290710 3.329020 3.458919 0.580392 0.337255 0.266667
+v -17.710503 -6.541460 20.280298 0.498039 0.250980 0.207843
+v -16.597502 -9.103660 19.840698 0.560784 0.290196 0.239216
+v -20.254002 -10.584600 24.919098 0.725490 0.466667 0.380392
+v -19.110601 -13.563800 23.751698 0.756863 0.490196 0.400000
+v -11.853701 1.210450 5.993569 0.552941 0.313725 0.247059
+v -12.033501 -1.648010 5.569239 0.631373 0.388235 0.313725
+v -8.771261 9.366440 7.745239 0.549020 0.317647 0.247059
+v -8.091181 14.909600 10.967199 0.588235 0.352941 0.278431
+v -11.574701 11.120500 14.904899 0.564706 0.341176 0.262745
+v -8.252582 23.095100 17.156300 0.592157 0.356863 0.274510
+v -12.164402 18.366899 20.500298 0.607843 0.372549 0.286275
+v -21.284002 3.497090 24.570698 0.768627 0.525490 0.431373
+v -10.742502 43.732601 19.227800 0.772549 0.552941 0.447059
+v -12.018201 50.872398 15.973799 0.796078 0.596078 0.513725
+v -53.977802 58.171299 39.694695 0.501961 0.325490 0.250980
+v -55.620903 64.281502 44.285595 0.509804 0.329412 0.247059
+v -20.308903 46.723099 20.792599 0.607843 0.427451 0.345098
+v -12.843102 40.605900 23.333698 0.694118 0.474510 0.368627
+v -11.662601 58.885700 14.851099 0.890196 0.662745 0.576471
+v -20.507902 69.580101 17.878298 0.800000 0.549020 0.435294
+v -27.933802 70.843803 20.968597 0.749020 0.509804 0.400000
+v -36.014503 72.235001 26.102497 0.690196 0.466667 0.356863
+v -43.836502 71.610802 32.086895 0.631373 0.423529 0.317647
+v -43.620102 65.109001 29.763796 0.635294 0.427451 0.329412
+v -50.451702 68.894699 38.239597 0.564706 0.372549 0.278431
+v -49.542004 63.241699 35.253395 0.576471 0.380392 0.290196
+v -29.960102 38.037498 27.851099 0.282353 0.203922 0.168627
+v -32.476303 35.348900 29.197298 0.176471 0.149020 0.133333
+v -36.246204 37.764400 28.355696 0.329412 0.254902 0.223529
+v -29.328802 28.733900 30.342098 0.529412 0.392157 0.360784
+v -35.523403 30.304701 31.130497 0.482353 0.411765 0.400000
+v -40.727005 36.324501 31.155497 0.392157 0.313725 0.294118
+v -40.839203 30.600300 33.689297 0.568627 0.435294 0.411765
+v -46.191402 34.452000 36.131695 0.427451 0.274510 0.223529
+v -24.977001 28.765301 30.636297 0.627451 0.431373 0.372549
+v -23.804102 30.909100 31.155798 0.686275 0.556863 0.521569
+v -24.319902 35.130699 30.242397 0.592157 0.521569 0.505882
+v -19.257402 31.576700 30.228998 0.611765 0.411765 0.341176
+v -23.152302 22.654400 30.191898 0.721569 0.498039 0.431373
+v -44.272903 23.796400 36.654297 0.666667 0.454902 0.360784
+v -48.903404 28.476500 40.922897 0.552941 0.352941 0.274510
+v -30.033503 20.755199 30.809198 0.784314 0.556863 0.474510
+v -37.799706 20.942499 32.892296 0.772549 0.545098 0.443137
+v -43.489803 41.473999 30.150797 0.639216 0.439216 0.360784
+v -30.361101 43.423199 25.189299 0.698039 0.494118 0.392157
+v -22.342802 38.114101 28.789198 0.560784 0.376471 0.301961
+v -19.248802 37.309799 29.324799 0.603922 0.396078 0.309804
+v -26.459501 38.629799 27.978798 0.427451 0.305882 0.258824
+v -24.593601 42.781898 25.542498 0.670588 0.466667 0.360784
+v -33.258904 40.763699 26.193096 0.592157 0.419608 0.345098
+v -37.950806 45.187599 25.795197 0.741176 0.533333 0.435294
+v -37.224903 41.155800 26.895596 0.592157 0.411765 0.337255
+v -40.750904 39.547798 29.199097 0.537255 0.368627 0.305882
+v -17.907701 40.627102 25.913097 0.686275 0.478431 0.372549
+v -12.980102 34.449100 27.123398 0.639216 0.439216 0.352941
+v -9.614821 36.339699 23.000999 0.678431 0.458824 0.352941
+v -42.234406 27.275101 34.868195 0.588235 0.388235 0.329412
+v -10.124502 28.367300 23.751200 0.580392 0.372549 0.282353
+v -18.888203 24.715599 29.982798 0.658824 0.454902 0.380392
+v -17.454302 20.764400 27.755598 0.639216 0.419608 0.321569
+v -13.572302 24.756201 26.250998 0.592157 0.388235 0.286275
+v -22.865002 17.581499 29.052998 0.733333 0.505882 0.411765
+v -30.623402 15.645700 30.650799 0.803922 0.576471 0.482353
+v -41.184105 16.006500 34.079998 0.796078 0.560784 0.450980
+v -47.160904 20.448400 38.277298 0.705882 0.490196 0.388235
+v -51.919205 25.598900 43.368195 0.623529 0.423529 0.321569
+v -52.268005 32.498501 43.699497 0.568627 0.372549 0.290196
+v -55.200302 32.461800 46.434196 0.611765 0.419608 0.329412
+v -53.071705 37.138699 42.057297 0.576471 0.388235 0.301961
+v -29.791203 24.710300 30.338198 0.729412 0.513725 0.439216
+v -36.564205 24.958700 31.851896 0.698039 0.490196 0.415686
+v -7.289592 -47.139000 20.103399 0.694118 0.474510 0.392157
+v -14.413102 -46.868698 23.217598 0.705882 0.486275 0.392157
+v -14.603202 -50.371601 25.371597 0.741176 0.517647 0.407843
+v -59.491802 32.046799 51.147495 0.603922 0.415686 0.321569
+v -55.639103 22.632401 45.670296 0.639216 0.431373 0.325490
+v -49.984604 16.973101 39.163597 0.725490 0.494118 0.388235
+v -41.621704 8.346640 32.884995 0.843137 0.576471 0.478431
+v -56.876003 39.973801 45.675396 0.556863 0.372549 0.298039
+v -59.767105 43.045399 49.976097 0.501961 0.329412 0.258824
+v -54.420605 42.338902 40.295395 0.568627 0.384314 0.313725
+v -56.593704 46.599098 42.486496 0.454902 0.294118 0.239216
+v -51.546104 43.681000 36.144497 0.615686 0.423529 0.349020
+v -53.910103 50.838902 37.405296 0.403922 0.266667 0.215686
+v -47.176605 46.687801 30.461897 0.658824 0.470588 0.400000
+v -49.335606 53.263500 31.465897 0.419608 0.286275 0.239216
+v -41.835705 49.451500 25.770996 0.623529 0.458824 0.388235
+v -43.368805 54.963402 26.223997 0.435294 0.305882 0.258824
+v -36.252106 50.171902 23.068497 0.549020 0.400000 0.341176
+v -37.054005 55.634499 22.359797 0.482353 0.352941 0.301961
+v -28.583101 48.147099 22.169699 0.588235 0.423529 0.345098
+v -30.643501 56.322201 19.778698 0.596078 0.443137 0.384314
+v -35.441105 81.907204 28.633097 0.662745 0.443137 0.337255
+v -44.335304 80.277100 35.155895 0.584314 0.388235 0.286275
+v -23.805302 80.078201 22.057999 0.756863 0.517647 0.403922
+v -11.024801 69.106598 16.104898 0.835294 0.584314 0.470588
+v -11.198602 81.384598 19.098099 0.800000 0.556863 0.435294
+v -21.602503 53.225300 17.283098 0.596078 0.443137 0.384314
+v -5.917871 37.487801 19.084600 0.811765 0.580392 0.466667
+v -4.247971 24.037399 12.356400 0.686275 0.435294 0.345098
+v -14.467901 -2.056030 9.347089 0.611765 0.356863 0.290196
+v -15.684502 8.225480 20.506998 0.647059 0.411765 0.325490
+v -30.492302 1.468230 27.575397 0.847059 0.576471 0.486275
+v -29.855103 -6.375450 26.988998 0.890196 0.627451 0.537255
+v -41.125404 -1.490210 31.515596 0.854902 0.580392 0.494118
+v -35.953903 -6.553680 28.526196 0.882353 0.615686 0.525490
+v -44.049503 -10.001100 33.293495 0.819608 0.564706 0.478431
+v -36.408905 -12.117100 29.324495 0.878431 0.615686 0.517647
+v -23.772902 26.589300 30.467398 0.690196 0.466667 0.400000
+v -7.501412 -50.686699 23.065300 0.737255 0.509804 0.403922
+v -13.754702 -61.147598 27.150497 0.772549 0.545098 0.443137
+v -7.330832 -54.754902 24.476400 0.796078 0.552941 0.447059
+v -5.799892 -60.043400 24.272900 0.815686 0.568627 0.478431
+v -14.562901 -54.969002 26.952198 0.784314 0.549020 0.431373
+v -20.703901 -49.802700 28.816797 0.729412 0.501961 0.392157
+v -14.287502 -43.688099 21.461899 0.698039 0.447059 0.392157
+v -9.185982 -44.236801 18.529900 0.749020 0.454902 0.427451
+v -4.844681 -44.535198 17.332600 0.772549 0.458824 0.443137
+v -13.385301 1.321240 9.319969 0.517647 0.282353 0.223529
+v -17.594803 -2.992360 17.184399 0.494118 0.258824 0.207843
+v -15.316101 0.718187 13.531699 0.490196 0.262745 0.211765
+v -16.484503 -2.517690 13.317098 0.556863 0.305882 0.247059
+v -63.277405 35.791199 57.119396 0.525490 0.345098 0.266667
+v -62.593704 26.610701 54.955097 0.576471 0.380392 0.290196
+v -62.468002 46.489700 55.637897 0.466667 0.301961 0.227451
+v -65.474205 48.985901 62.915993 0.419608 0.262745 0.196078
+v -66.472511 36.767601 64.102196 0.454902 0.290196 0.219608
+v -66.524406 22.424500 62.011292 0.490196 0.309804 0.235294
+v -61.938904 10.699000 51.148296 0.568627 0.364706 0.286275
+v -68.543007 11.577000 68.501892 0.411765 0.258824 0.200000
+v -64.340508 3.476640 57.093597 0.498039 0.313725 0.247059
+v -7.594552 30.348700 20.315800 0.627451 0.396078 0.298039
+v -16.549402 14.557100 23.902597 0.662745 0.419608 0.325490
+v -22.511202 11.211700 26.775698 0.764706 0.505882 0.407843
+v -30.923302 8.988230 29.229298 0.815686 0.549020 0.447059
+v -59.490303 18.310400 48.448795 0.607843 0.396078 0.301961
+v -53.647404 10.434700 40.909695 0.737255 0.494118 0.403922
+v -4.381762 31.257401 16.638100 0.737255 0.494118 0.388235
+v -60.029804 58.640598 51.293797 0.462745 0.294118 0.219608
+v -64.019104 60.767300 59.519794 0.392157 0.250980 0.188235
+v -58.184303 51.842499 46.189598 0.458824 0.298039 0.227451
+v -57.762302 70.295197 48.925396 0.447059 0.290196 0.215686
+v -51.554703 76.438698 41.738796 0.513725 0.337255 0.247059
+v -11.345502 93.571404 23.007797 0.709804 0.482353 0.372549
+v -23.729801 93.736099 26.669699 0.647059 0.435294 0.329412
+v -35.271004 92.898697 32.481598 0.556863 0.364706 0.274510
+v -44.764503 89.974899 39.158897 0.482353 0.313725 0.235294
+v -52.434803 84.860397 45.720497 0.427451 0.278431 0.207843
+v -61.925304 71.077103 55.743397 0.360784 0.239216 0.180392
+v -58.381702 78.523102 51.614697 0.380392 0.250980 0.188235
+v -60.005302 84.621002 56.387897 0.313725 0.215686 0.168627
+v -64.720604 74.840401 61.180397 0.278431 0.196078 0.152941
+v -67.735107 63.471001 67.175995 0.274510 0.188235 0.149020
+v -68.669807 50.221901 71.097496 0.333333 0.215686 0.168627
+v -69.477310 37.736198 72.644493 0.352941 0.227451 0.176471
+v -69.931206 24.711700 72.207596 0.388235 0.239216 0.188235
+v -10.646601 -24.065701 15.460499 0.713725 0.498039 0.403922
+v -15.570902 -24.833599 18.256699 0.694118 0.490196 0.400000
+v -14.838702 -28.652700 17.550598 0.725490 0.498039 0.427451
+v -19.051203 -30.614100 21.181898 0.694118 0.478431 0.403922
+v -21.021603 -26.519899 21.785698 0.698039 0.501961 0.415686
+v -36.361103 -22.694599 30.639095 0.811765 0.568627 0.462745
+v -40.278202 -36.076199 36.963898 0.725490 0.509804 0.403922
+v -43.906403 -32.487301 39.282295 0.705882 0.486275 0.392157
+v -38.385506 -47.603401 40.941395 0.666667 0.470588 0.364706
+v -46.264404 -37.117699 43.960796 0.643137 0.439216 0.349020
+v -50.024704 -34.044201 47.640896 0.619608 0.419608 0.337255
+v -49.385105 -40.522999 50.694595 0.596078 0.403922 0.321569
+v -48.941204 -27.787399 43.411995 0.662745 0.450980 0.364706
+v -44.039505 -23.420401 36.323795 0.756863 0.529412 0.431373
+v -40.469105 -27.708000 34.571297 0.768627 0.537255 0.435294
+v -40.584103 -19.038799 32.502396 0.819608 0.576471 0.474510
+v -10.270801 -27.988501 14.987799 0.760784 0.501961 0.447059
+v -20.882402 -54.889301 30.517797 0.729412 0.509804 0.392157
+v -35.545204 -55.400200 42.849697 0.627451 0.439216 0.337255
+v -49.520702 -48.135101 57.375698 0.533333 0.360784 0.282353
+v -47.788506 -54.890400 62.359497 0.466667 0.313725 0.243137
+v -55.262604 -28.814100 53.853397 0.572549 0.380392 0.305882
+v -55.605106 -42.532398 65.974190 0.466667 0.313725 0.247059
+v -55.941505 -35.714100 59.684597 0.529412 0.349020 0.282353
+v -57.457302 -19.495899 52.872997 0.568627 0.376471 0.301961
+v -69.852905 -2.797300 89.256996 0.333333 0.215686 0.168627
+v -70.631210 9.026070 81.100189 0.333333 0.211765 0.164706
+v -72.047607 10.667300 91.656296 0.305882 0.200000 0.160784
+v -65.526306 -21.184401 81.961395 0.352941 0.227451 0.176471
+v -66.953011 -10.110300 77.137192 0.368627 0.235294 0.180392
+v -61.182209 -27.386299 67.969193 0.447059 0.290196 0.227451
+v -67.878204 0.099053 71.940689 0.384314 0.243137 0.188235
+v -62.927803 -15.756000 64.638695 0.447059 0.286275 0.223529
+v -67.671104 -16.365200 92.531090 0.329412 0.215686 0.168627
+v -63.259407 -31.738800 86.818794 0.321569 0.211765 0.164706
+v -60.684906 -35.904400 75.737091 0.392157 0.258824 0.203922
+v -59.222309 -41.333500 80.770195 0.352941 0.235294 0.184314
+v -63.768204 -5.981940 60.569996 0.462745 0.294118 0.231373
+v -57.061104 -11.211700 48.339996 0.615686 0.407843 0.329412
+v -71.885208 24.282400 81.760689 0.294118 0.184314 0.149020
+v -73.277611 35.372601 88.210091 0.105882 0.070588 0.058824
+v -73.102806 23.570700 90.919289 0.247059 0.160784 0.129412
+v -74.068710 29.841700 94.291794 0.094118 0.062745 0.050980
+v -73.606911 22.268999 95.906693 0.137255 0.090196 0.074510
+v -72.477509 7.978640 98.615295 0.152941 0.101961 0.082353
+v -69.024506 -13.136300 99.036591 0.333333 0.215686 0.172549
+v -71.098709 -3.316490 100.109993 0.341176 0.223529 0.180392
+v -70.885109 -6.826520 103.005989 0.192157 0.125490 0.101961
+v -67.819908 -17.923401 100.792992 0.145098 0.094118 0.074510
+v -72.112007 0.218792 102.677994 0.180392 0.117647 0.094118
+v -44.566803 99.373398 43.969296 0.364706 0.243137 0.184314
+v -53.142704 92.472603 50.275696 0.345098 0.231373 0.176471
+v -53.622902 97.571404 53.947998 0.262745 0.176471 0.137255
+v -58.044403 92.007202 57.123795 0.121569 0.086275 0.066667
+v -48.604305 101.376999 49.915794 0.156863 0.105882 0.082353
+v -41.549603 104.077003 43.819798 0.172549 0.113725 0.086275
+v -71.393410 49.452900 78.631592 0.247059 0.168627 0.137255
+v -72.491310 48.692600 82.087189 0.094118 0.066667 0.054902
+v -49.436703 58.866501 33.259895 0.545098 0.364706 0.290196
+v -43.665604 60.113602 27.862896 0.592157 0.403922 0.325490
+v -37.226006 60.068802 23.153795 0.670588 0.474510 0.392157
+v -4.352591 16.224400 7.395720 0.682353 0.419608 0.337255
+v -70.843208 58.216900 75.191589 0.101961 0.074510 0.058824
+v -67.510704 71.916199 66.095291 0.133333 0.098039 0.078431
+v -69.946907 66.516602 71.206192 0.109804 0.078431 0.066667
+v -71.842804 37.894199 81.078789 0.254902 0.168627 0.137255
+v -63.468903 81.661102 60.812897 0.137255 0.094118 0.074510
+v -53.695305 98.844704 54.975895 0.129412 0.086275 0.066667
+v -11.574702 100.000999 25.843098 0.317647 0.211765 0.160784
+v -64.204407 -29.280001 98.039894 0.129412 0.086275 0.066667
+v -57.808506 -44.627602 86.710495 0.313725 0.207843 0.164706
+v -61.904308 -36.036499 93.323090 0.290196 0.192157 0.152941
+v -60.306007 -39.574100 93.578392 0.125490 0.082353 0.066667
+v -55.835407 -48.028099 86.987991 0.156863 0.105882 0.082353
+v -49.323906 -56.665699 77.172592 0.176471 0.117647 0.090196
+v -65.933609 -24.037500 96.256691 0.290196 0.188235 0.149020
+v -37.446705 33.968201 30.858696 0.407843 0.384314 0.380392
+v -39.703102 33.516899 32.215996 0.572549 0.533333 0.541176
+v -27.674603 33.585201 30.179197 0.356863 0.309804 0.290196
+v -23.713802 -35.788601 28.594898 0.482353 0.282353 0.235294
+v -3.734711 -35.148399 16.966900 0.596078 0.274510 0.290196
+v -3.894611 -38.155800 17.165199 0.756863 0.372549 0.407843
+v -8.034562 -35.082500 18.211599 0.584314 0.262745 0.282353
+v -8.452592 -38.003201 18.352200 0.713725 0.349020 0.380392
+v -13.179702 -35.288399 20.849298 0.541176 0.258824 0.270588
+v -13.330002 -37.864399 20.994598 0.635294 0.317647 0.337255
+v -17.577602 -35.332802 23.885798 0.447059 0.223529 0.223529
+v -17.583403 -37.785702 24.382399 0.549020 0.274510 0.278431
+v -21.285702 -34.999901 26.378998 0.474510 0.262745 0.231373
+v -21.699402 -38.312199 27.727598 0.564706 0.325490 0.294118
+v -21.295403 -34.956501 26.346899 0.486275 0.270588 0.239216
+v -14.235902 -31.116699 18.050999 0.666667 0.407843 0.368627
+v -6.377821 -27.889299 13.529899 0.796078 0.509804 0.466667
+v -17.588001 -35.285000 23.855999 0.427451 0.215686 0.211765
+v -18.272202 -33.384800 22.694597 0.576471 0.337255 0.298039
+v -13.185702 -35.228500 20.810898 0.533333 0.254902 0.266667
+v -13.791502 -33.117401 19.544298 0.596078 0.329412 0.313725
+v -8.025412 -35.018002 18.154800 0.576471 0.266667 0.282353
+v -9.206802 -33.017300 17.168400 0.631373 0.345098 0.337255
+v -3.731441 -35.080601 16.883200 0.596078 0.274510 0.290196
+v -5.548481 -33.276402 16.041901 0.643137 0.341176 0.337255
+v -2.653011 -33.159401 15.113800 0.650980 0.341176 0.341176
+v -2.764671 -31.169201 13.618300 0.705882 0.388235 0.384314
+v -5.831661 -30.993200 14.292099 0.698039 0.388235 0.380392
+v -4.266491 -41.302898 16.592800 0.807843 0.458824 0.490196
+v -8.956841 -41.049999 17.898399 0.749020 0.415686 0.439216
+v -9.671581 -30.964100 15.692699 0.686275 0.400000 0.376471
+v -13.864402 -40.635300 20.778898 0.654902 0.360784 0.364706
+v -18.022501 -40.030899 24.391897 0.603922 0.345098 0.325490
+v -36.597202 65.101097 24.312796 0.701961 0.478431 0.380392
+v -29.114101 63.241402 19.640799 0.772549 0.541176 0.447059
+v -21.118702 60.808300 16.469297 0.843137 0.607843 0.517647
+# 845 vertices, 0 vertices normals
+
+f 1 2 3
+f 4 5 6
+f 7 6 5
+f 8 7 9
+f 10 4 6
+f 3 12 13
+f 14 15 13
+f 14 16 17
+f 4 10 18
+f 17 16 19
+f 19 16 13
+f 5 4 17
+f 20 17 19
+f 21 19 22
+f 22 13 12
+f 23 24 25
+f 26 23 27
+f 10 6 11
+f 18 28 29
+f 14 29 30
+f 3 15 31
+f 32 20 21
+f 32 33 34
+f 27 25 33
+f 9 5 20
+f 35 36 37
+f 7 8 35
+f 31 15 30
+f 18 10 38
+f 39 40 41
+f 42 43 44
+f 45 46 47
+f 47 48 49
+f 45 47 50
+f 28 38 51
+f 46 45 52
+f 29 28 52
+f 53 31 30
+f 54 53 55
+f 56 54 57
+f 61 62 49
+f 61 48 63
+f 64 65 66
+f 67 68 69
+f 48 70 63
+f 71 72 70
+f 73 74 75
+f 64 76 77
+f 78 79 80
+f 79 81 82
+f 83 84 85
+f 84 86 87
+f 86 88 89
+f 91 60 92
+f 60 90 93
+f 94 89 95
+f 94 95 96
+f 94 96 97
+f 94 97 98
+f 94 98 99
+f 94 99 100
+f 94 100 101
+f 94 101 89
+f 101 100 102
+f 103 95 89
+f 104 105 106
+f 107 108 105
+f 112 109 106
+f 113 109 80
+f 115 116 117
+f 116 118 119
+f 119 120 121
+f 122 121 120
+f 124 125 126
+f 125 127 128
+f 127 118 116
+f 129 117 119
+f 130 131 132
+f 131 121 122
+f 120 133 134
+f 133 135 136
+f 135 137 138
+f 137 139 140
+f 141 126 128
+f 125 142 127
+f 144 142 125
+f 96 95 132
+f 139 145 146
+f 145 110 114
+f 110 147 114
+f 147 148 114
+f 147 82 148
+f 104 109 113
+f 144 107 149
+f 96 123 134
+f 120 143 150
+f 143 144 149
+f 151 152 153
+f 152 154 155
+f 100 146 114
+f 145 113 80
+f 78 112 156
+f 161 162 157
+f 166 167 168
+f 167 169 170
+f 169 160 159
+f 172 171 173
+f 190 192 182
+f 193 189 190
+f 189 194 192
+f 195 196 194
+f 188 197 196
+f 162 198 199
+f 198 191 182
+f 178 185 184
+f 202 199 182
+f 203 204 205
+f 203 183 206
+f 183 186 207
+f 186 187 208
+f 187 184 209
+f 184 185 210
+f 185 211 212
+f 213 211 178
+f 179 174 214
+f 174 175 215
+f 201 215 175
+f 216 217 218
+f 210 212 219
+f 209 210 220
+f 221 222 208
+f 223 224 219
+f 225 226 223
+f 227 228 226
+f 229 230 228
+f 229 231 232
+f 231 233 234
+f 233 235 236
+f 166 237 238
+f 165 239 237
+f 164 239 165
+f 240 241 239
+f 161 242 236
+f 243 157 244
+f 245 154 152
+f 246 135 133
+f 153 247 106
+f 106 247 156
+f 156 248 249
+f 248 250 251
+f 250 252 251
+f 250 44 43
+f 253 251 252
+f 82 74 65
+f 202 205 155
+f 214 215 187
+f 183 254 214
+f 182 180 179
+f 193 190 191
+f 189 193 235
+f 195 189 233
+f 188 195 231
+f 178 188 229
+f 178 225 213
+f 196 197 176
+f 194 196 173
+f 192 194 171
+f 182 192 181
+f 255 256 257
+f 258 37 36
+f 259 260 261
+f 262 259 255
+f 71 263 218
+f 264 72 71
+f 265 264 217
+f 266 265 216
+f 266 216 257
+f 150 149 267
+f 143 118 127
+f 144 125 124
+f 80 79 147
+f 109 104 106
+f 104 113 268
+f 105 104 269
+f 107 105 269
+f 106 105 108
+f 151 111 107
+f 267 149 107
+f 151 107 144
+f 268 113 145
+f 109 112 80
+f 270 246 267
+f 268 270 269
+f 270 268 139
+f 246 270 137
+f 119 118 143
+f 131 129 121
+f 136 98 97
+f 138 140 99
+f 100 99 140
+f 102 114 148
+f 101 102 77
+f 87 89 101
+f 89 88 271
+f 103 272 132
+f 129 131 130
+f 128 116 115
+f 126 141 273
+f 153 108 111
+f 211 213 223
+f 219 274 275
+f 220 219 275
+f 274 276 277
+f 275 274 278
+f 275 279 280
+f 281 282 280
+f 154 283 202
+f 155 205 204
+f 284 204 206
+f 285 206 207
+f 286 207 208
+f 221 220 287
+f 288 287 280
+f 286 222 250
+f 285 286 248
+f 284 285 156
+f 155 284 247
+f 182 203 205
+f 289 244 200
+f 244 289 290
+f 202 283 200
+f 276 274 219
+f 291 292 277
+f 293 291 276
+f 291 293 159
+f 294 291 160
+f 169 295 294
+f 158 157 243
+f 163 158 296
+f 240 163 297
+f 298 241 240
+f 241 298 299
+f 239 241 300
+f 237 239 301
+f 238 237 302
+f 295 238 303
+f 295 169 167
+f 294 295 304
+f 305 292 291
+f 305 294 306
+f 305 306 307
+f 292 305 308
+f 277 292 309
+f 278 277 310
+f 279 278 311
+f 312 281 279
+f 42 44 280
+f 313 314 315
+f 313 87 76
+f 87 313 85
+f 69 68 316
+f 66 69 317
+f 64 317 314
+f 77 148 65
+f 92 93 318
+f 319 56 57
+f 56 319 320
+f 57 321 62
+f 57 54 321
+f 54 56 322
+f 323 324 322
+f 325 323 322
+f 326 327 318
+f 253 328 318
+f 66 73 93
+f 93 73 75
+f 82 81 75
+f 82 147 79
+f 249 81 79
+f 253 249 251
+f 249 253 75
+f 85 313 329
+f 314 317 316
+f 76 87 77
+f 84 83 330
+f 86 84 331
+f 88 86 332
+f 36 261 260
+f 259 262 258
+f 259 333 256
+f 47 46 334
+f 55 335 50
+f 262 334 51
+f 334 262 218
+f 46 52 51
+f 11 37 258
+f 38 11 51
+f 335 29 45
+f 55 30 29
+f 53 54 324
+f 336 337 31
+f 91 61 60
+f 327 320 319
+f 326 325 320
+f 59 328 252
+f 325 338 323
+f 1 337 336
+f 339 336 324
+f 340 324 323
+f 338 325 58
+f 342 343 344
+f 345 346 342
+f 346 345 347
+f 348 346 349
+f 350 351 345
+f 345 351 352
+f 352 353 1
+f 347 352 339
+f 349 347 338
+f 354 349 341
+f 43 58 59
+f 355 58 43
+f 282 354 355
+f 281 348 354
+f 344 343 356
+f 343 342 346
+f 343 348 281
+f 356 343 312
+f 356 312 311
+f 357 358 356
+f 358 357 359
+f 344 358 360
+f 342 344 361
+f 362 350 342
+f 48 47 263
+f 49 62 321
+f 61 91 57
+f 61 49 48
+f 363 364 365
+f 366 364 363
+f 326 58 325
+f 326 59 58
+f 326 328 59
+f 183 182 254
+f 184 187 215
+f 303 367 368
+f 302 40 367
+f 369 370 307
+f 371 369 368
+f 372 371 367
+f 373 374 357
+f 301 41 40
+f 226 293 224
+f 228 159 293
+f 230 375 159
+f 232 376 375
+f 234 377 376
+f 234 236 377
+f 161 235 193
+f 162 200 244
+f 162 161 198
+f 242 161 158
+f 378 379 380
+f 245 289 283
+f 380 290 289
+f 378 124 273
+f 152 151 124
+f 381 382 383
+f 384 311 310
+f 381 374 373
+f 385 307 370
+f 308 307 385
+f 309 308 382
+f 373 310 309
+f 306 304 368
+f 352 351 353
+f 65 74 73
+f 82 65 148
+f 44 250 222
+f 288 222 221
+f 371 386 369
+f 387 300 299
+f 301 300 387
+f 386 370 369
+f 39 372 40
+f 388 117 129
+f 390 391 392
+f 390 393 394
+f 2 394 12
+f 391 395 389
+f 390 2 1
+f 390 353 351
+f 391 351 395
+f 395 351 350
+f 395 362 365
+f 397 175 396
+f 173 176 397
+f 397 178 177
+f 175 397 177
+f 180 398 174
+f 398 180 181
+f 178 397 176
+f 172 398 171
+f 172 173 396
+f 172 396 175
+f 399 68 67
+f 400 401 402
+f 403 404 401
+f 405 406 404
+f 407 408 406
+f 409 410 408
+f 399 410 409
+f 410 399 63
+f 413 315 316
+f 411 415 416
+f 415 417 418
+f 417 419 420
+f 419 421 422
+f 421 423 422
+f 421 424 425
+f 426 427 422
+f 426 83 414
+f 399 67 63
+f 266 428 429
+f 427 430 420
+f 70 410 63
+f 264 265 429
+f 72 264 431
+f 410 70 72
+f 406 408 432
+f 404 406 431
+f 401 404 429
+f 428 266 412
+f 402 401 428
+f 413 316 416
+f 430 413 418
+f 329 315 413
+f 414 329 430
+f 433 426 423
+f 330 83 426
+f 159 375 170
+f 375 376 168
+f 376 377 434
+f 435 434 377
+f 165 166 434
+f 164 165 435
+f 436 163 164
+f 436 435 236
+f 436 242 158
+f 1 3 337
+f 7 5 9
+f 8 9 34
+f 11 6 37
+f 3 13 15
+f 14 13 16
+f 14 17 18
+f 4 18 17
+f 19 13 22
+f 5 17 20
+f 20 19 21
+f 21 22 26
+f 23 25 27
+f 26 27 21
+f 10 11 38
+f 18 29 14
+f 14 30 15
+f 3 31 337
+f 32 21 27
+f 32 34 9
+f 27 33 32
+f 9 20 32
+f 35 37 6
+f 35 6 7
+f 18 38 28
+f 47 49 50
+f 45 50 335
+f 28 51 52
+f 29 52 45
+f 53 30 55
+f 54 55 321
+f 60 61 67
+f 61 63 67
+f 67 69 90
+f 71 70 48
+f 64 77 65
+f 78 80 112
+f 83 85 414
+f 84 87 85
+f 86 89 87
+f 67 90 60
+f 91 92 319
+f 60 93 92
+f 103 89 437
+f 115 117 388
+f 116 119 117
+f 119 121 129
+f 122 120 123
+f 123 96 122
+f 124 126 273
+f 125 128 126
+f 127 116 128
+f 130 132 272
+f 131 122 132
+f 120 134 123
+f 133 136 134
+f 135 138 136
+f 137 140 138
+f 141 128 438
+f 143 142 144
+f 96 132 122
+f 139 146 140
+f 145 114 146
+f 96 134 97
+f 120 150 133
+f 143 149 150
+f 151 153 111
+f 152 155 153
+f 108 107 111
+f 100 114 102
+f 145 80 110
+f 157 158 161
+f 164 163 240
+f 167 170 168
+f 169 159 170
+f 182 183 203
+f 188 178 197
+f 190 182 191
+f 189 192 190
+f 195 194 189
+f 188 196 195
+f 162 199 200
+f 198 182 199
+f 178 201 177
+f 178 184 201
+f 202 182 205
+f 203 206 204
+f 183 207 206
+f 186 208 207
+f 187 209 208
+f 184 210 209
+f 185 212 210
+f 179 214 254
+f 174 215 214
+f 201 175 177
+f 216 218 255
+f 210 219 220
+f 209 220 221
+f 221 208 209
+f 178 211 185
+f 223 219 212
+f 225 223 213
+f 227 226 225
+f 229 228 227
+f 229 232 230
+f 231 234 232
+f 233 236 234
+f 166 238 167
+f 165 237 166
+f 240 239 164
+f 161 236 235
+f 243 244 439
+f 245 152 378
+f 246 133 150
+f 153 106 108
+f 106 156 112
+f 156 249 78
+f 248 251 249
+f 250 43 252
+f 253 252 328
+f 214 187 186
+f 183 214 186
+f 182 179 254
+f 193 191 198
+f 189 235 233
+f 195 233 231
+f 188 231 229
+f 178 229 227
+f 178 227 225
+f 197 178 176
+f 196 176 173
+f 194 173 171
+f 192 171 181
+f 182 181 180
+f 255 257 216
+f 258 36 260
+f 259 261 333
+f 262 255 218
+f 71 218 217
+f 264 71 217
+f 265 217 216
+f 266 257 412
+f 150 267 246
+f 143 127 142
+f 144 124 151
+f 80 147 110
+f 107 269 267
+f 268 145 139
+f 270 267 269
+f 268 269 104
+f 270 139 137
+f 246 137 135
+f 119 143 120
+f 136 97 134
+f 98 136 138
+f 138 99 98
+f 100 140 146
+f 102 148 77
+f 101 77 87
+f 89 271 437
+f 103 132 95
+f 129 130 440
+f 128 115 438
+f 211 223 212
+f 220 275 287
+f 274 277 278
+f 275 278 279
+f 275 280 287
+f 281 280 279
+f 154 202 155
+f 155 204 284
+f 284 206 285
+f 285 207 286
+f 286 208 222
+f 221 287 288
+f 288 280 44
+f 286 250 248
+f 285 248 156
+f 284 156 247
+f 155 247 153
+f 289 200 283
+f 244 290 439
+f 202 200 199
+f 276 219 224
+f 291 277 276
+f 293 276 224
+f 291 159 160
+f 169 294 160
+f 158 243 296
+f 163 296 297
+f 240 297 441
+f 298 240 441
+f 241 299 300
+f 239 300 301
+f 237 301 302
+f 238 302 303
+f 295 303 304
+f 295 167 238
+f 294 304 306
+f 305 291 294
+f 305 307 308
+f 292 308 309
+f 277 309 310
+f 278 310 311
+f 279 311 312
+f 42 280 282
+f 313 315 329
+f 313 76 314
+f 69 316 317
+f 66 317 64
+f 64 314 76
+f 92 318 327
+f 319 57 91
+f 56 320 322
+f 54 322 324
+f 325 322 320
+f 326 318 328
+f 253 318 75
+f 90 69 66
+f 66 93 90
+f 93 75 318
+f 82 75 74
+f 249 79 78
+f 249 75 81
+f 85 329 414
+f 314 316 315
+f 84 330 331
+f 86 331 332
+f 88 332 271
+f 259 258 260
+f 259 256 255
+f 47 334 263
+f 55 50 321
+f 262 51 258
+f 334 218 263
+f 46 51 334
+f 11 258 51
+f 55 29 335
+f 53 324 336
+f 336 31 53
+f 327 319 92
+f 326 320 327
+f 1 336 339
+f 339 324 340
+f 340 323 338
+f 338 58 341
+f 341 58 355
+f 345 342 350
+f 346 347 349
+f 348 349 354
+f 345 352 347
+f 352 1 339
+f 347 339 340
+f 347 340 338
+f 349 338 341
+f 354 341 355
+f 43 59 252
+f 355 43 42
+f 282 355 42
+f 281 354 282
+f 344 356 358
+f 343 346 348
+f 343 281 312
+f 356 311 384
+f 357 356 384
+f 358 359 360
+f 344 360 361
+f 342 361 363
+f 362 342 363
+f 48 263 71
+f 49 321 50
+f 61 57 62
+f 363 365 362
+f 184 215 201
+f 303 368 304
+f 302 367 303
+f 369 307 368
+f 371 368 367
+f 372 367 40
+f 373 357 384
+f 301 40 302
+f 226 224 223
+f 228 293 226
+f 230 159 228
+f 232 375 230
+f 234 376 232
+f 161 193 198
+f 162 244 157
+f 378 380 245
+f 245 283 154
+f 380 289 245
+f 378 273 379
+f 152 124 378
+f 384 310 373
+f 308 385 382
+f 309 382 381
+f 373 309 381
+f 306 368 307
+f 65 73 66
+f 44 222 288
+f 387 299 442
+f 301 387 41
+f 388 129 440
+f 390 392 393
+f 390 394 2
+f 2 12 3
+f 391 389 392
+f 390 1 353
+f 390 351 391
+f 395 350 362
+f 395 365 389
+f 173 397 396
+f 180 174 179
+f 398 181 171
+f 172 175 174
+f 172 174 398
+f 400 402 443
+f 403 401 400
+f 405 404 403
+f 407 406 405
+f 409 408 407
+f 411 68 399
+f 411 416 68
+f 415 418 416
+f 417 420 418
+f 419 422 420
+f 421 425 423
+f 426 422 423
+f 426 414 427
+f 266 429 265
+f 427 420 422
+f 264 429 431
+f 72 431 432
+f 410 72 432
+f 408 410 432
+f 406 432 431
+f 404 431 429
+f 401 429 428
+f 428 412 444
+f 402 428 444
+f 413 416 418
+f 430 418 420
+f 316 68 416
+f 329 413 430
+f 414 430 427
+f 433 423 425
+f 330 426 433
+f 375 168 170
+f 376 434 168
+f 435 377 236
+f 166 168 434
+f 165 434 435
+f 164 435 436
+f 436 236 242
+f 436 158 163
+f 363 361 366
+f 445 447 446
+f 448 450 449
+f 451 449 450
+f 8 452 451
+f 453 450 448
+f 447 456 455
+f 457 456 458
+f 457 460 459
+f 448 461 453
+f 460 462 459
+f 462 456 459
+f 449 460 448
+f 463 462 460
+f 464 465 462
+f 465 455 456
+f 466 25 24
+f 467 468 466
+f 453 454 450
+f 461 470 469
+f 457 471 470
+f 447 472 458
+f 473 464 463
+f 473 34 33
+f 468 33 25
+f 452 463 449
+f 35 474 36
+f 451 35 8
+f 472 471 458
+f 461 475 453
+f 476 478 477
+f 479 481 480
+f 482 484 483
+f 484 486 485
+f 482 487 484
+f 469 488 475
+f 483 489 482
+f 470 489 469
+f 490 471 472
+f 491 492 490
+f 493 494 491
+f 498 486 499
+f 498 500 485
+f 501 503 502
+f 504 506 505
+f 485 500 507
+f 508 507 509
+f 510 512 511
+f 501 514 513
+f 515 517 516
+f 516 519 518
+f 520 522 521
+f 521 524 523
+f 523 526 525
+f 528 529 497
+f 497 530 527
+f 531 532 526
+f 531 533 532
+f 531 534 533
+f 531 535 534
+f 531 536 535
+f 531 537 536
+f 531 538 537
+f 531 526 538
+f 538 539 537
+f 103 526 532
+f 540 542 541
+f 543 541 544
+f 548 542 545
+f 549 517 545
+f 115 552 551
+f 551 554 553
+f 554 556 555
+f 557 555 556
+f 559 561 560
+f 560 563 562
+f 562 551 553
+f 564 554 552
+f 130 566 565
+f 565 557 556
+f 555 568 567
+f 567 570 569
+f 569 572 571
+f 571 574 573
+f 141 563 561
+f 560 562 575
+f 577 560 575
+f 533 566 532
+f 573 579 578
+f 578 550 546
+f 546 550 580
+f 580 550 581
+f 580 581 519
+f 540 549 545
+f 577 582 543
+f 533 568 558
+f 555 583 576
+f 576 582 577
+f 584 586 585
+f 585 588 587
+f 537 550 579
+f 578 517 549
+f 515 589 548
+f 594 590 595
+f 599 601 600
+f 600 603 602
+f 602 592 593
+f 605 606 604
+f 623 615 625
+f 626 623 622
+f 622 625 627
+f 628 627 629
+f 621 629 630
+f 595 632 631
+f 631 615 624
+f 611 617 618
+f 635 615 632
+f 636 638 637
+f 636 639 616
+f 616 640 619
+f 619 641 620
+f 620 642 617
+f 617 643 618
+f 618 645 644
+f 646 611 644
+f 612 647 607
+f 607 648 608
+f 634 608 648
+f 649 651 650
+f 643 652 645
+f 642 653 643
+f 654 641 655
+f 656 652 657
+f 658 656 659
+f 660 659 661
+f 662 661 663
+f 662 665 664
+f 664 667 666
+f 666 669 668
+f 599 671 670
+f 598 670 672
+f 597 598 672
+f 673 672 674
+f 594 669 675
+f 243 676 590
+f 677 585 587
+f 678 567 569
+f 586 542 679
+f 542 589 679
+f 589 681 680
+f 680 683 682
+f 682 683 684
+f 682 480 481
+f 685 684 683
+f 519 502 511
+f 635 588 638
+f 647 620 648
+f 616 647 686
+f 615 612 613
+f 626 624 623
+f 622 668 626
+f 628 666 622
+f 621 664 628
+f 611 662 621
+f 611 646 658
+f 629 609 630
+f 627 606 629
+f 625 604 627
+f 615 614 625
+f 687 257 256
+f 688 36 474
+f 689 261 690
+f 691 687 689
+f 508 651 692
+f 693 508 509
+f 694 650 693
+f 695 649 694
+f 695 257 649
+f 583 696 582
+f 576 562 553
+f 577 559 560
+f 517 580 516
+f 545 542 540
+f 540 697 549
+f 541 698 540
+f 543 698 541
+f 542 544 541
+f 584 543 547
+f 696 543 582
+f 584 577 543
+f 697 578 549
+f 545 517 548
+f 699 696 678
+f 697 698 699
+f 699 573 697
+f 678 571 699
+f 554 576 553
+f 565 556 564
+f 570 534 535
+f 572 536 574
+f 537 574 536
+f 539 581 550
+f 538 514 539
+f 524 538 526
+f 526 271 525
+f 103 566 272
+f 564 130 565
+f 563 115 551
+f 561 273 141
+f 586 547 544
+f 644 656 646
+f 652 701 700
+f 653 701 652
+f 700 703 702
+f 701 704 700
+f 701 706 705
+f 707 706 708
+f 587 635 709
+f 588 637 638
+f 710 639 637
+f 711 640 639
+f 712 641 640
+f 654 713 653
+f 714 706 713
+f 712 682 655
+f 711 680 712
+f 710 589 711
+f 588 679 710
+f 615 638 636
+f 715 633 676
+f 676 290 715
+f 635 633 709
+f 702 652 700
+f 716 703 717
+f 718 702 716
+f 716 592 718
+f 719 593 716
+f 602 719 720
+f 591 243 590
+f 596 296 591
+f 673 297 596
+f 298 673 674
+f 674 299 298
+f 672 721 674
+f 670 722 672
+f 671 723 670
+f 720 724 671
+f 720 600 602
+f 719 725 720
+f 726 716 717
+f 726 727 719
+f 726 728 727
+f 717 729 726
+f 703 730 717
+f 704 731 703
+f 705 732 704
+f 733 705 707
+f 479 706 481
+f 734 736 735
+f 734 513 524
+f 524 522 734
+f 506 737 505
+f 503 738 506
+f 501 735 738
+f 514 502 581
+f 529 739 530
+f 740 494 493
+f 493 741 740
+f 494 499 742
+f 494 742 491
+f 491 743 493
+f 744 743 745
+f 746 743 744
+f 747 739 748
+f 685 739 749
+f 503 530 510
+f 530 512 510
+f 519 512 518
+f 519 516 580
+f 681 516 518
+f 685 683 681
+f 681 512 685
+f 522 750 734
+f 735 737 738
+f 513 514 524
+f 521 330 520
+f 523 331 521
+f 525 332 523
+f 36 690 261
+f 689 688 691
+f 689 256 333
+f 484 751 483
+f 492 487 752
+f 691 488 751
+f 751 651 691
+f 483 488 489
+f 454 688 474
+f 475 488 454
+f 752 482 470
+f 492 470 471
+f 490 745 491
+f 753 472 754
+f 528 497 498
+f 748 740 741
+f 747 741 746
+f 496 684 749
+f 746 744 755
+f 445 753 754
+f 756 745 753
+f 757 744 745
+f 755 495 746
+f 759 761 760
+f 762 759 763
+f 763 764 762
+f 765 766 763
+f 767 762 768
+f 762 769 768
+f 769 445 770
+f 764 756 769
+f 766 755 764
+f 771 758 766
+f 480 496 495
+f 772 480 495
+f 708 772 771
+f 707 771 765
+f 761 773 760
+f 760 763 759
+f 760 707 765
+f 773 733 760
+f 773 732 733
+f 774 773 775
+f 775 776 774
+f 761 777 775
+f 759 778 761
+f 779 759 767
+f 485 692 484
+f 486 742 499
+f 498 494 528
+f 498 485 486
+f 780 782 781
+f 783 780 781
+f 747 746 495
+f 747 495 496
+f 747 496 749
+f 616 686 615
+f 617 648 620
+f 724 785 784
+f 723 784 477
+f 786 728 787
+f 788 785 786
+f 789 784 788
+f 790 774 791
+f 722 477 478
+f 659 657 718
+f 661 718 592
+f 663 592 792
+f 665 792 793
+f 667 793 794
+f 667 794 669
+f 594 626 668
+f 595 676 633
+f 595 631 594
+f 675 591 594
+f 795 380 379
+f 677 709 715
+f 380 715 290
+f 795 273 559
+f 585 559 584
+f 796 798 797
+f 799 731 732
+f 796 790 791
+f 800 787 728
+f 729 800 728
+f 730 797 729
+f 790 730 731
+f 727 785 725
+f 769 770 768
+f 502 510 511
+f 519 581 502
+f 481 655 682
+f 714 654 655
+f 788 786 801
+f 802 299 721
+f 722 802 721
+f 801 786 787
+f 476 477 789
+f 388 564 552
+f 804 806 805
+f 804 808 807
+f 446 455 808
+f 805 803 809
+f 804 445 446
+f 804 768 770
+f 805 809 768
+f 809 767 768
+f 809 782 779
+f 811 810 608
+f 606 811 609
+f 811 610 611
+f 608 610 811
+f 613 607 812
+f 812 614 613
+f 611 609 811
+f 605 604 812
+f 605 810 606
+f 605 608 810
+f 813 504 505
+f 814 402 815
+f 816 815 817
+f 818 817 819
+f 820 819 821
+f 822 821 823
+f 813 822 823
+f 823 500 813
+f 825 737 736
+f 824 828 827
+f 827 830 829
+f 829 832 831
+f 831 834 833
+f 833 834 835
+f 833 425 424
+f 836 834 837
+f 836 826 520
+f 813 500 504
+f 695 839 838
+f 837 832 840
+f 507 500 823
+f 693 839 694
+f 509 841 693
+f 823 509 507
+f 819 842 821
+f 817 841 819
+f 815 839 817
+f 838 412 695
+f 402 838 815
+f 825 828 737
+f 840 830 825
+f 750 825 736
+f 826 840 750
+f 433 835 836
+f 330 836 520
+f 592 603 792
+f 792 601 793
+f 793 843 794
+f 844 794 843
+f 598 843 599
+f 597 844 598
+f 845 597 596
+f 845 669 844
+f 845 591 675
+f 445 754 447
+f 451 452 449
+f 8 34 452
+f 454 474 450
+f 447 458 456
+f 457 459 456
+f 457 461 460
+f 448 460 461
+f 462 465 456
+f 449 463 460
+f 463 464 462
+f 464 467 465
+f 466 468 25
+f 467 464 468
+f 453 475 454
+f 461 457 470
+f 457 458 471
+f 447 754 472
+f 473 468 464
+f 473 452 34
+f 468 473 33
+f 452 473 463
+f 35 450 474
+f 35 451 450
+f 461 469 475
+f 484 487 486
+f 482 752 487
+f 469 489 488
+f 470 482 489
+f 490 492 471
+f 491 742 492
+f 497 504 498
+f 498 504 500
+f 504 527 506
+f 508 485 507
+f 501 502 514
+f 515 548 517
+f 520 826 522
+f 521 522 524
+f 523 524 526
+f 504 497 527
+f 528 740 529
+f 497 529 530
+f 103 437 526
+f 115 388 552
+f 551 552 554
+f 554 564 556
+f 557 558 555
+f 558 557 533
+f 559 273 561
+f 560 561 563
+f 562 563 551
+f 130 272 566
+f 565 566 557
+f 555 558 568
+f 567 568 570
+f 569 570 572
+f 571 572 574
+f 141 438 563
+f 576 577 575
+f 533 557 566
+f 573 574 579
+f 578 579 550
+f 533 534 568
+f 555 567 583
+f 576 583 582
+f 584 547 586
+f 585 586 588
+f 544 547 543
+f 537 539 550
+f 578 546 517
+f 590 594 591
+f 597 673 596
+f 600 601 603
+f 602 603 592
+f 615 636 616
+f 621 630 611
+f 623 624 615
+f 622 623 625
+f 628 622 627
+f 621 628 629
+f 595 633 632
+f 631 632 615
+f 611 610 634
+f 611 634 617
+f 635 638 615
+f 636 637 639
+f 616 639 640
+f 619 640 641
+f 620 641 642
+f 617 642 643
+f 618 643 645
+f 612 686 647
+f 607 647 648
+f 634 610 608
+f 649 687 651
+f 643 653 652
+f 642 654 653
+f 654 642 641
+f 611 618 644
+f 656 645 652
+f 658 646 656
+f 660 658 659
+f 662 660 661
+f 662 663 665
+f 664 665 667
+f 666 667 669
+f 599 600 671
+f 598 599 670
+f 673 597 672
+f 594 668 669
+f 243 439 676
+f 677 795 585
+f 678 583 567
+f 586 544 542
+f 542 548 589
+f 589 515 681
+f 680 681 683
+f 682 684 480
+f 685 749 684
+f 647 619 620
+f 616 619 647
+f 615 686 612
+f 626 631 624
+f 622 666 668
+f 628 664 666
+f 621 662 664
+f 611 660 662
+f 611 658 660
+f 630 609 611
+f 629 606 609
+f 627 604 606
+f 625 614 604
+f 615 613 614
+f 687 649 257
+f 688 690 36
+f 689 333 261
+f 691 651 687
+f 508 650 651
+f 693 650 508
+f 694 649 650
+f 695 412 257
+f 583 678 696
+f 576 575 562
+f 577 584 559
+f 517 546 580
+f 543 696 698
+f 697 573 578
+f 699 698 696
+f 697 540 698
+f 699 571 573
+f 678 569 571
+f 554 555 576
+f 570 568 534
+f 535 572 570
+f 572 535 536
+f 537 579 574
+f 539 514 581
+f 538 524 514
+f 526 437 271
+f 103 532 566
+f 564 440 130
+f 563 438 115
+f 644 645 656
+f 653 713 701
+f 700 704 703
+f 701 705 704
+f 701 713 706
+f 707 705 706
+f 587 588 635
+f 588 710 637
+f 710 711 639
+f 711 712 640
+f 712 655 641
+f 654 714 713
+f 714 481 706
+f 712 680 682
+f 711 589 680
+f 710 679 589
+f 588 586 679
+f 715 709 633
+f 676 439 290
+f 635 632 633
+f 702 657 652
+f 716 702 703
+f 718 657 702
+f 716 593 592
+f 602 593 719
+f 591 296 243
+f 596 297 296
+f 673 441 297
+f 298 441 673
+f 674 721 299
+f 672 722 721
+f 670 723 722
+f 671 724 723
+f 720 725 724
+f 720 671 600
+f 719 727 725
+f 726 719 716
+f 726 729 728
+f 717 730 729
+f 703 731 730
+f 704 732 731
+f 705 733 732
+f 479 708 706
+f 734 750 736
+f 734 735 513
+f 506 738 737
+f 503 501 738
+f 501 513 735
+f 529 748 739
+f 740 528 494
+f 493 743 741
+f 491 745 743
+f 746 741 743
+f 747 749 739
+f 685 512 739
+f 527 503 506
+f 503 527 530
+f 530 739 512
+f 519 511 512
+f 681 515 516
+f 681 518 512
+f 522 826 750
+f 735 736 737
+f 521 331 330
+f 523 332 331
+f 525 271 332
+f 689 690 688
+f 689 687 256
+f 484 692 751
+f 492 742 487
+f 691 688 488
+f 751 692 651
+f 483 751 488
+f 454 488 688
+f 492 752 470
+f 490 753 745
+f 753 490 472
+f 748 529 740
+f 747 748 741
+f 445 756 753
+f 756 757 745
+f 757 755 744
+f 755 758 495
+f 758 772 495
+f 762 767 759
+f 763 766 764
+f 765 771 766
+f 762 764 769
+f 769 756 445
+f 764 757 756
+f 764 755 757
+f 766 758 755
+f 771 772 758
+f 480 684 496
+f 772 479 480
+f 708 479 772
+f 707 708 771
+f 761 775 773
+f 760 765 763
+f 760 733 707
+f 773 799 732
+f 774 799 773
+f 775 777 776
+f 761 778 777
+f 759 780 778
+f 779 780 759
+f 485 508 692
+f 486 487 742
+f 498 499 494
+f 780 779 782
+f 617 634 648
+f 724 725 785
+f 723 724 784
+f 786 785 728
+f 788 784 785
+f 789 477 784
+f 790 799 774
+f 722 723 477
+f 659 656 657
+f 661 659 718
+f 663 661 592
+f 665 663 792
+f 667 665 793
+f 594 631 626
+f 595 590 676
+f 795 677 380
+f 677 587 709
+f 380 677 715
+f 795 379 273
+f 585 795 559
+f 799 790 731
+f 729 797 800
+f 730 796 797
+f 790 796 730
+f 727 728 785
+f 502 503 510
+f 481 714 655
+f 802 442 299
+f 722 478 802
+f 388 440 564
+f 804 807 806
+f 804 446 808
+f 446 447 455
+f 805 806 803
+f 804 770 445
+f 804 805 768
+f 809 779 767
+f 809 803 782
+f 606 810 811
+f 613 612 607
+f 812 604 614
+f 605 607 608
+f 605 812 607
+f 814 443 402
+f 816 814 815
+f 818 816 817
+f 820 818 819
+f 822 820 821
+f 824 813 505
+f 824 505 828
+f 827 828 830
+f 829 830 832
+f 831 832 834
+f 833 835 425
+f 836 835 834
+f 836 837 826
+f 695 694 839
+f 837 834 832
+f 693 841 839
+f 509 842 841
+f 823 842 509
+f 821 842 823
+f 819 841 842
+f 817 839 841
+f 815 838 839
+f 838 444 412
+f 402 444 838
+f 825 830 828
+f 840 832 830
+f 737 828 505
+f 750 840 825
+f 826 837 840
+f 433 425 835
+f 330 433 836
+f 792 603 601
+f 793 601 843
+f 844 669 794
+f 599 843 601
+f 598 844 843
+f 597 845 844
+f 845 675 669
+f 845 596 591
+f 780 783 778
+# 1610 faces, 0 coords texture
+
+# End of File
diff --git a/gradio/media_assets/models3d/sofia.stl b/gradio/media_assets/models3d/sofia.stl
new file mode 100644
index 0000000..8a811c8
Binary files /dev/null and b/gradio/media_assets/models3d/sofia.stl differ
diff --git a/gradio/media_assets/videos/a.mp4 b/gradio/media_assets/videos/a.mp4
new file mode 100644
index 0000000..95a61f6
Binary files /dev/null and b/gradio/media_assets/videos/a.mp4 differ
diff --git a/gradio/media_assets/videos/b.avi b/gradio/media_assets/videos/b.avi
new file mode 100644
index 0000000..6a146c5
Binary files /dev/null and b/gradio/media_assets/videos/b.avi differ
diff --git a/gradio/media_assets/videos/b.mp4 b/gradio/media_assets/videos/b.mp4
new file mode 100644
index 0000000..7b2d7c7
Binary files /dev/null and b/gradio/media_assets/videos/b.mp4 differ
diff --git a/gradio/media_assets/videos/muted_b.mp4 b/gradio/media_assets/videos/muted_b.mp4
new file mode 100644
index 0000000..031284a
Binary files /dev/null and b/gradio/media_assets/videos/muted_b.mp4 differ
diff --git a/gradio/media_assets/videos/world.mp4 b/gradio/media_assets/videos/world.mp4
new file mode 100644
index 0000000..b11552f
Binary files /dev/null and b/gradio/media_assets/videos/world.mp4 differ
diff --git a/gradio/monitoring_dashboard.py b/gradio/monitoring_dashboard.py
new file mode 100644
index 0000000..9d11e6b
--- /dev/null
+++ b/gradio/monitoring_dashboard.py
@@ -0,0 +1,105 @@
+import random
+import time
+
+import pandas as pd
+
+import gradio as gr
+
+data = {"data": {}}
+
+with gr.Blocks() as demo:
+ gr.Markdown("# Monitoring Dashboard")
+ timer = gr.Timer(5)
+ with gr.Row():
+ start = gr.DateTime("now - 24h", label="Start Time")
+ end = gr.DateTime("now", label="End Time")
+ selected_fn = gr.Dropdown(
+ ["All"],
+ value="All",
+ label="Endpoint",
+ info="Select the function to see analytics for, or 'All' for aggregate.",
+ )
+ demo.load(
+ lambda: gr.Dropdown(
+ choices=["All"]
+ + list({row["function"] for row in data["data"].values()}) # type: ignore
+ ),
+ None,
+ selected_fn,
+ )
+
+ with gr.Group():
+ with gr.Row():
+ unique_users = gr.Label(label="Unique Users")
+ total_requests = gr.Label(label="Total Requests")
+ process_time = gr.Label(label="Avg Process Time")
+
+ plot = gr.BarPlot(
+ x="time",
+ y="function",
+ color="status",
+ title="Requests over Time",
+ y_title="Requests",
+ x_bin="1m",
+ y_aggregate="count",
+ color_map={
+ "success": "#22c55e",
+ "failure": "#ef4444",
+ "pending": "#eab308",
+ "queued": "#3b82f6",
+ },
+ )
+
+ @gr.on(
+ [demo.load, timer.tick, start.change, end.change, selected_fn.change],
+ inputs=[start, end, selected_fn],
+ outputs=[plot, unique_users, total_requests, process_time],
+ )
+ def gen_plot(start, end, selected_fn):
+ if len(data["data"]) == 0:
+ return {plot: gr.skip()}
+ df = pd.DataFrame(list(data["data"].values()))
+ if selected_fn != "All":
+ df = df[df["function"] == selected_fn]
+ df = df[(df["time"] >= start) & (df["time"] <= end)]
+ df["time"] = pd.to_datetime(df["time"], unit="s") # type: ignore
+
+ unique_users = len(df["session_hash"].unique()) # type: ignore
+ total_requests = len(df)
+ process_time = round(df["process_time"].mean(), 2)
+
+ duration = end - start
+ x_bin = (
+ "1h"
+ if duration >= 60 * 60 * 24
+ else "15m"
+ if duration >= 60 * 60 * 3
+ else "1m"
+ )
+ df = df.drop(columns=["session_hash"]) # type: ignore
+ assert isinstance(df, pd.DataFrame) # noqa: S101
+ return (
+ gr.BarPlot(value=df, x_bin=x_bin, x_lim=[start, end]),
+ unique_users,
+ total_requests,
+ process_time,
+ )
+
+
+if __name__ == "__main__":
+ data["data"] = {}
+ for _ in range(random.randint(300, 500)):
+ timedelta = random.randint(0, 60 * 60 * 24 * 3)
+ data["data"][random.randint(1, 100000)] = {
+ "time": time.time() - timedelta,
+ "status": random.choice(
+ ["success", "success", "failure"]
+ if timedelta > 30 * 60
+ else ["queued", "pending"]
+ ),
+ "function": random.choice(["predict", "chat", "chat"]),
+ "process_time": random.randint(0, 10),
+ "session_hash": str(random.randint(0, 4)),
+ }
+
+ demo.launch()
diff --git a/gradio/networking.py b/gradio/networking.py
new file mode 100644
index 0000000..7b97b71
--- /dev/null
+++ b/gradio/networking.py
@@ -0,0 +1,83 @@
+"""
+Defines helper methods useful for setting up ports, launching servers, and
+creating tunnels.
+"""
+
+from __future__ import annotations
+
+import os
+import time
+import warnings
+from pathlib import Path
+
+import httpx
+
+from gradio.exceptions import ShareCertificateWriteError
+from gradio.routes import App # HACK: to avoid circular import # noqa: F401
+from gradio.tunneling import CERTIFICATE_PATH, Tunnel
+
+GRADIO_API_SERVER = "https://api.gradio.app/v3/tunnel-request"
+GRADIO_SHARE_SERVER_ADDRESS = os.getenv("GRADIO_SHARE_SERVER_ADDRESS")
+
+
+def setup_tunnel(
+ local_host: str,
+ local_port: int,
+ share_token: str,
+ share_server_address: str | None,
+ share_server_tls_certificate: str | None,
+) -> str:
+ share_server_address = (
+ GRADIO_SHARE_SERVER_ADDRESS
+ if share_server_address is None
+ else share_server_address
+ )
+ if share_server_address is None:
+ try:
+ response = httpx.get(GRADIO_API_SERVER, timeout=30)
+ payload = response.json()[0]
+ remote_host, remote_port = payload["host"], int(payload["port"])
+ certificate = payload["root_ca"]
+ except Exception as e:
+ raise RuntimeError(
+ "Could not get share link from Gradio API Server."
+ ) from e
+ try:
+ Path(CERTIFICATE_PATH).parent.mkdir(parents=True, exist_ok=True)
+ with open(CERTIFICATE_PATH, "w") as f:
+ f.write(certificate)
+ except Exception as e:
+ raise ShareCertificateWriteError(
+ f"{e}. This can happen when the current working directory is read-only."
+ ) from e
+ share_server_tls_certificate = CERTIFICATE_PATH
+
+ else:
+ remote_host, remote_port = share_server_address.split(":")
+ remote_port = int(remote_port)
+ tunnel = Tunnel(
+ remote_host,
+ remote_port,
+ local_host,
+ local_port,
+ share_token,
+ share_server_tls_certificate,
+ )
+ address = tunnel.start_tunnel()
+ return address
+
+
+def url_ok(url: str) -> bool:
+ try:
+ for _ in range(5):
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ r = httpx.head(url, timeout=3, verify=False)
+ if (
+ r.status_code in (200, 401, 301, 302, 303, 307, 308)
+ ): # 401 or 302 if auth is set; 303 or 307 are alternatives to 302 for temporary redirects; 301 and 308 are permanent redirects
+ return True
+ time.sleep(0.500)
+ except (ConnectionError, httpx.ConnectError, httpx.TimeoutException):
+ return False
+ return False
diff --git a/gradio/node_server.py b/gradio/node_server.py
new file mode 100644
index 0000000..a775397
--- /dev/null
+++ b/gradio/node_server.py
@@ -0,0 +1,225 @@
+from __future__ import annotations
+
+import os
+import signal
+import socket
+import subprocess
+import sys
+import time
+import warnings
+from concurrent.futures import TimeoutError
+from contextlib import closing
+from http.client import HTTPConnection
+from pathlib import Path
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING: # Only import for type checking (to avoid circular imports).
+ pass
+
+# By default, the local server will try to open on localhost, port 7860.
+# If that is not available, then it will try 7861, 7862, ... 7959.
+INITIAL_PORT_VALUE = int(os.getenv("GRADIO_SERVER_PORT", "7860"))
+TRY_NUM_PORTS = int(os.getenv("GRADIO_NODE_NUM_PORTS", "100"))
+LOCALHOST_NAME = os.getenv(
+ "GRADIO_NODE_SERVER_NAME", os.getenv("GRADIO_SERVER_NAME", "127.0.0.1")
+)
+
+
+def start_node_server(
+ server_name: str | None = None,
+ server_port: int | None = None,
+ node_path: str | None = None,
+ python_port: int | None = None,
+ python_host: str | None = None,
+ static_worker_ports: list[int] | None = None,
+ debug: bool = False,
+) -> tuple[str | None, subprocess.Popen[bytes] | None, int | None]:
+ """Launches the Node SSR server as a front proxy.
+
+ Parameters:
+ server_name: to make app accessible on local network, set this to "0.0.0.0". Can be set by environment variable GRADIO_SERVER_NAME.
+ server_port: will start gradio app on this port (if available). Can be set by environment variable GRADIO_SERVER_PORT.
+ node_path: the path to the node executable. Can be set by environment variable GRADIO_NODE_PATH.
+ python_port: the port of the main Python (FastAPI) server that Node will proxy to.
+ python_host: the host of the main Python server (default 127.0.0.1).
+ static_worker_ports: ports of static file worker processes for round-robin proxying.
+
+ Returns:
+ server_name: the name of the server (default is "localhost")
+ node_process: the node process that is running the SSR app
+ node_port: the port the node server is running on
+ """
+
+ server_name = server_name or LOCALHOST_NAME
+
+ # Strip IPv6 brackets from the address if they exist.
+ # This is needed as http://[::1]:port/ is a valid browser address,
+ # but not a valid IPv6 address, so asyncio will throw an exception.
+ if server_name.startswith("[") and server_name.endswith("]"):
+ host = server_name[1:-1]
+ else:
+ host = server_name
+
+ server_ports = (
+ [server_port]
+ if server_port is not None
+ else range(INITIAL_PORT_VALUE, INITIAL_PORT_VALUE + TRY_NUM_PORTS)
+ )
+
+ node_process, node_port = start_node_process(
+ node_path=node_path or os.getenv("GRADIO_NODE_PATH"),
+ server_name=host,
+ server_ports=server_ports,
+ python_port=python_port,
+ python_host=python_host or "127.0.0.1",
+ static_worker_ports=static_worker_ports or [],
+ debug=debug,
+ )
+
+ return server_name, node_process, node_port
+
+
+GRADIO_LOCAL_DEV_MODE = os.getenv("GRADIO_LOCAL_DEV_MODE") is not None
+SSR_APP_PATH = Path(__file__).parent.joinpath("templates", "node", "build")
+
+
+def start_node_process(
+ node_path: str | None,
+ server_name: str,
+ server_ports: list[int] | range,
+ python_port: int | None = None,
+ python_host: str = "127.0.0.1",
+ static_worker_ports: list[int] | None = None,
+ debug: bool = False,
+) -> tuple[subprocess.Popen[bytes] | None, int | None]:
+ if GRADIO_LOCAL_DEV_MODE:
+ return None, 9876
+ if not node_path:
+ return None, None
+
+ node_process = None
+
+ for port in server_ports:
+ try:
+ # The fastest way to check if a port is available is to try to bind to it with socket.
+ # If the port is not available, socket will throw an OSError.
+ s = socket.socket()
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ # Really, we should be checking if (server_name, server_port) is available, but
+ # socket.bind() doesn't seem to throw an OSError with ipv6 addresses, based on my testing.
+ # Instead, we just check if the port is available on localhost.
+ s.bind((server_name, port))
+ s.close()
+
+ # Set environment variables for the Node server
+ env = os.environ
+ env["PORT"] = str(port)
+ env["HOST"] = server_name
+ if GRADIO_LOCAL_DEV_MODE:
+ env["GRADIO_LOCAL_DEV_MODE"] = "1"
+
+ # Proxy configuration: tell Node where Python and workers are
+ if python_port is not None:
+ env["GRADIO_PYTHON_PORT"] = str(python_port)
+ env["GRADIO_PYTHON_HOST"] = python_host
+ if static_worker_ports:
+ env["GRADIO_STATIC_WORKER_PORTS"] = ",".join(
+ str(p) for p in static_worker_ports
+ )
+
+ register_file = str(
+ Path(__file__).parent.joinpath("templates", "register.mjs")
+ )
+
+ if sys.platform == "win32":
+ register_file = "file://" + register_file
+
+ node_process = subprocess.Popen(
+ [node_path, "--import", register_file, SSR_APP_PATH],
+ env=env,
+ stdout=subprocess.DEVNULL if not debug else None,
+ stderr=subprocess.DEVNULL if not debug else None,
+ )
+
+ # Node starts only after the Python backend is already
+ # listening (Blocks.launch defers the front-proxy start), so we
+ # can verify that Node actually renders a page rather than merely
+ # opening its TCP port. Polling HEAD / until it succeeds means the
+ # SSR runtime is warm before we return — otherwise the user-facing
+ # port is reachable while the first requests 502 as SSR initialises.
+ is_working = verify_server_startup(server_name, port, timeout=30)
+ if is_working:
+ signal.signal(
+ signal.SIGTERM, lambda _, __: handle_sigterm(node_process)
+ )
+ signal.signal(signal.SIGINT, lambda _, __: handle_sigterm(node_process))
+
+ return node_process, port
+
+ else:
+ # If verification failed, terminate the process and try the next port
+ node_process.terminate()
+ node_process.wait(timeout=2)
+ node_process = None
+
+ except OSError:
+ continue
+ except Exception as e:
+ warnings.warn(
+ f"Unexpected error while starting Node server: {e}. Trying next port..."
+ )
+ if node_process:
+ node_process.terminate()
+ node_process = None
+ continue
+
+ # If all attempts fail
+ print(
+ f"Cannot start Node server on any port in the range {server_ports[0]}-{server_ports[-1]}."
+ )
+ print(
+ "Please install Node 20 or higher and set the environment variable GRADIO_NODE_PATH to the path of your Node executable."
+ )
+ print(
+ "You can explicitly specify a port by setting the environment variable GRADIO_NODE_PORT."
+ )
+
+ return None, None
+
+
+def attempt_connection(host: str, port: int) -> bool:
+ """Attempts a single connection to the server."""
+ try:
+ with closing(socket.create_connection((host, port), timeout=1)):
+ return True
+ except (TimeoutError, ConnectionRefusedError):
+ return False
+ except Exception:
+ return False
+
+
+def verify_server_startup(host: str, port: int, timeout: float = 15.0) -> bool:
+ """Polls ``HEAD /`` until the server returns a non-5xx status (or the
+ timeout elapses), confirming it can actually serve requests rather than
+ just that its TCP port is open."""
+ start_time = time.time()
+ while time.time() - start_time < timeout:
+ try:
+ conn = HTTPConnection(host, port, timeout=2)
+ conn.request("HEAD", "/")
+ resp = conn.getresponse()
+ conn.close()
+ if resp.status < 500:
+ return True
+ except Exception:
+ pass
+ time.sleep(0.2)
+ return False
+
+
+def handle_sigterm(node_process: subprocess.Popen[bytes] | None):
+ if node_process is not None:
+ print("\nStopping Node.js server...")
+ node_process.terminate()
+ node_process.wait()
+ sys.exit(0)
diff --git a/gradio/oauth.py b/gradio/oauth.py
new file mode 100644
index 0000000..0622fcd
--- /dev/null
+++ b/gradio/oauth.py
@@ -0,0 +1,378 @@
+from __future__ import annotations
+
+import hashlib
+import os
+import time
+import typing
+import urllib.parse
+import warnings
+from dataclasses import dataclass, field
+
+import fastapi
+from fastapi.responses import RedirectResponse
+from huggingface_hub import get_token, whoami
+
+from gradio.utils import get_space
+
+OAUTH_CLIENT_ID = os.environ.get("OAUTH_CLIENT_ID")
+OAUTH_CLIENT_SECRET = os.environ.get("OAUTH_CLIENT_SECRET")
+OAUTH_SCOPES = os.environ.get("OAUTH_SCOPES")
+OPENID_PROVIDER_URL = os.environ.get("OPENID_PROVIDER_URL")
+
+MAX_REDIRECTS = 2
+
+
+def attach_oauth(app: fastapi.FastAPI):
+ try:
+ from starlette.middleware.sessions import SessionMiddleware
+ except ImportError as e:
+ raise ImportError(
+ "Cannot initialize OAuth to due a missing library. Please run `pip install gradio[oauth]` or add "
+ "`gradio[oauth]` to your requirements.txt file in order to install the required dependencies."
+ ) from e
+
+ # Add `/login/huggingface`, `/login/callback` and `/logout` routes to enable OAuth in the Gradio app.
+ # If the app is running in a Space, OAuth is enabled normally. Otherwise, we mock the "real" routes to make the
+ # user log in with a fake user profile - without any calls to hf.co.
+
+ if get_space() is not None:
+ _add_oauth_routes(app)
+ else:
+ _add_mocked_oauth_routes(app)
+
+ # Session Middleware requires a secret key to sign the cookies. Let's use a hash
+ # of the OAuth secret key to make it unique to the Space + updated in case OAuth
+ # config gets updated.
+ session_secret = (OAUTH_CLIENT_SECRET or "") + "-v4"
+ # ^ if we change the session cookie format in the future, we can bump the version of the session secret to make
+ # sure cookies are invalidated. Otherwise some users with an old cookie format might get a HTTP 500 error.
+ app.add_middleware(
+ SessionMiddleware, # type: ignore
+ secret_key=hashlib.sha256(session_secret.encode()).hexdigest(),
+ same_site="none",
+ https_only=True,
+ )
+
+
+def _add_oauth_routes(app: fastapi.FastAPI) -> None:
+ """Add OAuth routes to the FastAPI app (login, callback handler and logout)."""
+ try:
+ from authlib.integrations.base_client.errors import MismatchingStateError
+ from authlib.integrations.starlette_client import OAuth
+ except ImportError as e:
+ raise ImportError(
+ "Cannot initialize OAuth to due a missing library. Please run `pip install gradio[oauth]` or add "
+ "`gradio[oauth]` to your requirements.txt file in order to install the required dependencies."
+ ) from e
+
+ # Check environment variables
+ msg = (
+ "OAuth is required but {} environment variable is not set. Make sure you've enabled OAuth in your Space by"
+ " setting `hf_oauth: true` in the Space metadata."
+ )
+
+ if OAUTH_CLIENT_ID is None:
+ raise ValueError(msg.format("OAUTH_CLIENT_ID"))
+ if OAUTH_CLIENT_SECRET is None:
+ raise ValueError(msg.format("OAUTH_CLIENT_SECRET"))
+ if OAUTH_SCOPES is None:
+ raise ValueError(msg.format("OAUTH_SCOPES"))
+ if OPENID_PROVIDER_URL is None:
+ raise ValueError(msg.format("OPENID_PROVIDER_URL"))
+
+ # Register OAuth server
+ oauth = OAuth()
+ oauth.register(
+ name="huggingface",
+ client_id=OAUTH_CLIENT_ID,
+ client_secret=OAUTH_CLIENT_SECRET,
+ client_kwargs={"scope": OAUTH_SCOPES},
+ server_metadata_url=OPENID_PROVIDER_URL + "/.well-known/openid-configuration",
+ )
+
+ # Define OAuth routes
+ @app.get("/login/huggingface")
+ async def oauth_login(request: fastapi.Request):
+ """Endpoint that redirects to HF OAuth page."""
+ # Define target (where to redirect after login)
+ redirect_uri = _generate_redirect_uri(request)
+ return await oauth.huggingface.authorize_redirect(request, redirect_uri) # type: ignore
+
+ @app.get("/login/callback")
+ async def oauth_redirect_callback(request: fastapi.Request) -> RedirectResponse:
+ """Endpoint that handles the OAuth callback."""
+ try:
+ oauth_info = await oauth.huggingface.authorize_access_token(request) # type: ignore
+ except MismatchingStateError:
+ # If the state mismatch, it is very likely that the cookie is corrupted.
+ # There is a bug reported in authlib that causes the token to grow indefinitely if the user tries to login
+ # repeatedly. Since cookies cannot get bigger than 4kb, the token will be truncated at some point - hence
+ # losing the state. A workaround is to delete the cookie and redirect the user to the login page again.
+ # See https://github.com/lepture/authlib/issues/622 for more details.
+
+ # Delete all keys that are related to the OAuth state, just in case
+ for key in list(request.session.keys()):
+ if key.startswith("_state_huggingface"):
+ request.session.pop(key)
+
+ # Parse query params
+ nb_redirects = int(request.query_params.get("_nb_redirects", 0))
+ target_url = request.query_params.get("_target_url")
+
+ # Build /login URI with the same query params as before and bump nb_redirects count
+ query_params: dict[str, str | int] = {"_nb_redirects": nb_redirects + 1}
+ if target_url:
+ query_params["_target_url"] = target_url
+
+ login_uri = f"/login/huggingface?{urllib.parse.urlencode(query_params)}"
+
+ # If the user is redirected more than 3 times, it is very likely that the cookie is not working properly.
+ # (e.g. browser is blocking third-party cookies in iframe). In this case, redirect the user in the
+ # non-iframe view.
+ if nb_redirects > MAX_REDIRECTS:
+ host = os.environ.get("SPACE_HOST")
+ if host is None: # cannot happen in a Space
+ raise RuntimeError(
+ "Gradio is not running in a Space (SPACE_HOST environment variable is not set)."
+ " Cannot redirect to non-iframe view."
+ ) from None
+ host_url = "https://" + host.rstrip("/")
+ return RedirectResponse(host_url + login_uri)
+
+ # Redirect the user to the login page again
+ return RedirectResponse(login_uri)
+
+ # OAuth login worked => store the user info in the session and redirect
+ request.session["oauth_info"] = oauth_info
+ return _redirect_to_target(request)
+
+ @app.get("/logout")
+ async def oauth_logout(request: fastapi.Request) -> RedirectResponse:
+ """Endpoint that logs out the user (e.g. delete cookie session)."""
+ request.session.pop("oauth_info", None)
+ return _redirect_to_target(request)
+
+
+def _add_mocked_oauth_routes(app: fastapi.FastAPI) -> None:
+ """Add fake oauth routes if Gradio is run locally and OAuth is enabled.
+
+ Clicking on a gr.LoginButton will have the same behavior as in a Space (i.e. gets redirected in a new tab) but
+ instead of authenticating with HF, a mocked user profile is added to the session.
+ """
+ warnings.warn(
+ "Gradio does not support OAuth features outside of a Space environment. To help"
+ " you debug your app locally, the login and logout buttons are mocked with your"
+ " profile. To make it work, your machine must be logged in to Huggingface."
+ )
+ mocked_oauth_info = _get_mocked_oauth_info()
+
+ # Define OAuth routes
+ @app.get("/login/huggingface")
+ async def oauth_login(request: fastapi.Request): # noqa: ARG001
+ """Fake endpoint that redirects to HF OAuth page."""
+ # Define target (where to redirect after login)
+ redirect_uri = _generate_redirect_uri(request)
+ return RedirectResponse(
+ "/login/callback?" + urllib.parse.urlencode({"_target_url": redirect_uri})
+ )
+
+ @app.get("/login/callback")
+ async def oauth_redirect_callback(request: fastapi.Request) -> RedirectResponse:
+ """Endpoint that handles the OAuth callback."""
+ # `mocked_oauth_info` is computed once at startup, so its `expires_at` would
+ # eventually be in the past and every login would be treated as expired.
+ request.session["oauth_info"] = {
+ **mocked_oauth_info,
+ "expires_at": int(time.time()) + 8 * 60 * 60, # 8 hours
+ }
+ return _redirect_to_target(request)
+
+ @app.get("/logout")
+ async def oauth_logout(request: fastapi.Request) -> RedirectResponse:
+ """Endpoint that logs out the user (e.g. delete cookie session)."""
+ request.session.pop("oauth_info", None)
+ return _redirect_to_target(request)
+
+
+def _generate_redirect_uri(request: fastapi.Request) -> str:
+ if "_target_url" in request.query_params:
+ # if `_target_url` already in query params => respect it
+ target = request.query_params["_target_url"]
+ else:
+ # otherwise => keep query params
+ target = "/?" + urllib.parse.urlencode(request.query_params)
+
+ # On Spaces, the redirect URI must always be https:///login/callback,
+ # so if a custom domain is used, we need to replace it with the hf.space URL
+ if space_host := os.getenv("SPACE_HOST"):
+ print(f"SPACE_HOST: {space_host}")
+ space_host = space_host.split(",")[
+ 0
+ ] # When custom domain is used, SPACE_HOST is a comma-separated list
+ print(f"SPACE_HOST after split: {space_host}")
+ redirect_uri = f"https://{space_host}/login/callback?{urllib.parse.urlencode({'_target_url': target})}"
+ print(f"Redirect URI: {redirect_uri}")
+ return redirect_uri
+
+ redirect_uri = request.url_for("oauth_redirect_callback").include_query_params(
+ _target_url=target
+ )
+ redirect_uri_as_str = str(redirect_uri)
+ return redirect_uri_as_str
+
+
+def _redirect_to_target(
+ request: fastapi.Request, default_target: str = "/"
+) -> RedirectResponse:
+ target = request.query_params.get("_target_url", default_target)
+ # Prevent open redirect by stripping scheme/host and only using the path.
+ parsed = urllib.parse.urlparse(target)
+ # Collapse any leading slashes/backslashes so the result is always a
+ # single-slash local path. urlparse leaves 4+ leading slashes in `.path`
+ # (e.g. "////evil.com" -> "//evil.com"), which browsers resolve as a
+ # scheme-relative URL to an external host — restoring the CVE-2026-28415
+ # open redirect (GHSA-vwgg-rgg9-xx9q).
+ safe_target = "/" + (parsed.path or "").lstrip("/\\")
+ if parsed.query:
+ safe_target += "?" + parsed.query
+ if parsed.fragment:
+ safe_target += "#" + parsed.fragment
+ return RedirectResponse(safe_target)
+
+
+def _get_valid_oauth_info_from_session(
+ session: typing.MutableMapping[str, typing.Any],
+) -> dict[str, typing.Any] | None:
+ oauth_info = session.get("oauth_info")
+ if oauth_info is None:
+ return None
+
+ expires_at = oauth_info.get("expires_at")
+ if expires_at is not None and expires_at < time.time():
+ session.pop("oauth_info", None)
+ return None
+
+ return oauth_info
+
+
+@dataclass
+class OAuthProfile(typing.Dict): # inherit from Dict for backward compatibility
+ """
+ A Gradio OAuthProfile object that can be used to inject the profile of a user in a
+ function. If a function expects `OAuthProfile` or `Optional[OAuthProfile]` as input,
+ the value will be injected from the FastAPI session if the user is logged in. If the
+ user is not logged in and the function expects `OAuthProfile`, an error will be
+ raised.
+
+ Attributes:
+ name (str): The name of the user (e.g. 'Abubakar Abid').
+ username (str): The username of the user (e.g. 'abidlabs')
+ profile (str): The profile URL of the user (e.g. 'https://huggingface.co/abidlabs').
+ picture (str): The profile picture URL of the user.
+
+ Example:
+ import gradio as gr
+ from typing import Optional
+
+
+ def hello(profile: Optional[gr.OAuthProfile]) -> str:
+ if profile is None:
+ return "I don't know you."
+ return f"Hello {profile.name}"
+
+
+ with gr.Blocks() as demo:
+ gr.LoginButton()
+ gr.Markdown().attach_load_event(hello, None)
+ """
+
+ name: str = field(init=False)
+ username: str = field(init=False)
+ profile: str = field(init=False)
+ picture: str = field(init=False)
+
+ def __init__(self, data: dict): # hack to make OAuthProfile backward compatible
+ self.update(data)
+ self.name = self["name"]
+ self.username = self["preferred_username"]
+ self.profile = self["profile"]
+ self.picture = self["picture"]
+
+
+@dataclass
+class OAuthToken:
+ """
+ A Gradio OAuthToken object that can be used to inject the access token of a user in a
+ function. If a function expects `OAuthToken` or `Optional[OAuthToken]` as input,
+ the value will be injected from the FastAPI session if the user is logged in. If the
+ user is not logged in and the function expects `OAuthToken`, an error will be
+ raised.
+
+ Attributes:
+ token (str): The access token of the user.
+ scope (str): The scope of the access token.
+ expires_at (int): The expiration timestamp of the access token.
+
+ Example:
+ import gradio as gr
+ from typing import Optional
+ from huggingface_hub import whoami
+
+
+ def list_organizations(oauth_token: Optional[gr.OAuthToken]) -> str:
+ if oauth_token is None:
+ return "Please log in to list organizations."
+ org_names = [org["name"] for org in whoami(oauth_token.token)["orgs"]]
+ return f"You belong to {', '.join(org_names)}."
+
+
+ with gr.Blocks() as demo:
+ gr.LoginButton()
+ gr.Markdown().attach_load_event(list_organizations, None)
+ """
+
+ token: str
+ scope: str
+ expires_at: int
+
+
+def _get_mocked_oauth_info() -> typing.Dict:
+ token = get_token()
+ if token is None:
+ raise ValueError(
+ "Your machine must be logged in to HF to debug a Gradio app locally. Please"
+ " run `huggingface-cli login` or set `HF_TOKEN` as environment variable "
+ "with one of your access token. You can generate a new token in your "
+ "settings page (https://huggingface.co/settings/tokens)."
+ )
+
+ user = whoami()
+ if user["type"] != "user":
+ raise ValueError(
+ "Your machine is not logged in with a personal account. Please use a "
+ "personal access token. You can generate a new token in your settings page"
+ " (https://huggingface.co/settings/tokens)."
+ )
+
+ return {
+ "access_token": "mock-oauth-token-for-local-dev",
+ "token_type": "bearer",
+ "expires_in": 3600,
+ "id_token": "AAAAAAAAAAAAAAAAAAAAAAAAAA",
+ "scope": "openid profile",
+ "expires_at": int(time.time()) + 8 * 60 * 60, # 8 hours
+ "userinfo": {
+ "sub": "11111111111111111111111",
+ "name": user["fullname"],
+ "preferred_username": user["name"],
+ "profile": f"https://huggingface.co/{user['name']}",
+ "picture": user["avatarUrl"],
+ "website": "",
+ "aud": "00000000-0000-0000-0000-000000000000",
+ "auth_time": 1691672844,
+ "nonce": "aaaaaaaaaaaaaaaaaaa",
+ "iat": 1691672844,
+ "exp": 1691676444,
+ "iss": "https://huggingface.co",
+ },
+ }
diff --git a/gradio/package.json b/gradio/package.json
new file mode 100644
index 0000000..1912343
--- /dev/null
+++ b/gradio/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "gradio",
+ "version": "6.20.0",
+ "description": "This is the package.json",
+ "python": "true"
+}
\ No newline at end of file
diff --git a/gradio/pipelines.py b/gradio/pipelines.py
new file mode 100644
index 0000000..f42a60f
--- /dev/null
+++ b/gradio/pipelines.py
@@ -0,0 +1,120 @@
+"""This module should not be used directly as its API is subject to change. Instead,
+please use the `gr.Interface.from_pipeline()` function."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from gradio.pipelines_utils import (
+ handle_diffusers_pipeline,
+ handle_transformers_js_pipeline,
+ handle_transformers_pipeline,
+)
+
+if TYPE_CHECKING:
+ import diffusers # ty: ignore[unresolved-import]
+ import transformers
+
+
+def load_from_pipeline(
+ pipeline: transformers.Pipeline | diffusers.DiffusionPipeline, # type: ignore
+) -> dict:
+ """
+ Gets the appropriate Interface kwargs for a given Hugging Face transformers.Pipeline or diffusers.DiffusionPipeline.
+ pipeline (transformers.Pipeline): the transformers.Pipeline from which to create an interface
+ Returns:
+ (dict): a dictionary of kwargs that can be used to construct an Interface object
+ """
+
+ if str(type(pipeline).__module__).startswith("transformers.pipelines."):
+ pipeline_info = handle_transformers_pipeline(pipeline)
+ elif str(type(pipeline).__module__).startswith("diffusers.pipelines."):
+ pipeline_info = handle_diffusers_pipeline(pipeline)
+ else:
+ raise ValueError(
+ "pipeline must be a transformers.pipeline or diffusers.pipeline"
+ )
+
+ def fn(*params):
+ if pipeline_info:
+ data = pipeline_info["preprocess"](*params)
+ if str(type(pipeline).__module__).startswith("transformers.pipelines"):
+ from transformers import pipelines
+
+ # special cases that needs to be handled differently
+ if isinstance(
+ pipeline,
+ (
+ pipelines.text_classification.TextClassificationPipeline, # type: ignore
+ pipelines.text2text_generation.Text2TextGenerationPipeline, # type: ignore
+ pipelines.text2text_generation.TranslationPipeline, # type: ignore
+ pipelines.token_classification.TokenClassificationPipeline, # type: ignore
+ ),
+ ):
+ data = pipeline(*data)
+ else:
+ data = pipeline(**data) # type: ignore
+ # special case for object-detection and token-classification pipelines
+ # original input image / text sent to postprocess function
+ if isinstance(
+ pipeline,
+ (
+ pipelines.object_detection.ObjectDetectionPipeline, # type: ignore
+ pipelines.token_classification.TokenClassificationPipeline, # type: ignore
+ ),
+ ):
+ output = pipeline_info["postprocess"](data, params[0])
+ else:
+ output = pipeline_info["postprocess"](data)
+ return output
+
+ elif str(type(pipeline).__module__).startswith("diffusers.pipelines"):
+ data = pipeline(**data) # type: ignore
+ output = pipeline_info["postprocess"](data)
+ return output
+ else:
+ raise ValueError("pipeline_info can not be None.")
+
+ interface_info = pipeline_info.copy() if pipeline_info else {}
+ interface_info["fn"] = fn
+ del interface_info["preprocess"]
+ del interface_info["postprocess"]
+
+ # define the title/description of the Interface
+ interface_info["title"] = (
+ pipeline.model.config.name_or_path # type: ignore
+ if str(type(pipeline).__module__).startswith("transformers.pipelines")
+ else pipeline.__class__.__name__
+ )
+
+ return interface_info
+
+
+def load_from_js_pipeline(pipeline) -> dict:
+ if str(type(pipeline).__module__).startswith("transformers_js_py."):
+ pipeline_info = handle_transformers_js_pipeline(pipeline)
+ else:
+ raise ValueError("pipeline must be a transformers_js_py's pipeline")
+
+ async def fn(*params):
+ preprocess = pipeline_info["preprocess"]
+ postprocess = pipeline_info["postprocess"]
+ postprocess_takes_inputs = pipeline_info.get("postprocess_takes_inputs", False)
+
+ preprocessed_params = preprocess(*params) if preprocess else params
+ pipeline_output = await pipeline(*preprocessed_params)
+ postprocessed_output = (
+ postprocess(pipeline_output, *(params if postprocess_takes_inputs else ()))
+ if postprocess
+ else pipeline_output
+ )
+
+ return postprocessed_output
+
+ interface_info = {
+ "fn": fn,
+ "inputs": pipeline_info["inputs"],
+ "outputs": pipeline_info["outputs"],
+ "title": f"{pipeline.task} ({pipeline.model.config._name_or_path})",
+ }
+ return interface_info
diff --git a/gradio/pipelines_utils.py b/gradio/pipelines_utils.py
new file mode 100644
index 0000000..048e1fb
--- /dev/null
+++ b/gradio/pipelines_utils.py
@@ -0,0 +1,810 @@
+"""
+Defines internal helper methods for handling transformers and diffusers pipelines.
+These are used by load_from_pipeline method in pipelines.py.
+"""
+
+from typing import Any
+
+import numpy as np
+from PIL import Image
+
+from gradio import components
+
+
+def handle_transformers_pipeline(pipeline: Any) -> dict[str, Any] | None:
+ try:
+ import transformers
+ except ImportError as ie:
+ raise ImportError(
+ "transformers not installed. Please try `pip install transformers`"
+ ) from ie
+
+ def is_transformers_pipeline_type(pipeline, class_name: str):
+ cls = getattr(transformers, class_name, None)
+ return cls and isinstance(pipeline, cls)
+
+ # Handle the different pipelines. The has_attr() checks to make sure the pipeline exists in the
+ # version of the transformers library that the user has installed.
+ if is_transformers_pipeline_type(pipeline, "AudioClassificationPipeline"):
+ return {
+ "inputs": components.Audio(type="filepath", label="Input", render=False),
+ "outputs": components.Label(label="Class", render=False),
+ "preprocess": lambda i: {"inputs": i},
+ "postprocess": lambda r: {i["label"]: i["score"] for i in r},
+ }
+ if is_transformers_pipeline_type(pipeline, "AutomaticSpeechRecognitionPipeline"):
+ return {
+ "inputs": components.Audio(type="filepath", label="Input", render=False),
+ "outputs": components.Textbox(label="Output", render=False),
+ "preprocess": lambda i: {"inputs": i},
+ "postprocess": lambda r: r["text"],
+ }
+ if is_transformers_pipeline_type(pipeline, "FeatureExtractionPipeline"):
+ return {
+ "inputs": components.Textbox(label="Input", render=False),
+ "outputs": components.Dataframe(label="Output", render=False),
+ "preprocess": lambda x: {"inputs": x},
+ "postprocess": lambda r: r[0],
+ }
+ if is_transformers_pipeline_type(pipeline, "FillMaskPipeline"):
+ return {
+ "inputs": components.Textbox(label="Input", render=False),
+ "outputs": components.Label(label="Classification", render=False),
+ "preprocess": lambda x: {"inputs": x},
+ "postprocess": lambda r: {i["token_str"]: i["score"] for i in r},
+ }
+ if is_transformers_pipeline_type(pipeline, "ImageClassificationPipeline"):
+ return {
+ "inputs": components.Image(
+ type="filepath", label="Input Image", render=False
+ ),
+ "outputs": components.Label(label="Classification", render=False),
+ "preprocess": lambda i: {"images": i},
+ "postprocess": lambda r: {i["label"]: i["score"] for i in r},
+ }
+ if is_transformers_pipeline_type(pipeline, "QuestionAnsweringPipeline"):
+ return {
+ "inputs": [
+ components.Textbox(lines=7, label="Context", render=False),
+ components.Textbox(label="Question", render=False),
+ ],
+ "outputs": [
+ components.Textbox(label="Answer", render=False),
+ components.Label(label="Score", render=False),
+ ],
+ "preprocess": lambda c, q: {"context": c, "question": q},
+ "postprocess": lambda r: (r["answer"], r["score"]),
+ }
+ if is_transformers_pipeline_type(pipeline, "SummarizationPipeline"):
+ return {
+ "inputs": components.Textbox(lines=7, label="Input", render=False),
+ "outputs": components.Textbox(label="Summary", render=False),
+ "preprocess": lambda x: {"inputs": x},
+ "postprocess": lambda r: r[0]["summary_text"],
+ }
+ if is_transformers_pipeline_type(pipeline, "TextClassificationPipeline"):
+ return {
+ "inputs": components.Textbox(label="Input", render=False),
+ "outputs": components.Label(label="Classification", render=False),
+ "preprocess": lambda x: [x],
+ "postprocess": lambda r: {i["label"]: i["score"] for i in r},
+ }
+ if is_transformers_pipeline_type(pipeline, "TokenClassificationPipeline"):
+ return {
+ "inputs": components.Textbox(label="Input", render=False),
+ "outputs": components.HighlightedText(label="Entities", render=False),
+ "preprocess": lambda x: [x],
+ "postprocess": lambda r, text: {
+ "text": text,
+ "entities": r,
+ },
+ }
+ if is_transformers_pipeline_type(pipeline, "TextGenerationPipeline"):
+ return {
+ "inputs": components.Textbox(label="Input", render=False),
+ "outputs": components.Textbox(label="Output", render=False),
+ "preprocess": lambda x: {"text_inputs": x},
+ "postprocess": lambda r: r[0]["generated_text"],
+ }
+ if is_transformers_pipeline_type(pipeline, "TranslationPipeline"):
+ return {
+ "inputs": components.Textbox(label="Input", render=False),
+ "outputs": components.Textbox(label="Translation", render=False),
+ "preprocess": lambda x: [x],
+ "postprocess": lambda r: r[0]["translation_text"],
+ }
+ if is_transformers_pipeline_type(pipeline, "Text2TextGenerationPipeline"):
+ return {
+ "inputs": components.Textbox(label="Input", render=False),
+ "outputs": components.Textbox(label="Generated Text", render=False),
+ "preprocess": lambda x: [x],
+ "postprocess": lambda r: r[0]["generated_text"],
+ }
+ if is_transformers_pipeline_type(pipeline, "ZeroShotClassificationPipeline"):
+ return {
+ "inputs": [
+ components.Textbox(label="Input", render=False),
+ components.Textbox(
+ label="Possible class names (comma-separated)", render=False
+ ),
+ components.Checkbox(label="Allow multiple true classes", render=False),
+ ],
+ "outputs": components.Label(label="Classification", render=False),
+ "preprocess": lambda i, c, m: {
+ "sequences": i,
+ "candidate_labels": c,
+ "multi_label": m,
+ },
+ "postprocess": lambda r: {
+ r["labels"][i]: r["scores"][i] for i in range(len(r["labels"]))
+ },
+ }
+ if is_transformers_pipeline_type(pipeline, "DocumentQuestionAnsweringPipeline"):
+ return {
+ "inputs": [
+ components.Image(type="filepath", label="Input Document", render=False),
+ components.Textbox(label="Question", render=False),
+ ],
+ "outputs": components.Label(label="Label", render=False),
+ "preprocess": lambda img, q: {"image": img, "question": q},
+ "postprocess": lambda r: {i["answer"]: i["score"] for i in r},
+ }
+ if is_transformers_pipeline_type(pipeline, "VisualQuestionAnsweringPipeline"):
+ return {
+ "inputs": [
+ components.Image(type="filepath", label="Input Image", render=False),
+ components.Textbox(label="Question", render=False),
+ ],
+ "outputs": components.Label(label="Score", render=False),
+ "preprocess": lambda img, q: {"image": img, "question": q},
+ "postprocess": lambda r: {i["answer"]: i["score"] for i in r},
+ }
+ if is_transformers_pipeline_type(pipeline, "ImageToTextPipeline"):
+ return {
+ "inputs": components.Image(
+ type="filepath", label="Input Image", render=False
+ ),
+ "outputs": components.Textbox(label="Text", render=False),
+ "preprocess": lambda i: {"images": i},
+ "postprocess": lambda r: r[0]["generated_text"],
+ }
+ if is_transformers_pipeline_type(pipeline, "ObjectDetectionPipeline"):
+ return {
+ "inputs": components.Image(
+ type="filepath", label="Input Image", render=False
+ ),
+ "outputs": components.AnnotatedImage(
+ label="Objects Detected", render=False
+ ),
+ "preprocess": lambda i: {"inputs": i},
+ "postprocess": lambda r, img: (
+ img,
+ [
+ (
+ (
+ i["box"]["xmin"],
+ i["box"]["ymin"],
+ i["box"]["xmax"],
+ i["box"]["ymax"],
+ ),
+ i["label"],
+ )
+ for i in r
+ ],
+ ),
+ }
+ raise ValueError(f"Unsupported transformers pipeline type: {type(pipeline)}")
+
+
+def handle_diffusers_pipeline(pipeline: Any) -> dict[str, Any] | None:
+ try:
+ import diffusers # ty: ignore[unresolved-import]
+ except ImportError as ie:
+ raise ImportError(
+ "diffusers not installed. Please try `pip install diffusers`"
+ ) from ie
+
+ def is_diffusers_pipeline_type(pipeline, class_name: str):
+ cls = getattr(diffusers, class_name, None)
+ return cls and isinstance(pipeline, cls)
+
+ if is_diffusers_pipeline_type(pipeline, "StableDiffusionPipeline"):
+ return {
+ "inputs": [
+ components.Textbox(label="Prompt", render=False),
+ components.Textbox(label="Negative prompt", render=False),
+ components.Slider(
+ label="Number of inference steps",
+ minimum=1,
+ maximum=500,
+ value=50,
+ step=1,
+ ),
+ components.Slider(
+ label="Guidance scale",
+ minimum=1,
+ maximum=20,
+ value=7.5,
+ step=0.5,
+ ),
+ ],
+ "outputs": components.Image(
+ label="Generated Image", render=False, type="pil"
+ ),
+ "preprocess": lambda prompt, n_prompt, num_inf_steps, g_scale: {
+ "prompt": prompt,
+ "negative_prompt": n_prompt,
+ "num_inference_steps": num_inf_steps,
+ "guidance_scale": g_scale,
+ },
+ "postprocess": lambda r: r["images"][0],
+ }
+ if is_diffusers_pipeline_type(pipeline, "StableDiffusionImg2ImgPipeline"):
+ return {
+ "inputs": [
+ components.Textbox(label="Prompt", render=False),
+ components.Textbox(label="Negative prompt", render=False),
+ components.Image(type="filepath", label="Image", render=False),
+ components.Slider(
+ label="Strength", minimum=0, maximum=1, value=0.8, step=0.1
+ ),
+ components.Slider(
+ label="Number of inference steps",
+ minimum=1,
+ maximum=500,
+ value=50,
+ step=1,
+ ),
+ components.Slider(
+ label="Guidance scale",
+ minimum=1,
+ maximum=20,
+ value=7.5,
+ step=0.5,
+ ),
+ ],
+ "outputs": components.Image(
+ label="Generated Image", render=False, type="pil"
+ ),
+ "preprocess": lambda prompt,
+ n_prompt,
+ image,
+ strength,
+ num_inf_steps,
+ g_scale: {
+ "prompt": prompt,
+ "image": Image.open(image).resize((768, 768)),
+ "negative_prompt": n_prompt,
+ "num_inference_steps": num_inf_steps,
+ "guidance_scale": g_scale,
+ "strength": strength,
+ },
+ "postprocess": lambda r: r["images"][0],
+ }
+ if is_diffusers_pipeline_type(pipeline, "StableDiffusionInpaintPipeline"):
+ return {
+ "inputs": [
+ components.Textbox(label="Prompt", render=False),
+ components.Textbox(label="Negative prompt", render=False),
+ components.Image(type="filepath", label="Image", render=False),
+ components.Image(type="filepath", label="Mask Image", render=False),
+ components.Slider(
+ label="Strength", minimum=0, maximum=1, value=0.8, step=0.1
+ ),
+ components.Slider(
+ label="Number of inference steps",
+ minimum=1,
+ maximum=500,
+ value=50,
+ step=1,
+ ),
+ components.Slider(
+ label="Guidance scale",
+ minimum=1,
+ maximum=20,
+ value=7.5,
+ step=0.5,
+ ),
+ ],
+ "outputs": components.Image(
+ label="Generated Image", render=False, type="pil"
+ ),
+ "preprocess": lambda prompt,
+ n_prompt,
+ image,
+ mask_image,
+ strength,
+ num_inf_steps,
+ g_scale: {
+ "prompt": prompt,
+ "image": Image.open(image).resize((768, 768)),
+ "mask_image": Image.open(mask_image).resize((768, 768)),
+ "negative_prompt": n_prompt,
+ "num_inference_steps": num_inf_steps,
+ "guidance_scale": g_scale,
+ "strength": strength,
+ },
+ "postprocess": lambda r: r["images"][0],
+ }
+ if is_diffusers_pipeline_type(pipeline, "StableDiffusionDepth2ImgPipeline"):
+ return {
+ "inputs": [
+ components.Textbox(label="Prompt", render=False),
+ components.Textbox(label="Negative prompt", render=False),
+ components.Image(type="filepath", label="Image", render=False),
+ components.Slider(
+ label="Strength", minimum=0, maximum=1, value=0.8, step=0.1
+ ),
+ components.Slider(
+ label="Number of inference steps",
+ minimum=1,
+ maximum=500,
+ value=50,
+ step=1,
+ ),
+ components.Slider(
+ label="Guidance scale",
+ minimum=1,
+ maximum=20,
+ value=7.5,
+ step=0.5,
+ ),
+ ],
+ "outputs": components.Image(
+ label="Generated Image", render=False, type="pil"
+ ),
+ "preprocess": lambda prompt,
+ n_prompt,
+ image,
+ strength,
+ num_inf_steps,
+ g_scale: {
+ "prompt": prompt,
+ "image": Image.open(image).resize((768, 768)),
+ "negative_prompt": n_prompt,
+ "num_inference_steps": num_inf_steps,
+ "guidance_scale": g_scale,
+ "strength": strength,
+ },
+ "postprocess": lambda r: r["images"][0],
+ }
+ if is_diffusers_pipeline_type(pipeline, "StableDiffusionImageVariationPipeline"):
+ return {
+ "inputs": [
+ components.Image(type="filepath", label="Image", render=False),
+ components.Slider(
+ label="Number of inference steps",
+ minimum=1,
+ maximum=500,
+ value=50,
+ step=1,
+ ),
+ components.Slider(
+ label="Guidance scale",
+ minimum=1,
+ maximum=20,
+ value=7.5,
+ step=0.5,
+ ),
+ ],
+ "outputs": components.Image(
+ label="Generated Image", render=False, type="pil"
+ ),
+ "preprocess": lambda image, num_inf_steps, g_scale: {
+ "image": Image.open(image).resize((768, 768)),
+ "num_inference_steps": num_inf_steps,
+ "guidance_scale": g_scale,
+ },
+ "postprocess": lambda r: r["images"][0],
+ }
+ if is_diffusers_pipeline_type(pipeline, "StableDiffusionInstructPix2PixPipeline"):
+ return {
+ "inputs": [
+ components.Textbox(label="Prompt", render=False),
+ components.Textbox(label="Negative prompt", render=False),
+ components.Image(type="filepath", label="Image", render=False),
+ components.Slider(
+ label="Number of inference steps",
+ minimum=1,
+ maximum=500,
+ value=50,
+ step=1,
+ ),
+ components.Slider(
+ label="Guidance scale",
+ minimum=1,
+ maximum=20,
+ value=7.5,
+ step=0.5,
+ ),
+ components.Slider(
+ label="Image Guidance scale",
+ minimum=1,
+ maximum=5,
+ value=1.5,
+ step=0.5,
+ ),
+ ],
+ "outputs": components.Image(
+ label="Generated Image", render=False, type="pil"
+ ),
+ "preprocess": lambda prompt,
+ n_prompt,
+ image,
+ num_inf_steps,
+ g_scale,
+ img_g_scale: {
+ "prompt": prompt,
+ "image": Image.open(image).resize((768, 768)),
+ "negative_prompt": n_prompt,
+ "num_inference_steps": num_inf_steps,
+ "guidance_scale": g_scale,
+ "image_guidance_scale": img_g_scale,
+ },
+ "postprocess": lambda r: r["images"][0],
+ }
+ if is_diffusers_pipeline_type(pipeline, "StableDiffusionUpscalePipeline"):
+ return {
+ "inputs": [
+ components.Textbox(label="Prompt", render=False),
+ components.Textbox(label="Negative prompt", render=False),
+ components.Image(type="filepath", label="Image", render=False),
+ components.Slider(
+ label="Number of inference steps",
+ minimum=1,
+ maximum=500,
+ value=50,
+ step=1,
+ ),
+ components.Slider(
+ label="Guidance scale",
+ minimum=1,
+ maximum=20,
+ value=7.5,
+ step=0.5,
+ ),
+ components.Slider(
+ label="Noise level", minimum=1, maximum=100, value=20, step=1
+ ),
+ ],
+ "outputs": components.Image(
+ label="Generated Image", render=False, type="pil"
+ ),
+ "preprocess": lambda prompt,
+ n_prompt,
+ image,
+ num_inf_steps,
+ g_scale,
+ noise_level: {
+ "prompt": prompt,
+ "image": Image.open(image).resize((768, 768)),
+ "negative_prompt": n_prompt,
+ "num_inference_steps": num_inf_steps,
+ "guidance_scale": g_scale,
+ "noise_level": noise_level,
+ },
+ "postprocess": lambda r: r["images"][0],
+ }
+ raise ValueError(f"Unsupported diffusers pipeline type: {type(pipeline)}")
+
+
+def handle_transformers_js_pipeline(pipeline: Any) -> dict[str, Any]:
+ try:
+ from transformers_js_py import as_url, read_audio # type: ignore
+ except ImportError as ie:
+ raise ImportError(
+ "transformers_js_py not installed. Please add `transformers_js_py` to the requirements of your Gradio-Lite app"
+ ) from ie
+
+ ## Natural Language Processing ##
+ if pipeline.task == "fill-mask":
+ return {
+ "inputs": components.Textbox(label="Input"),
+ "outputs": components.Label(label="Classification"),
+ "preprocess": None,
+ "postprocess": lambda r: {i["token_str"]: i["score"] for i in r},
+ }
+ if pipeline.task == "question-answering":
+ return {
+ "inputs": [
+ components.Textbox(lines=7, label="Context"),
+ components.Textbox(label="Question"),
+ ],
+ "outputs": [
+ components.Textbox(label="Answer"),
+ components.Label(label="Score"),
+ ],
+ "preprocess": lambda c, q: (
+ q,
+ c,
+ ), # Placed the context first in the input UI to match `handle_transformers_pipeline`'s order of inputs, but Transformers.js' question-answering pipeline expects the question first.
+ "postprocess": lambda r: (r["answer"], r["score"]),
+ }
+ if pipeline.task == "summarization":
+ return {
+ "inputs": [
+ components.Textbox(lines=7, label="Input"),
+ components.Slider(
+ label="The maximum numbers of tokens to generate",
+ minimum=1,
+ maximum=500,
+ value=100,
+ step=1,
+ ),
+ ],
+ "outputs": components.Textbox(label="Summary"),
+ "preprocess": lambda text, max_new_tokens: (
+ text,
+ {"max_new_tokens": max_new_tokens},
+ ),
+ "postprocess": lambda r: r[0]["summary_text"],
+ }
+ if pipeline.task == "text-classification":
+ return {
+ "inputs": [
+ components.Textbox(label="Input"),
+ components.Number(label="Top k", value=5),
+ ],
+ "outputs": components.Label(label="Classification"),
+ "preprocess": lambda text, topk: (text, {"topk": topk}),
+ "postprocess": lambda r: {i["label"]: i["score"] for i in r},
+ }
+ if pipeline.task == "text-generation":
+ return {
+ "inputs": components.Textbox(label="Input"),
+ "outputs": components.Textbox(label="Output"),
+ "preprocess": None,
+ "postprocess": lambda r: r[0]["generated_text"],
+ }
+ if pipeline.task == "text2text-generation":
+ return {
+ "inputs": [
+ components.Textbox(label="Input"),
+ components.Slider(
+ label="The maximum numbers of tokens to generate",
+ minimum=1,
+ maximum=500,
+ value=100,
+ step=1,
+ ),
+ ],
+ "outputs": components.Textbox(label="Generated Text"),
+ "preprocess": lambda text, max_new_tokens: (
+ text,
+ {"max_new_tokens": max_new_tokens},
+ ),
+ "postprocess": lambda r: r[0]["generated_text"],
+ }
+ if pipeline.task == "token-classification":
+ return {
+ "inputs": components.Textbox(label="Input"),
+ "outputs": components.JSON(label="Output"),
+ "preprocess": None,
+ "postprocess": None,
+ "postprocess_takes_inputs": True,
+ }
+ if pipeline.task in {"translation", "translation_xx_to_yy"}:
+ return {
+ "inputs": [
+ components.Textbox(label="Input"),
+ components.Textbox(label="Source Language"),
+ components.Textbox(label="Target Language"),
+ ],
+ "outputs": components.Textbox(label="Translation"),
+ "preprocess": lambda x, s, t: (x, {"src_lang": s, "tgt_lang": t}),
+ "postprocess": lambda r: r[0]["translation_text"],
+ }
+ if pipeline.task == "zero-shot-classification":
+ return {
+ "inputs": [
+ components.Textbox(label="Input"),
+ components.Textbox(label="Possible class names (comma-separated)"),
+ ],
+ "outputs": components.Label(label="Classification"),
+ "preprocess": lambda text, classnames: (
+ text,
+ [c.strip() for c in classnames.split(",")],
+ ),
+ "postprocess": lambda result: dict(
+ zip(result["labels"], result["scores"], strict=False)
+ ),
+ }
+ if pipeline.task == "feature-extraction":
+ return {
+ "inputs": components.Textbox(label="Input"),
+ "outputs": components.Dataframe(label="Output"),
+ "preprocess": None,
+ "postprocess": lambda tensor: tensor.to_numpy()[0],
+ }
+
+ ## Vision ##
+ if pipeline.task == "depth-estimation":
+ return {
+ "inputs": components.Image(type="filepath", label="Input Image"),
+ "outputs": components.Image(label="Depth"),
+ "preprocess": lambda image_path: (as_url(image_path),),
+ "postprocess": lambda result: result["depth"].to_pil(),
+ }
+ if pipeline.task == "image-classification":
+ return {
+ "inputs": [
+ components.Image(type="filepath", label="Input Image"),
+ components.Number(label="Top k", value=5),
+ ],
+ "outputs": components.Label(label="Classification"),
+ "preprocess": lambda image_path, topk: (as_url(image_path), {"topk": topk}),
+ "postprocess": lambda result: {
+ item["label"]: item["score"] for item in result
+ },
+ }
+ if pipeline.task == "image-segmentation":
+ return {
+ "inputs": components.Image(type="filepath", label="Input Image"),
+ "outputs": components.AnnotatedImage(label="Segmentation"),
+ "preprocess": lambda image_path: (as_url(image_path),),
+ "postprocess": lambda result, image_path: (
+ image_path,
+ [
+ (
+ item["mask"].to_numpy()[:, :, 0]
+ / 255.0, # Reshape ([h,w,1] -> [h,w]) and normalize ([0,255] -> [0,1])
+ f"{item['label']} ({item['score']})",
+ )
+ for item in result
+ ],
+ ),
+ "postprocess_takes_inputs": True,
+ }
+ if pipeline.task == "image-to-image":
+ return {
+ "inputs": components.Image(type="filepath", label="Input Image"),
+ "outputs": components.Image(label="Output Image"),
+ "preprocess": lambda image_path: (as_url(image_path),),
+ "postprocess": lambda result: result.to_pil(),
+ }
+ if pipeline.task == "object-detection":
+ return {
+ "inputs": components.Image(type="filepath", label="Input Image"),
+ "outputs": components.AnnotatedImage(label="Objects Detected"),
+ "preprocess": lambda image_path: (as_url(image_path),),
+ "postprocess": lambda result, image_path: (
+ image_path,
+ [
+ (
+ (
+ int(item["box"]["xmin"]),
+ int(item["box"]["ymin"]),
+ int(item["box"]["xmax"]),
+ int(item["box"]["ymax"]),
+ ),
+ f"{item['label']} ({item['score']})",
+ )
+ for item in result
+ ],
+ ),
+ "postprocess_takes_inputs": True,
+ }
+ if pipeline.task == "image-feature-extraction":
+ return {
+ "inputs": components.Image(type="filepath", label="Input Image"),
+ "outputs": components.Dataframe(label="Output"),
+ "preprocess": lambda image_path: (as_url(image_path),),
+ "postprocess": lambda tensor: tensor.to_numpy(),
+ }
+
+ ## Audio ##
+ if pipeline.task == "audio-classification":
+ return {
+ "inputs": components.Audio(type="filepath", label="Input"),
+ "outputs": components.Label(label="Class"),
+ "preprocess": lambda i: (
+ read_audio(
+ i, pipeline.processor.feature_extractor.config["sampling_rate"]
+ ),
+ ),
+ "postprocess": lambda r: {i["label"]: i["score"] for i in r},
+ }
+ if pipeline.task == "automatic-speech-recognition":
+ return {
+ "inputs": components.Audio(type="filepath", label="Input"),
+ "outputs": components.Textbox(label="Output"),
+ "preprocess": lambda i: (
+ read_audio(
+ i, pipeline.processor.feature_extractor.config["sampling_rate"]
+ ),
+ ),
+ "postprocess": lambda r: r["text"],
+ }
+ if pipeline.task == "text-to-audio":
+ return {
+ "inputs": [
+ components.Textbox(label="Input"),
+ components.Textbox(label="Speaker Embeddings"),
+ ],
+ "outputs": components.Audio(label="Output"),
+ "preprocess": lambda text, speaker_embeddings: (
+ text,
+ {"speaker_embeddings": speaker_embeddings},
+ ),
+ "postprocess": lambda r: (r["sampling_rate"], np.asarray(r["audio"])),
+ }
+
+ ## Multimodal ##
+ if pipeline.task == "document-question-answering":
+ return {
+ "inputs": [
+ components.Image(type="filepath", label="Input Document"),
+ components.Textbox(label="Question"),
+ ],
+ "outputs": components.Textbox(label="Label"),
+ "preprocess": lambda img, q: (as_url(img), q),
+ "postprocess": lambda r: r[0][
+ "answer"
+ ], # This data structure is different from the original Transformers.
+ }
+ if pipeline.task == "image-to-text":
+ return {
+ "inputs": components.Image(type="filepath", label="Input Image"),
+ "outputs": components.Textbox(label="Output"),
+ "preprocess": lambda image_path: (as_url(image_path),),
+ "postprocess": lambda r: r[0]["generated_text"],
+ }
+ if pipeline.task == "zero-shot-audio-classification":
+ return {
+ "inputs": [
+ components.Audio(type="filepath", label="Input"),
+ components.Textbox(label="Possible class names (comma-separated)"),
+ ],
+ "outputs": components.Label(label="Classification"),
+ "preprocess": lambda audio_path, classnames: (
+ read_audio(
+ audio_path,
+ pipeline.processor.feature_extractor.config["sampling_rate"],
+ ),
+ [c.strip() for c in classnames.split(",")],
+ ),
+ "postprocess": lambda result: {i["label"]: i["score"] for i in result},
+ }
+ if pipeline.task == "zero-shot-image-classification":
+ return {
+ "inputs": [
+ components.Image(type="filepath", label="Input Image"),
+ components.Textbox(label="Possible class names (comma-separated)"),
+ ],
+ "outputs": components.Label(label="Classification"),
+ "preprocess": lambda image_path, classnames: (
+ as_url(image_path),
+ [c.strip() for c in classnames.split(",")],
+ ),
+ "postprocess": lambda result: {i["label"]: i["score"] for i in result},
+ }
+ if pipeline.task == "zero-shot-object-detection":
+ return {
+ "inputs": [
+ components.Image(type="filepath", label="Input Image"),
+ components.Textbox(label="Possible class names (comma-separated)"),
+ ],
+ "outputs": components.AnnotatedImage(label="Objects Detected"),
+ "preprocess": lambda image_path, classnames: (
+ as_url(image_path),
+ [c.strip() for c in classnames.split(",")],
+ ),
+ "postprocess": lambda result, image_path, _: (
+ image_path,
+ [
+ (
+ (
+ int(item["box"]["xmin"]),
+ int(item["box"]["ymin"]),
+ int(item["box"]["xmax"]),
+ int(item["box"]["ymax"]),
+ ),
+ f"{item['label']} ({item['score']})",
+ )
+ for item in result
+ ],
+ ),
+ "postprocess_takes_inputs": True,
+ }
+
+ raise ValueError(f"Unsupported transformers_js_py pipeline type: {pipeline.task}")
diff --git a/gradio/processing_utils.py b/gradio/processing_utils.py
new file mode 100644
index 0000000..86e5ada
--- /dev/null
+++ b/gradio/processing_utils.py
@@ -0,0 +1,1144 @@
+from __future__ import annotations
+
+import asyncio
+import base64
+import hashlib
+import ipaddress
+import json
+import logging
+import mimetypes
+import os
+import shutil
+import subprocess
+import tempfile
+import warnings
+from collections.abc import Awaitable, Callable, Coroutine
+from functools import lru_cache, wraps
+from io import BytesIO
+from pathlib import Path
+from typing import TYPE_CHECKING, Any, TypeVar
+from urllib.parse import urljoin, urlparse
+
+import httpx
+import numpy as np
+import safehttpx as sh
+from gradio_client import utils as client_utils
+from PIL import Image, ImageOps, ImageSequence, PngImagePlugin
+
+from gradio import utils
+from gradio._vendor import aiofiles
+from gradio.context import LocalContext
+from gradio.data_classes import FileData, GradioModel, GradioRootModel, JsonData
+from gradio.exceptions import Error, InvalidPathError
+from gradio.profiling import traced_sync
+from gradio.route_utils import API_PREFIX
+from gradio.utils import abspath, get_hash_seed, get_upload_folder, is_in_or_equal
+
+with warnings.catch_warnings():
+ warnings.simplefilter("ignore") # Ignore pydub warning if ffmpeg is not installed
+ from pydub import AudioSegment
+
+sync_transport = None
+async_transport = None
+
+sync_client = httpx.Client(transport=sync_transport)
+
+log = logging.getLogger(__name__)
+
+if TYPE_CHECKING:
+ from gradio.blocks import Block
+
+
+#########################
+# GENERAL
+#########################
+
+
+def to_binary(x: str | dict) -> bytes:
+ """Converts a base64 string or dictionary to a binary string that can be sent in a POST."""
+ if isinstance(x, dict):
+ if x.get("data"):
+ base64str = x["data"]
+ else:
+ base64str = client_utils.encode_url_or_file_to_base64(x["path"])
+ else:
+ base64str = x
+ return base64.b64decode(extract_base64_data(base64str)) # type: ignore
+
+
+def extract_base64_data(x: str) -> str:
+ """Just extracts the base64 data from a general base64 string."""
+ return x.rsplit(",", 1)[-1]
+
+
+#########################
+# IMAGE PRE-PROCESSING
+#########################
+
+
+def encode_plot_to_base64(plt, format: str = "png"):
+ fmt = format or "png"
+ with BytesIO() as output_bytes:
+ plt.savefig(output_bytes, format=fmt)
+ bytes_data = output_bytes.getvalue()
+ base64_str = str(base64.b64encode(bytes_data), "utf-8")
+ return f"data:image/{format or 'png'};base64,{base64_str}"
+
+
+def get_pil_exif_bytes(pil_image):
+ if "exif" in pil_image.info:
+ return pil_image.info["exif"]
+
+
+def get_pil_metadata(pil_image):
+ # Copy any text-only metadata
+ metadata = PngImagePlugin.PngInfo()
+ for key, value in pil_image.info.items():
+ if isinstance(key, str) and isinstance(value, str):
+ metadata.add_text(key, value)
+
+ return metadata
+
+
+def encode_pil_to_bytes(pil_image, format="png"):
+ with BytesIO() as output_bytes:
+ if format.lower() == "gif":
+ frames = [frame.copy() for frame in ImageSequence.Iterator(pil_image)]
+ frames[0].save(
+ output_bytes,
+ format=format,
+ save_all=True,
+ append_images=frames[1:],
+ loop=0,
+ )
+ else:
+ if format.lower() == "png":
+ params = {"pnginfo": get_pil_metadata(pil_image)}
+ else:
+ exif = get_pil_exif_bytes(pil_image)
+ params = {"exif": exif} if exif else {}
+ pil_image.save(output_bytes, format, **params)
+ return output_bytes.getvalue()
+
+
+hash_seed = get_hash_seed().encode("utf-8")
+
+
+def hash_file(file_path: str | Path, chunk_num_blocks: int = 128) -> str:
+ sha = hashlib.sha256()
+ sha.update(hash_seed)
+ with open(file_path, "rb") as f:
+ for chunk in iter(lambda: f.read(chunk_num_blocks * sha.block_size), b""):
+ sha.update(chunk)
+ return sha.hexdigest()
+
+
+def hash_url(url: str) -> str:
+ sha = hashlib.sha256()
+ sha.update(hash_seed)
+ sha.update(url.encode("utf-8"))
+ return sha.hexdigest()
+
+
+def hash_bytes(bytes: bytes):
+ sha = hashlib.sha256()
+ sha.update(hash_seed)
+ sha.update(bytes)
+ return sha.hexdigest()
+
+
+def hash_base64(base64_encoding: str, chunk_num_blocks: int = 128) -> str:
+ sha = hashlib.sha256()
+ sha.update(hash_seed)
+ for i in range(0, len(base64_encoding), chunk_num_blocks * sha.block_size):
+ data = base64_encoding[i : i + chunk_num_blocks * sha.block_size]
+ sha.update(data.encode("utf-8"))
+ return sha.hexdigest()
+
+
+@traced_sync("postprocess_save_pil_to_cache")
+def save_pil_to_cache(
+ img: Image.Image,
+ cache_dir: str,
+ name: str = "image",
+ format: str = "webp",
+) -> str:
+ bytes_data = encode_pil_to_bytes(img, format)
+ temp_dir = Path(cache_dir) / hash_bytes(bytes_data)
+ temp_dir.mkdir(exist_ok=True, parents=True)
+ filename = str((temp_dir / f"{name}.{format}").resolve())
+ (temp_dir / f"{name}.{format}").resolve().write_bytes(bytes_data)
+ return filename
+
+
+@traced_sync("postprocess_save_img_array_to_cache")
+def save_img_array_to_cache(
+ arr: np.ndarray, cache_dir: str, format: str = "webp"
+) -> str:
+ pil_image = Image.fromarray(_convert(arr, np.uint8, force_copy=False))
+ return save_pil_to_cache(pil_image, cache_dir, format=format)
+
+
+@traced_sync("postprocess_save_audio_to_cache")
+def save_audio_to_cache(
+ data: np.ndarray, sample_rate: int, format: str, cache_dir: str
+) -> str:
+ audio_metadata = {
+ "cache_schema": "audio-cache-v1",
+ "dtype": str(data.dtype),
+ "format": format,
+ "sample_rate": int(sample_rate),
+ "shape": data.shape,
+ }
+ audio_hash = hashlib.sha256()
+ audio_hash.update(hash_seed)
+ audio_hash.update(json.dumps(audio_metadata, sort_keys=True).encode("utf-8"))
+ audio_hash.update(data.tobytes())
+ temp_dir = Path(cache_dir) / audio_hash.hexdigest()
+ temp_dir.mkdir(exist_ok=True, parents=True)
+ filename = str((temp_dir / f"audio.{format}").resolve())
+ audio_to_file(sample_rate, data, filename, format=format)
+ return filename
+
+
+def detect_audio_format(data: bytes) -> str:
+ """Detect audio format from file header bytes.
+
+ Args:
+ data: File content as bytes
+
+ Returns:
+ Detected file extension with dot (e.g., ".wav", ".mp3") or empty string if not detected
+ """
+ # Check WAV format (RIFF header)
+ if len(data) >= 12 and data[:4] == b"RIFF" and data[8:12] == b"WAVE":
+ return ".wav"
+ # Check MP3 format (ID3 tag)
+ elif len(data) >= 3 and data[:3] == b"ID3": # noqa: SIM114
+ return ".mp3"
+ # Check MP3 format (sync frame)
+ elif len(data) >= 2 and data[:2] == b"\xff\xfb":
+ return ".mp3"
+ return ""
+
+
+@traced_sync("postprocess_save_bytes_to_cache")
+def save_bytes_to_cache(data: bytes, file_name: str, cache_dir: str) -> str:
+ path = Path(cache_dir) / hash_bytes(data)
+ path.mkdir(exist_ok=True, parents=True)
+ if not Path(file_name).suffix:
+ detected_extension = detect_audio_format(data)
+ file_name = file_name + detected_extension
+ path = path / Path(file_name).name
+ path.write_bytes(data)
+ return str(path.resolve())
+
+
+@traced_sync("save_file_to_cache")
+def save_file_to_cache(file_path: str | Path, cache_dir: str) -> str:
+ """Returns a temporary file path for a copy of the given file path if it does
+ not already exist. Otherwise returns the path to the existing temp file."""
+ temp_dir = hash_file(file_path)
+ temp_dir = Path(cache_dir) / temp_dir
+ temp_dir.mkdir(exist_ok=True, parents=True)
+
+ name = client_utils.strip_invalid_filename_characters(Path(file_path).name)
+ full_temp_file_path = str(abspath(temp_dir / name))
+
+ if not Path(full_temp_file_path).exists():
+ shutil.copy2(file_path, full_temp_file_path)
+
+ return full_temp_file_path
+
+
+# Always return these URLs as is, without checking to see if they resolve
+# to an internal IP address. This is because Hugging Face uses DNS splitting,
+# which means that requests from HF Spaces to HF Datasets or HF Models
+# may resolve to internal IP addresses even if they are publicly accessible.
+PUBLIC_HOSTNAME_WHITELIST = [
+ "hf.co",
+ "huggingface.co",
+ "*.hf.co",
+ "*.huggingface.co",
+]
+
+
+def is_public_ip(ip: str) -> bool:
+ try:
+ ip_obj = ipaddress.ip_address(ip)
+ return not (
+ ip_obj.is_private
+ or ip_obj.is_loopback
+ or ip_obj.is_link_local
+ or ip_obj.is_multicast
+ or ip_obj.is_reserved
+ )
+ except ValueError:
+ return False
+
+
+T = TypeVar("T")
+
+
+def lru_cache_async(maxsize: int = 128):
+ def decorator(
+ async_func: Callable[..., Coroutine[Any, Any, T]],
+ ) -> Callable[..., Awaitable[T]]:
+ @lru_cache(maxsize=maxsize)
+ @wraps(async_func)
+ def wrapper(*args: Any, **kwargs: Any) -> Awaitable[T]:
+ return asyncio.create_task(async_func(*args, **kwargs))
+
+ return wrapper
+
+ return decorator
+
+
+MAX_REDIRECTS = 20
+
+
+async def async_ssrf_protected_get(url: str) -> httpx.Response:
+ """SSRF-protected GET: routes through `safehttpx` with the public hostname
+ allow-list and re-validates each redirect. Returns the `httpx.Response`
+ without raising on non-2xx status (callers decide how to handle that)."""
+ response = await sh.get(
+ url, domain_whitelist=PUBLIC_HOSTNAME_WHITELIST, _transport=async_transport
+ )
+ redirects = 0
+ while response.has_redirect_location:
+ if redirects >= MAX_REDIRECTS:
+ raise Exception(f"Exceeded maximum of {MAX_REDIRECTS} redirects.")
+ redirects += 1
+ # Resolve the Location against the URL that produced this redirect, so
+ # relative/scheme-relative redirects and host changes across hops are
+ # handled per RFC 3986. safehttpx re-validates the resolved host.
+ url = urljoin(str(response.url), response.headers["Location"])
+ response = await sh.get(
+ url, domain_whitelist=PUBLIC_HOSTNAME_WHITELIST, _transport=async_transport
+ )
+ return response
+
+
+async def async_ssrf_protected_download(url: str, cache_dir: str) -> str:
+ temp_dir = Path(cache_dir) / hash_url(url)
+ temp_dir.mkdir(exist_ok=True, parents=True)
+
+ parsed_url = urlparse(url)
+ base_path = parsed_url.path.rstrip("/")
+ filename = (
+ client_utils.strip_invalid_filename_characters(Path(base_path).name) or "file"
+ )
+
+ full_temp_file_path = str(abspath(temp_dir / filename))
+ if Path(full_temp_file_path).exists():
+ return full_temp_file_path
+
+ response = await async_ssrf_protected_get(url)
+ if response.status_code != 200:
+ raise Exception(f"Failed to download file. Status code: {response.status_code}")
+
+ async with aiofiles.open(full_temp_file_path, "wb") as f:
+ async for chunk in response.aiter_bytes():
+ await f.write(chunk)
+
+ return full_temp_file_path
+
+
+def unsafe_download(url: str, cache_dir: str) -> str:
+ temp_dir = Path(cache_dir) / hash_url(url)
+ temp_dir.mkdir(exist_ok=True, parents=True)
+ filename = client_utils.strip_invalid_filename_characters(Path(url).name)
+ full_temp_file_path = str(abspath(temp_dir / filename))
+
+ with (
+ sync_client.stream("GET", url, follow_redirects=True) as r,
+ open(full_temp_file_path, "wb") as f,
+ ):
+ for chunk in r.iter_raw():
+ f.write(chunk)
+
+ # print path and file size
+ print(
+ f"Downloaded {full_temp_file_path} ({os.path.getsize(full_temp_file_path)} bytes)"
+ )
+ log.info(
+ f"Downloaded {full_temp_file_path} ({os.path.getsize(full_temp_file_path)} bytes)"
+ )
+
+ return full_temp_file_path
+
+
+def ssrf_protected_download(url: str, cache_dir: str) -> str:
+ return client_utils.synchronize_async(async_ssrf_protected_download, url, cache_dir)
+
+
+# Custom components created with versions of gradio < 5.0 may be using the processing_utils.save_url_to_cache method, so we alias to ssrf_protected_download to preserve backwards-compatibility
+save_url_to_cache = ssrf_protected_download
+
+
+def save_base64_to_cache(
+ base64_encoding: str, cache_dir: str, file_name: str | None = None
+) -> str:
+ """Converts a base64 encoding to a file and returns the path to the file if
+ the file doesn't already exist. Otherwise returns the path to the existing file.
+ """
+ temp_dir = hash_base64(base64_encoding)
+ temp_dir = Path(cache_dir) / temp_dir
+ temp_dir.mkdir(exist_ok=True, parents=True)
+
+ guess_extension = client_utils.get_extension(base64_encoding)
+ if file_name:
+ file_name = client_utils.strip_invalid_filename_characters(file_name)
+ elif guess_extension:
+ file_name = f"file.{guess_extension}"
+ else:
+ file_name = "file"
+
+ full_temp_file_path = str(abspath(temp_dir / file_name)) # type: ignore
+
+ if not Path(full_temp_file_path).exists():
+ data, _ = client_utils.decode_base64_to_binary(base64_encoding)
+ with open(full_temp_file_path, "wb") as fb:
+ fb.write(data)
+
+ return full_temp_file_path
+
+
+def move_resource_to_block_cache(
+ url_or_file_path: str | Path | None, block: Block
+) -> str | None:
+ """This method has been replaced by Block.move_resource_to_block_cache(), but is
+ left here for backwards compatibility for any custom components created in Gradio 4.2.0 or earlier.
+ """
+ return block.move_resource_to_block_cache(url_or_file_path)
+
+
+def check_all_files_in_cache(data: JsonData):
+ def _in_cache(d: dict):
+ if (
+ (path := d.get("path", ""))
+ and not client_utils.is_http_url_like(path)
+ and not is_in_or_equal(path, get_upload_folder())
+ and not utils.is_static_file(path)
+ ):
+ raise Error(
+ f"File {path} is not in the cache folder and cannot be accessed."
+ )
+
+ client_utils.traverse(data, _in_cache, client_utils.is_file_obj)
+
+
+def move_files_to_cache(
+ data: Any,
+ block: Block,
+ postprocess: bool = False,
+ check_in_upload_folder=False,
+ keep_in_cache=False,
+):
+ """Move any files in `data` to cache and (optionally), adds URL prefixes (/file=...) needed to access the cached file.
+ Also handles the case where the file is on an external Gradio app (/proxy=...).
+
+ Runs after .postprocess() and before .preprocess().
+
+ Args:
+ data: The input or output data for a component. Can be a dictionary or a dataclass
+ block: The component whose data is being processed
+ postprocess: Whether its running from postprocessing
+ check_in_upload_folder: If True, instead of moving the file to cache, checks if the file is in already in cache (exception if not).
+ keep_in_cache: If True, the file will not be deleted from cache when the server is shut down.
+ """
+
+ def _mark_svg_as_safe(payload: FileData):
+ # If the app has not launched, this path can be considered an "allowed path"
+ # This is mainly so that svg files can be displayed inline for button/chatbot icons
+ if (
+ (blocks := LocalContext.blocks.get(None)) is None or not blocks.is_running
+ ) and (mimetypes.guess_type(payload.path)[0] == "image/svg+xml"):
+ utils.set_static_paths([payload.path])
+
+ def _move_to_cache(d: dict):
+ payload = FileData(**d) # type: ignore
+ # If the gradio app developer is returning a URL from
+ # postprocess, it means the component can display a URL
+ # without it being served from the gradio server
+ # This makes it so that the URL is not downloaded and speeds up event processing
+ if payload.url and postprocess and client_utils.is_http_url_like(payload.url):
+ payload.path = payload.url
+ elif utils.is_static_file(payload):
+ pass
+ elif not block.proxy_url:
+ # If the file is on a remote server, do not move it to cache.
+ if not client_utils.is_http_url_like(payload.path):
+ _check_allowed(payload.path, check_in_upload_folder)
+ if not payload.is_stream:
+ temp_file_path = block.move_resource_to_block_cache(payload.path)
+ if temp_file_path is None:
+ raise ValueError("Did not determine a file path for the resource.")
+ payload.path = temp_file_path
+ if keep_in_cache:
+ block.keep_in_cache.add(payload.path)
+
+ url_prefix = (
+ f"{API_PREFIX}/stream/" if payload.is_stream else f"{API_PREFIX}/file="
+ )
+ if block.proxy_url:
+ proxy_url = block.proxy_url.rstrip("/")
+ url = f"{API_PREFIX}/proxy={proxy_url}{url_prefix}{payload.path}"
+ elif client_utils.is_http_url_like(payload.path) or payload.path.startswith(
+ f"{url_prefix}"
+ ):
+ url = f"{payload.path}"
+ else:
+ url = f"{url_prefix}{payload.path}"
+ payload.url = url
+ _mark_svg_as_safe(payload)
+ return payload.model_dump()
+
+ if isinstance(data, (GradioRootModel, GradioModel)):
+ data = data.model_dump()
+
+ return client_utils.traverse(
+ data, _move_to_cache, client_utils.is_file_obj_with_meta
+ )
+
+
+def _check_allowed(path: str | Path, check_in_upload_folder: bool):
+ blocks = LocalContext.blocks.get(None)
+ if blocks is None or not blocks.has_launched:
+ return
+
+ abs_path = utils.abspath(path)
+
+ created_paths = [utils.get_upload_folder()]
+ # if check_in_upload_folder=True, we are running this during pre-process
+ # in which case only files in the upload_folder (cache_dir) are accepted
+ if check_in_upload_folder:
+ allowed_paths = []
+ else:
+ allowed_paths = blocks.allowed_paths + [os.getcwd(), tempfile.gettempdir()]
+ allowed, reason = utils.is_allowed_file(
+ abs_path,
+ blocked_paths=blocks.blocked_paths,
+ allowed_paths=allowed_paths,
+ created_paths=created_paths,
+ )
+ if not allowed:
+ msg = f"Cannot move {abs_path} to the gradio cache dir because "
+ if reason == "in_blocklist":
+ msg += f"it is located in one of the blocked_paths ({', '.join(blocks.blocked_paths)})."
+ elif check_in_upload_folder:
+ msg += "it was not uploaded by a user."
+ else:
+ msg += "it was not created by the application or it is not "
+ msg += "located in either the current working directory or your system's temp directory. "
+ msg += "To fix this error, please ensure your function returns files located in either "
+ msg += f"the current working directory ({os.getcwd()}), your system's temp directory ({tempfile.gettempdir()}) "
+ msg += f"or add {str(abs_path.parent)} to the allowed_paths parameter of launch()."
+ raise InvalidPathError(msg)
+ if (
+ utils.is_in_or_equal(abs_path, os.getcwd())
+ and abs_path.name.startswith(".")
+ and not any(
+ is_in_or_equal(path, allowed_path) for allowed_path in blocks.allowed_paths
+ )
+ ):
+ raise InvalidPathError(
+ "Dotfiles located in the temporary directory cannot be moved to the cache for security reasons. "
+ "If you'd like to specifically allow this file to be served, you can add it to the allowed_paths parameter of launch()."
+ )
+
+
+async def async_move_files_to_cache(
+ data: Any,
+ block: Block,
+ postprocess: bool = False,
+ check_in_upload_folder=False,
+ keep_in_cache=False,
+) -> dict:
+ """Move any files in `data` to cache and (optionally), adds URL prefixes (/file=...) needed to access the cached file.
+ Also handles the case where the file is on an external Gradio app (/proxy=...).
+
+ Runs after .postprocess() and before .preprocess().
+
+ Args:
+ data: The input or output data for a component. Can be a dictionary or a dataclass
+ block: The component whose data is being processed
+ postprocess: Whether its running from postprocessing
+ check_in_upload_folder: If True, instead of moving the file to cache, checks if the file is in already in cache (exception if not).
+ keep_in_cache: If True, the file will not be deleted from cache when the server is shut down.
+ """
+
+ def _mark_svg_as_safe(payload: FileData):
+ # If the app has not launched, this path can be considered an "allowed path"
+ # This is mainly so that svg files can be displayed inline for button/chatbot icons
+ if (
+ (blocks := LocalContext.blocks.get(None)) is None or not blocks.is_running
+ ) and (mimetypes.guess_type(payload.path)[0] == "image/svg+xml"):
+ utils.set_static_paths([payload.path])
+
+ async def _move_to_cache(d: dict):
+ payload = FileData(**d) # type: ignore
+ # If the gradio app developer is returning a URL from
+ # postprocess, it means the component can display a URL
+ # without it being served from the gradio server
+ # This makes it so that the URL is not downloaded and speeds up event processing
+ if payload.url and postprocess and client_utils.is_http_url_like(payload.url):
+ payload.path = payload.url
+ elif utils.is_static_file(payload):
+ pass
+ elif not block.proxy_url:
+ # If the file is on a remote server, do not move it to cache.
+ if not client_utils.is_http_url_like(payload.path):
+ _check_allowed(payload.path, check_in_upload_folder)
+ if not payload.is_stream:
+ temp_file_path = await block.async_move_resource_to_block_cache(
+ payload.path
+ )
+ if temp_file_path is None:
+ raise ValueError("Did not determine a file path for the resource.")
+ payload.path = temp_file_path
+ if keep_in_cache:
+ block.keep_in_cache.add(payload.path)
+
+ url_prefix = (
+ f"{API_PREFIX}/stream/" if payload.is_stream else f"{API_PREFIX}/file="
+ )
+ if block.proxy_url:
+ proxy_url = block.proxy_url.rstrip("/")
+ url = f"{API_PREFIX}/proxy={proxy_url}{url_prefix}{payload.path}"
+ elif client_utils.is_http_url_like(payload.path) or payload.path.startswith(
+ f"{url_prefix}"
+ ):
+ url = payload.path
+ else:
+ url = f"{url_prefix}{payload.path}"
+ payload.url = url
+ _mark_svg_as_safe(payload)
+ return payload.model_dump()
+
+ if isinstance(data, (GradioRootModel, GradioModel)):
+ data = data.model_dump()
+ return await client_utils.async_traverse(
+ data, _move_to_cache, client_utils.is_file_obj_with_meta
+ )
+
+
+def add_root_url(data: dict | list, root_url: str, previous_root_url: str | None):
+ def _add_root_url(file_dict: dict):
+ if previous_root_url and file_dict["url"].startswith(previous_root_url):
+ file_dict["url"] = file_dict["url"][len(previous_root_url) :]
+ elif client_utils.is_http_url_like(file_dict["url"]):
+ return file_dict
+ file_dict["url"] = f"{root_url}{file_dict['url']}"
+ return file_dict
+
+ return client_utils.traverse(data, _add_root_url, client_utils.is_file_obj_with_url)
+
+
+def resize_and_crop(img, size, crop_type="center"):
+ """
+ Resize and crop an image to fit the specified size.
+ args:
+ size: `(width, height)` tuple. Pass `None` for either width or height
+ to only crop and resize the other.
+ crop_type: can be 'top', 'middle' or 'bottom', depending on this
+ value, the image will cropped getting the 'top/left', 'middle' or
+ 'bottom/right' of the image to fit the size.
+ raises:
+ ValueError: if an invalid `crop_type` is provided.
+ """
+ if crop_type == "top":
+ center = (0, 0)
+ elif crop_type == "center":
+ center = (0.5, 0.5)
+ else:
+ raise ValueError
+
+ resize = list(size)
+ if size[0] is None:
+ resize[0] = img.size[0]
+ if size[1] is None:
+ resize[1] = img.size[1]
+ return ImageOps.fit(img, resize, centering=center) # type: ignore
+
+
+##################
+# Audio
+##################
+
+
+def audio_from_file(
+ filename: str, crop_min: float = 0, crop_max: float = 100
+) -> tuple[int, np.ndarray]:
+ from gradio.profiling import trace_phase_sync
+
+ with trace_phase_sync("preprocess_audio_from_file"):
+ try:
+ audio = AudioSegment.from_file(filename)
+ except FileNotFoundError as e:
+ isfile = Path(filename).is_file()
+ msg = (
+ f"Cannot load audio from file: `{'ffprobe' if isfile else filename}` not found."
+ + " Please install `ffmpeg` in your system to use non-WAV audio file formats"
+ " and make sure `ffprobe` is in your PATH."
+ if isfile
+ else ""
+ )
+ raise RuntimeError(msg) from e
+ except OSError as e:
+ raise e
+ if crop_min != 0 or crop_max != 100:
+ audio_start = len(audio) * crop_min / 100
+ audio_end = len(audio) * crop_max / 100
+ audio = audio[audio_start:audio_end]
+ data = np.array(audio.get_array_of_samples())
+ if audio.channels > 1:
+ data = data.reshape(-1, audio.channels)
+ return audio.frame_rate, data
+
+
+def audio_to_file(sample_rate, data, filename, format="wav"):
+ # pydub's `AudioSegment` raw constructor only supports integer PCM, and
+ # interprets `sample_width=4` as int32 rather than float32. Without an
+ # explicit conversion, non-WAV formats (mp3, flac, ogg, ...) end up
+ # feeding float32 bytes through ffmpeg as `pcm_s32le`, which decodes as
+ # noise. Run the same int16 conversion for every format so the encoded
+ # output matches the input waveform regardless of `format`. See #13364.
+ if data.dtype != np.int16:
+ data = convert_to_16_bit_audio(data)
+
+ audio = AudioSegment(
+ data.tobytes(),
+ frame_rate=sample_rate,
+ sample_width=data.dtype.itemsize,
+ channels=(1 if len(data.shape) == 1 else data.shape[1]),
+ )
+ file = audio.export(filename, format=format)
+ file.close() # type: ignore
+
+
+def convert_to_16_bit_audio(data):
+ # Based on: https://docs.scipy.org/doc/scipy/reference/generated/scipy.io.wavfile.write.html
+ warning = "Trying to convert audio automatically from {} to 16-bit int format."
+ if data.dtype in [np.float64, np.float32, np.float16]:
+ warnings.warn(warning.format(data.dtype))
+ peak = np.abs(data).max()
+ if peak == 0:
+ # Silence: avoid dividing by zero (which would produce NaNs that
+ # cast to nonzero int16 garbage and turn silence into noise).
+ data = np.zeros_like(data, dtype=np.int16)
+ else:
+ data = data / peak
+ data = data * 32767
+ data = data.astype(np.int16)
+ elif data.dtype == np.int32:
+ warnings.warn(warning.format(data.dtype))
+ data = data / 65536
+ data = data.astype(np.int16)
+ elif data.dtype == np.int16:
+ pass
+ elif data.dtype == np.uint16:
+ warnings.warn(warning.format(data.dtype))
+ data = data - 32768
+ data = data.astype(np.int16)
+ elif data.dtype == np.uint8:
+ warnings.warn(warning.format(data.dtype))
+ data = data * 257 - 32768
+ data = data.astype(np.int16)
+ elif data.dtype == np.int8:
+ warnings.warn(warning.format(data.dtype))
+ data = data * 256
+ data = data.astype(np.int16)
+ else:
+ raise ValueError(
+ "Audio data cannot be converted automatically from "
+ f"{data.dtype} to 16-bit int format."
+ )
+ return data
+
+
+# Backwards-compatible alias: this function now handles non-WAV formats too,
+# but it was previously named `convert_to_16_bit_wav` and may be imported by
+# external code.
+convert_to_16_bit_wav = convert_to_16_bit_audio
+
+
+##################
+# OUTPUT
+##################
+
+
+def _convert(image, dtype, force_copy=False, uniform=False):
+ """
+ Adapted from: https://github.com/scikit-image/scikit-image/blob/main/skimage/util/dtype.py#L510-L531
+
+ Convert an image to the requested data-type.
+ Warnings are issued in case of precision loss, or when negative values
+ are clipped during conversion to unsigned integer types (sign loss).
+ Floating point values are expected to be normalized and will be clipped
+ to the range [0.0, 1.0] or [-1.0, 1.0] when converting to unsigned or
+ signed integers respectively.
+ Numbers are not shifted to the negative side when converting from
+ unsigned to signed integer types. Negative values will be clipped when
+ converting to unsigned integers.
+ Parameters
+ ----------
+ image : ndarray
+ Input image.
+ dtype : dtype
+ Target data-type.
+ force_copy : bool, optional
+ Force a copy of the data, irrespective of its current dtype.
+ uniform : bool, optional
+ Uniformly quantize the floating point range to the integer range.
+ By default (uniform=False) floating point values are scaled and
+ rounded to the nearest integers, which minimizes back and forth
+ conversion errors.
+ .. versionchanged :: 0.15
+ ``_convert`` no longer warns about possible precision or sign
+ information loss. See discussions on these warnings at:
+ https://github.com/scikit-image/scikit-image/issues/2602
+ https://github.com/scikit-image/scikit-image/issues/543#issuecomment-208202228
+ https://github.com/scikit-image/scikit-image/pull/3575
+ References
+ ----------
+ .. [1] DirectX data conversion rules.
+ https://msdn.microsoft.com/en-us/library/windows/desktop/dd607323%28v=vs.85%29.aspx
+ .. [2] Data Conversions. In "OpenGL ES 2.0 Specification v2.0.25",
+ pp 7-8. Khronos Group, 2010.
+ .. [3] Proper treatment of pixels as integers. A.W. Paeth.
+ In "Graphics Gems I", pp 249-256. Morgan Kaufmann, 1990.
+ .. [4] Dirty Pixels. J. Blinn. In "Jim Blinn's corner: Dirty Pixels",
+ pp 47-57. Morgan Kaufmann, 1998.
+ """
+ dtype_range = {
+ bool: (False, True),
+ np.bool_: (False, True),
+ float: (-1, 1),
+ np.float16: (-1, 1),
+ np.float32: (-1, 1),
+ np.float64: (-1, 1),
+ }
+
+ if hasattr(np, "float_"):
+ dtype_range[np.float_] = dtype_range[float] # type: ignore
+ if hasattr(np, "bool8"):
+ dtype_range[np.bool8] = dtype_range[np.bool_] # type: ignore
+
+ def _dtype_itemsize(itemsize, *dtypes):
+ """Return first of `dtypes` with itemsize greater than `itemsize`
+ Parameters
+ ----------
+ itemsize: int
+ The data type object element size.
+ Other Parameters
+ ----------------
+ *dtypes:
+ Any Object accepted by `np.dtype` to be converted to a data
+ type object
+ Returns
+ -------
+ dtype: data type object
+ First of `dtypes` with itemsize greater than `itemsize`.
+ """
+ return next(dt for dt in dtypes if np.dtype(dt).itemsize >= itemsize)
+
+ def _dtype_bits(kind, bits, itemsize=1):
+ """Return dtype of `kind` that can store a `bits` wide unsigned int
+ Parameters:
+ kind: str
+ Data type kind.
+ bits: int
+ Desired number of bits.
+ itemsize: int
+ The data type object element size.
+ Returns
+ -------
+ dtype: data type object
+ Data type of `kind` that can store a `bits` wide unsigned int
+ """
+
+ s = next(
+ i
+ for i in (itemsize,) + (2, 4, 8)
+ if bits < (i * 8) or (bits == (i * 8) and kind == "u")
+ )
+
+ return np.dtype(kind + str(s))
+
+ def _scale(a, n, m, copy=True):
+ """Scale an array of unsigned/positive integers from `n` to `m` bits.
+ Numbers can be represented exactly only if `m` is a multiple of `n`.
+ Parameters
+ ----------
+ a : ndarray
+ Input image array.
+ n : int
+ Number of bits currently used to encode the values in `a`.
+ m : int
+ Desired number of bits to encode the values in `out`.
+ copy : bool, optional
+ If True, allocates and returns new array. Otherwise, modifies
+ `a` in place.
+ Returns
+ -------
+ out : array
+ Output image array. Has the same kind as `a`.
+ """
+ kind = a.dtype.kind
+ if n > m and a.max() < 2**m:
+ return a.astype(_dtype_bits(kind, m))
+ elif n == m:
+ return a.copy() if copy else a
+ elif n > m:
+ # downscale with precision loss
+ if copy:
+ b = np.empty(a.shape, _dtype_bits(kind, m))
+ np.floor_divide(a, 2 ** (n - m), out=b, dtype=a.dtype, casting="unsafe")
+ return b
+ else:
+ a //= 2 ** (n - m)
+ return a
+ elif m % n == 0:
+ # exact upscale to a multiple of `n` bits
+ if copy:
+ b = np.empty(a.shape, _dtype_bits(kind, m))
+ np.multiply(a, (2**m - 1) // (2**n - 1), out=b, dtype=b.dtype)
+ return b
+ else:
+ a = a.astype(_dtype_bits(kind, m, a.dtype.itemsize), copy=False)
+ a *= (2**m - 1) // (2**n - 1)
+ return a
+ else:
+ # upscale to a multiple of `n` bits,
+ # then downscale with precision loss
+ o = (m // n + 1) * n
+ if copy:
+ b = np.empty(a.shape, _dtype_bits(kind, o))
+ np.multiply(a, (2**o - 1) // (2**n - 1), out=b, dtype=b.dtype)
+ b //= 2 ** (o - m)
+ return b
+ else:
+ a = a.astype(_dtype_bits(kind, o, a.dtype.itemsize), copy=False)
+ a *= (2**o - 1) // (2**n - 1)
+ a //= 2 ** (o - m)
+ return a
+
+ image = np.asarray(image)
+ dtypeobj_in = image.dtype
+ dtypeobj_out = (
+ dtypeobj_in
+ if dtype is np.floating
+ else np.dtype("float64")
+ if dtype is float
+ else np.dtype(dtype)
+ )
+ dtype_in = dtypeobj_in.type
+ dtype_out = dtypeobj_out.type
+ kind_in = dtypeobj_in.kind
+ kind_out = dtypeobj_out.kind
+ itemsize_in = dtypeobj_in.itemsize
+ itemsize_out = dtypeobj_out.itemsize
+
+ # Below, we do an `issubdtype` check. Its purpose is to find out
+ # whether we can get away without doing any image conversion. This happens
+ # when:
+ #
+ # - the output and input dtypes are the same or
+ # - when the output is specified as a type, and the input dtype
+ # is a subclass of that type (e.g. `np.floating` will allow
+ # `float32` and `float64` arrays through)
+
+ if hasattr(np, "obj2sctype"):
+ is_subdtype = np.issubdtype(dtype_in, np.obj2sctype(dtype)) # type: ignore
+ else:
+ is_subdtype = np.issubdtype(dtype_in, dtypeobj_out.type)
+
+ if is_subdtype:
+ if force_copy:
+ image = image.copy()
+ return image
+
+ if kind_in in "ui":
+ imin_in = np.iinfo(dtype_in).min
+ imax_in = np.iinfo(dtype_in).max
+ if kind_out in "ui":
+ imin_out = np.iinfo(dtype_out).min # type: ignore
+ imax_out = np.iinfo(dtype_out).max # type: ignore
+
+ # any -> binary
+ if kind_out == "b":
+ return image > dtype_in(dtype_range[dtype_in][1] / 2)
+
+ # binary -> any
+ if kind_in == "b":
+ result = image.astype(dtype_out)
+ if kind_out != "f":
+ result *= dtype_out(dtype_range[dtype_out][1])
+ return result
+
+ # float -> any
+ if kind_in == "f":
+ if kind_out == "f":
+ # float -> float
+ return image.astype(dtype_out)
+
+ if np.min(image) < -1.0 or np.max(image) > 1.0:
+ raise ValueError("Images of type float must be between -1 and 1.")
+ # floating point -> integer
+ # use float type that can represent output integer type
+ computation_type = _dtype_itemsize(
+ itemsize_out, dtype_in, np.float32, np.float64
+ )
+
+ if not uniform:
+ if kind_out == "u":
+ image_out = np.multiply(image, imax_out, dtype=computation_type) # type: ignore
+ else:
+ image_out = np.multiply(
+ image,
+ (imax_out - imin_out) / 2, # type: ignore
+ dtype=computation_type,
+ )
+ image_out -= 1.0 / 2.0
+ np.rint(image_out, out=image_out)
+ np.clip(image_out, imin_out, imax_out, out=image_out) # type: ignore
+ elif kind_out == "u":
+ image_out = np.multiply(image, imax_out + 1, dtype=computation_type) # type: ignore
+ np.clip(image_out, 0, imax_out, out=image_out) # type: ignore
+ else:
+ image_out = np.multiply(
+ image,
+ (imax_out - imin_out + 1.0) / 2.0, # type: ignore
+ dtype=computation_type,
+ )
+ np.floor(image_out, out=image_out)
+ np.clip(image_out, imin_out, imax_out, out=image_out) # type: ignore
+ return image_out.astype(dtype_out)
+
+ # signed/unsigned int -> float
+ if kind_out == "f":
+ # use float type that can exactly represent input integers
+ computation_type = _dtype_itemsize(
+ itemsize_in, dtype_out, np.float32, np.float64
+ )
+
+ if kind_in == "u":
+ # using np.divide or np.multiply doesn't copy the data
+ # until the computation time
+ image = np.multiply(image, 1.0 / imax_in, dtype=computation_type) # type: ignore
+ # DirectX uses this conversion also for signed ints
+ # if imin_in:
+ # np.maximum(image, -1.0, out=image)
+ else:
+ image = np.add(image, 0.5, dtype=computation_type)
+ image *= 2 / (imax_in - imin_in) # type: ignore
+
+ return np.asarray(image, dtype_out)
+
+ # unsigned int -> signed/unsigned int
+ if kind_in == "u":
+ if kind_out == "i":
+ # unsigned int -> signed int
+ image = _scale(image, 8 * itemsize_in, 8 * itemsize_out - 1)
+ return image.view(dtype_out)
+ else:
+ # unsigned int -> unsigned int
+ return _scale(image, 8 * itemsize_in, 8 * itemsize_out)
+
+ # signed int -> unsigned int
+ if kind_out == "u":
+ image = _scale(image, 8 * itemsize_in - 1, 8 * itemsize_out)
+ result = np.empty(image.shape, dtype_out)
+ np.maximum(image, 0, out=result, dtype=image.dtype, casting="unsafe")
+ return result
+
+ # signed int -> signed int
+ if itemsize_in > itemsize_out:
+ return _scale(image, 8 * itemsize_in - 1, 8 * itemsize_out - 1)
+
+ image = image.astype(_dtype_bits("i", itemsize_out * 8))
+ image -= imin_in # type: ignore
+ image = _scale(image, 8 * itemsize_in, 8 * itemsize_out, copy=False)
+ image += imin_out # type: ignore
+ return image.astype(dtype_out)
+
+
+def ffmpeg_installed() -> bool:
+ return shutil.which("ffmpeg") is not None
+
+
+def video_is_playable(video_filepath: str) -> bool:
+ """Determines if a video is playable in the browser.
+
+ A video is playable if it has a playable container and codec.
+ .mp4 -> h264
+ .webm -> vp9
+ .ogg -> theora
+ """
+ from gradio._vendor.ffmpy import FFprobe, FFRuntimeError
+
+ try:
+ container = Path(video_filepath).suffix.lower()
+ probe = FFprobe(
+ global_options="-show_format -show_streams -select_streams v -print_format json",
+ inputs={video_filepath: None},
+ )
+ output = probe.run(stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+ output = json.loads(output[0]) # type: ignore
+ video_codec = output["streams"][0]["codec_name"]
+ return (container, video_codec) in [
+ (".mp4", "h264"),
+ (".mp4", "av1"),
+ (".ogg", "theora"),
+ (".webm", "vp9"),
+ (".webm", "vp8"),
+ (".webm", "av1"),
+ ]
+ # If anything goes wrong, assume the video can be played to not convert downstream
+ except (FFRuntimeError, IndexError, KeyError):
+ return True
+
+
+def convert_video_to_playable_mp4(video_path: str) -> str:
+ """Convert the video to mp4. If something goes wrong return the original video."""
+ from gradio._vendor.ffmpy import FFmpeg, FFRuntimeError
+
+ try:
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
+ output_path = Path(video_path).with_suffix(".mp4")
+ shutil.copy2(video_path, tmp_file.name)
+ # ffmpeg will automatically use h264 codec (playable in browser) when converting to mp4
+ ff = FFmpeg(
+ inputs={str(tmp_file.name): None},
+ outputs={str(output_path): None},
+ global_options="-y -loglevel quiet",
+ )
+ ff.run()
+ except FFRuntimeError as e:
+ print(f"Error converting video to browser-playable format {str(e)}")
+ output_path = video_path
+ finally:
+ # Remove temp file
+ os.remove(tmp_file.name) # type: ignore
+ return str(output_path)
+
+
+def get_video_length(video_path: str | Path):
+ duration = subprocess.check_output(
+ [
+ "ffprobe",
+ "-i",
+ str(video_path),
+ "-show_entries",
+ "format=duration",
+ "-v",
+ "quiet",
+ "-of",
+ "csv={}".format("p=0"),
+ ]
+ )
+ duration_str = duration.decode("utf-8").strip()
+ duration_float = float(duration_str)
+
+ return duration_float
diff --git a/gradio/profiling.py b/gradio/profiling.py
new file mode 100644
index 0000000..2e777f0
--- /dev/null
+++ b/gradio/profiling.py
@@ -0,0 +1,243 @@
+from __future__ import annotations
+
+import contextvars
+import os
+import time
+from collections import deque
+from contextlib import asynccontextmanager, contextmanager
+from dataclasses import dataclass, field
+from functools import wraps
+from typing import Any
+
+PROFILING_ENABLED = os.environ.get("GRADIO_PROFILING", "").strip() in ("1", "true")
+
+
+@dataclass
+class RequestTrace:
+ event_id: str | None = None
+ fn_name: str | None = None
+ session_hash: str | None = None
+ timestamp: float = field(default_factory=time.time)
+
+ queue_wait_ms: float = 0.0
+ preprocess_ms: float = 0.0
+ fn_call_ms: float = 0.0
+ postprocess_ms: float = 0.0
+ streaming_diff_ms: float = 0.0
+ total_ms: float = 0.0
+ n_iterations: int = 0
+ upload_ms: float = 0.0
+ preprocess_move_to_cache_ms: float = 0.0
+ preprocess_format_image_ms: float = 0.0
+ postprocess_save_img_array_to_cache_ms: float = 0.0
+ preprocess_audio_from_file_ms: float = 0.0
+ postprocess_save_audio_to_cache_ms: float = 0.0
+ preprocess_video_ms: float = 0.0
+ postprocess_video_convert_video_to_playable_mp4_ms: float = 0.0
+ postprocess_update_state_in_config_ms: float = 0.0
+ postprocess_move_to_cache_ms: float = 0.0
+ postprocess_video_ms: float = 0.0
+ postprocess_save_pil_to_cache_ms: float = 0.0
+ postprocess_save_bytes_to_cache_ms: float = 0.0
+ save_file_to_cache_ms: float = 0.0
+
+ def set_phase(self, name: str, duration_ms: float):
+ attr = f"{name}_ms"
+ if hasattr(self, attr):
+ # Accumulate across generator iterations
+ setattr(self, attr, getattr(self, attr) + duration_ms)
+ if name == "total":
+ self.n_iterations += 1
+
+ def to_dict(self) -> dict[str, Any]:
+ return {
+ "event_id": self.event_id,
+ "fn_name": self.fn_name,
+ "session_hash": self.session_hash,
+ "timestamp": self.timestamp,
+ "queue_wait_ms": self.queue_wait_ms,
+ "preprocess_ms": self.preprocess_ms,
+ "fn_call_ms": self.fn_call_ms,
+ "postprocess_ms": self.postprocess_ms,
+ "streaming_diff_ms": self.streaming_diff_ms,
+ "total_ms": self.total_ms,
+ "n_iterations": self.n_iterations,
+ "preprocess_move_to_cache_ms": self.preprocess_move_to_cache_ms,
+ "preprocess_format_image_ms": self.preprocess_format_image_ms,
+ "postprocess_save_img_array_to_cache_ms": self.postprocess_save_img_array_to_cache_ms,
+ "preprocess_audio_from_file_ms": self.preprocess_audio_from_file_ms,
+ "postprocess_save_audio_to_cache_ms": self.postprocess_save_audio_to_cache_ms,
+ "preprocess_video_ms": self.preprocess_video_ms,
+ "postprocess_video_convert_video_to_playable_mp4_ms": self.postprocess_video_convert_video_to_playable_mp4_ms,
+ "postprocess_update_state_in_config_ms": self.postprocess_update_state_in_config_ms,
+ "postprocess_move_to_cache_ms": self.postprocess_move_to_cache_ms,
+ "postprocess_video_ms": self.postprocess_video_ms,
+ "postprocess_save_pil_to_cache_ms": self.postprocess_save_pil_to_cache_ms,
+ "postprocess_save_bytes_to_cache_ms": self.postprocess_save_bytes_to_cache_ms,
+ "save_file_to_cache_ms": self.save_file_to_cache_ms,
+ }
+
+
+_current_trace: contextvars.ContextVar[RequestTrace | None] = contextvars.ContextVar(
+ "_current_trace", default=None
+)
+
+
+def get_current_trace() -> RequestTrace | None:
+ return _current_trace.get()
+
+
+def set_current_trace(trace: RequestTrace) -> contextvars.Token:
+ return _current_trace.set(trace)
+
+
+@asynccontextmanager
+async def trace_phase(name: str):
+ """Async context manager that records timing for a named phase into the current trace."""
+ trace = _current_trace.get()
+ if trace is None:
+ yield
+ return
+ start = time.monotonic()
+ try:
+ yield
+ finally:
+ duration_ms = (time.monotonic() - start) * 1000
+ trace.set_phase(name, duration_ms)
+
+
+@contextmanager
+def trace_phase_sync(name: str):
+ """Context manager that records timing for a named phase into the current trace."""
+ trace = _current_trace.get()
+ if trace is None:
+ yield
+ return
+ start = time.monotonic()
+ try:
+ yield
+ finally:
+ duration_ms = (time.monotonic() - start) * 1000
+ trace.set_phase(name, duration_ms)
+
+
+def traced(phase):
+ if not PROFILING_ENABLED:
+ return lambda f: f
+
+ def _factory(f):
+ @wraps(f)
+ async def wrapper(*args, **kwargs):
+ async with trace_phase(phase):
+ return await f(*args, **kwargs)
+
+ return wrapper
+
+ return _factory
+
+
+def traced_sync(phase):
+ if not PROFILING_ENABLED:
+ return lambda f: f
+
+ def _factory(f):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ with trace_phase_sync(phase):
+ return f(*args, **kwargs)
+
+ return wrapper
+
+ return _factory
+
+
+class TraceCollector:
+ def __init__(self, maxlen: int = 100_000):
+ self._traces: deque[RequestTrace] = deque(maxlen=maxlen)
+
+ def add(self, trace: RequestTrace):
+ self._traces.append(trace)
+
+ def get_all(self, last_n: int | None = None) -> list[dict[str, Any]]:
+ traces = list(self._traces)
+ if last_n is not None:
+ traces = traces[-last_n:]
+ return [t.to_dict() for t in traces]
+
+ def get_summary(self) -> dict[str, Any]:
+ if not self._traces:
+ return {"count": 0, "phases": {}}
+
+ import numpy as np
+
+ prediction_traces = [
+ t for t in self._traces if t.fn_name != "gradio_file_upload"
+ ]
+ upload_traces = [t for t in self._traces if t.fn_name == "gradio_file_upload"]
+
+ phases = [
+ "queue_wait",
+ "preprocess",
+ "fn_call",
+ "postprocess",
+ "streaming_diff",
+ "total",
+ ]
+
+ def _percentiles(arr):
+ return {
+ "p50": float(np.percentile(arr, 50)),
+ "p90": float(np.percentile(arr, 90)),
+ "p95": float(np.percentile(arr, 95)),
+ "p99": float(np.percentile(arr, 99)),
+ "mean": float(np.mean(arr)),
+ "min": float(np.min(arr)),
+ "max": float(np.max(arr)),
+ }
+
+ result: dict[str, Any] = {
+ "count": len(prediction_traces),
+ "phases": {},
+ }
+ for phase in phases:
+ values = [getattr(t, f"{phase}_ms") for t in prediction_traces]
+ if values:
+ result["phases"][phase] = _percentiles(np.array(values))
+ else:
+ result["phases"][phase] = {
+ "p50": 0.0,
+ "p90": 0.0,
+ "p95": 0.0,
+ "p99": 0.0,
+ "mean": 0.0,
+ "min": 0.0,
+ "max": 0.0,
+ }
+
+ if upload_traces:
+ upload_values = [t.upload_ms for t in upload_traces]
+ result["upload"] = {
+ "count": len(upload_traces),
+ **_percentiles(np.array(upload_values)),
+ }
+
+ return result
+
+ def clear(self):
+ self._traces.clear()
+
+
+# Global collector instance
+collector = TraceCollector()
+
+
+if not PROFILING_ENABLED:
+ # Replace with no-ops for zero overhead
+
+ @asynccontextmanager
+ async def trace_phase(name: str): # noqa: ARG001
+ yield
+
+ @contextmanager
+ def trace_phase_sync(name: str): # noqa: ARG001
+ yield
diff --git a/gradio/py.typed b/gradio/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/gradio/queueing.py b/gradio/queueing.py
new file mode 100644
index 0000000..3c47071
--- /dev/null
+++ b/gradio/queueing.py
@@ -0,0 +1,1134 @@
+from __future__ import annotations
+
+import asyncio
+import copy
+import inspect
+import os
+import platform
+import random
+import time
+import traceback
+import uuid
+from asyncio import Queue as AsyncQueue
+from collections import defaultdict
+from typing import TYPE_CHECKING, Any, Literal, cast
+
+import fastapi
+import numpy as np
+from anyio.to_thread import run_sync
+
+from gradio import route_utils, routes
+from gradio.caching import CacheMissError, ProbeCache
+from gradio.data_classes import (
+ PredictBodyInternal,
+)
+from gradio.exceptions import Error
+from gradio.helpers import TrackedIterable
+from gradio.server_messages import (
+ EstimationMessage,
+ EventMessage,
+ LogMessage,
+ ProcessCompletedMessage,
+ ProcessGeneratingMessage,
+ ProcessStartsMessage,
+ ProgressMessage,
+ ProgressUnit,
+ ServerMessage,
+)
+from gradio.utils import (
+ LRUCache,
+ error_payload,
+ run_coro_in_background,
+ safe_aclose_iterator,
+ safe_get_lock,
+ set_task_name,
+)
+
+from .block_function import BlockFunction
+
+if TYPE_CHECKING:
+ from gradio.block_function import BlockFunction
+ from gradio.blocks import Blocks
+
+
+class Event:
+ def __init__(
+ self,
+ session_hash: str | None,
+ fn: BlockFunction,
+ request: fastapi.Request,
+ username: str | None,
+ ):
+ self._id = uuid.uuid4().hex
+ self.session_hash: str = session_hash or self._id
+ self.fn = fn
+ self.request = request
+ self.username = username
+ self.concurrency_id = fn.concurrency_id
+ self.data: PredictBodyInternal | None = None
+ self.progress: ProgressMessage | None = None
+ self.progress_pending: bool = False
+ self.alive = True
+ self.closed = False
+ self.n_calls = 0
+ self.run_time: float = 0
+ self.enqueue_time: float = time.monotonic()
+ self.signal = asyncio.Event()
+
+ @property
+ def streaming(self):
+ return self.fn.connection == "stream"
+
+ @property
+ def is_finished(self):
+ if not self.streaming:
+ raise ValueError("Cannot access if_finished during a non-streaming event")
+ if self.closed:
+ return True
+ if self.fn.time_limit is None:
+ return False
+ return self.run_time >= self.fn.time_limit
+
+
+class EventQueue:
+ def __init__(self, concurrency_id: str, concurrency_limit: int | None):
+ self.queue: list[Event] = []
+ self.concurrency_id = concurrency_id
+ self.concurrency_limit = concurrency_limit
+ self.current_concurrency = 0
+ self.start_times_per_fn: defaultdict[BlockFunction, set[float]] = defaultdict(
+ set
+ )
+
+
+class ProcessTime:
+ def __init__(self):
+ self.process_time = 0
+ self.count = 0
+ self.avg_time = 0
+
+ def add(self, time: float):
+ self.process_time += time
+ self.count += 1
+ self.avg_time = self.process_time / self.count
+
+
+class Queue:
+ def __init__(
+ self,
+ live_updates: bool,
+ concurrency_count: int,
+ update_intervals: float,
+ max_size: int | None,
+ blocks: Blocks,
+ default_concurrency_limit: int | None | Literal["not_set"] = "not_set",
+ ):
+ self.pending_messages_per_session: LRUCache[str, AsyncQueue[EventMessage]] = (
+ LRUCache(2000)
+ )
+ self.pending_event_ids_session: dict[str, set[str]] = {}
+ self.event_ids_to_events: dict[str, Event] = {}
+ self.pending_message_lock = safe_get_lock()
+ self.event_queue_per_concurrency_id: dict[str, EventQueue] = {}
+ self.stopped = False
+ self.max_thread_count = concurrency_count
+ self.update_intervals = update_intervals
+ self.active_jobs: list[None | list[Event]] = []
+ self.delete_lock = safe_get_lock()
+ self.server_app = None
+ self.process_time_per_fn: defaultdict[BlockFunction, ProcessTime] = defaultdict(
+ ProcessTime
+ )
+ self.live_updates = live_updates
+ self.sleep_when_free = 0.05 if platform.system() == "Windows" else 0.001
+ self.progress_update_sleep_when_free = (
+ 0.1 if platform.system() == "Windows" else 0.01
+ )
+ self.max_size = max_size
+ self.blocks = blocks
+ self._asyncio_tasks: list[asyncio.Task] = []
+ self.default_concurrency_limit = self._resolve_concurrency_limit(
+ default_concurrency_limit
+ )
+ self.event_analytics: dict[str, dict[str, float | str | None]] = {}
+ self.cached_event_analytics_summary = {"functions": {}}
+ self.event_count_at_last_cache = 0
+ self.ANAYLTICS_CACHE_FREQUENCY = int(
+ os.getenv("GRADIO_ANALYTICS_CACHE_FREQUENCY", "1")
+ )
+
+ @staticmethod
+ def _get_df(event_analytics):
+ import pandas as pd
+
+ try:
+ with pd.option_context("future.no_silent_downcasting", True):
+ return (
+ pd.DataFrame(list(event_analytics.values()))
+ .fillna(value=np.nan)
+ .infer_objects(copy=False) # type: ignore
+ )
+ except Exception as e:
+ if "No such keys(s)" in str(e):
+ return (
+ pd.DataFrame(list(event_analytics.values()))
+ .fillna(value=np.nan)
+ .infer_objects(copy=False) # type: ignore
+ )
+ raise e
+
+ def compute_analytics_summary(self, event_analytics):
+ if (
+ len(event_analytics) - self.event_count_at_last_cache
+ >= self.ANAYLTICS_CACHE_FREQUENCY
+ ):
+ df = self._get_df(event_analytics)
+ self.event_count_at_last_cache = len(event_analytics)
+ grouped = df.groupby("function")
+ metrics = {"functions": {}}
+ for fn_name, fn_df in grouped:
+ status = fn_df["status"].values
+ success = np.sum(status == "success")
+ failure = np.sum(status == "failed")
+ total = success + failure
+ success_rate = success / total if total > 0 else None
+ percentiles = np.percentile(fn_df["process_time"].values, [50, 90, 99]) # type: ignore
+ metrics["functions"][fn_name] = {
+ "success_rate": success_rate,
+ "process_time_percentiles": {
+ "50th": percentiles[0], # type: ignore
+ "90th": percentiles[1], # type: ignore
+ "99th": percentiles[2], # type: ignore
+ },
+ "total_requests": fn_df.shape[0],
+ }
+ self.cached_event_analytics_summary = metrics
+ return self.cached_event_analytics_summary
+
+ def start(self):
+ self.active_jobs = [None] * self.max_thread_count
+
+ run_coro_in_background(self.start_processing)
+ run_coro_in_background(self.start_progress_updates)
+ if not self.live_updates:
+ run_coro_in_background(self.notify_clients)
+
+ def create_event_queue_for_fn(self, block_fn: BlockFunction):
+ concurrency_id = block_fn.concurrency_id
+ concurrency_limit: int | None
+ if block_fn.concurrency_limit == "default":
+ concurrency_limit = self.default_concurrency_limit
+ else:
+ concurrency_limit = block_fn.concurrency_limit
+ if concurrency_id not in self.event_queue_per_concurrency_id:
+ self.event_queue_per_concurrency_id[concurrency_id] = EventQueue(
+ concurrency_id, concurrency_limit
+ )
+ elif (
+ concurrency_limit is not None
+ ): # Update concurrency limit if it is lower than existing limit
+ existing_event_queue = self.event_queue_per_concurrency_id[concurrency_id]
+ if (
+ existing_event_queue.concurrency_limit is None
+ or concurrency_limit < existing_event_queue.concurrency_limit
+ ):
+ existing_event_queue.concurrency_limit = concurrency_limit
+
+ def close(self):
+ self.stopped = True
+
+ def send_message(
+ self,
+ event: Event,
+ event_message: EventMessage,
+ ):
+ if not event.alive:
+ return
+ event_message.event_id = event._id
+ messages = self.pending_messages_per_session[event.session_hash]
+ messages.put_nowait(event_message)
+
+ def _resolve_concurrency_limit(
+ self, default_concurrency_limit: int | None | Literal["not_set"]
+ ) -> int | None:
+ """
+ Handles the logic of resolving the default_concurrency_limit as this can be specified via a combination
+ of the `default_concurrency_limit` parameter of the `Blocks.queue()` or the `GRADIO_DEFAULT_CONCURRENCY_LIMIT`
+ environment variable. The parameter in `Blocks.queue()` takes precedence over the environment variable.
+ Parameters:
+ default_concurrency_limit: The default concurrency limit, as specified by a user in `Blocks.queue()`.
+ """
+ if default_concurrency_limit != "not_set":
+ return default_concurrency_limit
+ if default_concurrency_limit_env := os.environ.get(
+ "GRADIO_DEFAULT_CONCURRENCY_LIMIT"
+ ):
+ if default_concurrency_limit_env.lower() == "none":
+ return None
+ else:
+ return int(default_concurrency_limit_env)
+ else:
+ return 1
+
+ def __len__(self):
+ total_len = 0
+ for event_queue in self.event_queue_per_concurrency_id.values():
+ total_len += len(event_queue.queue)
+ return total_len
+
+ async def push(
+ self, body: PredictBodyInternal, request: fastapi.Request, username: str | None
+ ) -> tuple[
+ bool,
+ str | list[dict[str, Any]],
+ Literal["success", "error", "queue_full", "validator_error"],
+ ]:
+ if body.fn_index is None:
+ return False, "No function index provided.", "error"
+ if self.max_size is not None and len(self) >= self.max_size:
+ return (
+ False,
+ f"Queue is full. Max size is {self.max_size} and size is {len(self)}.",
+ "queue_full",
+ )
+
+ if body.session_hash:
+ session_state = self.blocks.state_holder[body.session_hash]
+ fn = session_state.blocks_config.fns[body.fn_index]
+ else:
+ fn = self.blocks.fns[body.fn_index]
+
+ fn = route_utils.get_fn(self.blocks, None, body)
+ self.create_event_queue_for_fn(fn)
+ if fn.validator is not None:
+ gr_request = route_utils.compile_gr_request(
+ body=body,
+ fn=fn,
+ username=username,
+ request=None,
+ )
+ assert body.request is not None # noqa: S101
+ api_route_path = route_utils.get_api_call_path(request=body.request)
+ root_path = route_utils.get_root_url(
+ request=body.request,
+ route_path=api_route_path,
+ root_path=self.blocks.app.root_path,
+ )
+ validator_fn = BlockFunction(
+ fn=fn.validator,
+ api_name=None,
+ api_visibility="undocumented",
+ batch=fn.batch,
+ concurrency_id=None,
+ concurrency_limit=None,
+ inputs=fn.inputs,
+ outputs=fn.inputs,
+ preprocess=fn.preprocess,
+ postprocess=False,
+ inputs_as_dict=fn.inputs_as_dict,
+ targets=[],
+ _id=-1,
+ max_batch_size=fn.max_batch_size,
+ tracks_progress=fn.tracks_progress,
+ js=None,
+ show_progress="hidden",
+ show_progress_on=fn.show_progress_on,
+ cancels=fn.cancels,
+ collects_event_data=fn.collects_event_data,
+ )
+
+ event = Event(
+ body.session_hash,
+ validator_fn,
+ request,
+ username,
+ )
+ try:
+ response = await route_utils.call_process_api(
+ app=self.blocks.app,
+ body=body,
+ gr_request=gr_request,
+ fn=validator_fn,
+ root_path=root_path,
+ )
+
+ validation_response: list[dict[str, Any]] | dict[str, Any] | None = (
+ response.get("data")
+ )
+
+ if validation_response is not None:
+ (is_valid, validation_data) = process_validation_response(
+ validation_response, fn
+ )
+ if is_valid is False:
+ return (
+ False,
+ validation_data,
+ "validator_error",
+ )
+
+ except Exception as e:
+ print(str(e))
+ return False, str(e), "error"
+ event = Event(
+ body.session_hash,
+ fn,
+ request,
+ username,
+ )
+ event.data = body
+ if body.session_hash is None:
+ body.session_hash = event.session_hash
+ async with self.pending_message_lock:
+ if body.session_hash not in self.pending_messages_per_session:
+ self.pending_messages_per_session[body.session_hash] = AsyncQueue()
+ if body.session_hash not in self.pending_event_ids_session:
+ self.pending_event_ids_session[body.session_hash] = set()
+ self.pending_event_ids_session[body.session_hash].add(event._id)
+ self.event_ids_to_events[event._id] = event
+ body.event_id = event._id if not fn.batch else None
+
+ if hasattr(fn.fn, "cache"):
+ try:
+ cache_start = time.time()
+ gr_request = route_utils.compile_gr_request(
+ body=body,
+ fn=fn,
+ username=username,
+ request=None,
+ )
+ assert body.request is not None # noqa: S101
+ api_route_path = route_utils.get_api_call_path(request=body.request)
+ root_path = route_utils.get_root_url(
+ request=body.request,
+ route_path=api_route_path,
+ root_path=self.blocks.app.root_path,
+ )
+ with ProbeCache():
+ response = await route_utils.call_process_api(
+ app=self.blocks.app,
+ body=body,
+ gr_request=gr_request,
+ fn=fn,
+ root_path=root_path,
+ )
+ while response and response.get("is_generating", False):
+ self.send_message(
+ event,
+ ProcessGeneratingMessage(
+ output=response,
+ success=True,
+ ),
+ )
+ response = await route_utils.call_process_api(
+ app=self.blocks.app,
+ body=body,
+ gr_request=gr_request,
+ fn=fn,
+ root_path=root_path,
+ )
+ cache_duration = time.time() - cache_start
+ avg_time = (
+ self.process_time_per_fn[fn].avg_time
+ if fn in self.process_time_per_fn
+ else None
+ )
+ self.send_message(
+ event,
+ ProcessCompletedMessage(
+ output=response,
+ success=True,
+ used_cache="full",
+ cache_duration=cache_duration,
+ avg_time=avg_time,
+ ),
+ )
+ return True, event._id, "success"
+ except CacheMissError:
+ pass # Fall through to normal queue path
+ except Exception:
+ raise
+
+ try:
+ event_queue = self.event_queue_per_concurrency_id[event.concurrency_id]
+ except KeyError as e:
+ raise KeyError(
+ "Event not found in queue. If you are deploying this Gradio app with multiple replicas, please enable stickiness to ensure that all requests from the same user are routed to the same instance."
+ ) from e
+ event_queue.queue.append(event)
+ self.event_analytics[event._id] = {
+ "time": time.time(),
+ "status": "queued",
+ "process_time": None,
+ "function": fn.api_name,
+ "session_hash": body.session_hash,
+ }
+
+ self.broadcast_estimations(event.concurrency_id, len(event_queue.queue) - 1)
+ return True, event._id, "success"
+
+ async def remove_from_queue(self, event_id: str):
+ event = self.event_ids_to_events.get(event_id)
+ if event:
+ async with self.delete_lock:
+ q = self.event_queue_per_concurrency_id[event.concurrency_id]
+ try:
+ q.queue.remove(event)
+ self.event_ids_to_events.pop(event_id, None)
+ except ValueError:
+ pass
+
+ def _cancel_asyncio_tasks(self):
+ for task in self._asyncio_tasks:
+ task.cancel()
+ self._asyncio_tasks = []
+
+ def set_server_app(self, app: routes.App):
+ self.server_app = app
+
+ def get_active_worker_count(self) -> int:
+ count = 0
+ for worker in self.active_jobs:
+ if worker is not None:
+ count += 1
+ return count
+
+ def get_events(self) -> tuple[list[Event], bool, str] | None:
+ concurrency_ids = list(self.event_queue_per_concurrency_id.keys())
+ random.shuffle(concurrency_ids)
+ for concurrency_id in concurrency_ids:
+ event_queue = self.event_queue_per_concurrency_id[concurrency_id]
+ if len(event_queue.queue) and (
+ event_queue.concurrency_limit is None
+ or event_queue.current_concurrency < event_queue.concurrency_limit
+ ):
+ first_event = event_queue.queue[0]
+ block_fn = first_event.fn
+ events = [first_event]
+ batch = block_fn.batch
+ if batch:
+ events += [
+ event
+ for event in event_queue.queue[1:]
+ if event.fn == first_event.fn
+ ][: block_fn.max_batch_size - 1]
+
+ for event in events:
+ event_queue.queue.remove(event)
+
+ return events, batch, concurrency_id
+
+ async def start_processing(self) -> None:
+ try:
+ while not self.stopped:
+ if len(self) == 0:
+ await asyncio.sleep(self.sleep_when_free)
+ continue
+
+ if None not in self.active_jobs:
+ await asyncio.sleep(self.sleep_when_free)
+ continue
+
+ # Using mutex to avoid editing a list in use
+ async with self.delete_lock:
+ event_batch = self.get_events()
+
+ if event_batch:
+ events, batch, concurrency_id = event_batch
+ self.active_jobs[self.active_jobs.index(None)] = events
+ event_queue = self.event_queue_per_concurrency_id[concurrency_id]
+ event_queue.current_concurrency += 1
+ start_time = time.time()
+ event_queue.start_times_per_fn[events[0].fn].add(start_time)
+ for event in events:
+ self.event_analytics[event._id]["status"] = "processing"
+ process_event_task = run_coro_in_background(
+ self.process_events, events, batch, start_time
+ )
+ set_task_name(
+ process_event_task,
+ events[0].session_hash,
+ events[0].fn._id,
+ events[0]._id,
+ batch,
+ )
+
+ self._asyncio_tasks.append(process_event_task)
+ if self.live_updates:
+ self.broadcast_estimations(concurrency_id)
+ else:
+ await asyncio.sleep(self.sleep_when_free)
+ finally:
+ self.stopped = True
+ self._cancel_asyncio_tasks()
+
+ async def start_progress_updates(self) -> None:
+ """
+ Because progress updates can be very frequent, we do not necessarily want to send a message per update.
+ Rather, we check for progress updates at regular intervals, and send a message if there is a pending update.
+ Consecutive progress updates between sends will overwrite each other so only the most recent update will be sent.
+ """
+ while not self.stopped:
+ events = [evt for job in self.active_jobs if job is not None for evt in job]
+
+ if len(events) == 0:
+ await asyncio.sleep(self.progress_update_sleep_when_free)
+ continue
+
+ for event in events:
+ if event.progress_pending and event.progress:
+ event.progress_pending = False
+ self.send_message(event, event.progress)
+
+ await asyncio.sleep(self.progress_update_sleep_when_free)
+
+ def set_progress(
+ self,
+ event_id: str,
+ iterables: list[TrackedIterable] | None,
+ ):
+ if iterables is None:
+ return
+ for job in self.active_jobs:
+ if job is None:
+ continue
+ for evt in job:
+ if evt._id == event_id:
+ progress_data: list[ProgressUnit] = []
+ for iterable in iterables:
+ progress_unit = ProgressUnit(
+ index=iterable.index,
+ length=iterable.length,
+ unit=iterable.unit,
+ progress=iterable.progress,
+ desc=iterable.desc,
+ )
+ progress_data.append(progress_unit)
+ evt.progress = ProgressMessage(progress_data=progress_data)
+ evt.progress_pending = True
+
+ def log_message(
+ self,
+ event_id: str,
+ log: str,
+ title: str,
+ level: Literal["info", "warning", "success"],
+ duration: float | None = 10,
+ visible: bool = True,
+ ):
+ events = [evt for job in self.active_jobs if job is not None for evt in job]
+ for event in events:
+ if event._id == event_id:
+ log_message = LogMessage(
+ log=log,
+ level=level,
+ duration=duration,
+ visible=visible,
+ title=title,
+ )
+ self.send_message(event, log_message)
+
+ async def clean_events(
+ self, *, session_hash: str | None = None, event_id: str | None = None
+ ) -> None:
+ for job_set in self.active_jobs:
+ if job_set:
+ for job in job_set:
+ if job.session_hash == session_hash or job._id == event_id:
+ job.alive = False
+
+ async with self.delete_lock:
+ events_to_remove: list[Event] = []
+ for event_queue in self.event_queue_per_concurrency_id.values():
+ for event in event_queue.queue:
+ if event.session_hash == session_hash or event._id == event_id:
+ events_to_remove.append(event)
+
+ for event in events_to_remove:
+ self.event_queue_per_concurrency_id[event.concurrency_id].queue.remove(
+ event
+ )
+ self.event_ids_to_events.pop(event._id, None)
+
+ if session_hash and session_hash in self.pending_event_ids_session:
+ removed_ids = {e._id for e in events_to_remove}
+ self.pending_event_ids_session[session_hash] -= removed_ids
+ if not self.pending_event_ids_session[session_hash]:
+ self.pending_event_ids_session.pop(session_hash, None)
+
+ async def notify_clients(self) -> None:
+ """
+ Notify clients about events statuses in the queue periodically.
+ """
+ while not self.stopped:
+ await asyncio.sleep(self.update_intervals)
+ if len(self) > 0:
+ for concurrency_id in self.event_queue_per_concurrency_id:
+ self.broadcast_estimations(concurrency_id)
+
+ def broadcast_estimations(
+ self, concurrency_id: str, after: int | None = None
+ ) -> None:
+ wait_so_far = 0
+ event_queue = self.event_queue_per_concurrency_id[concurrency_id]
+ time_till_available_worker: int | None = 0
+
+ if event_queue.current_concurrency == event_queue.concurrency_limit:
+ expected_end_times = []
+ for fn, start_times in event_queue.start_times_per_fn.items():
+ if fn not in self.process_time_per_fn:
+ time_till_available_worker = None
+ break
+ if fn.connection == "stream":
+ process_time = fn.time_limit or 0
+ else:
+ process_time = self.process_time_per_fn[fn].avg_time
+ expected_end_times += [
+ start_time + process_time for start_time in start_times
+ ]
+ if time_till_available_worker is not None and len(expected_end_times) > 0:
+ time_of_first_completion = min(expected_end_times)
+ time_till_available_worker = max(
+ time_of_first_completion - time.time(), 0
+ )
+
+ for rank, event in enumerate(event_queue.queue):
+ process_time_for_fn = (
+ self.process_time_per_fn[event.fn].avg_time
+ if event.fn in self.process_time_per_fn
+ else None
+ )
+
+ # eta is the time remaining from now until the result will be returned
+ # process_time_for_fn = time to run fn once worker assigned to it
+ # wait_so_far = time till event gets to the head of the queue
+ # time_till_available_worker = time for a worker to be assigned to it once its at the head
+ # For streaming events, we modify this calculation slightly to be the time until the first
+ # chunk is processed.
+ rank_eta = (
+ process_time_for_fn + wait_so_far + time_till_available_worker
+ if process_time_for_fn is not None
+ and wait_so_far is not None
+ and time_till_available_worker is not None
+ else None
+ )
+
+ if after is None or rank >= after:
+ self.send_message(
+ event,
+ EstimationMessage(
+ rank=rank, rank_eta=rank_eta, queue_size=len(event_queue.queue)
+ ),
+ )
+ if event_queue.concurrency_limit is None:
+ wait_so_far = 0
+ elif wait_so_far is not None and process_time_for_fn is not None:
+ delta = process_time_for_fn / event_queue.concurrency_limit
+ if event.streaming:
+ delta = (
+ time_till_available_worker or 0
+ ) / event_queue.concurrency_limit
+ wait_so_far += delta
+ else:
+ wait_so_far = None
+
+ def get_status(self) -> EstimationMessage:
+ return EstimationMessage(
+ queue_size=len(self),
+ )
+
+ @staticmethod
+ async def wait_for_event(event: Event) -> str:
+ await event.signal.wait()
+ return "signal"
+
+ @staticmethod
+ async def timeout(timeout: float) -> str:
+ await asyncio.sleep(timeout)
+ return "timeout"
+
+ @staticmethod
+ async def wait_for_event_or_timeout(
+ event: Event, timeout: float
+ ) -> Literal["signal", "timeout"]:
+ t1 = asyncio.create_task(Queue.wait_for_event(event))
+ t2 = asyncio.create_task(Queue.timeout(timeout))
+ done, _ = await asyncio.wait(
+ [t1, t2],
+ return_when=asyncio.FIRST_COMPLETED,
+ )
+ done = [d.result() for d in done]
+ event.signal.clear()
+ return cast(Literal["signal", "timeout"], done[0])
+
+ @staticmethod
+ async def wait_for_batch(
+ events: list[Event], timeouts: list[float]
+ ) -> tuple[list[Event], list[Event]]:
+ tasks = []
+ for event, timeout in zip(events, timeouts, strict=False):
+ tasks.append(
+ asyncio.create_task(Queue.wait_for_event_or_timeout(event, timeout))
+ )
+ done, _ = await asyncio.wait(
+ tasks,
+ return_when=asyncio.ALL_COMPLETED,
+ )
+ done = [d.result() for d in done]
+ awake_events = []
+ closed_events = []
+ for result, event in zip(done, events, strict=False):
+ if result == "signal":
+ awake_events.append(event)
+ else:
+ closed_events.append(event)
+ return awake_events, closed_events
+
+ async def process_events(
+ self, events: list[Event], batch: bool, begin_time: float
+ ) -> None:
+ awake_events: list[Event] = []
+ fn = events[0].fn
+ success = False
+ try:
+ for event in events:
+ if event.alive:
+ self.send_message(
+ event,
+ ProcessStartsMessage(
+ eta=self.process_time_per_fn[fn].avg_time
+ if fn in self.process_time_per_fn
+ else None
+ ),
+ )
+ awake_events.append(event)
+ if not awake_events:
+ return
+
+ events = awake_events
+ body = events[0].data
+ if body is None:
+ raise ValueError("No event data")
+ username = events[0].username
+ body.event_id = events[0]._id if not batch else None
+ try:
+ body.request = events[0].request
+ except ValueError:
+ pass
+
+ if batch:
+ body.data = list(
+ zip(
+ *[event.data.data for event in events if event.data],
+ strict=False,
+ )
+ )
+ body.request = events[0].request
+ body.batched = True
+
+ app = self.server_app
+ if app is None:
+ raise Exception("Server app has not been set.")
+
+ gr_request = route_utils.compile_gr_request(
+ body=body,
+ fn=fn,
+ username=username,
+ request=None,
+ )
+ assert body.request is not None # noqa: S101
+ api_route_path = route_utils.get_api_call_path(request=body.request)
+ root_path = route_utils.get_root_url(
+ request=body.request,
+ route_path=api_route_path,
+ root_path=app.root_path,
+ )
+ first_iteration = 0
+
+ from gradio.profiling import (
+ PROFILING_ENABLED,
+ RequestTrace,
+ set_current_trace,
+ )
+
+ if PROFILING_ENABLED:
+ trace = RequestTrace(
+ event_id=events[0]._id,
+ fn_name=fn.api_name or str(fn.fn),
+ session_hash=events[0].session_hash,
+ )
+ trace.queue_wait_ms = (time.monotonic() - events[0].enqueue_time) * 1000
+ set_current_trace(trace)
+ else:
+ trace = None
+
+ try:
+ start = time.monotonic()
+ response = await route_utils.call_process_api(
+ app=app,
+ body=body,
+ gr_request=gr_request,
+ fn=fn,
+ root_path=root_path,
+ )
+ end = time.monotonic()
+ first_iteration = end - start
+ err = None
+ for event in awake_events:
+ event.run_time += end - start
+ if event.streaming:
+ response["is_generating"] = not event.is_finished
+
+ except Exception as e:
+ if not isinstance(e, Error) or e.print_exception:
+ traceback.print_exc()
+ response = None
+ err = e
+ for event in awake_events:
+ content = error_payload(err, app.get_blocks().show_error)
+ self.send_message(
+ event,
+ ProcessCompletedMessage(
+ output=content,
+ title=content.get("title", "Error"), # type: ignore
+ success=False,
+ ),
+ )
+ await run_sync(self.compute_analytics_summary, self.event_analytics)
+ if response and response.get("is_generating", False):
+ old_response = response
+ old_err = err
+ while response and response.get("is_generating", False):
+ start = time.monotonic()
+ old_response = response
+ old_err = err
+ for event in awake_events:
+ self.send_message(
+ event,
+ ProcessGeneratingMessage(
+ msg=ServerMessage.process_generating
+ if not event.streaming
+ else ServerMessage.process_streaming,
+ output=old_response,
+ success=old_response is not None,
+ time_limit=None
+ if not fn.time_limit
+ else cast(int, fn.time_limit) - first_iteration
+ if event.streaming
+ else None,
+ ),
+ )
+ awake_events = [event for event in awake_events if event.alive]
+ if not awake_events:
+ return
+ try:
+ start = time.monotonic()
+ if awake_events[0].streaming:
+ awake_events, closed_events = await Queue.wait_for_batch(
+ awake_events,
+ # We need to wait for all of the events to have the latest input data
+ # the max time is the time limit of the function or 30 seconds (arbitrary) but should
+ # never really take that long to make a request from the client to the server unless
+ # the client disconnected.
+ [cast(float, fn.time_limit or 30) - first_iteration]
+ * len(awake_events),
+ )
+ for closed_event in closed_events:
+ self.send_message(
+ closed_event,
+ ProcessCompletedMessage(
+ output=response, success=True
+ ),
+ )
+ if not awake_events:
+ break
+ body = cast(PredictBodyInternal, awake_events[0].data)
+ if batch:
+ body.data = list(
+ zip(
+ *[
+ event.data.data
+ for event in events
+ if event.data
+ ],
+ strict=False,
+ )
+ )
+ response = await route_utils.call_process_api(
+ app=app,
+ body=body,
+ gr_request=gr_request,
+ fn=fn,
+ root_path=root_path,
+ )
+ end = time.monotonic()
+ for event in awake_events:
+ event.run_time += end - start
+ if event.streaming:
+ response["is_generating"] = not event.is_finished
+ except Exception as e:
+ if not isinstance(e, Error) or e.print_exception:
+ traceback.print_exc()
+ response = None
+ err = e
+
+ if response:
+ success = True
+ output = response
+ else:
+ success = False
+ error = err or old_err
+ output = error_payload(error, app.get_blocks().show_error)
+ cache_source = output
+ if (
+ success
+ and not output.get("used_cache")
+ and old_response
+ and old_response.get("used_cache")
+ ):
+ cache_source = old_response
+ used_cache = cache_source.get("used_cache") if success else None
+ used_cache = (
+ cast(Literal["full", "partial"], used_cache)
+ if used_cache in ("full", "partial")
+ else None
+ )
+ for event in awake_events:
+ self.send_message(
+ event,
+ ProcessCompletedMessage(
+ output=output,
+ success=success,
+ used_cache=used_cache,
+ cache_duration=cache_source.get("duration"), # type: ignore[arg-type]
+ avg_time=cache_source.get("average_duration"), # type: ignore[arg-type]
+ ),
+ )
+
+ elif response:
+ output = copy.deepcopy(response)
+ for e, event in enumerate(awake_events):
+ if batch and "data" in output:
+ output["data"] = list(zip(*response.get("data"), strict=False))[
+ e
+ ]
+ success = response is not None
+ used_cache = output.get("used_cache") if success else None
+ used_cache = (
+ cast(Literal["full", "partial"], used_cache)
+ if used_cache in ("full", "partial")
+ else None
+ )
+ self.send_message(
+ event,
+ ProcessCompletedMessage(
+ output=output,
+ success=success,
+ used_cache=used_cache,
+ cache_duration=output.get("duration"), # type: ignore[arg-type]
+ avg_time=output.get("average_duration"), # type: ignore[arg-type]
+ ),
+ )
+ end_time = time.time()
+ if response is not None:
+ duration = (
+ end_time - begin_time
+ if not events[0].streaming
+ else first_iteration
+ )
+ if not response.get("used_cache"):
+ self.process_time_per_fn[events[0].fn].add(duration)
+ for event in events:
+ self.event_analytics[event._id]["process_time"] = duration
+ except Exception as e:
+ if not isinstance(e, Error) or e.print_exception:
+ traceback.print_exc()
+ finally:
+ from gradio.profiling import PROFILING_ENABLED, collector, get_current_trace
+
+ if PROFILING_ENABLED:
+ trace = get_current_trace()
+ if trace is not None:
+ collector.add(trace)
+
+ event_queue = self.event_queue_per_concurrency_id[events[0].concurrency_id]
+ event_queue.current_concurrency -= 1
+ start_times = event_queue.start_times_per_fn[fn]
+ if begin_time in start_times:
+ start_times.remove(begin_time)
+ try:
+ self.active_jobs[self.active_jobs.index(events)] = None
+ except ValueError:
+ # `events` can be absent from `self.active_jobs`
+ # when this coroutine is called from the `join_queue` endpoint handler in `routes.py`
+ # without putting the `events` into `self.active_jobs`.
+ # https://github.com/gradio-app/gradio/blob/f09aea34d6bd18c1e2fef80c86ab2476a6d1dd83/gradio/routes.py#L594-L596
+ pass
+ for event in events:
+ # Always reset the state of the iterator
+ # If the job finished successfully, this has no effect
+ # If the job is cancelled, this will enable future runs
+ # to start "from scratch"
+ await self.reset_iterators(event._id)
+
+ if event in awake_events:
+ self.event_analytics[event._id]["status"] = (
+ "success" if success else "failed"
+ )
+ else:
+ self.event_analytics[event._id]["status"] = "cancelled"
+ await run_sync(self.compute_analytics_summary, self.event_analytics)
+
+ async def reset_iterators(self, event_id: str):
+ # Do the same thing as the /reset route
+ app = self.server_app
+ if app is None:
+ raise Exception("Server app has not been set.")
+ if event_id not in app.iterators:
+ # Failure, but don't raise an error
+ return
+ async with app.lock:
+ try:
+ await safe_aclose_iterator(app.iterators[event_id])
+ except Exception:
+ pass
+ del app.iterators[event_id]
+ app.iterators_to_reset.add(event_id)
+ return
+
+
+def process_validation_response(
+ validation_response: list[dict[str, Any]] | dict[str, Any],
+ fn: BlockFunction | None = None,
+) -> tuple[bool, list[dict[str, Any]]]:
+ validation_data: list[dict[str, Any]] = []
+
+ param_names = []
+ if fn and fn.fn:
+ sig = inspect.signature(fn.fn)
+ param_names = list(sig.parameters.keys())
+
+ if isinstance(validation_response, list):
+ for i, data in enumerate(validation_response):
+ if isinstance(data, dict) and data.get("__type__", None) == "validate":
+ param_name = (
+ param_names[i] if i < len(param_names) else f"parameter_{i}"
+ )
+ data_with_name = {**data, "parameter_name": param_name}
+ validation_data.append(data_with_name)
+ else:
+ validation_data.append({"is_valid": True, "message": ""})
+
+ elif (
+ isinstance(validation_response, dict)
+ and validation_response.get("is_valid", None) is False
+ ):
+ validation_data.append(
+ validation_response,
+ )
+ else:
+ validation_data.append({"is_valid": True, "message": ""})
+
+ return all(
+ x.get("is_valid", None) is True for x in validation_data
+ ), validation_data
diff --git a/gradio/ranged_response.py b/gradio/ranged_response.py
new file mode 100644
index 0000000..5df52a0
--- /dev/null
+++ b/gradio/ranged_response.py
@@ -0,0 +1,190 @@
+# Taken from https://gist.github.com/kevinastone/a6a62db57577b3f24e8a6865ed311463
+# Context: https://github.com/encode/starlette/pull/1090
+from __future__ import annotations
+
+import os
+import re
+import stat
+from typing import NamedTuple
+from urllib.parse import quote
+
+from starlette.datastructures import Headers
+from starlette.exceptions import HTTPException
+from starlette.responses import Response, guess_type # type: ignore
+from starlette.staticfiles import StaticFiles
+from starlette.types import Receive, Scope, Send
+
+from gradio._vendor import aiofiles
+from gradio._vendor.aiofiles.os import stat as aio_stat
+
+RANGE_REGEX = re.compile(r"^bytes=(?P\d+)-(?P\d*)$")
+
+
+class ClosedRange(NamedTuple):
+ start: int
+ end: int
+
+ def __len__(self) -> int: # type: ignore[override]
+ return self.end - self.start + 1
+
+ def __bool__(self) -> bool:
+ return len(self) > 0
+
+
+class OpenRange(NamedTuple):
+ start: int
+ end: int | None = None
+
+ def clamp(self, start: int, end: int) -> ClosedRange:
+ begin = max(self.start, start)
+ end = min(x for x in (self.end, end) if x)
+
+ begin = min(begin, end)
+ end = max(begin, end)
+
+ return ClosedRange(begin, end)
+
+
+class RangedFileResponse(Response):
+ chunk_size = 4096
+
+ def __init__(
+ self,
+ path: str | os.PathLike,
+ range: OpenRange,
+ headers: dict[str, str] | None = None,
+ media_type: str | None = None,
+ filename: str | None = None,
+ stat_result: os.stat_result | None = None,
+ method: str | None = None,
+ ) -> None:
+ if aiofiles is None:
+ raise ModuleNotFoundError(
+ "'aiofiles' must be installed to use FileResponse"
+ )
+ self.path = path
+ self.range = range
+ self.filename = filename
+ self.background = None
+ self.send_header_only = method is not None and method.upper() == "HEAD"
+ if media_type is None:
+ media_type = guess_type(filename or path)[0] or "text/plain"
+ self.media_type = media_type
+ self.init_headers(headers or {})
+ if self.filename is not None:
+ content_disposition_filename = quote(self.filename)
+ if content_disposition_filename != self.filename:
+ content_disposition = (
+ f"attachment; filename*=utf-8''{content_disposition_filename}"
+ )
+ else:
+ content_disposition = f'attachment; filename="{self.filename}"'
+ self.headers.setdefault("content-disposition", content_disposition)
+ self.stat_result = stat_result
+
+ def set_range_headers(self, range: ClosedRange) -> None:
+ if not self.stat_result:
+ raise ValueError("No stat result to set range headers with")
+ total_length = self.stat_result.st_size
+ content_length = len(range)
+ self.headers["content-range"] = (
+ f"bytes {range.start}-{range.end}/{total_length}"
+ )
+ self.headers["content-length"] = str(content_length)
+ pass
+
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: # noqa: ARG002
+ if self.stat_result is None:
+ try:
+ stat_result = await aio_stat(self.path)
+ self.stat_result = stat_result
+ except FileNotFoundError as fnfe:
+ raise RuntimeError(
+ f"File at path {self.path} does not exist."
+ ) from fnfe
+ else:
+ mode = stat_result.st_mode
+ if not stat.S_ISREG(mode):
+ raise RuntimeError(f"File at path {self.path} is not a file.")
+
+ byte_range = self.range.clamp(0, self.stat_result.st_size)
+ self.set_range_headers(byte_range)
+
+ async with aiofiles.open(self.path, mode="rb") as file:
+ await file.seek(byte_range.start)
+ await send(
+ {
+ "type": "http.response.start",
+ "status": 206,
+ "headers": self.raw_headers,
+ }
+ )
+ if self.send_header_only:
+ await send(
+ {"type": "http.response.body", "body": b"", "more_body": False}
+ )
+ else:
+ remaining_bytes = len(byte_range)
+
+ if not byte_range:
+ await send(
+ {"type": "http.response.body", "body": b"", "more_body": False}
+ )
+ return
+
+ while remaining_bytes > 0:
+ chunk_size = min(self.chunk_size, remaining_bytes)
+ chunk = await file.read(chunk_size)
+ remaining_bytes -= len(chunk)
+ await send(
+ {
+ "type": "http.response.body",
+ "body": chunk,
+ "more_body": remaining_bytes > 0,
+ }
+ )
+
+
+class RangedStaticFiles(StaticFiles):
+ def file_response(
+ self,
+ full_path: str | os.PathLike,
+ stat_result: os.stat_result,
+ scope: Scope,
+ status_code: int = 200,
+ ) -> Response:
+ request_headers = Headers(scope=scope)
+
+ if request_headers.get("range"):
+ response = self.ranged_file_response(
+ full_path, stat_result=stat_result, scope=scope
+ )
+ else:
+ response = super().file_response(
+ full_path, stat_result=stat_result, scope=scope, status_code=status_code
+ )
+ response.headers["accept-ranges"] = "bytes"
+ return response
+
+ def ranged_file_response(
+ self,
+ full_path: str | os.PathLike,
+ stat_result: os.stat_result,
+ scope: Scope,
+ ) -> Response:
+ method = scope["method"]
+ request_headers = Headers(scope=scope)
+
+ range_header = request_headers["range"]
+
+ match = RANGE_REGEX.search(range_header)
+ if not match:
+ raise HTTPException(400)
+
+ start, end = match.group("start"), match.group("end")
+
+ range = OpenRange(int(start), int(end) if end else None)
+
+ return RangedFileResponse(
+ full_path, range, stat_result=stat_result, method=method
+ )
diff --git a/gradio/renderable.py b/gradio/renderable.py
new file mode 100644
index 0000000..ef8d18c
--- /dev/null
+++ b/gradio/renderable.py
@@ -0,0 +1,191 @@
+from __future__ import annotations
+
+from collections.abc import Callable, Sequence
+from typing import TYPE_CHECKING, Literal, Union, cast
+
+from gradio_client.documentation import document
+
+from gradio.blocks import Block
+from gradio.components import Component
+from gradio.context import Context, LocalContext
+from gradio.events import EventListener, EventListenerMethod
+from gradio.layouts import Column, Row
+
+if TYPE_CHECKING:
+ from gradio.blocks import BlockFunction
+ from gradio.events import Trigger
+
+
+class Renderable:
+ def __init__(
+ self,
+ fn: Callable,
+ inputs: Sequence[Component],
+ triggers: list[tuple[Block | None, str]],
+ concurrency_limit: int | None | Literal["default"],
+ concurrency_id: str | None,
+ trigger_mode: Literal["once", "multiple", "always_last"] | None,
+ queue: bool,
+ show_progress: Literal["full", "minimal", "hidden"],
+ ):
+ if Context.root_block is None:
+ raise ValueError("Reactive render must be inside a Blocks context.")
+
+ self._id = len(Context.root_block.renderables)
+ Context.root_block.renderables.append(self)
+ self.ContainerClass = Row if isinstance(Context.block, Row) else Column
+ self.container = self.ContainerClass(show_progress=True)
+ self.container_id = self.container._id
+
+ self.fn = fn
+ self.inputs = inputs
+ self.triggers: list[EventListenerMethod] = []
+ self.page = Context.root_block.current_page
+ self.key_to_id_map: dict[int | str | tuple[str | int, ...], int] = {}
+ self.render_iteration = 0
+
+ self.triggers = [EventListenerMethod(*t) for t in triggers]
+ Context.root_block.default_config.set_event_trigger(
+ self.triggers,
+ self.apply,
+ self.inputs,
+ self.container,
+ api_visibility="undocumented",
+ concurrency_limit=concurrency_limit,
+ concurrency_id=concurrency_id,
+ renderable=self,
+ trigger_mode=trigger_mode,
+ postprocess=False,
+ queue=queue,
+ show_progress=show_progress,
+ )
+
+ def apply(self, *args, **kwargs):
+ blocks_config = LocalContext.blocks_config.get(None)
+ if blocks_config is None:
+ raise ValueError("Reactive render must be inside a LocalContext.")
+
+ fns_from_last_render: list[BlockFunction] = []
+ for fn in blocks_config.fns.values():
+ if fn.rendered_in is self:
+ fns_from_last_render.append(fn)
+
+ container_copy = self.ContainerClass(render=False, show_progress=True)
+ container_copy._id = self.container_id
+ container_copy.page = self.page
+ LocalContext.renderable.set(self)
+ LocalContext.key_to_id_map.set(self.key_to_id_map)
+
+ try:
+ with container_copy:
+ self.fn(*args, **kwargs)
+ blocks_config.blocks[self.container_id] = container_copy
+ blocks_config.attach_load_events(self)
+ finally:
+ LocalContext.renderable.set(None)
+ LocalContext.key_to_id_map.set(None)
+
+ for fn in fns_from_last_render:
+ # The block-function may already have been removed from `blocks_config.fns`
+ # during the inner render -- e.g. `gr.Examples` adds and then pops a
+ # transient "fake_event" function (gradio/helpers.py) for cache-on-launch
+ # processing. If the inner render never re-registered ``fn``, indexing
+ # would raise `KeyError`. The cleanup intent is "drop stale fns from the
+ # previous iteration", so a missing entry is already in the desired state.
+ current_fn = blocks_config.fns.get(fn._id)
+ if current_fn is None:
+ continue
+ if current_fn.render_iteration != self.render_iteration:
+ del blocks_config.fns[fn._id]
+
+ self.render_iteration += 1
+
+
+@document()
+def render(
+ inputs: Sequence[Component] | Component | None = None,
+ triggers: Sequence[Trigger] | Trigger | None = None,
+ *,
+ queue: bool = True,
+ trigger_mode: Literal["once", "multiple", "always_last"] | None = "always_last",
+ concurrency_limit: int | None | Literal["default"] = None,
+ concurrency_id: str | None = None,
+ show_progress: Literal["full", "minimal", "hidden"] = "full",
+):
+ """
+ The render decorator allows Gradio Blocks apps to have dynamic layouts, so that the components and event listeners in your app can change depending on custom logic.
+ Attaching a @gr.render decorator to a function will cause the function to be re-run whenever the inputs are changed (or specified triggers are activated). The function contains the components and event listeners that will update based on the inputs.
+ With render, you can:\n
+ - Show or hide components\n
+ - Change text or layout\n
+ - Create components based on what users enter\n
+ The basic usage of @gr.render is as follows:\n
+ 1. Create a function and attach the @gr.render decorator to it.\n
+ 2. Add the input components to the `inputs=` argument of @gr.render, and create a corresponding argument in your function for each component.\n
+ 3. Add all components inside the function that you want to update based on the inputs. Any event listeners that use these components should also be inside this function.\n
+ Parameters:
+ inputs: List of gradio.components to use as inputs. If the function takes no inputs, this should be an empty list.
+ triggers: List of triggers to listen to, e.g. [btn.click, number.change]. If None, will listen to changes to any inputs.
+ queue: If True, will place the request on the queue, if the queue has been enabled. If False, will not put this event on the queue, even if the queue has been enabled. If None, will use the queue setting of the gradio app.
+ trigger_mode: If "once" (default for all events except `.change()`) would not allow any submissions while an event is pending. If set to "multiple", unlimited submissions are allowed while pending, and "always_last" (default for `.change()` and `.key_up()` events) would allow a second submission after the pending event is complete.
+ concurrency_limit: If set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `Blocks.queue()`, which itself is 1 by default).
+ concurrency_id: If set, this is the id of the concurrency group. Events with the same concurrency_id will be limited by the lowest set concurrency_limit.
+ Example:
+ import gradio as gr
+
+ with gr.Blocks() as demo:
+ input_text = gr.Textbox()
+
+ @gr.render(inputs=input_text)
+ def show_split(text):
+ if len(text) == 0:
+ gr.Markdown("## No Input Provided")
+ else:
+ for letter in text:
+ with gr.Row():
+ text = gr.Textbox(letter)
+ btn = gr.Button("Clear")
+ btn.click(lambda: gr.Textbox(value=""), None, text)
+ """
+ new_triggers = cast(Union[list[EventListener], EventListener, None], triggers)
+
+ if Context.root_block is None:
+ raise ValueError("Reactive render must be inside a Blocks context.")
+
+ inputs = (
+ [inputs] if isinstance(inputs, Component) else [] if inputs is None else inputs
+ )
+ _triggers: list[tuple[Block | None, str]] = []
+ if new_triggers is None:
+ _triggers = [(Context.root_block, "load")]
+ for input in inputs:
+ if hasattr(input, "change"):
+ _triggers.append((input, "change"))
+ else:
+ new_triggers = (
+ [new_triggers] if isinstance(new_triggers, EventListener) else new_triggers
+ )
+ _triggers = [
+ (getattr(t, "__self__", None) if t.has_trigger else None, t.event_name)
+ for t in new_triggers
+ ]
+
+ if new_triggers:
+ for trigger in new_triggers:
+ if trigger.callback:
+ trigger.callback(trigger.__self__) # type: ignore
+
+ def wrapper_function(fn):
+ Renderable(
+ fn,
+ inputs, # type: ignore
+ _triggers,
+ concurrency_limit,
+ concurrency_id,
+ trigger_mode,
+ queue,
+ show_progress,
+ )
+ return fn
+
+ return wrapper_function
diff --git a/gradio/route_utils.py b/gradio/route_utils.py
new file mode 100644
index 0000000..e0ee087
--- /dev/null
+++ b/gradio/route_utils.py
@@ -0,0 +1,1422 @@
+from __future__ import annotations
+
+import asyncio
+import functools
+import hashlib
+import hmac
+import importlib.resources
+import json
+import mimetypes
+import os
+import pickle
+import re
+import secrets
+import shutil
+import tempfile
+import threading
+import unicodedata
+import uuid
+from collections import defaultdict, deque
+from collections.abc import AsyncGenerator, Callable
+from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
+from dataclasses import dataclass as python_dataclass
+from datetime import datetime
+from pathlib import Path
+from tempfile import NamedTemporaryFile, _TemporaryFileWrapper
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ BinaryIO,
+ Union,
+ cast,
+)
+from urllib.parse import urlparse
+
+import anyio
+import fastapi
+import gradio_client.utils as client_utils
+import httpx
+import safehttpx
+from gradio_client.documentation import document
+from python_multipart.multipart import MultipartParser, parse_options_header
+from starlette.background import BackgroundTask
+from starlette.datastructures import (
+ FormData,
+ Headers,
+ MutableHeaders,
+ State,
+ UploadFile,
+)
+from starlette.exceptions import HTTPException
+from starlette.formparsers import MultiPartException, MultipartPart
+from starlette.requests import Request as StarletteRequest
+from starlette.responses import (
+ FileResponse,
+ PlainTextResponse,
+ Response,
+ StreamingResponse,
+)
+from starlette.types import ASGIApp, Message, Receive, Scope, Send
+
+from gradio import processing_utils, utils
+from gradio.data_classes import (
+ BlocksConfigDict,
+ DeveloperPath,
+ MediaStreamChunk,
+ PredictBody,
+ PredictBodyInternal,
+ UserProvidedPath,
+)
+from gradio.exceptions import Error, InvalidPathError
+from gradio.state_holder import SessionState
+from gradio.utils import get_package_version
+
+if TYPE_CHECKING:
+ from gradio.blocks import BlockFunction, Blocks, BlocksConfig
+ from gradio.helpers import EventData
+ from gradio.routes import App
+
+
+config_lock = threading.Lock()
+API_PREFIX = "/gradio_api"
+
+
+mimetypes.init()
+
+
+class Obj:
+ """
+ Using a class to convert dictionaries into objects. Used by the `Request` class.
+ Credit: https://www.geeksforgeeks.org/convert-nested-python-dictionary-to-object/
+ """
+
+ def __init__(self, dict_):
+ self.__dict__.update(dict_)
+ for key, value in dict_.items():
+ if isinstance(value, (dict, list)):
+ value = Obj(value)
+ setattr(self, key, value)
+
+ def __getitem__(self, item):
+ return self.__dict__[item]
+
+ def __setitem__(self, item, value):
+ self.__dict__[item] = value
+
+ def __iter__(self):
+ for key, value in self.__dict__.items():
+ if isinstance(value, Obj):
+ yield (key, dict(value))
+ else:
+ yield (key, value)
+
+ def __contains__(self, item) -> bool:
+ if item in self.__dict__:
+ return True
+ for value in self.__dict__.values():
+ if isinstance(value, Obj) and item in value:
+ return True
+ return False
+
+ def get(self, item, default=None):
+ if item in self:
+ return self.__dict__[item]
+ return default
+
+ def keys(self):
+ return self.__dict__.keys()
+
+ def values(self):
+ return self.__dict__.values()
+
+ def items(self):
+ return self.__dict__.items()
+
+ def __str__(self) -> str:
+ return str(self.__dict__)
+
+ def __repr__(self) -> str:
+ return str(self.__dict__)
+
+ def pop(self, item, default=None):
+ if item in self:
+ return self.__dict__.pop(item)
+ return default
+
+
+@document()
+class Request:
+ """
+ A Gradio request object that can be used to access the request headers, cookies,
+ query parameters and other information about the request from within the prediction
+ function. The class is a thin wrapper around the fastapi.Request class. Attributes
+ of this class include: `headers`, `client`, `query_params`, `session_hash`, and `path_params`. If
+ auth is enabled, the `username` attribute can be used to get the logged in user. In some environments,
+ the dict-like attributes (e.g. `requests.headers`, `requests.query_params`) of this class are automatically
+ converted to dictionaries, so we recommend converting them to dictionaries before accessing
+ attributes for consistent behavior in different environments.
+ Example:
+ import gradio as gr
+ def echo(text, request: gr.Request):
+ if request:
+ print("Request headers dictionary:", dict(request.headers))
+ print("Query parameters:", dict(request.query_params))
+ print("IP address:", request.client.host)
+ print("Gradio session hash:", request.session_hash)
+ return text
+ io = gr.Interface(echo, "textbox", "textbox").launch()
+ Demos: request_ip_headers
+ """
+
+ def __init__(
+ self,
+ request: fastapi.Request | None = None,
+ username: str | None = None,
+ session_hash: str | None = None,
+ **kwargs,
+ ):
+ """
+ Can be instantiated with either a fastapi.Request or by manually passing in
+ attributes (needed for queueing).
+ Parameters:
+ request: A fastapi.Request
+ username: The username of the logged in user (if auth is enabled)
+ session_hash: The session hash of the current session. It is unique for each page load.
+ """
+
+ self.request = request
+ self.username = username
+ self.session_hash: str | None = session_hash
+ self.kwargs: dict[str, Any] = kwargs
+
+ def dict_to_obj(self, d):
+ if isinstance(d, dict):
+ return json.loads(json.dumps(d), object_hook=Obj)
+ else:
+ return d
+
+ def __getattr__(self, name: str):
+ if self.request:
+ return self.dict_to_obj(getattr(self.request, name))
+ else:
+ try:
+ obj = self.kwargs[name]
+ except KeyError as ke:
+ raise AttributeError(
+ f"'Request' object has no attribute '{name}'"
+ ) from ke
+ return self.dict_to_obj(obj)
+
+ def __getstate__(self) -> dict[str, Any]:
+ self.kwargs.update(
+ {
+ "headers": dict(getattr(self, "headers", {})),
+ "query_params": dict(getattr(self, "query_params", {})),
+ "cookies": dict(getattr(self, "cookies", {})),
+ "path_params": dict(getattr(self, "path_params", {})),
+ "client": {
+ "host": getattr(self, "client", {}) and self.client.host,
+ "port": getattr(self, "client", {}) and self.client.port,
+ },
+ "url": getattr(self, "url", ""),
+ }
+ )
+ state_obj = getattr(self, "state", None)
+ if state_obj is not None:
+ request_state = dict(getattr(state_obj, "_state", {}))
+ if request_state:
+ try:
+ pickle.dumps(request_state)
+ self.kwargs["request_state"] = request_state
+ except pickle.PicklingError:
+ pass
+ self.request = None
+ return self.__dict__
+
+ def __setstate__(self, state: dict[str, Any]):
+ request_state = state.pop("request_state", None)
+ self.__dict__ = state
+ if request_state is not None:
+ self.state = State(request_state)
+
+
+@document()
+class Header(str):
+ """A string that represents a header value in an incoming HTTP request to the Gradio app.
+
+ When you type a function argument of type `Header`, Gradio will automatically extract that header from the request and pass it to the function.
+ Note that it's common for header values to use hyphens, e.g. `x-forwarded-host`, and these will automatically be converted to underscores.
+ So make sure you use underscores in your function arguments.
+
+ Example:
+ import gradio as gr
+
+ def make_api_request_on_behalf_of_user(prompt: str, x_api_token: gr.Header):
+ return "Hello from the API" if not x_api_token else "Hello from the API with token!"
+
+ demo = gr.Interface(
+ make_api_request_on_behalf_of_user,
+ [
+ gr.Textbox(label="Prompt"),
+ ],
+ gr.Textbox(label="Response"),
+ )
+
+ demo.launch(mcp_server=True)
+ """
+
+ pass
+
+
+class FnIndexInferError(Exception):
+ pass
+
+
+def get_fn(blocks: Blocks, api_name: str | None, body: PredictBody) -> BlockFunction:
+ if body.session_hash:
+ session_state = blocks.state_holder[body.session_hash]
+ fns = session_state.blocks_config.fns
+ else:
+ fns = blocks.fns
+
+ if body.fn_index is None:
+ if api_name is not None:
+ for fn in fns.values():
+ if fn.api_name == api_name:
+ return fn
+ raise FnIndexInferError(
+ f"Could not infer function index for API name: {api_name}"
+ )
+ else:
+ return fns[body.fn_index]
+
+
+def compile_gr_request(
+ body: PredictBodyInternal,
+ fn: BlockFunction,
+ username: str | None,
+ request: fastapi.Request | None,
+):
+ # If this fn_index cancels jobs, then the only input we need is the
+ # current session hash
+ if fn.cancels:
+ body.data = [body.session_hash]
+ if body.request:
+ if body.batched:
+ gr_request = [
+ Request(
+ username=username,
+ request=body.request,
+ session_hash=body.session_hash,
+ )
+ ]
+ else:
+ gr_request = Request(
+ username=username, request=body.request, session_hash=body.session_hash
+ )
+ else:
+ if request is None:
+ raise ValueError("request must be provided if body.request is None")
+ gr_request = Request(
+ username=username, request=request, session_hash=body.session_hash
+ )
+
+ return gr_request
+
+
+def restore_session_state(app: App, body: PredictBodyInternal):
+ event_id = body.event_id
+ session_hash = getattr(body, "session_hash", None)
+ if session_hash is not None:
+ session_state = app.state_holder[session_hash]
+ # The should_reset set keeps track of the fn_indices
+ # that have been cancelled. When a job is cancelled,
+ # the /reset route will mark the jobs as having been reset.
+ # That way if the cancel job finishes BEFORE the job being cancelled
+ # the job being cancelled will not overwrite the state of the iterator.
+ if event_id is None:
+ iterator = None
+ elif event_id in app.iterators_to_reset:
+ iterator = None
+ app.iterators_to_reset.remove(event_id)
+ else:
+ iterator = app.iterators.get(event_id)
+ else:
+ session_state = SessionState(app.get_blocks())
+ iterator = None
+
+ return session_state, iterator
+
+
+def prepare_event_data(
+ blocks_config: BlocksConfig,
+ body: PredictBodyInternal,
+) -> EventData:
+ from gradio.helpers import EventData
+
+ target = body.trigger_id
+ event_data = EventData(
+ blocks_config.blocks.get(target) if target else None,
+ body.event_data,
+ )
+ # Set parent to None to avoid pickle issues in ZeroGPU
+ # See https://github.com/gradio-app/gradio/issues/11551
+ if hasattr(event_data.target, "parent"):
+ event_data.target.parent = None # type: ignore
+ return event_data
+
+
+async def call_process_api(
+ app: App,
+ body: PredictBodyInternal,
+ gr_request: Union[Request, list[Request]],
+ fn: BlockFunction,
+ root_path: str,
+):
+ session_state, iterator = restore_session_state(app=app, body=body)
+
+ event_data = prepare_event_data(session_state.blocks_config, body)
+ event_id = body.event_id
+
+ session_hash = getattr(body, "session_hash", None)
+ inputs = body.data
+
+ batch_in_single_out = not body.batched and fn.batch
+ if batch_in_single_out:
+ inputs = [inputs]
+
+ try:
+ from gradio.profiling import trace_phase
+
+ with utils.MatplotlibBackendMananger():
+ async with trace_phase("total"):
+ output = await app.get_blocks().process_api(
+ block_fn=fn,
+ inputs=inputs,
+ request=gr_request,
+ state=session_state,
+ iterator=iterator,
+ session_hash=session_hash,
+ event_id=event_id,
+ event_data=event_data,
+ in_event_listener=True,
+ simple_format=body.simple_format,
+ root_path=root_path,
+ )
+ iterator = output.pop("iterator", None)
+ if event_id is not None:
+ app.iterators[event_id] = iterator # type: ignore
+ if isinstance(output, Error):
+ raise output
+ except BaseException:
+ iterator = app.iterators.get(event_id) if event_id is not None else None
+ if iterator is not None: # close off any streams that are still open
+ run_id = id(iterator)
+ pending_streams: dict[int, MediaStream] = (
+ app.get_blocks().pending_streams[session_hash].get(run_id, {})
+ )
+ for stream in pending_streams.values():
+ stream.end_stream()
+ raise
+
+ if batch_in_single_out:
+ output["data"] = output["data"][0]
+ return output
+
+
+def get_first_header_value(request: fastapi.Request, header_name: str):
+ header_value = request.headers.get(header_name)
+ if header_value:
+ return header_value.split(",")[0].strip()
+ return None
+
+
+def get_request_origin(request: fastapi.Request, route_path: str) -> httpx.URL:
+ """
+ Examines the request headers to determine the origin of the request.
+ If the request includes the x-forwarded-host header, it is used directly to determine the origin.
+ Otherwise, the request url is used and the route path is stripped off.
+
+ The returned URL is a httpx.URL object without a trailing slash, e.g. "https://example.com"
+ """
+
+ x_forwarded_host = get_first_header_value(request, "x-forwarded-host")
+ x_gradio_server = get_first_header_value(request, "x-gradio-server")
+ root_url = (
+ f"http://{x_forwarded_host}"
+ if x_forwarded_host
+ else str(x_gradio_server or request.url)
+ )
+ root_url = httpx.URL(root_url)
+ root_url = root_url.copy_with(query=None)
+ root_url = str(root_url).rstrip("/")
+
+ if get_first_header_value(request, "x-forwarded-proto") == "https":
+ root_url = root_url.replace("http://", "https://")
+
+ route_path = route_path.rstrip("/")
+
+ if len(route_path) > 0 and not x_forwarded_host and root_url.endswith(route_path):
+ root_url = root_url[: -len(route_path)]
+
+ root_url = root_url.rstrip("/")
+ root_url = httpx.URL(root_url)
+
+ return root_url
+
+
+def get_api_call_path(request: fastapi.Request) -> str:
+ """
+ Extracts the API call path from the request URL.
+
+ If the URL (without query parameters) ends with "{API_PREFIX}/queue/join", that exact path is returned.
+ Otherwise, if the URL contains "{API_PREFIX}/call", the substring starting from "{API_PREFIX}/call" is returned.
+ This allows for dynamic API calls to methods other than "predict".
+
+ Raises:
+ ValueError: If the request URL does not match any recognized API call pattern.
+ """
+ queue_api_url = f"{API_PREFIX}/queue/join"
+ generic_api_url = f"{API_PREFIX}/call"
+ request_path = request.url.path.rstrip("/")
+
+ if request_path.endswith(queue_api_url):
+ return queue_api_url
+
+ start_index = request_path.rfind(generic_api_url)
+ if start_index >= 0:
+ return request_path[start_index : len(request_path)]
+
+ raise ValueError(
+ f"Request url '{str(request.url)}' has an unknown api call pattern."
+ )
+
+
+def get_root_url(
+ request: fastapi.Request, route_path: str, root_path: str | None
+) -> str:
+ """
+ Gets the root url of the Gradio app (i.e. the public url of the app) without a trailing slash.
+
+ This is how the root_url is resolved:
+ 1. If a user provides a `root_path` manually that is a full URL, it is returned directly.
+ 2. If the request has an x-forwarded-host header (e.g. because it is behind a proxy), the root url is
+ constructed from the x-forwarded-host header. In this case, `route_path` is not used to construct the root url.
+ 3. Otherwise, the root url is constructed from the request url. The query parameters and `route_path` are stripped off.
+ And if a relative `root_path` is provided, and it is not already the subpath of the URL, it is appended to the root url.
+
+ In cases (2) and (3), We also check to see if the x-forwarded-proto header is present, and if so, convert the root url to https.
+ And if there are multiple hosts in the x-forwarded-host or multiple protocols in the x-forwarded-proto, the first one is used.
+ """
+
+ if root_path and client_utils.is_http_url_like(root_path):
+ return root_path.rstrip("/")
+
+ root_url = get_request_origin(request, route_path)
+
+ if root_path and root_url.path != root_path:
+ root_url = root_url.copy_with(path=root_path)
+
+ return str(root_url).rstrip("/")
+
+
+def _user_safe_decode(src: bytes, codec: str) -> str:
+ try:
+ return src.decode(codec)
+ except (UnicodeDecodeError, LookupError):
+ return src.decode("latin-1")
+
+
+class GradioUploadFile(UploadFile):
+ """UploadFile with a sha attribute."""
+
+ def __init__(
+ self,
+ file: BinaryIO,
+ *,
+ size: int | None = None,
+ filename: str | None = None,
+ headers: Headers | None = None,
+ ) -> None:
+ super().__init__(file, size=size, filename=filename, headers=headers)
+ self.sha = hashlib.sha256()
+ self.sha.update(processing_utils.hash_seed)
+
+
+@python_dataclass(frozen=True)
+class FileUploadProgressUnit:
+ filename: str
+ chunk_size: int
+
+
+@python_dataclass
+class FileUploadProgressTracker:
+ deque: deque[FileUploadProgressUnit]
+ is_done: bool
+
+
+class FileUploadProgressNotTrackedError(Exception):
+ pass
+
+
+class FileUploadProgressNotQueuedError(Exception):
+ pass
+
+
+class FileUploadProgress:
+ def __init__(self) -> None:
+ self._statuses: dict[str, FileUploadProgressTracker] = {}
+ self._signals = defaultdict(asyncio.Event)
+
+ def track(self, upload_id: str):
+ if upload_id not in self._statuses:
+ self._statuses[upload_id] = FileUploadProgressTracker(deque(), False)
+ self._signals[upload_id].set()
+
+ async def is_tracked(self, upload_id: str) -> bool:
+ return await self._signals[upload_id].wait()
+
+ def append(self, upload_id: str, filename: str, message_bytes: bytes):
+ if upload_id not in self._statuses:
+ self.track(upload_id)
+ queue = self._statuses[upload_id].deque
+
+ if len(queue) == 0:
+ queue.append(FileUploadProgressUnit(filename, len(message_bytes)))
+ else:
+ last_unit = queue.popleft()
+ if last_unit.filename != filename:
+ queue.append(FileUploadProgressUnit(filename, len(message_bytes)))
+ else:
+ queue.append(
+ FileUploadProgressUnit(
+ filename,
+ last_unit.chunk_size + len(message_bytes),
+ )
+ )
+
+ def set_done(self, upload_id: str):
+ if upload_id not in self._statuses:
+ self.track(upload_id)
+ self._statuses[upload_id].is_done = True
+
+ def is_done(self, upload_id: str):
+ if upload_id not in self._statuses:
+ raise FileUploadProgressNotTrackedError()
+ return self._statuses[upload_id].is_done
+
+ def stop_tracking(self, upload_id: str):
+ if upload_id in self._statuses:
+ del self._statuses[upload_id]
+
+ def pop(self, upload_id: str) -> FileUploadProgressUnit:
+ if upload_id not in self._statuses:
+ raise FileUploadProgressNotTrackedError()
+ try:
+ return self._statuses[upload_id].deque.pop()
+ except IndexError as e:
+ raise FileUploadProgressNotQueuedError() from e
+
+
+class GradioMultiPartParser:
+ """Vendored from starlette.MultipartParser.
+
+ Thanks starlette!
+
+ Made the following modifications
+ - Use GradioUploadFile instead of UploadFile
+ - Use NamedTemporaryFile instead of SpooledTemporaryFile
+ - Compute hash of data as the request is streamed
+
+ """
+
+ max_header_size = 1024 * 8
+
+ def __init__(
+ self,
+ headers: Headers,
+ stream: AsyncGenerator[bytes, None],
+ *,
+ max_files: Union[int, float] = 1000,
+ max_fields: Union[int, float] = 1000,
+ upload_id: str | None = None,
+ upload_progress: FileUploadProgress | None = None,
+ max_file_size: int | float,
+ max_header_size: int = max_header_size,
+ ) -> None:
+ self.headers = headers
+ self.stream = stream
+ self.max_files = max_files
+ self.max_fields = max_fields
+ self.items: list[tuple[str, Union[str, UploadFile]]] = []
+ self.upload_id = upload_id
+ self.upload_progress = upload_progress
+ self._current_files = 0
+ self._current_fields = 0
+ self.max_file_size = max_file_size
+ self.max_header_size = max_header_size
+ self._current_partial_header_name: bytes = b""
+ self._current_partial_header_value: bytes = b""
+ self._current_header_size: int = 0
+ self._current_part = MultipartPart()
+ self._charset = ""
+ self._file_parts_to_write: list[tuple[MultipartPart, bytes]] = []
+ self._file_parts_to_finish: list[MultipartPart] = []
+ self._files_to_close_on_error: list[_TemporaryFileWrapper] = []
+
+ def on_part_begin(self) -> None:
+ self._current_part = MultipartPart()
+ self._current_header_size = 0
+
+ def on_part_data(self, data: bytes, start: int, end: int) -> None:
+ message_bytes = data[start:end]
+ if self.upload_progress is not None:
+ self.upload_progress.append(
+ self.upload_id, # type: ignore
+ self._current_part.file.filename, # type: ignore
+ message_bytes,
+ )
+ if self._current_part.file is None:
+ self._current_part.data += message_bytes
+ else:
+ self._file_parts_to_write.append((self._current_part, message_bytes))
+
+ def on_part_end(self) -> None:
+ if self._current_part.file is None:
+ data = self._current_part.data
+ data_bytes = bytes(data) if isinstance(data, bytearray) else data
+ self.items.append(
+ (
+ self._current_part.field_name,
+ _user_safe_decode(data_bytes, str(self._charset)),
+ )
+ )
+ else:
+ self._file_parts_to_finish.append(self._current_part)
+ # The file can be added to the items right now even though it's not
+ # finished yet, because it will be finished in the `parse()` method, before
+ # self.items is used in the return value.
+ self.items.append((self._current_part.field_name, self._current_part.file))
+
+ def _check_header_size(self, additional_bytes: int):
+ if self._current_header_size + additional_bytes > self.max_header_size:
+ raise MultiPartException(
+ f"Headers exceeded maximum allowed size of {self.max_header_size} bytes."
+ )
+
+ def on_header_field(self, data: bytes, start: int, end: int) -> None:
+ additional_header_bytes = end - start
+ self._check_header_size(additional_header_bytes)
+ self._current_partial_header_name += data[start:end]
+ self._current_header_size += additional_header_bytes
+
+ def on_header_value(self, data: bytes, start: int, end: int) -> None:
+ additional_header_bytes = end - start
+ self._check_header_size(additional_header_bytes)
+ self._current_partial_header_value += data[start:end]
+ self._current_header_size += additional_header_bytes
+
+ def on_header_end(self) -> None:
+ field = self._current_partial_header_name.lower()
+ if field == b"content-disposition":
+ self._current_part.content_disposition = self._current_partial_header_value
+ self._current_part.item_headers.append(
+ (field, self._current_partial_header_value)
+ )
+ self._current_partial_header_name = b""
+ self._current_partial_header_value = b""
+
+ def on_headers_finished(self) -> None:
+ _, options = parse_options_header(self._current_part.content_disposition or b"")
+ try:
+ self._current_part.field_name = _user_safe_decode(
+ options[b"name"], str(self._charset)
+ )
+ except KeyError as e:
+ raise MultiPartException(
+ 'The Content-Disposition header field "name" must be provided.'
+ ) from e
+ if b"filename" in options:
+ self._current_files += 1
+ if self._current_files > self.max_files:
+ raise MultiPartException(
+ f"Too many files. Maximum number of files is {self.max_files}."
+ )
+ filename = _user_safe_decode(options[b"filename"], str(self._charset))
+ tempfile = NamedTemporaryFile(delete=False)
+ self._files_to_close_on_error.append(tempfile)
+ self._current_part.file = GradioUploadFile(
+ file=tempfile, # type: ignore[arg-type]
+ size=0,
+ filename=filename,
+ headers=Headers(raw=self._current_part.item_headers),
+ )
+ else:
+ self._current_fields += 1
+ if self._current_fields > self.max_fields:
+ raise MultiPartException(
+ f"Too many fields. Maximum number of fields is {self.max_fields}."
+ )
+ self._current_part.file = None
+
+ def on_end(self) -> None:
+ pass
+
+ async def parse(self) -> FormData:
+ # Parse the Content-Type header to get the multipart boundary.
+ _, params = parse_options_header(self.headers["Content-Type"])
+ charset = params.get(b"charset", "utf-8")
+ if isinstance(charset, bytes):
+ charset = charset.decode("latin-1")
+ self._charset = charset
+ try:
+ boundary = params[b"boundary"]
+ except KeyError as e:
+ raise MultiPartException("Missing boundary in multipart.") from e
+
+ # Callbacks dictionary.
+ callbacks = {
+ "on_part_begin": self.on_part_begin,
+ "on_part_data": self.on_part_data,
+ "on_part_end": self.on_part_end,
+ "on_header_field": self.on_header_field,
+ "on_header_value": self.on_header_value,
+ "on_header_end": self.on_header_end,
+ "on_headers_finished": self.on_headers_finished,
+ "on_end": self.on_end,
+ }
+
+ # Create the parser.
+ parser = MultipartParser(boundary, callbacks) # type: ignore
+ try:
+ # Feed the parser with data from the request.
+ async for chunk in self.stream:
+ parser.write(chunk)
+ # Write file data, it needs to use await with the UploadFile methods
+ # that call the corresponding file methods *in a threadpool*,
+ # otherwise, if they were called directly in the callback methods above
+ # (regular, non-async functions), that would block the event loop in
+ # the main thread.
+ for part, data in self._file_parts_to_write:
+ assert part.file # for type checkers # noqa: S101
+ if (part.file.size or 0) + len(data) > self.max_file_size:
+ if self.upload_progress is not None:
+ self.upload_progress.set_done(self.upload_id) # type: ignore
+ raise MultiPartException(
+ f"File size exceeded maximum allowed size of {self.max_file_size} bytes."
+ )
+ await part.file.write(data)
+ part.file.sha.update(data) # type: ignore
+ for part in self._file_parts_to_finish:
+ assert part.file # for type checkers # noqa: S101
+ await part.file.seek(0)
+ self._file_parts_to_write.clear()
+ self._file_parts_to_finish.clear()
+ except MultiPartException as exc:
+ # Close all the files if there was an error.
+ for file in self._files_to_close_on_error:
+ file.close()
+ Path(file.name).unlink()
+ raise exc
+
+ parser.finalize()
+ if self.upload_progress is not None:
+ self.upload_progress.set_done(self.upload_id) # type: ignore
+ return FormData(self.items)
+
+
+def move_uploaded_files_to_cache(files: list[str], destinations: list[str]) -> None:
+ for file, dest in zip(files, destinations, strict=False):
+ shutil.move(file, dest)
+
+
+def update_root_in_config(config: BlocksConfigDict, root: str) -> BlocksConfigDict:
+ """
+ Updates the root "key" in the config dictionary to the new root url. If the
+ root url has changed, all of the urls in the config that correspond to component
+ file urls are updated to use the new root url.
+ """
+ previous_root = config.get("root")
+ if previous_root is None or previous_root != root:
+ config["root"] = root
+ config = processing_utils.add_root_url(config, root, previous_root) # type: ignore
+ return config
+
+
+def update_example_values_to_use_public_url(api_info: dict[str, Any]) -> dict[str, Any]:
+ """
+ Updates the example values in the api_info dictionary to use a public url
+ """
+
+ def _add_root_url(file_dict: dict):
+ default_value = file_dict.get("parameter_default")
+ if default_value is not None and client_utils.is_file_obj_with_url(
+ default_value
+ ):
+ if client_utils.is_http_url_like(default_value["url"]):
+ return file_dict
+ # If the default value's url is not already a full public url,
+ # we use the example_input url. This makes it so that the example
+ # value for images, audio, and video components pass SSRF checks.
+ default_value["url"] = file_dict["example_input"]["url"]
+ return file_dict
+
+ return client_utils.traverse(
+ api_info,
+ _add_root_url,
+ lambda d: isinstance(d, dict) and "parameter_default" in d,
+ )
+
+
+def compare_passwords_securely(input_password: str, correct_password: str) -> bool:
+ return hmac.compare_digest(input_password.encode(), correct_password.encode())
+
+
+def starts_with_protocol(string: str) -> bool:
+ """This regex matches strings that start with a scheme (one or more characters not including colon, slash, or space)
+ followed by ://, or start with just //, \\/, /\\, or \\ as they are interpreted as SMB paths on Windows.
+ """
+ pattern = r"^(?:[a-zA-Z][a-zA-Z0-9+\-.]*://|//|\\\\|\\/|/\\)"
+ return re.match(pattern, string) is not None
+
+
+def get_hostname(url: str) -> str:
+ """
+ Returns the hostname of a given url, or an empty string if the url cannot be parsed.
+ Examples:
+ get_hostname("https://www.gradio.app") -> "www.gradio.app"
+ get_hostname("localhost:7860") -> "localhost"
+ get_hostname("127.0.0.1") -> "127.0.0.1"
+ """
+ if not url:
+ return ""
+ if "://" not in url:
+ url = "http://" + url
+ try:
+ return urlparse(url).hostname or ""
+ except Exception:
+ return ""
+
+
+class CustomCORSMiddleware:
+ # This is a modified version of the Starlette CORSMiddleware that restricts the allowed origins when the host is localhost.
+ # Adapted from: https://github.com/encode/starlette/blob/89fae174a1ea10f59ae248fe030d9b7e83d0b8a0/starlette/middleware/cors.py
+
+ def __init__(
+ self,
+ app: ASGIApp,
+ strict_cors: bool = True,
+ ) -> None:
+ self.app = app
+ self.all_methods = ("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT")
+ self.preflight_headers = {
+ "Access-Control-Allow-Methods": ", ".join(self.all_methods),
+ "Access-Control-Max-Age": str(600),
+ "Access-Control-Allow-Credentials": "true",
+ }
+ self.simple_headers = {"Access-Control-Allow-Credentials": "true"}
+ # Any of these hosts suggests that the Gradio app is running locally.
+ self.localhost_aliases = ["localhost", "127.0.0.1", "0.0.0.0"]
+ if not strict_cors or os.getenv("GRADIO_LOCAL_DEV_MODE") is not None: # type: ignore
+ # Note: "null" is a special case that happens if a Gradio app is running
+ # as an embedded web component in a local static webpage. However, it can
+ # also be used maliciously for CSRF attacks, so it is not allowed by default.
+ self.localhost_aliases.append("null")
+
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
+ if scope["type"] != "http":
+ await self.app(scope, receive, send)
+ return
+ headers = Headers(scope=scope)
+ origin = headers.get("origin")
+ if origin is None:
+ await self.app(scope, receive, send)
+ return
+ if scope["method"] == "OPTIONS" and "access-control-request-method" in headers:
+ response = self.preflight_response(request_headers=headers)
+ await response(scope, receive, send)
+ return
+ await self.simple_response(scope, receive, send, request_headers=headers)
+
+ def preflight_response(self, request_headers: Headers) -> Response:
+ headers = dict(self.preflight_headers)
+ origin = request_headers["Origin"]
+ if self.is_valid_origin(request_headers):
+ headers["Access-Control-Allow-Origin"] = origin
+ requested_headers = request_headers.get("access-control-request-headers")
+ if requested_headers is not None:
+ headers["Access-Control-Allow-Headers"] = requested_headers
+ return PlainTextResponse("OK", status_code=200, headers=headers)
+
+ async def simple_response(
+ self, scope: Scope, receive: Receive, send: Send, request_headers: Headers
+ ) -> None:
+ send = functools.partial(self._send, send=send, request_headers=request_headers)
+ await self.app(scope, receive, send)
+
+ async def _send(
+ self, message: Message, send: Send, request_headers: Headers
+ ) -> None:
+ if message["type"] != "http.response.start":
+ await send(message)
+ return
+ message.setdefault("headers", [])
+ headers = MutableHeaders(scope=message)
+ headers.update(self.simple_headers)
+ origin = request_headers["Origin"]
+ if self.is_valid_origin(request_headers):
+ self.allow_explicit_origin(headers, origin)
+ await send(message)
+
+ def is_valid_origin(self, request_headers: Headers) -> bool:
+ origin = request_headers["Origin"]
+ host = request_headers["Host"]
+ host_name = get_hostname(host)
+ origin_name = get_hostname(origin)
+
+ return (
+ host_name not in self.localhost_aliases
+ or origin_name in self.localhost_aliases
+ )
+
+ @staticmethod
+ def allow_explicit_origin(headers: MutableHeaders, origin: str) -> None:
+ headers["Access-Control-Allow-Origin"] = origin
+ headers.add_vary_header("Origin")
+
+
+def delete_files_created_by_app(blocks: Blocks, age: int | None) -> None:
+ """Delete files that are older than age. If age is None, delete all files."""
+ dont_delete = set()
+
+ for component in blocks.blocks.values():
+ dont_delete.update(getattr(component, "keep_in_cache", set()))
+ for temp_set in blocks.temp_file_sets:
+ # We use a copy of the set to avoid modifying the set while iterating over it
+ # otherwise we would get an exception: Set changed size during iteration
+ to_remove = set()
+ for file in temp_set:
+ if file in dont_delete:
+ continue
+ try:
+ file_path = Path(file)
+ modified_time = datetime.fromtimestamp(file_path.lstat().st_ctime)
+ if (
+ age is None
+ or (datetime.now() - modified_time).total_seconds() > age
+ ):
+ os.remove(file)
+ to_remove.add(file)
+ except FileNotFoundError:
+ continue
+ temp_set -= to_remove
+
+
+async def delete_files_on_schedule(app: App, frequency: int, age: int) -> None:
+ """Startup task to delete files created by the app based on time since last modification."""
+ while True:
+ await asyncio.sleep(frequency)
+ await anyio.to_thread.run_sync(
+ delete_files_created_by_app, app.get_blocks(), age
+ )
+
+
+@asynccontextmanager
+async def _lifespan_handler(
+ app: App, frequency: int = 1, age: int = 1
+) -> AsyncGenerator:
+ """A context manager that triggers the startup and shutdown events of the app."""
+ asyncio.create_task(delete_files_on_schedule(app, frequency, age))
+ yield
+ delete_files_created_by_app(app.get_blocks(), age=None)
+
+
+async def _delete_state(app: App):
+ """Delete all expired state every second."""
+ while True:
+ app.state_holder.delete_all_expired_state()
+ await asyncio.sleep(1)
+
+
+@asynccontextmanager
+async def _delete_state_handler(app: App):
+ """When the server launches, regularly delete expired state."""
+ asyncio.create_task(_delete_state(app))
+ yield
+
+
+def create_lifespan_handler(
+ user_lifespan: Callable[[App], AbstractAsyncContextManager] | None,
+ frequency: int | None = 1,
+ age: int | None = 1,
+) -> Callable[[App], AbstractAsyncContextManager]:
+ """Return a context manager that applies _lifespan_handler and user_lifespan if it exists."""
+
+ @asynccontextmanager
+ async def _handler(app: App):
+ state = None
+ async with AsyncExitStack() as stack:
+ await stack.enter_async_context(_delete_state_handler(app))
+ if frequency and age:
+ await stack.enter_async_context(_lifespan_handler(app, frequency, age))
+ if user_lifespan is not None:
+ state = await stack.enter_async_context(user_lifespan(app))
+ yield state
+
+ return _handler
+
+
+class MediaStream:
+ def __init__(self, desired_output_format: str | None = None):
+ self.segments: list[MediaStreamChunk] = []
+ self.combined_file: str | None = None
+ self.ended = False
+ self.segment_index = 0
+ self.playlist = "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT\n#EXT-X-TARGETDURATION:10\n#EXT-X-VERSION:4\n#EXT-X-MEDIA-SEQUENCE:0\n"
+ self.max_duration = 5
+ self.desired_output_format = desired_output_format
+
+ async def add_segment(self, data: MediaStreamChunk | None):
+ if not data:
+ return
+
+ segment_id = str(uuid.uuid4())
+ self.segments.append({"id": segment_id, **data}) # type: ignore
+ self.max_duration = max(self.max_duration, data["duration"]) + 1
+
+ def end_stream(self):
+ self.ended = True
+
+
+def create_url_safe_hash(data: bytes, digest_size=8):
+ """Create a URL-safe short hash of the data. Used to generate unique short deep links."""
+ import base64
+
+ hash_obj = hashlib.blake2b(data, digest_size=digest_size, usedforsecurity=False)
+ url_safe_hash = base64.urlsafe_b64encode(hash_obj.digest()).decode().rstrip("=")
+
+ return url_safe_hash
+
+
+def slugify(value):
+ """
+ Convert to ASCII. Convert spaces or repeated dashes to single dashes.
+ Remove characters that aren't alphanumerics, underscores, or hyphens.
+ Convert to lowercase. Also strip leading and trailing whitespace,
+ dashes, and underscores.
+ """
+ value = str(value)
+ value = (
+ unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
+ )
+ value = re.sub(r"[^\w\s-]", "", value.lower())
+ return re.sub(r"[-\s]+", "-", value).strip("-_")
+
+
+# Prefixes that identify "dumb" routes safe to proxy to static workers.
+# These routes serve files, static assets, or handle uploads — no queue/state needed.
+STATIC_ROUTE_PREFIXES = (
+ "/svelte/",
+ "/static/",
+ "/assets/",
+ "/favicon.ico",
+ "/file=",
+ "/file/",
+ "/upload",
+ "/custom_component/",
+)
+
+
+def routes_safe_join(directory: DeveloperPath, path: UserProvidedPath) -> str:
+ """Safely join the user path to the directory while performing some additional http-related checks,
+ e.g. ensuring that the full path exists on the local file system and is not a directory
+ """
+ if path == "":
+ raise fastapi.HTTPException(400)
+ if starts_with_protocol(path):
+ raise fastapi.HTTPException(403)
+ try:
+ fullpath = Path(utils.safe_join(directory, path))
+ except InvalidPathError as e:
+ raise fastapi.HTTPException(403) from e
+ if fullpath.is_dir():
+ raise fastapi.HTTPException(403)
+ if not fullpath.exists():
+ raise fastapi.HTTPException(404)
+ return str(fullpath)
+
+
+STATIC_TEMPLATE_LIB = cast(
+ DeveloperPath,
+ importlib.resources.files("gradio").joinpath("templates").as_posix(), # type: ignore
+)
+STATIC_PATH_LIB = cast(
+ DeveloperPath,
+ importlib.resources.files("gradio")
+ .joinpath("templates/frontend/static")
+ .as_posix(), # type: ignore
+)
+BUILD_PATH_LIB = cast(
+ DeveloperPath,
+ importlib.resources.files("gradio")
+ .joinpath("templates/frontend/assets")
+ .as_posix(), # type: ignore
+)
+VERSION = get_package_version()
+XSS_SAFE_MIMETYPES = {
+ "image/jpeg",
+ "image/png",
+ "image/gif",
+ "image/webp",
+ "audio/mpeg",
+ "audio/wav",
+ "audio/ogg",
+ "video/mp4",
+ "video/webm",
+ "video/ogg",
+ "text/plain",
+ "application/json",
+}
+
+DEFAULT_TEMP_DIR = os.environ.get("GRADIO_TEMP_DIR") or str(
+ Path(tempfile.gettempdir()) / "gradio"
+)
+
+
+def file_response(developer_path: DeveloperPath, user_path: UserProvidedPath):
+ return FileResponse(routes_safe_join(developer_path, user_path))
+
+
+def favicon(favicon_path: str | Path | None = None):
+ if favicon_path is None:
+ return file_response(STATIC_PATH_LIB, UserProvidedPath("img/logo.svg"))
+ else:
+ return FileResponse(favicon_path)
+
+
+_FILE_STREAM_MAX_REDIRECTS = 20
+_FILE_STREAM_PASSTHROUGH_HEADERS = (
+ "content-length",
+ "content-range",
+ "accept-ranges",
+ "last-modified",
+ "etag",
+)
+
+
+async def secure_url_stream_response(url: str, request: StarletteRequest):
+ """
+ SSRF-safe way to serve an http(s) URL from the `/gradio_api/file=`
+ endpoint. Instead of redirecting the caller to `url` (an open redirect, and
+ a client-side SSRF vector because `gradio_client` follows redirects), the
+ server fetches `url` itself through `safehttpx` — which resolves the host,
+ confirms it maps to a public IP, and pins that IP for the connection so a DNS
+ rebind cannot swap in an internal address — and streams the bytes back,
+ re-validating every redirect hop. Hosts that are unresolvable or resolve to a
+ private / loopback / link-local / reserved address are rejected. See #13593.
+ """
+ forward_headers = {}
+ range_header = request.headers.get("Range")
+ if range_header:
+ forward_headers["Range"] = range_header
+
+ current_url = url
+ redirects = 0
+ while True:
+ try:
+ parsed = httpx.URL(current_url)
+ except Exception as e:
+ raise HTTPException(403, f"File not allowed: {url}.") from e
+ if parsed.scheme not in ("http", "https") or not parsed.host:
+ raise HTTPException(403, f"File not allowed: {url}.")
+ try:
+ verified_ip = await safehttpx.async_validate_url(parsed.host)
+ except Exception as e:
+ raise HTTPException(403, f"File not allowed: {url}.") from e
+
+ transport = safehttpx.AsyncSecureTransport(verified_ip)
+ client = httpx.AsyncClient(
+ transport=transport, timeout=httpx.Timeout(None, connect=10.0)
+ )
+ try:
+ req = client.build_request(
+ request.method, current_url, headers=forward_headers
+ )
+ upstream = await client.send(req, stream=True)
+ except Exception as e:
+ await client.aclose()
+ raise HTTPException(502, f"Could not fetch file: {url}.") from e
+
+ if upstream.has_redirect_location:
+ location = upstream.headers.get("location", "")
+ await upstream.aclose()
+ await client.aclose()
+ redirects += 1
+ if redirects > _FILE_STREAM_MAX_REDIRECTS or not location:
+ raise HTTPException(502, f"Could not fetch file: {url}.")
+ current_url = str(httpx.URL(current_url).join(location))
+ continue
+
+ upstream_mime = upstream.headers.get("content-type", "").split(";")[0].strip()
+ guessed_mime, _ = mimetypes.guess_type(current_url)
+ if upstream_mime in XSS_SAFE_MIMETYPES or guessed_mime in XSS_SAFE_MIMETYPES:
+ content_type = upstream_mime or guessed_mime or "application/octet-stream"
+ content_disposition = "inline"
+ else:
+ content_type = "application/octet-stream"
+ content_disposition = "attachment"
+
+ response_headers = {
+ "Content-Type": content_type,
+ "Content-Disposition": content_disposition,
+ "X-Content-Type-Options": "nosniff",
+ }
+ for header in _FILE_STREAM_PASSTHROUGH_HEADERS:
+ if header in upstream.headers:
+ response_headers[header] = upstream.headers[header]
+
+ async def _close(upstream=upstream, client=client) -> None:
+ await upstream.aclose()
+ await client.aclose()
+
+ return StreamingResponse(
+ upstream.aiter_raw(),
+ status_code=upstream.status_code,
+ headers=response_headers,
+ background=BackgroundTask(_close),
+ )
+
+
+def file_fetch(
+ path_or_url,
+ request,
+ blocks_or_config,
+ upload_dir,
+):
+ # NOTE: http(s) URLs are intentionally NOT handled here. Previously this
+ # returned a 302 redirect to the URL, which was an open redirect and — since
+ # `gradio_client` follows redirects — a client-side SSRF vector (#13593).
+ # They are now served by `secure_url_stream_response()` from the async route,
+ # which fetches through `safehttpx` (public-IP validated, IP-pinned) and
+ # streams the bytes back. Any http(s) URL reaching this sync path falls
+ # through to the `starts_with_protocol` guard below and is rejected.
+ if starts_with_protocol(path_or_url):
+ raise HTTPException(403, f"File not allowed: {path_or_url}.")
+
+ abs_path = utils.abspath(path_or_url)
+ try:
+ if abs_path.is_dir() or not abs_path.exists():
+ raise HTTPException(403, f"File not allowed: {path_or_url}.")
+ except Exception as e:
+ raise HTTPException(403, f"File not allowed: {path_or_url}.") from e
+
+ from gradio.data_classes import _StaticFiles
+
+ allowed, reason = utils.is_allowed_file(
+ abs_path,
+ blocked_paths=blocks_or_config.blocked_paths,
+ allowed_paths=blocks_or_config.allowed_paths + _StaticFiles.all_paths,
+ created_paths=[upload_dir, str(utils.get_cache_folder())],
+ )
+ if not allowed:
+ raise HTTPException(403, f"File not allowed: {path_or_url}.")
+
+ mime_type, _ = mimetypes.guess_type(abs_path)
+ if mime_type in XSS_SAFE_MIMETYPES or reason == "allowed":
+ media_type = mime_type or "application/octet-stream"
+ content_disposition_type = "inline"
+ else:
+ media_type = "application/octet-stream"
+ content_disposition_type = "attachment"
+
+ range_val = request.headers.get("Range", "").strip()
+ if range_val.startswith("bytes=") and "-" in range_val:
+ range_val = range_val[6:]
+ start, end = range_val.split("-")
+ if start.isnumeric() and end.isnumeric():
+ from gradio import ranged_response # type: ignore
+
+ start = int(start)
+ end = int(end)
+ headers = dict(request.headers)
+ headers["Content-Disposition"] = content_disposition_type
+ headers["Content-Type"] = media_type
+ return ranged_response.RangedFileResponse(
+ abs_path,
+ ranged_response.OpenRange(start, end),
+ headers,
+ stat_result=os.stat(abs_path),
+ )
+
+ return FileResponse(
+ abs_path,
+ headers={"Accept-Ranges": "bytes"},
+ content_disposition_type=content_disposition_type,
+ media_type=media_type,
+ filename=abs_path.name,
+ )
+
+
+async def upload_fn(
+ request: StarletteRequest,
+ upload_dir,
+ max_file_size,
+ upload_id,
+ force_move: bool = True,
+ upload_progress: FileUploadProgress | None = None,
+):
+ content_type_header = request.headers.get("Content-Type")
+ content_type: bytes
+ content_type, _ = parse_options_header(content_type_header or "")
+ if content_type != b"multipart/form-data":
+ raise HTTPException(status_code=400, detail="Invalid content type.")
+
+ if upload_id and upload_progress:
+ upload_progress.track(upload_id)
+
+ multipart_parser = GradioMultiPartParser(
+ request.headers,
+ request.stream(),
+ max_files=1000,
+ max_fields=1000,
+ max_file_size=max_file_size,
+ upload_id=upload_id,
+ upload_progress=upload_progress,
+ )
+ form = await multipart_parser.parse()
+
+ output_files = []
+ files_to_copy = []
+ locations = []
+ for temp_file in form.getlist("files"):
+ if not isinstance(temp_file, GradioUploadFile):
+ raise TypeError("File is not an instance of GradioUploadFile")
+ if temp_file.filename:
+ file_name = Path(temp_file.filename).name
+ name = client_utils.strip_invalid_filename_characters(file_name)
+ else:
+ name = f"tmp{secrets.token_hex(5)}"
+ directory = Path(upload_dir) / temp_file.sha.hexdigest()
+ directory.mkdir(exist_ok=True, parents=True)
+ try:
+ dest = utils.safe_join(
+ DeveloperPath(str(directory)), UserProvidedPath(name)
+ )
+ except InvalidPathError as err:
+ raise HTTPException(
+ status_code=400, detail=f"Invalid file name: {name}"
+ ) from err
+ temp_file.file.close()
+ try:
+ os.rename(temp_file.file.name, dest)
+ except OSError:
+ if force_move:
+ import shutil
+
+ shutil.move(temp_file.file.name, dest)
+ else:
+ files_to_copy.append(temp_file.file.name)
+ locations.append(dest)
+ output_files.append(dest)
+
+ return output_files, files_to_copy, locations
diff --git a/gradio/routes.py b/gradio/routes.py
new file mode 100644
index 0000000..a3d901b
--- /dev/null
+++ b/gradio/routes.py
@@ -0,0 +1,2639 @@
+"""Implements a FastAPI server to run the gradio interface. Note that some types in this
+module use the Optional/Union notation so that they work correctly with pydantic."""
+
+from __future__ import annotations
+
+import asyncio
+import contextlib
+import hashlib
+import inspect
+import io
+import json
+import math
+import mimetypes
+import os
+import secrets
+import sys
+import time
+import traceback
+import warnings
+from collections.abc import AsyncIterator, Callable, Sequence
+from pathlib import Path
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Literal,
+ Union,
+ cast,
+)
+
+import fastapi
+import httpx
+import markupsafe
+import orjson
+from fastapi import (
+ APIRouter,
+ BackgroundTasks,
+ Body,
+ Depends,
+ FastAPI,
+ HTTPException,
+ status,
+)
+from fastapi.responses import (
+ FileResponse,
+ HTMLResponse,
+ JSONResponse,
+ PlainTextResponse,
+ Response,
+ StreamingResponse,
+)
+from fastapi.security import OAuth2PasswordRequestForm
+from fastapi.templating import Jinja2Templates
+from gradio_client import utils as client_utils
+from gradio_client.documentation import document
+from gradio_client.snippet import generate_code_snippets
+from gradio_client.utils import ServerMessage
+from hf_gradio.cli import _condense_info, generate_cli_snippet
+from jinja2.exceptions import TemplateNotFound
+from python_multipart.multipart import parse_options_header
+from starlette.background import BackgroundTask
+from starlette.datastructures import UploadFile as StarletteUploadFile
+from starlette.formparsers import MultiPartException
+from starlette.responses import RedirectResponse
+
+import gradio
+from gradio import (
+ caching,
+ route_utils,
+ themes,
+ utils,
+)
+from gradio.brotli_middleware import BrotliMiddleware
+from gradio.context import Context
+from gradio.data_classes import (
+ CancelBody,
+ ComponentServerBlobBody,
+ ComponentServerJSONBody,
+ DataWithFiles,
+ DeveloperPath,
+ JsonData,
+ PredictBody,
+ PredictBodyInternal,
+ ResetBody,
+ SimplePredictBody,
+ UserProvidedPath,
+ VibeCodeBody,
+ VibeEditBody,
+)
+from gradio.exceptions import Error, InvalidPathError
+from gradio.helpers import special_args
+from gradio.i18n import I18n
+from gradio.node_server import (
+ start_node_server,
+)
+from gradio.oauth import attach_oauth
+from gradio.route_utils import ( # noqa: F401
+ API_PREFIX,
+ BUILD_PATH_LIB,
+ DEFAULT_TEMP_DIR,
+ STATIC_PATH_LIB,
+ STATIC_TEMPLATE_LIB,
+ VERSION,
+ XSS_SAFE_MIMETYPES,
+ CustomCORSMiddleware,
+ FileUploadProgress,
+ FileUploadProgressNotQueuedError,
+ FileUploadProgressNotTrackedError,
+ GradioMultiPartParser,
+ GradioUploadFile,
+ Request,
+ compare_passwords_securely,
+ create_lifespan_handler,
+ favicon,
+ file_fetch,
+ file_response,
+ move_uploaded_files_to_cache,
+ routes_safe_join,
+ secure_url_stream_response,
+ upload_fn,
+)
+from gradio.screen_recording_utils import process_video_with_ffmpeg
+from gradio.server_messages import (
+ CloseStreamMessage,
+ EstimationMessage,
+ EventMessage,
+ HeartbeatMessage,
+ ProcessCompletedMessage,
+ ProcessGeneratingMessage,
+ UnexpectedErrorMessage,
+)
+from gradio.state_holder import StateHolder
+from gradio.themes import ThemeClass as Theme
+from gradio.utils import (
+ cancel_tasks,
+ get_node_path,
+ get_upload_folder,
+ safe_aclose_iterator,
+)
+
+if TYPE_CHECKING:
+ from gradio.blocks import Block
+
+import difflib
+import re
+import shutil
+import tempfile
+
+mimetypes.init()
+
+BUILT_IN_THEMES: dict[str, Theme] = {
+ t.name: t # type: ignore
+ for t in [
+ themes.Base(),
+ themes.Default(),
+ themes.Monochrome(),
+ themes.Soft(),
+ themes.Glass(),
+ themes.Origin(),
+ themes.Citrus(),
+ themes.Ocean(),
+ themes.Mario(),
+ ]
+ if t.name is not None
+}
+
+
+class ORJSONResponse(JSONResponse):
+ media_type = "application/json"
+
+ @staticmethod
+ def default(content: Any) -> str:
+ if isinstance(content, JsonData):
+ return content.model_dump()
+ return str(content)
+
+ @staticmethod
+ def _render(content: Any) -> bytes:
+ return orjson.dumps(
+ content,
+ option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_PASSTHROUGH_DATETIME,
+ default=ORJSONResponse.default,
+ )
+
+ def render(self, content: Any) -> bytes:
+ return ORJSONResponse._render(content)
+
+ @staticmethod
+ def _render_str(content: Any) -> str:
+ return ORJSONResponse._render(content).decode("utf-8")
+
+
+def toorjson(value):
+ return markupsafe.Markup(
+ ORJSONResponse._render_str(value)
+ .replace("<", "\\u003c")
+ .replace(">", "\\u003e")
+ .replace("&", "\\u0026")
+ .replace("'", "\\u0027")
+ )
+
+
+templates = Jinja2Templates(directory=STATIC_TEMPLATE_LIB)
+templates.env.filters["toorjson"] = toorjson
+
+# Shared transport keeps the connection pool warm without sharing an
+# `httpx.AsyncClient` (and therefore a cookie jar) across `/proxy=` requests.
+# A single shared `AsyncClient` would persist `Set-Cookie` headers from one
+# proxied response and replay them on subsequent requests to any sibling
+# `*.hf.space` URL — see GHSA-2mr9-9r47-px2g.
+_proxy_transport = httpx.AsyncHTTPTransport(
+ limits=httpx.Limits(
+ max_connections=100,
+ max_keepalive_connections=20,
+ ),
+)
+
+
+file_upload_statuses = FileUploadProgress()
+
+
+class App(FastAPI):
+ """
+ FastAPI App Wrapper
+ """
+
+ app_port = None
+
+ def __init__(
+ self,
+ auth_dependency: Callable[[fastapi.Request], str | None] | None = None,
+ **kwargs,
+ ):
+ self.tokens = {}
+ self.auth = None
+ self.analytics_key = secrets.token_urlsafe(16)
+ self.monitoring_enabled = False
+ self.blocks: gradio.Blocks | None = None
+ self.state_holder = StateHolder()
+ self.iterators: dict[str, AsyncIterator] = {}
+ self.iterators_to_reset: set[str] = set()
+ self.lock = utils.safe_get_lock()
+ self.stop_event = utils.safe_get_stop_event()
+ self.cookie_id = secrets.token_urlsafe(32)
+ self.queue_token = secrets.token_urlsafe(32)
+ self.startup_events_triggered = False
+ self.uploaded_file_dir = get_upload_folder()
+ self.change_count: int = 0
+ self.change_type: Literal["reload", "error"] | None = None
+ self.reload_error_message: str | None = None
+ self._asyncio_tasks: list[asyncio.Task] = []
+ self.auth_dependency = auth_dependency
+ self.api_info = None
+ self.static_worker_pool = None # Set by launch() when num_workers > 0
+ self.all_app_info = None
+ self._static_prefixes: tuple[
+ str, ...
+ ] = () # Populated by enable_static_workers
+
+ # Allow user to manually set `docs_url` and `redoc_url`
+ # when instantiating an App; when they're not set, disable docs and redoc.
+ kwargs.setdefault("docs_url", None)
+ kwargs.setdefault("redoc_url", None)
+ self.custom_component_hashes: dict[str, str] = {}
+ super().__init__(**kwargs)
+
+ def configure_app(self, blocks: gradio.Blocks) -> None:
+ auth = blocks.auth
+ if auth is not None:
+ if not callable(auth):
+ self.auth = {account[0]: account[1] for account in auth} # type: ignore
+ else:
+ self.auth = auth
+ else:
+ self.auth = None
+ self.blocks = blocks
+ self.cwd = os.getcwd()
+ self.favicon_path = blocks.favicon_path
+ self.tokens = {}
+ self.root_path = blocks.root_path or ""
+ self.state_holder.set_blocks(blocks)
+
+ def get_blocks(self) -> gradio.Blocks:
+ if self.blocks is None:
+ raise ValueError("No Blocks has been configured for this app.")
+ return self.blocks
+
+ def build_proxy_request(self, url_path):
+ url = httpx.URL(url_path)
+ assert self.blocks # noqa: S101
+ # Don't proxy a URL unless it's a URL specifically loaded by the user using
+ # gr.load() to prevent SSRF or harvesting of HF tokens by malicious Spaces.
+ is_safe_url = any(
+ url.host == httpx.URL(root).host for root in self.blocks.proxy_urls
+ )
+ if not is_safe_url:
+ raise PermissionError("This URL cannot be proxied.")
+ # Only allow proxying to Hugging Face Space URLs to prevent SSRF
+ # via malicious proxy_url values in untrusted configs.
+ if not url.host.endswith(".hf.space"):
+ raise PermissionError("This URL cannot be proxied.")
+ headers = {}
+ if Context.token is not None:
+ headers["Authorization"] = f"Bearer {Context.token}"
+ # Build a plain request rather than `client.build_request` so that
+ # the proxy does not share an `httpx.AsyncClient` (or cookie jar)
+ # across calls (see GHSA-2mr9-9r47-px2g).
+ return url, headers
+
+ def _cancel_asyncio_tasks(self):
+ for task in self._asyncio_tasks:
+ task.cancel()
+ self._asyncio_tasks = []
+
+ @staticmethod
+ def setup_mcp_server(
+ blocks: gradio.Blocks,
+ app_kwargs: dict[str, Any],
+ mcp_server: bool | None = None,
+ ):
+ mcp_subpath = API_PREFIX + "/mcp"
+ if mcp_server is None:
+ mcp_server = os.environ.get("GRADIO_MCP_SERVER", "False").lower() == "true"
+ if mcp_server:
+ try:
+ import gradio.mcp
+ except ImportError as e:
+ raise ImportError(
+ 'In order to use `mcp_server=True`, you must install gradio with the `mcp` extra. Please install it with `pip install "gradio[mcp]"`'
+ ) from e
+ try:
+ blocks.mcp_server_obj = gradio.mcp.GradioMCPServer(blocks)
+ blocks.mcp_server = True
+ user_lifespan = None
+ if "lifespan" in app_kwargs:
+ user_lifespan = app_kwargs["lifespan"]
+
+ @contextlib.asynccontextmanager
+ async def _lifespan(app: App):
+ async with contextlib.AsyncExitStack() as stack:
+ if blocks.mcp_server_obj:
+ await stack.enter_async_context(
+ blocks.mcp_server_obj.lifespan(app)
+ )
+ if user_lifespan is not None:
+ await stack.enter_async_context(user_lifespan(app))
+ yield
+
+ app_kwargs["lifespan"] = _lifespan
+ except Exception as e:
+ blocks.mcp_server = False
+ blocks.mcp_error = f"Error launching MCP server: {e}"
+
+ blocks.config = (
+ blocks.get_config_file()
+ ) # Because the config should include the fact that the MCP server is enabled
+ return mcp_subpath
+
+ @staticmethod
+ def create_app(
+ blocks: gradio.Blocks,
+ app: App | None = None,
+ app_kwargs: dict[str, Any] | None = None,
+ auth_dependency: Callable[[fastapi.Request], str | None] | None = None,
+ strict_cors: bool = True,
+ mcp_server: bool | None = None,
+ debug: bool = False,
+ ) -> App:
+ app_kwargs = app_kwargs or {}
+ app_kwargs.setdefault("default_response_class", ORJSONResponse)
+ mcp_subpath = App.setup_mcp_server(blocks, app_kwargs, mcp_server)
+
+ delete_cache = blocks.delete_cache or (None, None)
+ if app is None:
+ app_kwargs["lifespan"] = create_lifespan_handler(
+ app_kwargs.get("lifespan", None), *delete_cache
+ )
+ app = App(auth_dependency=auth_dependency, **app_kwargs, debug=debug)
+ else:
+ app.router.lifespan_context = create_lifespan_handler(
+ app_kwargs.get("lifespan", None), *delete_cache
+ )
+ if blocks.mcp_server_obj:
+ blocks.mcp_server_obj.launch_mcp_on_sse(app, mcp_subpath, blocks.root_path)
+ router = APIRouter(prefix=API_PREFIX)
+
+ app.configure_app(blocks)
+
+ app.add_middleware(CustomCORSMiddleware, strict_cors=strict_cors) # type: ignore
+ app.add_middleware(
+ BrotliMiddleware, # type: ignore
+ quality=4,
+ excluded_handlers=[mcp_subpath],
+ )
+
+ # Note: In the current architecture, Node is the front proxy and
+ # routes requests to Python. The old conditional_routing_middleware
+ # that proxied Python -> Node is no longer needed.
+
+ @router.get("/user")
+ @router.get("/user/")
+ def get_current_user(request: fastapi.Request) -> str | None:
+ if app.auth_dependency is not None:
+ return app.auth_dependency(request)
+ token = request.cookies.get(
+ f"access-token-{app.cookie_id}"
+ ) or request.cookies.get(f"access-token-unsecure-{app.cookie_id}")
+ return app.tokens.get(token)
+
+ @router.get("/login_check")
+ @router.get("/login_check/")
+ def login_check(user: str = Depends(get_current_user)):
+ if (app.auth is None and app.auth_dependency is None) or user is not None:
+ return
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail={
+ "error": "Not authenticated",
+ "auth_message": blocks.auth_message,
+ },
+ )
+
+ @router.get("/token")
+ @router.get("/token/")
+ def get_token(request: fastapi.Request) -> dict:
+ token = request.cookies.get(f"access-token-{app.cookie_id}")
+ return {"token": token, "user": app.tokens.get(token)}
+
+ @router.get("/app_id")
+ @router.get("/app_id/")
+ def app_id(request: fastapi.Request) -> dict: # noqa: ARG001
+ return {"app_id": app.get_blocks().app_id}
+
+ @router.get("/dev/reload", dependencies=[Depends(login_check)])
+ async def notify_changes(
+ request: fastapi.Request,
+ ):
+ async def reload_checker(request: fastapi.Request):
+ heartbeat_rate = 15
+ check_rate = 0.05
+ last_heartbeat = time.perf_counter()
+ current_count = app.change_count
+
+ while True:
+ if await request.is_disconnected():
+ return
+
+ if app.change_count != current_count:
+ current_count = app.change_count
+ msg = (
+ json.dumps(f"{app.reload_error_message}")
+ if app.change_type == "error"
+ else "{}"
+ )
+ yield f"""event: {app.change_type}\ndata: {msg}\n\n"""
+
+ await asyncio.sleep(check_rate)
+ if time.perf_counter() - last_heartbeat > heartbeat_rate:
+ yield """event: heartbeat\ndata: {}\n\n"""
+ last_heartbeat = time.perf_counter()
+
+ return StreamingResponse(
+ reload_checker(request),
+ media_type="text/event-stream",
+ )
+
+ @app.post("/login")
+ @app.post("/login/")
+ async def login(
+ request: fastapi.Request, form_data: OAuth2PasswordRequestForm = Depends()
+ ):
+ username, password = form_data.username.strip(), form_data.password
+ if app.auth is None:
+ root = route_utils.get_root_url(
+ request=request,
+ route_path="/login",
+ root_path=app.root_path,
+ )
+ return RedirectResponse(url=root, status_code=status.HTTP_302_FOUND)
+ if (
+ not callable(app.auth)
+ and username in app.auth # type: ignore
+ and compare_passwords_securely(password, app.auth[username]) # type: ignore
+ ) or (
+ callable(app.auth)
+ and (
+ await app.auth(username, password)
+ if inspect.iscoroutinefunction(app.auth)
+ else app.auth(username, password)
+ )
+ ): # type: ignore
+ token = secrets.token_urlsafe(16)
+ app.tokens[token] = username
+ response = JSONResponse(content={"success": True})
+ response.set_cookie(
+ key=f"access-token-{app.cookie_id}",
+ value=token,
+ httponly=True,
+ samesite="none",
+ secure=True,
+ )
+ response.set_cookie(
+ key=f"access-token-unsecure-{app.cookie_id}",
+ value=token,
+ httponly=True,
+ )
+ return response
+ else:
+ raise HTTPException(status_code=400, detail="Incorrect credentials.")
+
+ ###############
+ # OAuth Routes
+ ###############
+
+ # Define OAuth routes if the app expects it (i.e. a LoginButton is defined).
+ # It allows users to "Sign in with HuggingFace". Otherwise, add the default
+ # logout route.
+ if app.blocks is not None and app.blocks.expects_oauth:
+ attach_oauth(app)
+ else:
+
+ @app.get("/logout")
+ def logout(
+ request: fastapi.Request,
+ user: str = Depends(get_current_user),
+ all_session: bool = True,
+ ):
+ root = route_utils.get_root_url(
+ request=request,
+ route_path="/logout",
+ root_path=app.root_path,
+ )
+ response = RedirectResponse(url=root, status_code=status.HTTP_302_FOUND)
+ response.delete_cookie(key=f"access-token-{app.cookie_id}", path="/")
+ response.delete_cookie(
+ key=f"access-token-unsecure-{app.cookie_id}", path="/"
+ )
+ if all_session:
+ # Delete the tokens of all sessions associated with the current user.
+ for token in list(app.tokens.keys()):
+ if app.tokens[token] == user:
+ del app.tokens[token]
+ # Delete only the token associated with the current session.
+ elif request.cookies.get(f"access-token-{app.cookie_id}") in app.tokens:
+ del app.tokens[request.cookies.get(f"access-token-{app.cookie_id}")]
+ return response
+
+ ###############
+ # Main Routes
+ ###############
+
+ @app.get("/svelte/{path:path}")
+ async def _(path: str):
+ svelte_path = DeveloperPath(str(Path(BUILD_PATH_LIB) / "svelte"))
+ return file_response(svelte_path, UserProvidedPath(path))
+
+ def attach_page(page):
+ @app.get(f"/{page}", response_class=HTMLResponse)
+ def page_route(
+ request: fastapi.Request,
+ user: str = Depends(get_current_user),
+ deep_link: str = "",
+ ):
+ return main(request, user, page, deep_link)
+
+ @app.get(f"/{page}/")
+ def page_redirect():
+ return RedirectResponse(
+ url=f"/{page}", status_code=status.HTTP_301_MOVED_PERMANENTLY
+ )
+
+ for pageset in blocks.pages:
+ page = pageset[0]
+ if page != "":
+ attach_page(page)
+
+ def load_deep_link(
+ deep_link: str, config: dict[str, Any], page: str | None = None
+ ):
+ components = config["components"]
+ try:
+ user_path = Path("deep_links") / deep_link / "state.json"
+ path = Path(
+ routes_safe_join(
+ DeveloperPath(app.uploaded_file_dir),
+ UserProvidedPath(str(user_path)),
+ )
+ )
+ if path.exists():
+ components = orjson.loads(path.read_bytes())
+ deep_link_state = "valid"
+ else:
+ deep_link_state = "invalid"
+ except (FileNotFoundError, OSError, orjson.JSONDecodeError):
+ deep_link_state = "invalid"
+ components = []
+ if page:
+ components = [
+ component
+ for component in components
+ if component["id"] in config["page"][page]["components"]
+ ]
+ return components, deep_link_state
+
+ @app.head("/", response_class=HTMLResponse)
+ @app.get("/", response_class=HTMLResponse)
+ def main(
+ request: fastapi.Request,
+ user: str = Depends(get_current_user),
+ page: str = "",
+ deep_link: str = "",
+ ):
+ mimetypes.add_type("application/javascript", ".js")
+ blocks = app.get_blocks()
+ root = route_utils.get_root_url(
+ request=request,
+ route_path=f"/{page}",
+ root_path=app.root_path
+ or request.scope.get("root_path")
+ or blocks.custom_mount_path,
+ )
+ if (app.auth is None and app.auth_dependency is None) or user is not None:
+ config = utils.safe_deepcopy(blocks.config)
+ deep_link_state = "none"
+ components = [
+ component
+ for component in config["components"]
+ if component["id"] in config["page"][page]["components"]
+ ]
+ if deep_link:
+ components, deep_link_state = load_deep_link(
+ deep_link,
+ config, # type: ignore
+ page,
+ )
+ config["username"] = user
+ config["deep_link_state"] = deep_link_state
+ config["components"] = components # type: ignore
+ config["dependencies"] = [
+ dependency
+ for dependency in config.get("dependencies", [])
+ if dependency["id"] in config["page"][page]["dependencies"]
+ ]
+ config["layout"] = config["page"][page]["layout"]
+ config["current_page"] = page
+ # Update root after loading the deep link state (if applicable)
+ # so that static files are served from the correct root
+ config = route_utils.update_root_in_config(config, root)
+ elif app.auth_dependency:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail={
+ "error": "Not authenticated",
+ "auth_message": blocks.auth_message,
+ },
+ )
+ else:
+ config = {
+ "auth_required": True,
+ "auth_message": blocks.auth_message,
+ "space_id": blocks.space_id,
+ "root": root,
+ "page": {"": {"layout": {}}},
+ "pages": [""],
+ "components": [],
+ "dependencies": [],
+ "current_page": "",
+ }
+
+ try:
+ template = (
+ "frontend/share.html" if blocks.share else "frontend/index.html"
+ )
+ gradio_api_info = api_info(request)
+ resp = templates.TemplateResponse(
+ request=request,
+ name=template,
+ context={
+ "config": config,
+ "gradio_api_info": gradio_api_info,
+ },
+ )
+ return resp
+ except TemplateNotFound as err:
+ if blocks.share:
+ raise ValueError(
+ "Did you install Gradio from source files? Share mode only "
+ "works when Gradio is installed through the pip package."
+ ) from err
+ else:
+ raise ValueError(
+ "Did you install Gradio from source files? You need to build "
+ "the frontend by running /scripts/build_frontend.sh"
+ ) from err
+
+ @app.get("/gradio_api/deep_link")
+ def deep_link(session_hash: str):
+ if session_hash in app.state_holder:
+ components = [
+ utils.safe_deepcopy(c)
+ for c in app.state_holder[session_hash].components
+ ]
+ components_json = orjson.dumps(
+ components,
+ option=orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_PASSTHROUGH_DATETIME,
+ default=str,
+ )
+ deep_link = route_utils.create_url_safe_hash(components_json)
+ directory = Path(app.uploaded_file_dir) / "deep_links" / deep_link
+ directory.mkdir(parents=True, exist_ok=True)
+ with open(directory / "state.json", "wb") as f:
+ f.write(components_json)
+ return deep_link
+ else:
+ return ""
+
+ @router.get("/info/", dependencies=[Depends(login_check)])
+ @router.get("/info", dependencies=[Depends(login_check)])
+ def api_info(request: fastapi.Request):
+ all_endpoints = request.query_params.get("all_endpoints", False)
+ if all_endpoints:
+ if not app.all_app_info:
+ app.all_app_info = app.get_blocks().get_api_info(all_endpoints=True)
+ return app.all_app_info
+ if not app.api_info:
+ api_info = utils.safe_deepcopy(app.get_blocks().get_api_info())
+ api_info = cast(dict[str, Any], api_info)
+ api_info = route_utils.update_example_values_to_use_public_url(api_info)
+ root = route_utils.get_root_url(
+ request=request,
+ route_path=f"{API_PREFIX}/info",
+ root_path=app.root_path,
+ )
+ space_id = app.get_blocks().space_id
+ cli_snippets = generate_cli_snippet(api_info["named_endpoints"])
+ for k, v in cli_snippets.items():
+ cli_snippets[k] = v.replace("{space_id}", space_id or str(root))
+ api_prefix = API_PREFIX + "/"
+ for ep_name, ep_info in api_info.get("named_endpoints", {}).items():
+ ep_info["code_snippets"] = generate_code_snippets(
+ ep_name,
+ ep_info,
+ str(root),
+ space_id=space_id,
+ api_prefix=api_prefix,
+ )
+ ep_info["code_snippets"]["cli"] = cli_snippets[ep_name]
+ app.api_info = api_info
+ return app.api_info
+
+ @router.get("/openapi.json", dependencies=[Depends(login_check)])
+ def openapi_schema(request: fastapi.Request):
+ """Generate an OpenAPI schema from the Gradio app's API info."""
+ info = api_info(request)
+ info_simple = _condense_info(info, url_only=True)
+ schema = {
+ "openapi": "3.0.2",
+ "info": {
+ "title": getattr(app.get_blocks(), "title", "Gradio App"),
+ "description": getattr(app.get_blocks(), "description", ""),
+ "version": VERSION,
+ },
+ "paths": {
+ "/gradio_api/upload": {
+ "post": {
+ "summary": "Upload File",
+ "operationId": "upload_file_upload_post",
+ "parameters": [
+ {
+ "name": "upload_id",
+ "in": "query",
+ "required": False,
+ "schema": {
+ "type": "string",
+ "nullable": True,
+ "default": None,
+ },
+ "description": "Optional ID to track upload progress",
+ }
+ ],
+ "requestBody": {
+ "required": True,
+ "content": {
+ "multipart/form-data": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "files": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "format": "binary",
+ },
+ "description": "One or more files to upload",
+ }
+ },
+ "required": ["files"],
+ }
+ }
+ },
+ },
+ "responses": {
+ "200": {
+ "description": "List of file paths where the uploaded files were saved",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "items": {"type": "string"},
+ }
+ }
+ },
+ },
+ "400": {
+ "description": "Invalid content type or invalid file name",
+ "content": {
+ "text/plain": {"schema": {"type": "string"}}
+ },
+ },
+ "413": {
+ "description": "File exceeds maximum allowed size",
+ "content": {
+ "text/plain": {"schema": {"type": "string"}}
+ },
+ },
+ },
+ "security": [{"login_check": []}],
+ }
+ }
+ },
+ "components": {"schemas": {}},
+ }
+
+ for endpoint_path, endpoint_info in info.get("named_endpoints", {}).items(): # type: ignore
+ if endpoint_info.get("api_visibility", "public") == "private":
+ continue
+ endpoint_name = endpoint_path.strip("/").replace("/", "_")
+ has_file_params = any(
+ p.get("type", {}).get("type") == "filepath"
+ for p in info_simple[endpoint_path].get("parameters", [])
+ )
+ summary = (
+ endpoint_info.get("description", "") or f"Endpoint {endpoint_path}"
+ )
+ if has_file_params:
+ summary += '. File inputs must first be uploaded via POST /gradio_api/upload (multipart/form-data with a "files" field). Use the returned path in the request body as {"path": "", "meta": {"_type": "gradio.FileData"}}.'
+ path_item = {
+ "post": {
+ "summary": summary,
+ "description": endpoint_info.get("description", ""),
+ "operationId": endpoint_name,
+ "requestBody": {
+ "required": True,
+ "content": {
+ "application/json": {
+ "schema": {"type": "object", "properties": {}}
+ },
+ },
+ },
+ "responses": {
+ "200": {
+ "description": "Successful response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "event_id": {"type": "string"}
+ },
+ }
+ }
+ },
+ }
+ },
+ }
+ }
+
+ request_properties = path_item["post"]["requestBody"]["content"][ # type: ignore
+ "application/json"
+ ]["schema"]["properties"] # type: ignore
+ for param in info_simple[endpoint_path].get("parameters", []):
+ param_name = param["name"]
+ param_type = param.get("type", {})
+
+ if "additional_description" in param_type:
+ param_type = dict(param_type)
+ param_type.pop("additional_description", None)
+
+ if "properties" in param_type and "type" not in param_type:
+ param_type = dict(param_type)
+ param_type["type"] = "object"
+
+ if param_type.get("type") == "filepath":
+ param_type = dict(param_type)
+ param_type["type"] = "string"
+ param_type["format"] = "filepath"
+
+ request_properties[param_name] = param_type # type: ignore
+
+ if "example_input" in param:
+ if (
+ "examples"
+ not in path_item["post"]["requestBody"]["content"][ # type: ignore
+ "application/json"
+ ]
+ ):
+ path_item["post"]["requestBody"]["content"][ # type: ignore
+ "application/json"
+ ]["examples"] = {"example1": {"value": {}}}
+ path_item["post"]["requestBody"]["content"]["application/json"][ # type: ignore
+ "examples"
+ ]["example1"]["value"][param_name] = param["example_input"] # type: ignore
+
+ returns_info = []
+ for i, ret in enumerate(info_simple[endpoint_path].get("returns", [])):
+ ret_name = f"output_{i}" if i > 0 else "output"
+ ret_type = ret.get("type", {})
+ desc = ""
+ returns_info.append(
+ f"{ret_name} ({ret_type})" + "" if not desc else f"desc: {desc}"
+ ) # type: ignore
+ path_item["post"]["description"] += ( # type: ignore
+ f"Output must be fetched from GET /gradio_api/call{endpoint_path}/{{event_id}}. Returns an array of {len(returns_info)} elements of the following format: {returns_info}"
+ )
+
+ schema["paths"][f"/gradio_api/call/v2{endpoint_path}"] = path_item # type: ignore
+
+ get_path = f"/gradio_api/call{endpoint_path}/{{event_id}}"
+ schema["paths"][get_path] = { # type: ignore
+ "get": {
+ "summary": f"Fetch results for {endpoint_path}",
+ "description": "Returns a stream of server-sent events (SSE). The final event has `event: complete` with `data` containing a JSON array of outputs.",
+ "operationId": f"{endpoint_name}_get",
+ "parameters": [
+ {
+ "name": "event_id",
+ "in": "path",
+ "required": True,
+ "schema": {"type": "string"},
+ "description": "The event_id returned by the POST request",
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "SSE stream with event: complete containing a JSON array of outputs",
+ "content": {
+ "text/event-stream": {"schema": {"type": "string"}}
+ },
+ }
+ },
+ }
+ }
+
+ return schema
+
+ @app.get("/config/", dependencies=[Depends(login_check)])
+ @app.get("/config", dependencies=[Depends(login_check)])
+ def get_config(request: fastapi.Request, deep_link: str = ""):
+ config = utils.safe_deepcopy(app.get_blocks().config)
+ root = route_utils.get_root_url(
+ request=request,
+ route_path="/config",
+ root_path=app.root_path
+ or request.scope.get("root_path")
+ or blocks.custom_mount_path,
+ )
+ config["username"] = get_current_user(request)
+ if deep_link:
+ components, deep_link_state = load_deep_link(deep_link, config, page="") # type: ignore
+ config["components"] = components # type: ignore
+ config["deep_link_state"] = deep_link_state
+ if hasattr(blocks, "i18n_instance") and blocks.i18n_instance:
+ config["i18n_translations"] = blocks.i18n_instance.translations_dict
+ else:
+ config["i18n_translations"] = None
+ config = route_utils.update_root_in_config(config, root)
+ return ORJSONResponse(content=config)
+
+ @app.get("/static/{path:path}")
+ async def static_resource(path: str):
+ return file_response(STATIC_PATH_LIB, UserProvidedPath(path))
+
+ @router.get("/custom_component/{id}/{environment}/{type}/{file_name}")
+ def custom_component_path(
+ id: str,
+ environment: Literal["client", "server"],
+ type: str,
+ file_name: str,
+ req: fastapi.Request,
+ ):
+ print(
+ f"id={id}, environment={environment}, type={type}, file_name={file_name}"
+ )
+ if environment not in ["client", "server"]:
+ raise HTTPException(
+ status_code=404, detail="Environment not supported."
+ )
+ components = utils.get_all_components()
+ location = next(
+ (item for item in components if item.get_component_class_id() == id),
+ None,
+ )
+ if location is None:
+ raise HTTPException(status_code=404, detail="Component not found.")
+
+ module_name = location.__module__
+ module_path = sys.modules[module_name].__file__
+
+ if module_path is None:
+ raise HTTPException(status_code=404, detail="Component not found.")
+
+ try:
+ requested_path = utils.safe_join(
+ location.TEMPLATE_DIR,
+ UserProvidedPath(f"{type}/{file_name}"),
+ )
+ except InvalidPathError:
+ raise HTTPException(
+ status_code=404, detail="Component not found."
+ ) from None
+
+ path = routes_safe_join(
+ DeveloperPath(str(Path(module_path).parent)),
+ UserProvidedPath(requested_path),
+ )
+
+ # Uncomment when we support custom component SSR
+ # if environment == "server":
+ # return PlainTextResponse(path)
+
+ key = f"{id}-{type}-{file_name}"
+
+ if key not in app.custom_component_hashes:
+ app.custom_component_hashes[key] = hashlib.sha256(
+ Path(path).read_text(encoding="utf-8").encode()
+ ).hexdigest()
+
+ version = app.custom_component_hashes.get(key)
+ headers = {"Cache-Control": "max-age=0, must-revalidate"}
+ if version:
+ headers["ETag"] = version
+
+ if version and req.headers.get("if-none-match") == version:
+ return PlainTextResponse(status_code=304, headers=headers)
+
+ return FileResponse(path, headers=headers)
+
+ @app.get("/assets/{path:path}")
+ async def build_resource(path: str):
+ return file_response(BUILD_PATH_LIB, UserProvidedPath(path))
+
+ @app.get("/favicon.ico")
+ async def _():
+ favicon_path = app.get_blocks().favicon_path
+ return favicon(favicon_path)
+
+ @router.head("/proxy={url_path:path}", dependencies=[Depends(login_check)])
+ @router.get("/proxy={url_path:path}", dependencies=[Depends(login_check)])
+ async def reverse_proxy(url_path: str):
+ # Adapted from: https://github.com/tiangolo/fastapi/issues/1788
+ try:
+ proxy_client = httpx.AsyncClient(
+ transport=_proxy_transport,
+ timeout=httpx.Timeout(10.0),
+ )
+ url, headers = app.build_proxy_request(url_path)
+ rp_req = proxy_client.build_request("GET", url, headers=headers)
+ except PermissionError as err:
+ raise HTTPException(status_code=400, detail=str(err)) from err
+
+ rp_resp = await proxy_client.send(rp_req, stream=True)
+
+ mime_type, _ = mimetypes.guess_type(url_path)
+ if mime_type not in XSS_SAFE_MIMETYPES:
+ rp_resp.headers.update({"Content-Disposition": "attachment"})
+ rp_resp.headers.update({"Content-Type": "application/octet-stream"})
+ return StreamingResponse(
+ rp_resp.aiter_raw(),
+ status_code=rp_resp.status_code,
+ headers=rp_resp.headers, # type: ignore
+ background=BackgroundTask(rp_resp.aclose),
+ )
+
+ @router.head("/file={path_or_url:path}", dependencies=[Depends(login_check)])
+ @router.get("/file={path_or_url:path}", dependencies=[Depends(login_check)])
+ async def file(path_or_url: str, request: fastapi.Request):
+ if client_utils.is_http_url_like(path_or_url):
+ return await secure_url_stream_response(path_or_url, request)
+ blocks = app.get_blocks()
+ return file_fetch(path_or_url, request, blocks, app.uploaded_file_dir)
+
+ @router.post("/stream/{event_id}")
+ async def _(event_id: str, body: PredictBody, request: fastapi.Request):
+ event = app.get_blocks()._queue.event_ids_to_events[event_id]
+ body = PredictBodyInternal(**body.model_dump(), request=request) # type: ignore
+ event.data = body
+ event.signal.set()
+ return {"msg": "success"}
+
+ @router.post("/stream/{event_id}/close")
+ async def _(event_id: str):
+ event = app.get_blocks()._queue.event_ids_to_events[event_id]
+ event.run_time = math.inf
+ event.closed = True
+ event.signal.set()
+ return {"msg": "success"}
+
+ @router.get("/stream/{session_hash}/{run}/{component_id}/playlist.m3u8")
+ async def _(session_hash: str, run: int, component_id: int):
+ stream: route_utils.MediaStream | None = (
+ app.get_blocks()
+ .pending_streams[session_hash]
+ .get(run, {})
+ .get(component_id, None)
+ )
+
+ if not stream:
+ return Response(status_code=404)
+
+ playlist = f"#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT\n#EXT-X-TARGETDURATION:{stream.max_duration}\n#EXT-X-VERSION:4\n#EXT-X-MEDIA-SEQUENCE:0\n"
+
+ for segment in stream.segments:
+ playlist += f"#EXTINF:{segment['duration']:.3f},\n"
+ playlist += f"{segment['id']}{segment['extension']}\n" # type: ignore
+ # HLS expects the start time of the video segments to be continuous
+ # Instead of re-encoding the user video chunks, we add a discontinuity tag
+ if segment["extension"] == ".ts":
+ playlist += "#EXT-X-DISCONTINUITY\n"
+
+ if stream.ended:
+ playlist += "#EXT-X-ENDLIST\n"
+
+ return Response(
+ content=playlist, media_type="application/vnd.apple.mpegurl"
+ )
+
+ @router.get("/stream/{session_hash}/{run}/{component_id}/{segment_id}.{ext}")
+ async def _(
+ session_hash: str, run: int, component_id: int, segment_id: str, ext: str
+ ):
+ if ext not in ["aac", "ts"]:
+ return Response(status_code=400, content="Unsupported file extension")
+ stream: route_utils.MediaStream | None = (
+ app.get_blocks()
+ .pending_streams[session_hash]
+ .get(run, {})
+ .get(component_id, None)
+ )
+
+ if not stream:
+ return Response(status_code=404, content="Stream not found")
+
+ segment = next((s for s in stream.segments if s["id"] == segment_id), None) # type: ignore
+
+ if segment is None:
+ return Response(status_code=404, content="Segment not found")
+
+ if ext == "aac":
+ return Response(content=segment["data"], media_type="audio/aac")
+ else:
+ return Response(content=segment["data"], media_type="video/MP2T")
+
+ @router.get("/stream/{session_hash}/{run}/{component_id}/playlist-file")
+ async def _(session_hash: str, run: int, component_id: int):
+ stream: route_utils.MediaStream | None = (
+ app.get_blocks()
+ .pending_streams[session_hash]
+ .get(run, {})
+ .get(component_id, None)
+ )
+
+ if not stream:
+ return Response(status_code=404)
+
+ if not stream.combined_file:
+ stream_data = [s["data"] for s in stream.segments]
+ combined_file = (
+ await app.get_blocks()
+ .get_component(component_id)
+ .combine_stream( # type: ignore
+ stream_data,
+ only_file=True,
+ desired_output_format=stream.desired_output_format,
+ )
+ )
+ stream.combined_file = combined_file.path
+ return FileResponse(stream.combined_file)
+
+ @router.get("/file/{path:path}", dependencies=[Depends(login_check)])
+ async def file_deprecated(path: str, request: fastapi.Request):
+ return await file(path, request)
+
+ @router.post("/reset/")
+ @router.post("/reset")
+ async def reset_iterator(body: ResetBody): # noqa: ARG001
+ # No-op, all the cancelling/reset logic handled by /cancel
+ return {"success": True}
+
+ @router.get("/heartbeat/{session_hash}")
+ def heartbeat(
+ session_hash: str,
+ request: fastapi.Request,
+ background_tasks: BackgroundTasks,
+ username: str = Depends(get_current_user),
+ ):
+ """Clients make a persistent connection to this endpoint to keep the session alive.
+ When the client disconnects, the session state is deleted.
+ """
+ heartbeat_rate = utils.get_heartbeat_rate()
+
+ async def iterator():
+ stop_stream_task = asyncio.create_task(app.stop_event.wait())
+ while True:
+ try:
+ yield "data: ALIVE\n\n"
+ # We need to close the heartbeat connections as soon as the server stops
+ # otherwise the server can take forever to close
+ wait_task = asyncio.create_task(asyncio.sleep(heartbeat_rate))
+ done, _ = await asyncio.wait(
+ [wait_task, stop_stream_task],
+ return_when=asyncio.FIRST_COMPLETED,
+ )
+ if stop_stream_task in done:
+ raise asyncio.CancelledError()
+ except asyncio.CancelledError:
+ if not stop_stream_task.done():
+ stop_stream_task.cancel()
+
+ req = Request(request, username, session_hash=session_hash)
+ root_path = route_utils.get_root_url(
+ request=request,
+ route_path=f"{API_PREFIX}/heartbeat/{session_hash}",
+ root_path=app.root_path,
+ )
+ body = PredictBodyInternal(
+ session_hash=session_hash, data=[], request=request
+ )
+ unload_fn_indices = [
+ i
+ for i, dep in app.get_blocks().fns.items()
+ if any(t for t in dep.targets if t[1] == "unload")
+ ]
+ for fn_index in unload_fn_indices:
+ # The task running this loop has been cancelled
+ # so we add tasks in the background
+ background_tasks.add_task(
+ route_utils.call_process_api,
+ app=app,
+ body=body,
+ gr_request=req,
+ fn=app.get_blocks().fns[fn_index],
+ root_path=root_path,
+ )
+ # This will mark the state to be deleted in an hour
+ if session_hash in app.state_holder.session_data:
+ app.state_holder.session_data[session_hash].is_closed = True
+ caching.clear_session_caches(session_hash)
+ for (
+ event_id
+ ) in app.get_blocks()._queue.pending_event_ids_session.get(
+ session_hash, []
+ ):
+ event = app.get_blocks()._queue.event_ids_to_events[
+ event_id
+ ]
+ event.run_time = math.inf
+ event.signal.set()
+ return
+
+ return StreamingResponse(iterator(), media_type="text/event-stream")
+
+ # had to use '/run' endpoint for Colab compatibility, '/api' supported for backwards compatibility
+ @router.post("/run/{api_name}", dependencies=[Depends(login_check)])
+ @router.post("/run/{api_name}/", dependencies=[Depends(login_check)])
+ @router.post("/api/{api_name}", dependencies=[Depends(login_check)])
+ @router.post("/api/{api_name}/", dependencies=[Depends(login_check)])
+ async def predict(
+ api_name: str,
+ body: PredictBody,
+ request: fastapi.Request,
+ username: str = Depends(get_current_user),
+ ):
+ body = PredictBodyInternal(**body.model_dump(), request=request) # type: ignore
+ fn = route_utils.get_fn(
+ blocks=app.get_blocks(), api_name=api_name, body=body
+ )
+
+ if not app.get_blocks().api_open and fn.queue:
+ raise HTTPException(
+ detail="This API endpoint does not accept direct HTTP POST requests. Please join the queue to use this API.",
+ status_code=status.HTTP_404_NOT_FOUND,
+ )
+ gr_request = route_utils.compile_gr_request(
+ body,
+ fn=fn,
+ username=username,
+ request=request,
+ )
+ root_path = route_utils.get_root_url(
+ request=request,
+ route_path=request.url.path,
+ root_path=app.root_path,
+ )
+ try:
+ output = await route_utils.call_process_api(
+ app=app,
+ body=body,
+ gr_request=gr_request,
+ fn=fn,
+ root_path=root_path,
+ )
+ except BaseException as error:
+ content = utils.error_payload(error, app.get_blocks().show_error)
+ if not isinstance(error, Error) or error.print_exception:
+ traceback.print_exc()
+ return JSONResponse(
+ content=content,
+ status_code=500,
+ )
+ return ORJSONResponse(output)
+
+ def prepare_simple_api_data(body: PredictBody, fn: Any) -> None:
+ if len(body.data) == len(fn.inputs):
+ return
+ api_inputs = [block for block in fn.inputs if not block.skip_api]
+ if len(body.data) != len(api_inputs):
+ return
+ data = iter(body.data)
+ body.data = [None if block.skip_api else next(data) for block in fn.inputs]
+
+ @router.post("/call/v2/{api_name}", dependencies=[Depends(login_check)])
+ @router.post("/call/v2/{api_name}/", dependencies=[Depends(login_check)])
+ async def _(
+ api_name: str,
+ body: dict[str, Any],
+ request: fastapi.Request,
+ username: str = Depends(get_current_user),
+ ):
+ parameters_info = app.api_info["named_endpoints"]["/" + api_name][ # type: ignore
+ "parameters"
+ ]
+ processed_args = client_utils.construct_args(
+ parameters_info,
+ (),
+ body,
+ )
+ simple_body = SimplePredictBody(data=processed_args)
+ full_body = PredictBody(**simple_body.model_dump(), simple_format=True) # type: ignore
+ fn = route_utils.get_fn(
+ blocks=app.get_blocks(), api_name=api_name, body=full_body
+ )
+ prepare_simple_api_data(full_body, fn)
+ full_body.fn_index = fn._id
+ return await queue_join_helper(full_body, request, username)
+
+ @router.post("/call/{api_name}", dependencies=[Depends(login_check)])
+ @router.post("/call/{api_name}/", dependencies=[Depends(login_check)])
+ async def simple_predict_post(
+ api_name: str,
+ body: SimplePredictBody,
+ request: fastapi.Request,
+ username: str = Depends(get_current_user),
+ ):
+ full_body = PredictBody(**body.model_dump(), simple_format=True) # type: ignore
+ fn = route_utils.get_fn(
+ blocks=app.get_blocks(), api_name=api_name, body=full_body
+ )
+ prepare_simple_api_data(full_body, fn)
+ full_body.fn_index = fn._id
+ return await queue_join_helper(full_body, request, username)
+
+ @router.post("/queue/join", dependencies=[Depends(login_check)])
+ async def queue_join(
+ body: PredictBody,
+ request: fastapi.Request,
+ username: str = Depends(get_current_user),
+ ):
+ if body.session_hash is None:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Session hash not found.",
+ )
+ return await queue_join_helper(body, request, username)
+
+ async def queue_join_helper(
+ body: PredictBody,
+ request: fastapi.Request,
+ username: str,
+ ):
+ blocks = app.get_blocks()
+
+ if blocks._queue.server_app is None:
+ blocks._queue.set_server_app(app)
+
+ if blocks._queue.stopped:
+ raise HTTPException(
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
+ detail="Queue is stopped.",
+ )
+ body = PredictBodyInternal(**body.model_dump(), request=request) # type: ignore
+ success, event_id, state = await blocks._queue.push(
+ body=body, request=request, username=username
+ )
+ error_map = {
+ "queue_full": status.HTTP_503_SERVICE_UNAVAILABLE,
+ "validator_error": status.HTTP_422_UNPROCESSABLE_CONTENT,
+ "error": status.HTTP_400_BAD_REQUEST,
+ "success": status.HTTP_200_OK,
+ }
+
+ if not success:
+ status_code = error_map[state]
+ raise HTTPException(status_code=status_code, detail=event_id)
+ return {"event_id": event_id}
+
+ @router.post("/cancel")
+ async def cancel_event(body: CancelBody):
+ await cancel_tasks({f"{body.session_hash}_{body.fn_index}"})
+ blocks = app.get_blocks()
+ # Need to complete the job so that the client disconnects
+ session_open = (
+ body.session_hash in blocks._queue.pending_messages_per_session
+ )
+ event_running = (
+ body.event_id
+ in blocks._queue.pending_event_ids_session.get(body.session_hash, {})
+ )
+ await blocks._queue.remove_from_queue(body.event_id)
+ if session_open and event_running:
+ message = ProcessCompletedMessage(
+ output={}, success=True, event_id=body.event_id
+ )
+ blocks._queue.pending_messages_per_session[
+ body.session_hash
+ ].put_nowait(message)
+ if body.event_id in app.iterators:
+ async with app.lock:
+ try:
+ await safe_aclose_iterator(app.iterators[body.event_id])
+ except Exception:
+ pass
+ del app.iterators[body.event_id]
+ app.iterators_to_reset.add(body.event_id)
+ return {"success": True}
+
+ @router.get(
+ "/call/v2/{api_name}/{event_id}", dependencies=[Depends(login_check)]
+ )
+ @router.get("/call/{api_name}/{event_id}", dependencies=[Depends(login_check)])
+ async def simple_predict_get(
+ request: fastapi.Request,
+ event_id: str,
+ ):
+ def process_msg(message: EventMessage) -> str | None:
+ msg = message.model_dump()
+ if isinstance(message, ProcessCompletedMessage):
+ event = "complete" if message.success else "error"
+ data = (
+ msg["output"].get("data") if message.success else msg["output"]
+ )
+ elif isinstance(message, ProcessGeneratingMessage):
+ event = "generating" if message.success else "error"
+ data = (
+ msg["output"].get("data") if message.success else msg["output"]
+ )
+ elif isinstance(message, HeartbeatMessage):
+ event = "heartbeat"
+ data = None
+ elif isinstance(message, UnexpectedErrorMessage):
+ event = "error"
+ data = message.message
+ else:
+ return None
+ return f"event: {event}\ndata: {json.dumps(data)}\n\n"
+
+ event = app.get_blocks()._queue.event_ids_to_events.get(event_id)
+ session_hash = event.session_hash if event else event_id
+ return await queue_data_helper(request, session_hash, process_msg)
+
+ @router.get("/queue/data", dependencies=[Depends(login_check)])
+ async def queue_data(
+ request: fastapi.Request,
+ session_hash: str,
+ ):
+ def process_msg(message: EventMessage) -> str:
+ return f"data: {orjson.dumps(message.model_dump(), default=str).decode('utf-8')}\n\n"
+
+ return await queue_data_helper(request, session_hash, process_msg)
+
+ async def queue_data_helper(
+ request: fastapi.Request,
+ session_hash: str,
+ process_msg: Callable[[EventMessage], str | None],
+ ):
+ blocks = app.get_blocks()
+ heartbeat_rate = utils.get_heartbeat_rate()
+
+ async def heartbeat():
+ while blocks.is_running:
+ await asyncio.sleep(heartbeat_rate)
+ # It's possible the event has finished by the time
+ # the heartbeat wakes up
+ queue = blocks._queue.pending_messages_per_session.get(session_hash)
+ if queue:
+ await queue.put(HeartbeatMessage())
+
+ async def sse_stream(request: fastapi.Request):
+ heartbeat_task = asyncio.create_task(heartbeat())
+ try:
+ while True:
+ if await request.is_disconnected():
+ await blocks._queue.clean_events(session_hash=session_hash)
+ heartbeat_task.cancel()
+ return
+
+ if (
+ session_hash
+ not in blocks._queue.pending_messages_per_session
+ ):
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ )
+
+ message = None
+ try:
+ messages = blocks._queue.pending_messages_per_session[
+ session_hash
+ ]
+ message = await asyncio.wait_for(messages.get(), timeout=10)
+ except (TimeoutError, asyncio.TimeoutError):
+ pass
+
+ if blocks._queue.stopped:
+ message = UnexpectedErrorMessage(
+ message="Server stopped unexpectedly.",
+ success=False,
+ )
+ if message:
+ response = process_msg(message)
+ if response is not None:
+ yield response
+ if (
+ isinstance(message, ProcessCompletedMessage)
+ and message.event_id
+ ):
+ # It's possible that the event_id has already been removed
+ # for example, the user sent two duplicate `/cancel` requests.
+ # The first one would have removed the event_id from pending_event_ids_session
+ if (
+ message.event_id
+ in (
+ blocks._queue.pending_event_ids_session[
+ session_hash
+ ]
+ )
+ ):
+ blocks._queue.pending_event_ids_session[
+ session_hash
+ ].remove(message.event_id)
+ if message.msg == ServerMessage.server_stopped or (
+ message.msg == ServerMessage.process_completed
+ and (
+ len(
+ blocks._queue.pending_event_ids_session[
+ session_hash
+ ]
+ )
+ == 0
+ )
+ ):
+ message = CloseStreamMessage()
+ response = process_msg(message)
+ if response is not None:
+ yield response
+ heartbeat_task.cancel()
+ return
+ except BaseException as e:
+ message = UnexpectedErrorMessage(
+ message=str(e),
+ session_not_found=isinstance(e, HTTPException),
+ )
+ response = process_msg(message)
+ if isinstance(e, asyncio.CancelledError):
+ del blocks._queue.pending_messages_per_session[session_hash]
+ await blocks._queue.clean_events(session_hash=session_hash)
+ if response is not None:
+ yield response
+ heartbeat_task.cancel()
+ raise e
+
+ return StreamingResponse(
+ sse_stream(request),
+ media_type="text/event-stream",
+ )
+
+ async def get_item_or_file(
+ request: fastapi.Request,
+ ) -> Union[ComponentServerJSONBody, ComponentServerBlobBody]:
+ content_type = request.headers.get("Content-Type")
+
+ if isinstance(content_type, str) and content_type.startswith(
+ "multipart/form-data"
+ ):
+ files = []
+ data = {}
+ max_file_size = (
+ blocks.max_file_size
+ if blocks.max_file_size is not None
+ else math.inf
+ )
+ multipart_parser = GradioMultiPartParser(
+ request.headers,
+ request.stream(),
+ max_files=1000,
+ max_fields=1000,
+ max_file_size=max_file_size,
+ )
+ try:
+ form = await multipart_parser.parse()
+ except MultiPartException as exc:
+ code = 413 if "File size exceeded" in exc.message else 400
+ raise HTTPException(status_code=code, detail=exc.message) from None
+ try:
+ for key, value in form.multi_items():
+ if isinstance(value, StarletteUploadFile):
+ contents = await value.read()
+ files.append((value.filename, contents))
+ else:
+ data[key] = value
+ finally:
+ for _, value in form.multi_items():
+ if isinstance(value, StarletteUploadFile):
+ await value.close()
+ with contextlib.suppress(OSError, AttributeError):
+ os.unlink(value.file.name)
+
+ return ComponentServerBlobBody(
+ data=DataWithFiles(data=data, files=files),
+ component_id=data["component_id"],
+ session_hash=data["session_hash"],
+ fn_name=data["fn_name"],
+ )
+ else:
+ try:
+ data = await request.json()
+ return ComponentServerJSONBody(
+ data=data["data"],
+ component_id=data["component_id"],
+ session_hash=data["session_hash"],
+ fn_name=data["fn_name"],
+ )
+
+ except Exception:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Invalid JSON body.",
+ ) from None
+
+ @router.post(
+ "/component_server",
+ dependencies=[Depends(login_check)],
+ )
+ @router.post(
+ "/component_server/",
+ dependencies=[Depends(login_check)],
+ )
+ async def component_server(
+ request: fastapi.Request,
+ ):
+ body = await get_item_or_file(request)
+ state = app.state_holder[body.session_hash]
+ component_id = body.component_id
+ block: Block
+ if component_id in state:
+ block = state[component_id]
+ else:
+ block = app.get_blocks().blocks[component_id]
+ fn = getattr(block, body.fn_name, None)
+ if fn is None or not getattr(fn, "_is_server_fn", False):
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND,
+ detail="Function not found.",
+ )
+ processed_input, *_ = special_args(
+ fn,
+ [body.data],
+ request, # type: ignore
+ None,
+ )
+ if inspect.iscoroutinefunction(fn):
+ return await fn(*processed_input)
+ else:
+ return fn(*processed_input)
+
+ @router.get(
+ "/queue/status",
+ dependencies=[Depends(login_check)],
+ response_model=EstimationMessage,
+ )
+ async def get_queue_status():
+ return app.get_blocks()._queue.get_status()
+
+ @router.get("/upload_progress")
+ async def get_upload_progress(upload_id: str, request: fastapi.Request):
+ async def sse_stream(request: fastapi.Request):
+ last_heartbeat = time.perf_counter()
+ is_done = False
+ while True:
+ if await request.is_disconnected():
+ file_upload_statuses.stop_tracking(upload_id)
+ return
+ if is_done:
+ file_upload_statuses.stop_tracking(upload_id)
+ return
+
+ heartbeat_rate = 15
+ check_rate = 0.05
+ try:
+ if file_upload_statuses.is_done(upload_id):
+ message = {"msg": "done"}
+ is_done = True
+ else:
+ update = file_upload_statuses.pop(upload_id)
+ message = {
+ "msg": "update",
+ "orig_name": update.filename,
+ "chunk_size": update.chunk_size,
+ }
+ yield f"data: {json.dumps(message)}\n\n"
+ except FileUploadProgressNotTrackedError:
+ return
+ except FileUploadProgressNotQueuedError:
+ await asyncio.sleep(check_rate)
+ if time.perf_counter() - last_heartbeat > heartbeat_rate:
+ message = {"msg": "heartbeat"}
+ yield f"data: {json.dumps(message)}\n\n"
+ last_heartbeat = time.perf_counter()
+
+ try:
+ await asyncio.wait_for(
+ file_upload_statuses.is_tracked(upload_id), timeout=3
+ )
+ except (asyncio.TimeoutError, TimeoutError):
+ return PlainTextResponse("Upload not found", status_code=404)
+
+ return StreamingResponse(
+ sse_stream(request),
+ media_type="text/event-stream",
+ )
+
+ def set_upload_trace(session_hash: str, start: float):
+ import uuid
+
+ from gradio.profiling import PROFILING_ENABLED, RequestTrace, collector
+
+ if PROFILING_ENABLED:
+ trace = RequestTrace(
+ event_id=str(uuid.uuid4()),
+ fn_name="gradio_file_upload",
+ session_hash=session_hash,
+ )
+ trace.upload_ms = (time.monotonic() - start) * 1000
+ collector.add(trace)
+
+ @router.post("/upload", dependencies=[Depends(login_check)])
+ async def upload_file(
+ request: fastapi.Request,
+ bg_tasks: BackgroundTasks,
+ upload_id: str | None = None,
+ ):
+ start = None
+ if PROFILING_ENABLED:
+ start = time.monotonic()
+ try:
+ output_files, files_to_copy, locations = await upload_fn(
+ request,
+ app.uploaded_file_dir,
+ blocks.max_file_size
+ if blocks.max_file_size is not None
+ else math.inf,
+ upload_id,
+ force_move=False,
+ upload_progress=file_upload_statuses if upload_id else None,
+ )
+ except MultiPartException as exc:
+ code = 413 if "maximum allowed size" in exc.message else 400
+ return PlainTextResponse(exc.message, status_code=code)
+
+ if files_to_copy:
+ bg_tasks.add_task(
+ move_uploaded_files_to_cache, files_to_copy, locations
+ )
+ if PROFILING_ENABLED:
+ bg_tasks.add_task(
+ set_upload_trace, request.headers.get("session_hash", ""), start
+ )
+ blocks.upload_file_set.update(output_files)
+
+ return output_files
+
+ @router.get("/startup-events")
+ async def startup_events():
+ if not app.startup_events_triggered:
+ app.get_blocks().run_startup_events()
+ await app.get_blocks().run_extra_startup_events()
+ app.startup_events_triggered = True
+ return True
+ return False
+
+ @router.get("/theme.css", response_class=PlainTextResponse)
+ @app.get("/theme.css", response_class=PlainTextResponse)
+ def theme_css():
+ return PlainTextResponse(app.get_blocks().theme_css, media_type="text/css")
+
+ @app.get("/robots.txt", response_class=PlainTextResponse)
+ def robots_txt():
+ if app.get_blocks().share:
+ return "User-agent: *\nDisallow: /"
+ else:
+ return "User-agent: *\nDisallow: "
+
+ @app.get("/pwa_icon")
+ @app.get("/pwa_icon/{size}")
+ async def pwa_icon(size: int | None = None):
+ blocks = app.get_blocks()
+ favicon_path = blocks.favicon_path
+ if favicon_path is None:
+ raise HTTPException(status_code=404)
+
+ if size is None:
+ return FileResponse(favicon_path)
+
+ import PIL.Image
+
+ img = PIL.Image.open(favicon_path)
+ img = img.resize((size, size))
+
+ img_byte_array = io.BytesIO()
+ img.save(img_byte_array, format="PNG")
+ img_byte_array.seek(0)
+
+ return StreamingResponse(
+ io.BytesIO(img_byte_array.read()), media_type="image/png"
+ )
+
+ @app.get("/manifest.json")
+ def manifest_json():
+ favicon_path = blocks.favicon_path
+ if isinstance(favicon_path, Path):
+ favicon_path = str(favicon_path)
+ if favicon_path is None:
+ icons = [
+ {
+ "src": "static/img/logo_nosize.svg",
+ "sizes": "any",
+ "type": "image/svg+xml",
+ "purpose": "any",
+ },
+ ]
+ elif favicon_path.endswith(".svg"):
+ icons = [
+ {
+ "src": app.url_path_for("pwa_icon"),
+ "sizes": "any",
+ "type": "image/svg+xml",
+ "purpose": "any",
+ },
+ ]
+ else:
+ icons = [
+ {
+ "src": app.url_path_for("pwa_icon", size=192),
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any",
+ },
+ {
+ "src": app.url_path_for("pwa_icon", size=512),
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any",
+ },
+ ]
+
+ return ORJSONResponse(
+ content={
+ # NOTE: Required members: https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable#required_manifest_members
+ "name": app.get_blocks().title or "Gradio",
+ "icons": icons,
+ "start_url": "./",
+ "display": "standalone",
+ },
+ media_type="application/manifest+json",
+ )
+
+ @app.get("/monitoring", dependencies=[Depends(login_check)])
+ async def analytics_login(request: fastapi.Request):
+ if not blocks.enable_monitoring:
+ raise HTTPException(
+ status_code=403, detail="Monitoring is not enabled."
+ )
+ root_url = route_utils.get_root_url(
+ request=request,
+ route_path=f"{API_PREFIX}/monitoring",
+ root_path=app.root_path,
+ )
+ monitoring_url = f"{root_url}/monitoring/{app.analytics_key}"
+ print(f"* Monitoring URL: {monitoring_url} *")
+ return HTMLResponse("See console for monitoring URL.")
+
+ @app.get("/monitoring/summary")
+ async def _():
+ return app.get_blocks()._queue.cached_event_analytics_summary
+
+ @app.get("/monitoring/{key}")
+ async def analytics_dashboard(key: str):
+ if not blocks.enable_monitoring:
+ raise HTTPException(
+ status_code=403, detail="Monitoring is not enabled."
+ )
+ if compare_passwords_securely(key, app.analytics_key):
+ analytics_url = f"/monitoring/{app.analytics_key}/dashboard"
+ if not app.monitoring_enabled:
+ from gradio.monitoring_dashboard import data
+ from gradio.monitoring_dashboard import demo as dashboard
+
+ mount_gradio_app(
+ app, dashboard, path=analytics_url, mcp_server=False
+ )
+ dashboard._queue.start()
+ analytics = app.get_blocks()._queue.event_analytics
+ data["data"] = analytics
+ app.monitoring_enabled = True
+ return RedirectResponse(
+ url=analytics_url, status_code=status.HTTP_302_FOUND
+ )
+ else:
+ raise HTTPException(status_code=403, detail="Invalid key.")
+
+ @router.post("/process_recording", dependencies=[Depends(login_check)])
+ async def process_recording(
+ request: fastapi.Request,
+ ):
+ try:
+ content_type_header = request.headers.get("Content-Type")
+ content_type: bytes
+ content_type, _ = parse_options_header(content_type_header or "")
+ if content_type != b"multipart/form-data":
+ raise HTTPException(status_code=400, detail="Invalid content type.")
+
+ app = request.app
+ max_file_size = (
+ app.get_blocks().max_file_size
+ if hasattr(app, "get_blocks")
+ else None
+ )
+ max_file_size = max_file_size if max_file_size is not None else math.inf
+
+ multipart_parser = GradioMultiPartParser(
+ request.headers,
+ request.stream(),
+ max_files=1,
+ max_fields=10,
+ max_file_size=max_file_size,
+ )
+ form = await multipart_parser.parse()
+ except MultiPartException as exc:
+ code = 413 if "maximum allowed size" in exc.message else 400
+ return PlainTextResponse(exc.message, status_code=code)
+
+ video_files = form.getlist("video")
+ if not video_files or not isinstance(video_files[0], GradioUploadFile):
+ raise HTTPException(status_code=400, detail="No video file provided")
+
+ video_file = video_files[0]
+
+ params = {}
+ if (
+ form.get("remove_segment_start") is not None
+ and form.get("remove_segment_end") is not None
+ ):
+ params["remove_segment_start"] = form.get("remove_segment_start")
+ params["remove_segment_end"] = form.get("remove_segment_end")
+
+ zoom_effects_json = form.get("zoom_effects")
+ if zoom_effects_json:
+ try:
+ params["zoom_effects"] = json.loads(str(zoom_effects_json))
+ except json.JSONDecodeError:
+ params["zoom_effects"] = []
+
+ with tempfile.NamedTemporaryFile(
+ delete=False, suffix=".mp4", dir=DEFAULT_TEMP_DIR
+ ) as input_file:
+ video_file.file.seek(0)
+ shutil.copyfileobj(video_file.file, input_file)
+ input_path = input_file.name
+
+ if shutil.which("ffmpeg") is None:
+ return FileResponse(
+ input_path,
+ media_type="video/mp4",
+ filename="gradio-screen-recording.mp4",
+ background=BackgroundTask(lambda: cleanup_files([input_path])),
+ )
+
+ output_path = tempfile.mkstemp(
+ suffix="_processed.mp4", dir=DEFAULT_TEMP_DIR
+ )[1]
+
+ try:
+ processed_path, temp_files = await process_video_with_ffmpeg(
+ input_path, output_path, params
+ )
+
+ return FileResponse(
+ processed_path,
+ media_type="video/mp4",
+ filename="gradio-screen-recording.mp4",
+ background=BackgroundTask(lambda: cleanup_files(temp_files)),
+ )
+ except Exception:
+ return FileResponse(
+ input_path,
+ media_type="video/mp4",
+ filename="gradio-screen-recording.mp4",
+ background=BackgroundTask(lambda: cleanup_files([input_path])),
+ )
+
+ vibe_edit_history_dir = Path(DEFAULT_TEMP_DIR) / "vibe_edit_history"
+ vibe_edit_history_dir.mkdir(exist_ok=True, parents=True)
+ chat_history = {"history": ""}
+ hash_to_chat_history = {}
+
+ def limit_chat_history(history: str, max_pairs: int = 5) -> str:
+ """Limit chat history in the prompt to the last max_pairs user-assistant pairs."""
+ if not history.strip():
+ return ""
+
+ user_messages = history.split("\nUser: ")
+ if len(user_messages) <= max_pairs:
+ return history
+
+ recent_messages = user_messages[-max_pairs:]
+
+ if len(recent_messages) > 0:
+ if recent_messages[0].startswith("User: "):
+ result = recent_messages[0]
+ else:
+ result = "User: " + recent_messages[0]
+
+ for msg in recent_messages[1:]:
+ result += "\nUser: " + msg
+
+ return result
+
+ return ""
+
+ control_token_re = re.compile(r"<\|[^>]*\|>")
+ final_start_re = re.compile(
+ r"<\|start\|>assistant<\|channel\|>final<\|message\|>", re.IGNORECASE
+ )
+ end_re = re.compile(r"<\|end\|>", re.IGNORECASE)
+ reasoning_block_re = re.compile(
+ r"<\s*reasoning\s*>\s*(?P.*?)\s*<\s*/\s*reasoning\s*>",
+ re.IGNORECASE | re.DOTALL,
+ )
+ think_block_re = re.compile(
+ r"<\s*think\s*>.*?<\s*/\s*think\s*>", re.IGNORECASE | re.DOTALL
+ )
+
+ # Remove analysis and weird markers from gpt-oss
+ def clean_out_markers(raw: str) -> str:
+ if not raw:
+ return raw
+
+ m = final_start_re.search(raw)
+ if m:
+ text = raw[m.end() :]
+ m_end = end_re.search(text)
+ if m_end:
+ text = text[: m_end.start()]
+ return text.strip()
+
+ text = control_token_re.sub("", raw)
+ return text.strip()
+
+ def strip_think_blocks(text: str) -> str:
+ """Remove any ... blocks entirely"""
+ return think_block_re.sub("", text)
+
+ def split_reasoning_code(text: str) -> tuple[str, str]:
+ """
+ Extract all ... and code. If multiple, concatenate with blank lines, de-duping exact copies.
+ """
+ reasoning_chunks = []
+ seen = set()
+ for m in reasoning_block_re.finditer(text):
+ body = m.group("body").strip()
+ if body and body not in seen:
+ reasoning_chunks.append(body)
+ seen.add(body)
+
+ reasoning_text = "\n\n".join(reasoning_chunks).strip()
+ code_text = reasoning_block_re.sub("", text).strip()
+
+ return reasoning_text, code_text
+
+ if blocks.vibe_mode:
+ from huggingface_hub import InferenceClient
+
+ inference_client = InferenceClient()
+
+ @router.post("/vibe-edit/")
+ @router.post("/vibe-edit")
+ async def vibe_edit(body: VibeEditBody):
+ if not blocks.vibe_mode:
+ raise HTTPException(
+ status_code=403,
+ detail="Vibe editor is not enabled. Use --vibe flag to enable.",
+ )
+
+ from gradio.http_server import GRADIO_WATCH_DEMO_PATH
+
+ with open(GRADIO_WATCH_DEMO_PATH) as f:
+ original_code = f.read()
+
+ snapshot_hash = secrets.token_hex(16)
+ snapshot_file = vibe_edit_history_dir / f"{snapshot_hash}.py"
+
+ with open(snapshot_file, "w") as f:
+ f.write(original_code)
+
+ hash_to_chat_history[snapshot_hash] = chat_history["history"]
+
+ content = ""
+ limited_history = limit_chat_history(chat_history["history"])
+
+ prompt = f"""
+You are a code generator for Gradio apps. Given the following existing code and prompt, return the full new code.
+Existing code:
+```python
+{original_code}
+```
+
+Prompt:
+{body.prompt}
+
+History:
+{limited_history if limited_history else "No chat history."}
+"""
+
+ system_prompt = load_system_prompt()
+ content = (
+ inference_client.chat_completion(
+ model="openai/gpt-oss-120b",
+ messages=[
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": prompt},
+ ],
+ max_tokens=10000,
+ )
+ .choices[0]
+ .message.content
+ )
+
+ if content is None:
+ raise HTTPException(status_code=500, detail="Error generating code")
+
+ content = clean_out_markers(content)
+ content = strip_think_blocks(content)
+
+ chat_history["history"] += f"\nUser: {body.prompt}\nAssistant: {content}\n"
+
+ reasoning, content = split_reasoning_code(content)
+
+ if "```python\n" in content:
+ start = content.index("```python\n") + len("```python\n")
+ end = content.find("\n```", start)
+ content = content[start:end] if end != -1 else content[start:]
+
+ # Calculate diff stats
+ original_lines = original_code.splitlines(keepends=True)
+ new_lines = content.splitlines(keepends=True)
+ diff = list(difflib.unified_diff(original_lines, new_lines, n=0))
+
+ lines_added = 0
+ lines_removed = 0
+ for line in diff:
+ if line.startswith("+") and not line.startswith("+++"):
+ lines_added += 1
+ elif line.startswith("-") and not line.startswith("---"):
+ lines_removed += 1
+
+ with open(GRADIO_WATCH_DEMO_PATH, "w") as f:
+ f.write(content)
+
+ return {
+ "hash": snapshot_hash,
+ "diff_stats": {
+ "lines_added": lines_added,
+ "lines_removed": lines_removed,
+ },
+ "reasoning": reasoning,
+ }
+
+ @router.post("/undo-vibe-edit/")
+ @router.post("/undo-vibe-edit")
+ async def undo_vibe_edit(hash: str = Body(..., embed=True)):
+ if not blocks.vibe_mode:
+ raise HTTPException(
+ status_code=403,
+ detail="Vibe editor is not enabled. Use --vibe flag to enable.",
+ )
+
+ from gradio.http_server import GRADIO_WATCH_DEMO_PATH
+
+ snapshot_file = vibe_edit_history_dir / f"{hash}.py"
+
+ if not snapshot_file.exists():
+ raise HTTPException(status_code=404, detail="Snapshot not found")
+
+ # Restore the file from the snapshot
+ with open(snapshot_file) as f:
+ saved_content = f.read()
+
+ with open(GRADIO_WATCH_DEMO_PATH, "w") as f:
+ f.write(saved_content)
+
+ chat_history["history"] = hash_to_chat_history.get(hash, "")
+
+ return {"success": True}
+
+ @router.get("/vibe-code/")
+ @router.get("/vibe-code")
+ async def get_vibe_code():
+ if not blocks.vibe_mode:
+ raise HTTPException(
+ status_code=403,
+ detail="Vibe editor is not enabled. Use --vibe flag to enable.",
+ )
+
+ from gradio.http_server import GRADIO_WATCH_DEMO_PATH
+
+ try:
+ with open(GRADIO_WATCH_DEMO_PATH) as f:
+ code = f.read()
+ return {"code": code}
+ except FileNotFoundError:
+ raise HTTPException(
+ status_code=404, detail="Demo file not found"
+ ) from None
+ except Exception as e:
+ raise HTTPException(
+ status_code=500, detail=f"Error reading file: {str(e)}"
+ ) from e
+
+ @router.post("/vibe-code/")
+ @router.post("/vibe-code")
+ async def update_vibe_code(body: VibeCodeBody):
+ if not blocks.vibe_mode:
+ raise HTTPException(
+ status_code=403,
+ detail="Vibe editor is not enabled. Use --vibe flag to enable.",
+ )
+
+ from gradio.http_server import GRADIO_WATCH_DEMO_PATH
+
+ try:
+ with open(GRADIO_WATCH_DEMO_PATH, "w") as f:
+ f.write(body.code)
+ return {"success": True}
+ except Exception as e:
+ raise HTTPException(
+ status_code=500, detail=f"Error writing file: {str(e)}"
+ ) from e
+
+ @router.post("/vibe-starter-queries/")
+ @router.post("/vibe-starter-queries")
+ async def get_vibe_starter_queries():
+ if not blocks.vibe_mode:
+ raise HTTPException(
+ status_code=403,
+ detail="Vibe editor is not enabled. Use --vibe flag to enable.",
+ )
+
+ from gradio.http_server import GRADIO_WATCH_DEMO_PATH
+
+ with open(GRADIO_WATCH_DEMO_PATH) as f:
+ code = f.read()
+
+ prompt = f"""
+You are a prompt generator for a gradio vibe editor. Given the following existing code, return a list of starter queries that can be used to generate a new code.
+Existing code:
+```python
+{code}
+```
+"""
+
+ system_prompt = load_system_prompt(starter_queries=True)
+ content = (
+ inference_client.chat_completion(
+ model="openai/gpt-oss-120b",
+ messages=[
+ {"role": "system", "content": system_prompt},
+ {"role": "user", "content": prompt},
+ ],
+ max_tokens=10000,
+ )
+ .choices[0]
+ .message.content
+ )
+
+ if content is None:
+ raise HTTPException(status_code=500, detail="Error generating code")
+
+ content = strip_think_blocks(content)
+ content = clean_out_markers(content)
+
+ starter_queries = content.split("\n")
+
+ return {
+ "starter_queries": starter_queries,
+ }
+
+ def cleanup_files(files):
+ for file in files:
+ try:
+ if file and os.path.exists(file):
+ os.unlink(file)
+ except Exception as e:
+ print(f"Error cleaning up file {file}: {str(e)}")
+
+ from gradio.profiling import PROFILING_ENABLED
+
+ if PROFILING_ENABLED:
+ from gradio.profiling import collector
+
+ @router.get("/profiling/traces")
+ async def profiling_traces(
+ last_n: int | None = None,
+ ):
+ return ORJSONResponse(collector.get_all(last_n=last_n))
+
+ @router.get("/profiling/summary")
+ async def profiling_summary():
+ return ORJSONResponse(collector.get_summary())
+
+ @router.post("/profiling/clear")
+ async def profiling_clear():
+ collector.clear()
+ return ORJSONResponse({"status": "cleared"})
+
+ app.include_router(router)
+ return app
+
+
+########
+# Helper functions
+########
+
+
+def load_system_prompt(starter_queries: bool = False):
+ prompt_rules = """Generate code for using the Gradio python library.
+
+The following RULES must be followed. Whenever you are forming a response, ensure all rules have been followed otherwise start over.
+
+RULES:
+Respond with code written in valid Python syntax, along with one coherent explanation surrounded by tags.
+Any text that is not code, should be surrounded by one large tag.
+Never include backticks in your response such as ``` or ```python.
+Do not include any code that is not necessary for the app to run.
+Respond with a full Gradio app.
+Respond with a full Gradio app using correct syntax and features of the latest Gradio version. DO NOT write code that doesn't follow the signatures listed.
+Do not add comments explaining the code, unless they are very necessary to understand the code.
+Make sure the code includes all necessary imports.
+Clearly explain the changes, summary, or reasoning for the code you respond with, inside one large tag. Make sure it's easy to parse. Use markdown formatting when it makes sense, including bullet points if there are multiple changes.
+
+
+Here's an example of a valid response:
+
+
+I created a simple Gradio app that greets the user. It defines a function then creates a gradio interface and launches it.
+
+
+import gradio as gr
+
+def greet(name):
+ return "Hello " + name + "!"
+
+demo = gr.Interface(fn=greet, inputs="textbox", outputs="textbox")
+
+demo.launch()
+"""
+ if starter_queries:
+ prompt_rules = """
+ You are a prompt generator for a gradio vibe editor.
+
+ Given python code of a gradio app, return a list of starter queries that can be used to generate new code.
+ Make sure the queries are short, useful and actually possible with Gradio.
+ The queries should be really simple and easy to understand.
+ You should respond with at most three queries, each on a new line. Do not include any other text.
+ Make sure the features you suggest are actually supported by Gradio, and documented in the docs section below.
+ Never suggest a query with more than one gradio feature or concept.
+ You may suggest queries that are not related to Gradio, but they must be related to the existing code and app. Never suggest queries that require external packages or libraries other than gradio.
+ Don't suggest adding a clear button if the app is an Interface, because Interface already has a clear button.
+
+ Here's an example of a gradio app:
+
+ ```python
+ import gradio as gr
+
+ def greet(name):
+ return "Hello " + name + "!"
+
+ demo = gr.Interface(fn=greet, inputs="textbox", outputs="textbox")
+
+ demo.launch()
+ ```
+
+ Here's an example of a valid response:
+ Add a title to the app
+ Add examples
+ Rewrite this app using Blocks
+
+ Here's an example of another valid response:
+ Add another textbox for name
+ Change the theme
+ Greet the user in many languages
+
+ """
+ try:
+ with httpx.Client() as client:
+ response = client.get("https://www.gradio.app/llms.txt")
+ system_prompt = response.text
+ except Exception:
+ system_prompt = ""
+ system_prompt = prompt_rules + system_prompt + prompt_rules
+ return system_prompt
+
+
+def get_types(cls_set: list[type]):
+ docset = []
+ types = []
+ for cls in cls_set:
+ doc = inspect.getdoc(cls) or ""
+ doc_lines = doc.split("\n")
+ for line in doc_lines:
+ if "value (" in line:
+ types.append(line.split("value (")[1].split(")")[0])
+ docset.append(doc_lines[1].split(":")[-1])
+ return docset, types
+
+
+@document()
+def mount_gradio_app(
+ app: fastapi.FastAPI,
+ blocks: gradio.Blocks,
+ path: str,
+ server_name: str = "0.0.0.0",
+ server_port: int = 7860,
+ footer_links: (
+ list[Literal["api", "gradio", "settings"] | dict[str, str]] | None
+ ) = None,
+ app_kwargs: dict[str, Any] | None = None,
+ *,
+ auth: Callable | tuple[str, str] | list[tuple[str, str]] | None = None,
+ auth_message: str | None = None,
+ auth_dependency: Callable[[fastapi.Request], str | None] | None = None,
+ root_path: str | None = None,
+ allowed_paths: list[str] | None = None,
+ blocked_paths: list[str] | None = None,
+ favicon_path: str | None = None,
+ show_error: bool = True,
+ max_file_size: str | int | None = None,
+ ssr_mode: bool | None = None,
+ node_server_name: str | None = None,
+ node_port: int | None = None,
+ enable_monitoring: bool | None = None,
+ pwa: bool | None = None,
+ i18n: I18n | None = None,
+ mcp_server: bool | None = None,
+ theme: Theme | str | None = None,
+ css: str | None = None,
+ css_paths: str | Path | Sequence[str | Path] | None = None,
+ js: str | Literal[True] | None = None,
+ head: str | None = None,
+ head_paths: str | Path | Sequence[str | Path] | None = None,
+) -> fastapi.FastAPI:
+ """Mount a gradio.Blocks to an existing FastAPI application.
+
+ Parameters:
+ app: The parent FastAPI application.
+ blocks: The blocks object we want to mount to the parent app.
+ path: The path at which the gradio application will be mounted, e.g. "/gradio".
+ server_name: The server name on which the Gradio app will be run.
+ server_port: The port on which the Gradio app will be run.
+ app_kwargs: Additional keyword arguments to pass to the underlying FastAPI app as a dictionary of parameter keys and argument values. For example, `{"docs_url": "/docs"}`
+ auth: If provided, username and password (or list of username-password tuples) required to access the gradio app. Can also provide function that takes username and password and returns True if valid login.
+ auth_message: If provided, HTML message provided on login page for this gradio app.
+ auth_dependency: A function that takes a FastAPI request and returns a string user ID or None. If the function returns None for a specific request, that user is not authorized to access the gradio app (they will see a 401 Unauthorized response). To be used with external authentication systems like OAuth. Cannot be used with `auth`.
+ root_path: The subpath corresponding to the public deployment of this FastAPI application. For example, if the application is served at "https://example.com/myapp", the `root_path` should be set to "/myapp". A full URL beginning with http:// or https:// can be provided, which will be used in its entirety. Normally, this does not need to provided (even if you are using a custom `path`). However, if you are serving the FastAPI app behind a proxy, the proxy may not provide the full path to the Gradio app in the request headers. In which case, you can provide the root path here.
+ allowed_paths: List of complete filepaths or parent directories that this gradio app is allowed to serve. Must be absolute paths. Warning: if you provide directories, any files in these directories or their subdirectories are accessible to all users of your app.
+ blocked_paths: List of complete filepaths or parent directories that this gradio app is not allowed to serve (i.e. users of your app are not allowed to access). Must be absolute paths. Warning: takes precedence over `allowed_paths` and all other directories exposed by Gradio by default.
+ favicon_path: If a path to a file (.png, .gif, or .ico) is provided, it will be used as the favicon for this gradio app's page.
+ show_error: If True, any errors in the gradio app will be displayed in an alert modal and printed in the browser console log. Otherwise, errors will only be visible in the terminal session running the Gradio app.
+ max_file_size: The maximum file size in bytes that can be uploaded. Can be a string of the form "", where value is any positive integer and unit is one of "b", "kb", "mb", "gb", "tb". If None, no limit is set.
+ footer_links: The links to display in the footer of the app. Accepts a list, where each element of the list must be one of "api", "gradio", or "settings" corresponding to the API docs, "built with Gradio", and settings pages respectively. If None, all three links will be shown in the footer. An empty list means that no footer is shown.
+ ssr_mode: If True, the Gradio app will be rendered using server-side rendering mode, which is typically more performant and provides better SEO, but this requires Node 20+ to be installed on the system. If False, the app will be rendered using client-side rendering mode. If None, will use GRADIO_SSR_MODE environment variable or default to False.
+ node_server_name: The name of the Node server to use for SSR. If None, will use GRADIO_NODE_SERVER_NAME environment variable or search for a node binary in the system.
+ i18n: If provided, the i18n instance to use for this gradio app.
+ node_port: The port on which the Node server should run. If None, will use GRADIO_NODE_SERVER_PORT environment variable or find a free port.
+ mcp_server: If True, the MCP server will be launched on the gradio app. If None, will use GRADIO_MCP_SERVER environment variable or default to False.
+ theme: A Theme object or a string representing a theme. If a string, will look for a built-in theme with that name (e.g. "soft" or "default"), or will attempt to load a theme from the Hugging Face Hub (e.g. "gradio/monochrome"). If None, will use the Default theme.
+ css: Custom css as a code string. This css will be included in the demo webpage.
+ css_paths: Custom css as a pathlib.Path to a css file or a list of such paths. This css files will be read, concatenated, and included in the demo webpage. If the `css` parameter is also set, the css from `css` will be included first.
+ js: Custom js as a code string. The custom js should be in the form of a single js function. This function will automatically be executed when the page loads. For more flexibility, use the head parameter to insert js inside ',
+)
+```
+
+## Server Functions
+
+You can call Python functions directly from your `js_on_load` code using the `server_functions` parameter. Pass a list of Python functions to `server_functions`, and they become available as async methods on a `server` object inside `js_on_load`.
+
+$code_html_server_functions
+$demo_html_server_functions
+
+
+## Component Classes
+
+If you are reusing the same HTML component in multiple places, you can create a custom component class by subclassing `gr.HTML` and setting default values for the templates and other arguments. Here's an example of creating a reusable StarRating component.
+
+$code_star_rating_component
+$demo_star_rating_component
+
+Note: Gradio requires all components to accept certain arguments, such as `render`. You do not need
+to handle these arguments, but you do need to accept them in your component constructor and pass
+them to the parent `gr.HTML` class. Otherwise, your component may not behave correctly. The easiest
+way is to add `**kwargs` to your `__init__` method and pass it to `super().__init__()`, just like in the code example above.
+
+We've created several custom HTML components as reusable components as examples you can reference in [this directory](https://github.com/gradio-app/gradio/tree/main/gradio/components/custom_html_components).
+
+
+## Embedding Components in HTML
+
+The `gr.HTML` component can also be used as a container for other Gradio components using the `@children` placeholder. This allows you to create custom layouts with HTML/CSS.
+
+The `@children` must be at the top-level of the `html_template`. Since children cannot be nested inside the template, target the parent element directly with your CSS and JavaScript if you need to style or interact with the container of the children.
+
+Here's a basic example:
+
+$code_html_children
+$demo_html_children
+
+In this example, the `@children` placeholder marks where the child components (the Name and Email textboxes) will be rendered. Notice how in the `css_template` we target the parent element to style the container div that wraps the children.
+
+
+### API / MCP support
+
+To make your custom HTML component work with Gradio's built-in support for API and MCP (Model Context Protocol) usage, you need to define how its data should be serialized. There are two ways to do this:
+
+**Option 1: Define an `api_info()` method**
+
+Add an `api_info()` method that returns a JSON schema dictionary describing your component's data format. This is what we do in the StarRating class above.
+
+**Option 2: Define a Pydantic data model**
+
+For more complex data structures, you can define a Pydantic model that inherits from `GradioModel` or `GradioRootModel`:
+
+```python
+from gradio.data_classes import GradioModel, GradioRootModel
+
+class MyComponentData(GradioModel):
+ items: List[str]
+ count: int
+
+class MyComponent(gr.HTML):
+ data_model = MyComponentData
+```
+
+Use `GradioModel` when your data is a dictionary with named fields, or `GradioRootModel` when your data is a simple type (string, list, etc.) that doesn't need to be wrapped in a dictionary. By defining a `data_model`, your component automatically implements API methods.
+
+## Sharing Components with `push_to_hub`
+
+Once you've built a custom HTML component, you can share it with the community by pushing it to the [HTML Components Gallery](https://www.gradio.app/custom-components/html-gallery). The gallery lets anyone browse, interact with, and copy the Python code for community-contributed components.
+
+Call `push_to_hub` on any `gr.HTML` instance or subclass:
+
+```python
+star_rating = StarRating()
+star_rating.push_to_hub(
+ name="Star Rating",
+ description="Interactive 5-star rating with click-to-rate",
+ author="your-hf-username",
+ tags=["input", "rating"],
+ repo_url="https://github.com/your-username/your-repo",
+)
+```
+
+This opens a pull request on the gallery's HuggingFace dataset repo. Once approved, your component will appear in the gallery for others to discover and use.
+
+Tip: The `push_to_hub` method has a `head` parameter that deserves special attention. If your component uses an external library loaded via the `head` parameter of `launch` (e.g. `head=''`), pass the same `head` string to `push_to_hub` so that the gallery can load those scripts when rendering your component.
+
+### Authentication
+
+You need a HuggingFace **write token** to push components. Either pass it directly:
+
+```python
+star_rating.push_to_hub(..., token="hf_xxxxx")
+```
+
+Or log in beforehand with the HuggingFace CLI, and the cached token will be used automatically:
+
+```bash
+huggingface-cli login
+```
+
+## Security Considerations
+
+Keep in mind that using `gr.HTML` to create custom components involves injecting raw HTML and JavaScript into your Gradio app. Be cautious about using untrusted user input into `html_template` and `js_on_load`, as this could lead to cross-site scripting (XSS) vulnerabilities.
+
+You should also expect that any Python event listeners that take your `gr.HTML` component as input could have any arbitrary value passed to them, not just the values you expect the frontend to be able to set for `value`. Sanitize and validate user input appropriately in public applications.
+
+## Next Steps
+
+- Browse the [HTML Components Gallery](https://www.gradio.app/custom-components/html-gallery) to see what the community has built and copy components into your own apps.
+- Check out more examples in [this directory](https://github.com/gradio-app/gradio/tree/main/gradio/components/custom_html_components).
+- Share your own components with `push_to_hub` to help others!
\ No newline at end of file
diff --git a/guides/03_building-with-blocks/07_custom-CSS-and-JS.md b/guides/03_building-with-blocks/07_custom-CSS-and-JS.md
new file mode 100644
index 0000000..dff5775
--- /dev/null
+++ b/guides/03_building-with-blocks/07_custom-CSS-and-JS.md
@@ -0,0 +1,163 @@
+# Customizing your demo with CSS and Javascript
+
+Gradio allows you to customize your demo in several ways. You can customize the layout of your demo, add custom HTML, and add custom theming as well. This tutorial will go beyond that and walk you through how to add custom CSS and JavaScript code to your demo in order to add custom styling, animations, custom UI functionality, analytics, and more.
+
+## Adding custom CSS to your demo
+
+Gradio themes are the easiest way to customize the look and feel of your app. You can choose from a variety of themes, or create your own. To do so, pass the `theme=` kwarg to the `launch()` method of the `Blocks` constructor. For example:
+
+```python
+with gr.Blocks() as demo:
+ ... # your code here
+demo.launch(theme=gr.themes.Glass())
+ ...
+```
+
+Gradio comes with a set of prebuilt themes which you can load from `gr.themes.*`. You can extend these themes or create your own themes from scratch - see the [Theming guide](/guides/theming-guide) for more details.
+
+For additional styling ability, you can pass any CSS to your app as a string using the `css=` kwarg in the `launch()` method. You can also pass a pathlib.Path to a css file or a list of such paths to the `css_paths=` kwarg in the `launch()` method.
+
+**Warning**: The use of query selectors in custom JS and CSS is _not_ guaranteed to work across Gradio versions that bind to Gradio's own HTML elements as the Gradio HTML DOM may change. We recommend using query selectors sparingly.
+
+The base class for the Gradio app is `gradio-container`, so here's an example that changes the background color of the Gradio app:
+
+```python
+with gr.Blocks() as demo:
+ ... # your code here
+demo.launch(css=".gradio-container {background-color: red}")
+ ...
+```
+
+If you'd like to reference external files in your css, preface the file path (which can be a relative or absolute path) with `"/gradio_api/file="`, for example:
+
+```python
+with gr.Blocks() as demo:
+ ... # your code here
+demo.launch(css=".gradio-container {background: url('/gradio_api/file=clouds.jpg')}")
+ ...
+```
+
+Note: By default, most files in the host machine are not accessible to users running the Gradio app. As a result, you should make sure that any referenced files (such as `clouds.jpg` here) are either URLs or [allowed paths, as described here](/main/guides/file-access).
+
+
+## The `elem_id` and `elem_classes` Arguments
+
+You can `elem_id` to add an HTML element `id` to any component, and `elem_classes` to add a class or list of classes. This will allow you to select elements more easily with CSS. This approach is also more likely to be stable across Gradio versions as built-in class names or ids may change (however, as mentioned in the warning above, we cannot guarantee complete compatibility between Gradio versions if you use custom CSS as the DOM elements may themselves change).
+
+```python
+css = """
+#warning {background-color: #FFCCCB}
+.feedback textarea {font-size: 24px !important}
+"""
+
+with gr.Blocks() as demo:
+ box1 = gr.Textbox(value="Good Job", elem_classes="feedback")
+ box2 = gr.Textbox(value="Failure", elem_id="warning", elem_classes="feedback")
+demo.launch(css=css)
+```
+
+The CSS `#warning` ruleset will only target the second Textbox, while the `.feedback` ruleset will target both. Note that when targeting classes, you might need to put the `!important` selector to override the default Gradio styles.
+
+## Adding custom JavaScript to your demo
+
+There are 3 ways to add javascript code to your Gradio demo:
+
+1. You can add JavaScript code as a string to the `js` parameter of the `Blocks` or `Interface` initializer. This will run the JavaScript code when the demo is first loaded.
+
+Below is an example of adding custom js to show an animated welcome message when the demo first loads.
+
+$code_blocks_js_load
+$demo_blocks_js_load
+
+
+2. When using `Blocks` and event listeners, events have a `js` argument that can take a JavaScript function as a string and treat it just like a Python event listener function. You can pass both a JavaScript function and a Python function (in which case the JavaScript function is run first) or only Javascript (and set the Python `fn` to `None`). Take a look at the code below:
+
+$code_blocks_js_methods
+$demo_blocks_js_methods
+
+3. Lastly, you can add JavaScript code to the `head` param of the `Blocks` initializer. This will add the code to the head of the HTML document. For example, you can add Google Analytics to your demo like so:
+
+
+```python
+head = f"""
+
+
+"""
+
+with gr.Blocks() as demo:
+ gr.HTML("My App
")
+
+demo.launch(head=head)
+```
+
+The `head` parameter accepts any HTML tags you would normally insert into the `` of a page. For example, you can also include `` tags to `head` in order to update the social sharing preview for your Gradio app like this:
+
+```py
+import gradio as gr
+
+custom_head = """
+
+Sample App
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+with gr.Blocks(title="My App") as demo:
+ gr.HTML("My App
")
+
+demo.launch(head=custom_head)
+```
+
+
+
+Note that injecting custom JS can affect browser behavior and accessibility (e.g. keyboard shortcuts may be lead to unexpected behavior if your Gradio app is embedded in another webpage). You should test your interface across different browsers and be mindful of how scripts may interact with browser defaults. Here's an example where pressing `Shift + s` triggers the `click` event of a specific `Button` component if the browser focus is _not_ on an input component (e.g. `Textbox` component):
+
+```python
+import gradio as gr
+
+shortcut_js = """
+
+"""
+
+with gr.Blocks() as demo:
+ action_button = gr.Button(value="Name", elem_id="my_btn")
+ textbox = gr.Textbox()
+ action_button.click(lambda : "button pressed", None, textbox)
+
+demo.launch(head=shortcut_js)
+```
+
diff --git a/guides/04_additional-features/01_queuing.md b/guides/04_additional-features/01_queuing.md
new file mode 100644
index 0000000..9f84bae
--- /dev/null
+++ b/guides/04_additional-features/01_queuing.md
@@ -0,0 +1,48 @@
+# Queuing
+
+Every Gradio app comes with a built-in queuing system that can scale to thousands of concurrent users. Because many of your event listeners may involve heavy processing, Gradio automatically creates a queue to handle every event listener in the backend. Every event listener in your app automatically has a queue to process incoming events.
+
+## Configuring the Queue
+
+By default, each event listener has its own queue, which handles one request at a time. This can be configured via two arguments:
+
+- `concurrency_limit`: This sets the maximum number of concurrent executions for an event listener. By default, the limit is 1 unless configured otherwise in `Blocks.queue()`. You can also set it to `None` for no limit (i.e., an unlimited number of concurrent executions). For example:
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ prompt = gr.Textbox()
+ image = gr.Image()
+ generate_btn = gr.Button("Generate Image")
+ generate_btn.click(image_gen, prompt, image, concurrency_limit=5)
+```
+
+In the code above, up to 5 requests can be processed simultaneously for this event listener. Additional requests will be queued until a slot becomes available.
+
+If you want to manage multiple event listeners using a shared queue, you can use the `concurrency_id` argument:
+
+- `concurrency_id`: This allows event listeners to share a queue by assigning them the same ID. For example, if your setup has only 2 GPUs but multiple functions require GPU access, you can create a shared queue for all those functions. Here's how that might look:
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ prompt = gr.Textbox()
+ image = gr.Image()
+ generate_btn_1 = gr.Button("Generate Image via model 1")
+ generate_btn_2 = gr.Button("Generate Image via model 2")
+ generate_btn_3 = gr.Button("Generate Image via model 3")
+ generate_btn_1.click(image_gen_1, prompt, image, concurrency_limit=2, concurrency_id="gpu_queue")
+ generate_btn_2.click(image_gen_2, prompt, image, concurrency_id="gpu_queue")
+ generate_btn_3.click(image_gen_3, prompt, image, concurrency_id="gpu_queue")
+```
+
+In this example, all three event listeners share a queue identified by `"gpu_queue"`. The queue can handle up to 2 concurrent requests at a time, as defined by the `concurrency_limit`.
+
+### Notes
+
+- To ensure unlimited concurrency for an event listener, set `concurrency_limit=None`. This is useful if your function is calling e.g. an external API which handles the rate limiting of requests itself.
+- The default concurrency limit for all queues can be set globally using the `default_concurrency_limit` parameter in `Blocks.queue()`.
+
+These configurations make it easy to manage the queuing behavior of your Gradio app.
diff --git a/guides/04_additional-features/02_streaming-outputs.md b/guides/04_additional-features/02_streaming-outputs.md
new file mode 100644
index 0000000..ab0cd1f
--- /dev/null
+++ b/guides/04_additional-features/02_streaming-outputs.md
@@ -0,0 +1,74 @@
+# Streaming outputs
+
+In some cases, you may want to stream a sequence of outputs rather than show a single output at once. For example, you might have an image generation model and you want to show the image that is generated at each step, leading up to the final image. Or you might have a chatbot which streams its response one token at a time instead of returning it all at once.
+
+In such cases, you can supply a **generator** function into Gradio instead of a regular function. Creating generators in Python is very simple: instead of a single `return` value, a function should `yield` a series of values instead. Usually the `yield` statement is put in some kind of loop. Here's an example of an generator that simply counts up to a given number:
+
+```python
+def my_generator(x):
+ for i in range(x):
+ yield i
+```
+
+You supply a generator into Gradio the same way as you would a regular function. For example, here's a a (fake) image generation model that generates noise for several steps before outputting an image using the `gr.Interface` class:
+
+$code_fake_diffusion
+$demo_fake_diffusion
+
+Note that we've added a `time.sleep(1)` in the iterator to create an artificial pause between steps so that you are able to observe the steps of the iterator (in a real image generation model, this probably wouldn't be necessary).
+
+Similarly, Gradio can handle streaming inputs, e.g. an image generation model that reruns every time a user types a letter in a textbox. This is covered in more details in our guide on building [reactive Interfaces](/guides/reactive-interfaces).
+
+## Streaming Media
+
+Gradio can stream audio and video directly from your generator function.
+This lets your user hear your audio or see your video nearly as soon as it's `yielded` by your function.
+All you have to do is
+
+1. Set `streaming=True` in your `gr.Audio` or `gr.Video` output component.
+2. Write a python generator that yields the next "chunk" of audio or video.
+3. Set `autoplay=True` so that the media starts playing automatically.
+
+For audio, the next "chunk" can be either an `.mp3` or `.wav` file or a `bytes` sequence of audio.
+For video, the next "chunk" has to be either `.mp4` file or a file with `h.264` codec with a `.ts` extension.
+For smooth playback, make sure chunks are consistent lengths and larger than 1 second.
+
+We'll finish with some simple examples illustrating these points.
+
+### Streaming Audio
+
+```python
+import gradio as gr
+from time import sleep
+
+def keep_repeating(audio_file):
+ for _ in range(10):
+ sleep(0.5)
+ yield audio_file
+
+gr.Interface(keep_repeating,
+ gr.Audio(sources=["microphone"], type="filepath"),
+ gr.Audio(streaming=True, autoplay=True)
+).launch()
+```
+
+### Streaming Video
+
+```python
+import gradio as gr
+from time import sleep
+
+def keep_repeating(video_file):
+ for _ in range(10):
+ sleep(0.5)
+ yield video_file
+
+gr.Interface(keep_repeating,
+ gr.Video(sources=["webcam"], format="mp4"),
+ gr.Video(streaming=True, autoplay=True)
+).launch()
+```
+
+## End-to-End Examples
+
+For an end-to-end example of streaming media, see the object detection from video [guide](/main/guides/object-detection-from-video) or the streaming AI-generated audio with [transformers](https://huggingface.co/docs/transformers/index) [guide](/main/guides/streaming-ai-generated-audio).
\ No newline at end of file
diff --git a/guides/04_additional-features/03_streaming-inputs.md b/guides/04_additional-features/03_streaming-inputs.md
new file mode 100644
index 0000000..0d5ed7a
--- /dev/null
+++ b/guides/04_additional-features/03_streaming-inputs.md
@@ -0,0 +1,70 @@
+# Streaming inputs
+
+Tip: Check out [FastRTC](https://fastrtc.org/), our companion library for building low latency streaming web apps with a familiar Gradio syntax.
+
+In the previous guide, we covered how to stream a sequence of outputs from an event handler. Gradio also allows you to stream images from a user's camera or audio chunks from their microphone **into** your event handler. This can be used to create real-time object detection apps or conversational chat applications with Gradio.
+
+Currently, the `gr.Image` and the `gr.Audio` components support input streaming via the `stream` event.
+Let's create the simplest streaming app possible, which simply returns the webcam stream unmodified.
+
+$code_streaming_simple
+$demo_streaming_simple
+
+Try it out! The streaming event is triggered when the user starts recording. Under the hood, the webcam will take a photo every 0.1 seconds and send it to the server. The server will then return that image.
+
+There are two unique keyword arguments for the `stream` event:
+
+* `time_limit` - This is the amount of time the gradio server will spend processing the event. Media streams are naturally unbounded so it's important to set a time limit so that one user does not hog the Gradio queue. The time limit only counts the time spent processing the stream, not the time spent waiting in the queue. The orange bar displayed at the bottom of the input image represents the remaining time. When the time limit expires, the user will automatically rejoin the queue.
+
+* `stream_every` - This is the frequency (in seconds) with which the stream will capture input and send it to the server. For demos like image detection or manipulation, setting a smaller value is desired to get a "real-time" effect. For demos like speech transcription, a higher value is useful so that the transcription algorithm has more context of what's being said.
+
+## A Realistic Image Demo
+
+Let's create a demo where a user can choose a filter to apply to their webcam stream. Users can choose from an edge-detection filter, a cartoon filter, or simply flipping the stream vertically.
+
+$code_streaming_filter
+$demo_streaming_filter
+
+You will notice that if you change the filter value it will immediately take effect in the output stream. That is an important difference of stream events in comparison to other Gradio events. The input values of the stream can be changed while the stream is being processed.
+
+Tip: We set the "streaming" parameter of the image output component to be "True". Doing so lets the server automatically convert our output images into base64 format, a format that is efficient for streaming.
+
+## Unified Image Demos
+
+For some image streaming demos, like the one above, we don't need to display separate input and output components. Our app would look cleaner if we could just display the modified output stream.
+
+We can do so by just specifying the input image component as the output of the stream event.
+
+$code_streaming_filter_unified
+$demo_streaming_filter_unified
+
+## Keeping track of past inputs or outputs
+
+Your streaming function should be stateless. It should take the current input and return its corresponding output. However, there are cases where you may want to keep track of past inputs or outputs. For example, you may want to keep a buffer of the previous `k` inputs to improve the accuracy of your transcription demo. You can do this with Gradio's `gr.State()` component.
+
+Let's showcase this with a sample demo:
+
+```python
+def transcribe_handler(current_audio, state, transcript):
+ next_text = transcribe(current_audio, history=state)
+ state.append(current_audio)
+ state = state[-3:]
+ return state, transcript + next_text
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column():
+ mic = gr.Audio(sources="microphone")
+ state = gr.State(value=[])
+ with gr.Column():
+ transcript = gr.Textbox(label="Transcript")
+ mic.stream(transcribe_handler, [mic, state, transcript], [state, transcript],
+ time_limit=10, stream_every=1)
+
+
+demo.launch()
+```
+
+## End-to-End Examples
+
+For an end-to-end example of streaming from the webcam, see the object detection from webcam [guide](/main/guides/object-detection-from-webcam-with-webrtc).
\ No newline at end of file
diff --git a/guides/04_additional-features/04_alerts.md b/guides/04_additional-features/04_alerts.md
new file mode 100644
index 0000000..761a6f9
--- /dev/null
+++ b/guides/04_additional-features/04_alerts.md
@@ -0,0 +1,18 @@
+# Alerts
+
+You may wish to display alerts to the user. To do so, raise a `gr.Error("custom message")` in your function to halt the execution of your function and display an error message to the user.
+
+You can also issue `gr.Warning("custom message")` or `gr.Info("custom message")` by having them as standalone lines in your function, which will immediately display modals while continuing the execution of your function. The only difference between `gr.Info()` and `gr.Warning()` is the color of the alert.
+
+```python
+def start_process(name):
+ gr.Info("Starting process")
+ if name is None:
+ gr.Warning("Name is empty")
+ ...
+ if success == False:
+ raise gr.Error("Process failed")
+```
+
+Tip: Note that `gr.Error()` is an exception that has to be raised, while `gr.Warning()` and `gr.Info()` are functions that are called directly.
+
diff --git a/guides/04_additional-features/05_progress-bars.md b/guides/04_additional-features/05_progress-bars.md
new file mode 100644
index 0000000..44d71d4
--- /dev/null
+++ b/guides/04_additional-features/05_progress-bars.md
@@ -0,0 +1,8 @@
+# Progress Bars
+
+Gradio supports the ability to create custom Progress Bars so that you have customizability and control over the progress update that you show to the user. In order to enable this, simply add an argument to your method that has a default value of a `gr.Progress` instance. Then you can update the progress levels by calling this instance directly with a float between 0 and 1, or using the `tqdm()` method of the `Progress` instance to track progress over an iterable, as shown below.
+
+$code_progress_simple
+$demo_progress_simple
+
+If you use the `tqdm` library, you can even report progress updates automatically from any `tqdm.tqdm` that already exists within your function by setting the default argument as `gr.Progress(track_tqdm=True)`!
diff --git a/guides/04_additional-features/06_batch-functions.md b/guides/04_additional-features/06_batch-functions.md
new file mode 100644
index 0000000..6e7e66f
--- /dev/null
+++ b/guides/04_additional-features/06_batch-functions.md
@@ -0,0 +1,59 @@
+# Batch functions
+
+Gradio supports the ability to pass _batch_ functions. Batch functions are just
+functions which take in a list of inputs and return a list of predictions.
+
+For example, here is a batched function that takes in two lists of inputs (a list of
+words and a list of ints), and returns a list of trimmed words as output:
+
+```py
+import time
+
+def trim_words(words, lens):
+ trimmed_words = []
+ time.sleep(5)
+ for w, l in zip(words, lens):
+ trimmed_words.append(w[:int(l)])
+ return [trimmed_words]
+```
+
+The advantage of using batched functions is that if you enable queuing, the Gradio server can automatically _batch_ incoming requests and process them in parallel,
+potentially speeding up your demo. Here's what the Gradio code looks like (notice the `batch=True` and `max_batch_size=16`)
+
+With the `gr.Interface` class:
+
+```python
+demo = gr.Interface(
+ fn=trim_words,
+ inputs=["textbox", "number"],
+ outputs=["output"],
+ batch=True,
+ max_batch_size=16
+)
+
+demo.launch()
+```
+
+With the `gr.Blocks` class:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ word = gr.Textbox(label="word")
+ leng = gr.Number(label="leng")
+ output = gr.Textbox(label="Output")
+ with gr.Row():
+ run = gr.Button()
+
+ event = run.click(trim_words, [word, leng], output, batch=True, max_batch_size=16)
+
+demo.launch()
+```
+
+In the example above, 16 requests could be processed in parallel (for a total inference time of 5 seconds), instead of each request being processed separately (for a total
+inference time of 80 seconds). Many Hugging Face `transformers` and `diffusers` models work very naturally with Gradio's batch mode: here's [an example demo using diffusers to
+generate images in batches](https://github.com/gradio-app/gradio/blob/main/demo/diffusers_with_batching/run.py)
+
+
diff --git a/guides/04_additional-features/07_sharing-your-app.md b/guides/04_additional-features/07_sharing-your-app.md
new file mode 100644
index 0000000..2c7da82
--- /dev/null
+++ b/guides/04_additional-features/07_sharing-your-app.md
@@ -0,0 +1,476 @@
+# Sharing Your App
+
+In this Guide, we dive more deeply into the various aspects of sharing a Gradio app with others. We will cover:
+
+1. [Sharing demos with the share parameter](#sharing-demos)
+2. [Hosting on HF Spaces](#hosting-on-hf-spaces)
+3. [Sharing Deep Links](#sharing-deep-links)
+4. [Embedding hosted spaces](#embedding-hosted-spaces)
+5. [Using the API page](#api-page)
+6. [Accessing network requests](#accessing-the-network-request-directly)
+7. [Mounting within FastAPI](#mounting-within-another-fast-api-app)
+8. [Authentication](#authentication)
+9. [MCP Servers](#mcp-servers)
+10. [Rate Limits](#rate-limits)
+11. [Analytics](#analytics)
+12. [Progressive Web Apps (PWAs)](#progressive-web-app-pwa)
+
+## Sharing Demos
+
+Gradio demos can be easily shared publicly by setting `share=True` in the `launch()` method. Like this:
+
+```python
+import gradio as gr
+
+def greet(name):
+ return "Hello " + name + "!"
+
+demo = gr.Interface(fn=greet, inputs="textbox", outputs="textbox")
+
+demo.launch(share=True) # Share your demo with just 1 extra parameter 🚀
+```
+
+This generates a public, shareable link that you can send to anybody! When you send this link, the user on the other side can try out the model in their browser. Because the processing happens on your device (as long as your device stays on), you don't have to worry about any packaging any dependencies.
+
+
+
+
+A share link usually looks something like this: **https://07ff8706ab.gradio.live**. Although the link is served through the Gradio Share Servers, these servers are only a proxy for your local server, and do not store any data sent through your app. Share links expire after 1 week. (it is [also possible to set up your own Share Server](https://github.com/huggingface/frp/) on your own cloud server to overcome this restriction.)
+
+Tip: Keep in mind that share links are publicly accessible, meaning that anyone can use your model for prediction! Therefore, make sure not to expose any sensitive information through the functions you write, or allow any critical changes to occur on your device. Or you can [add authentication to your Gradio app](#authentication) as discussed below.
+
+Note that by default, `share=False`, which means that your server is only running locally. (This is the default, except in Google Colab notebooks, where share links are automatically created). As an alternative to using share links, you can use use [SSH port-forwarding](https://www.ssh.com/ssh/tunneling/example) to share your local server with specific users.
+
+
+## Hosting on HF Spaces
+
+If you'd like to have a permanent link to your Gradio demo on the internet, use Hugging Face Spaces. [Hugging Face Spaces](http://huggingface.co/spaces/) provides the infrastructure to permanently host your machine learning model for free!
+
+After you have [created a free Hugging Face account](https://huggingface.co/join), you have two methods to deploy your Gradio app to Hugging Face Spaces:
+
+1. From terminal: run `gradio deploy` in your app directory. The CLI will gather some basic metadata, upload all the files in the current directory (respecting any `.gitignore` file that may be present in the root of the directory), and then launch your app on Spaces. To update your Space, you can re-run this command or enable the Github Actions option in the CLI to automatically update the Spaces on `git push`.
+
+2. From your browser: Drag and drop a folder containing your Gradio model and all related files [here](https://huggingface.co/new-space). See [this guide how to host on Hugging Face Spaces](https://huggingface.co/blog/gradio-spaces) for more information, or watch the embedded video:
+
+
+
+## Sharing Deep Links
+
+You can add a button to your Gradio app that creates a unique URL you can use to share your app and all components **as they currently are** with others. This is useful for sharing unique and interesting generations from your application , or for saving a snapshot of your app at a particular point in time.
+
+To add a deep link button to your app, place the `gr.DeepLinkButton` component anywhere in your app.
+For the URL to be accessible to others, your app must be available at a public URL. So be sure to host your app like Hugging Face Spaces or use the `share=True` parameter when launching your app.
+
+Let's see an example of how this works. Here's a simple Gradio chat ap that uses the `gr.DeepLinkButton` component. After a couple of messages, click the deep link button and paste it into a new browser tab to see the app as it is at that point in time.
+
+$code_deep_link
+$demo_deep_link
+
+
+## Embedding Hosted Spaces
+
+Once you have hosted your app on Hugging Face Spaces (or on your own server), you may want to embed the demo on a different website, such as your blog or your portfolio. Embedding an interactive demo allows people to try out the machine learning model that you have built, without needing to download or install anything — right in their browser! The best part is that you can embed interactive demos even in static websites, such as GitHub pages.
+
+There are two ways to embed your Gradio demos. You can find quick links to both options directly on the Hugging Face Space page, in the "Embed this Space" dropdown option:
+
+
+
+### Embedding with Web Components
+
+Web components typically offer a better experience to users than IFrames. Web components load lazily, meaning that they won't slow down the loading time of your website, and they automatically adjust their height based on the size of the Gradio app.
+
+To embed with Web Components:
+
+1. Import the gradio JS library into into your site by adding the script below in your site (replace {GRADIO_VERSION} in the URL with the library version of Gradio you are using).
+
+```html
+
+```
+
+2. Add
+
+```html
+
+```
+
+element where you want to place the app. Set the `src=` attribute to your Space's embed URL, which you can find in the "Embed this Space" button. For example:
+
+```html
+
+```
+
+
+
+You can see examples of how web components look on the Gradio landing page.
+
+You can also customize the appearance and behavior of your web component with attributes that you pass into the `` tag:
+
+- `src`: as we've seen, the `src` attributes links to the URL of the hosted Gradio demo that you would like to embed
+- `space`: an optional shorthand if your Gradio demo is hosted on Hugging Face Space. Accepts a `username/space_name` instead of a full URL. Example: `gradio/Echocardiogram-Segmentation`. If this attribute attribute is provided, then `src` does not need to be provided.
+- `control_page_title`: a boolean designating whether the html title of the page should be set to the title of the Gradio app (by default `"false"`)
+- `initial_height`: the initial height of the web component while it is loading the Gradio app, (by default `"300px"`). Note that the final height is set based on the size of the Gradio app.
+- `container`: whether to show the border frame and information about where the Space is hosted (by default `"true"`)
+- `info`: whether to show just the information about where the Space is hosted underneath the embedded app (by default `"true"`)
+- `autoscroll`: whether to autoscroll to the output when prediction has finished (by default `"false"`)
+- `eager`: whether to load the Gradio app as soon as the page loads (by default `"false"`)
+- `theme_mode`: whether to use the `dark`, `light`, or default `system` theme mode (by default `"system"`)
+- `render`: an event that is triggered once the embedded space has finished rendering.
+
+Here's an example of how to use these attributes to create a Gradio app that does not lazy load and has an initial height of 0px.
+
+```html
+
+```
+
+Here's another example of how to use the `render` event. An event listener is used to capture the `render` event and will call the `handleLoadComplete()` function once rendering is complete.
+
+```html
+
+```
+
+_Note: While Gradio's CSS will never impact the embedding page, the embedding page can affect the style of the embedded Gradio app. Make sure that any CSS in the parent page isn't so general that it could also apply to the embedded Gradio app and cause the styling to break. Element selectors such as `header { ... }` and `footer { ... }` will be the most likely to cause issues._
+
+### Embedding with IFrames
+
+To embed with IFrames instead (if you cannot add javascript to your website, for example), add this element:
+
+```html
+
+```
+
+Again, you can find the `src=` attribute to your Space's embed URL, which you can find in the "Embed this Space" button.
+
+Note: if you use IFrames, you'll probably want to add a fixed `height` attribute and set `style="border:0;"` to remove the border. In addition, if your app requires permissions such as access to the webcam or the microphone, you'll need to provide that as well using the `allow` attribute.
+
+## API Page
+
+You can use almost any Gradio app as an API! In the footer of a Gradio app [like this one](https://huggingface.co/spaces/gradio/hello_world), you'll see a "Use via API" link.
+
+
+
+This is a page that lists the endpoints that can be used to query the Gradio app, via our supported clients: either [the Python client](https://gradio.app/guides/getting-started-with-the-python-client/), or [the JavaScript client](https://gradio.app/guides/getting-started-with-the-js-client/). For each endpoint, Gradio automatically generates the parameters and their types, as well as example inputs, like this.
+
+
+
+The endpoints are automatically created when you launch a Gradio application. If you are using Gradio `Blocks`, you can also name each event listener, such as
+
+```python
+btn.click(add, [num1, num2], output, api_name="addition")
+```
+
+This will add and document the endpoint `/addition/` to the automatically generated API page. Read more about the [API page here](./view-api-page).
+
+## Accessing the Network Request Directly
+
+When a user makes a prediction to your app, you may need the underlying network request, in order to get the request headers (e.g. for advanced authentication), log the client's IP address, getting the query parameters, or for other reasons. Gradio supports this in a similar manner to FastAPI: simply add a function parameter whose type hint is `gr.Request` and Gradio will pass in the network request as that parameter. Here is an example:
+
+```python
+import gradio as gr
+
+def echo(text, request: gr.Request):
+ if request:
+ print("Request headers dictionary:", request.headers)
+ print("IP address:", request.client.host)
+ print("Query parameters:", dict(request.query_params))
+ return text
+
+io = gr.Interface(echo, "textbox", "textbox").launch()
+```
+
+Note: if your function is called directly instead of through the UI (this happens, for
+example, when examples are cached, or when the Gradio app is called via API), then `request` will be `None`.
+You should handle this case explicitly to ensure that your app does not throw any errors. That is why
+we have the explicit check `if request`.
+
+## Mounting Within Another FastAPI App
+
+In some cases, you might have an existing FastAPI app, and you'd like to add a path for a Gradio demo.
+You can easily do this with `gradio.mount_gradio_app()`.
+
+Here's a complete example:
+
+$code_custom_path
+
+Note that this approach also allows you run your Gradio apps on custom paths (`http://localhost:8000/gradio` in the example above).
+
+
+## Authentication
+
+### Password-protected app
+
+You may wish to put an authentication page in front of your app to limit who can open your app. With the `auth=` keyword argument in the `launch()` method, you can provide a tuple with a username and password, or a list of acceptable username/password tuples; Here's an example that provides password-based authentication for a single user named "admin":
+
+```python
+demo.launch(auth=("admin", "pass1234"))
+```
+
+For more complex authentication handling, you can even pass a function that takes a username and password as arguments, and returns `True` to allow access, `False` otherwise.
+
+Here's an example of a function that accepts any login where the username and password are the same:
+
+```python
+def same_auth(username, password):
+ return username == password
+demo.launch(auth=same_auth)
+```
+
+If you have multiple users, you may wish to customize the content that is shown depending on the user that is logged in. You can retrieve the logged in user by [accessing the network request directly](#accessing-the-network-request-directly) as discussed above, and then reading the `.username` attribute of the request. Here's an example:
+
+
+```python
+import gradio as gr
+
+def update_message(request: gr.Request):
+ return f"Welcome, {request.username}"
+
+with gr.Blocks() as demo:
+ m = gr.Markdown()
+ demo.load(update_message, None, m)
+
+demo.launch(auth=[("Abubakar", "Abubakar"), ("Ali", "Ali")])
+```
+
+Note: For authentication to work properly, third party cookies must be enabled in your browser. This is not the case by default for Safari or for Chrome Incognito Mode.
+
+If users visit the `/logout` page of your Gradio app, they will automatically be logged out and session cookies deleted. This allows you to add logout functionality to your Gradio app as well. Let's update the previous example to include a log out button:
+
+```python
+import gradio as gr
+
+def update_message(request: gr.Request):
+ return f"Welcome, {request.username}"
+
+with gr.Blocks() as demo:
+ m = gr.Markdown()
+ logout_button = gr.Button("Logout", link="/logout")
+ demo.load(update_message, None, m)
+
+demo.launch(auth=[("Pete", "Pete"), ("Dawood", "Dawood")])
+```
+By default, visiting `/logout` logs the user out from **all sessions** (e.g. if they are logged in from multiple browsers or devices, all will be signed out). If you want to log out only from the **current session**, add the query parameter `all_session=false` (i.e. `/logout?all_session=false`).
+
+Note: Gradio's built-in authentication provides a straightforward and basic layer of access control but does not offer robust security features for applications that require stringent access controls (e.g. multi-factor authentication, rate limiting, or automatic lockout policies).
+
+### OAuth (Login via Hugging Face)
+
+Gradio natively supports OAuth login via Hugging Face. In other words, you can easily add a _"Sign in with Hugging Face"_ button to your demo, which allows you to get a user's HF username as well as other information from their HF profile. Check out [this Space](https://huggingface.co/spaces/Wauplin/gradio-oauth-demo) for a live demo.
+
+To enable OAuth, you must set `hf_oauth: true` as a Space metadata in your README.md file. This will register your Space
+as an OAuth application on Hugging Face. Next, you can use `gr.LoginButton` to add a login button to
+your Gradio app. Once a user is logged in with their HF account, you can retrieve their profile by adding a parameter of type
+`gr.OAuthProfile` to any Gradio function. The user profile will be automatically injected as a parameter value. If you want
+to perform actions on behalf of the user (e.g. list user's private repos, create repo, etc.), you can retrieve the user
+token by adding a parameter of type `gr.OAuthToken`. You must define which scopes you will use in your Space metadata
+(see [documentation](https://huggingface.co/docs/hub/spaces-oauth#scopes) for more details).
+
+Here is a short example:
+
+$code_login_with_huggingface
+
+When the user clicks on the login button, they get redirected in a new page to authorize your Space.
+
+
+
+
+
+Users can revoke access to their profile at any time in their [settings](https://huggingface.co/settings/connected-applications).
+
+As seen above, OAuth features are available only when your app runs in a Space. However, you often need to test your app
+locally before deploying it. To test OAuth features locally, your machine must be logged in to Hugging Face. Please run `huggingface-cli login` or set `HF_TOKEN` as environment variable with one of your access token. You can generate a new token in your settings page (https://huggingface.co/settings/tokens). Then, clicking on the `gr.LoginButton` will log in to your local Hugging Face profile, allowing you to debug your app with your Hugging Face account before deploying it to a Space.
+
+**Security Note**: It is important to note that adding a `gr.LoginButton` does not restrict users from using your app, in the same way that adding [username-password authentication](/guides/sharing-your-app#password-protected-app) does. This means that users of your app who have not logged in with Hugging Face can still access and run events in your Gradio app -- the difference is that the `gr.OAuthProfile` or `gr.OAuthToken` will be `None` in the corresponding functions.
+
+
+### OAuth (with external providers)
+
+It is also possible to authenticate with external OAuth providers (e.g. Google OAuth) in your Gradio apps. To do this, first mount your Gradio app within a FastAPI app ([as discussed above](#mounting-within-another-fast-api-app)). Then, you must write an *authentication function*, which gets the user's username from the OAuth provider and returns it. This function should be passed to the `auth_dependency` parameter in `gr.mount_gradio_app`.
+
+Similar to [FastAPI dependency functions](https://fastapi.tiangolo.com/tutorial/dependencies/), the function specified by `auth_dependency` will run before any Gradio-related route in your FastAPI app. The function should accept a single parameter: the FastAPI `Request` and return either a string (representing a user's username) or `None`. If a string is returned, the user will be able to access the Gradio-related routes in your FastAPI app.
+
+First, let's show a simplistic example to illustrate the `auth_dependency` parameter:
+
+```python
+from fastapi import FastAPI, Request
+import gradio as gr
+
+app = FastAPI()
+
+def get_user(request: Request):
+ return request.headers.get("user")
+
+demo = gr.Interface(lambda s: f"Hello {s}!", "textbox", "textbox")
+
+app = gr.mount_gradio_app(app, demo, path="/demo", auth_dependency=get_user)
+
+if __name__ == '__main__':
+ uvicorn.run(app)
+```
+
+In this example, only requests that include a "user" header will be allowed to access the Gradio app. Of course, this does not add much security, since any user can add this header in their request.
+
+Here's a more complete example showing how to add Google OAuth to a Gradio app (assuming you've already created OAuth Credentials on the [Google Developer Console](https://console.cloud.google.com/project)):
+
+```python
+import os
+from authlib.integrations.starlette_client import OAuth, OAuthError
+from fastapi import FastAPI, Depends, Request
+from starlette.config import Config
+from starlette.responses import RedirectResponse
+from starlette.middleware.sessions import SessionMiddleware
+import uvicorn
+import gradio as gr
+
+app = FastAPI()
+
+# Replace these with your own OAuth settings
+GOOGLE_CLIENT_ID = "..."
+GOOGLE_CLIENT_SECRET = "..."
+SECRET_KEY = "..."
+
+config_data = {'GOOGLE_CLIENT_ID': GOOGLE_CLIENT_ID, 'GOOGLE_CLIENT_SECRET': GOOGLE_CLIENT_SECRET}
+starlette_config = Config(environ=config_data)
+oauth = OAuth(starlette_config)
+oauth.register(
+ name='google',
+ server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
+ client_kwargs={'scope': 'openid email profile'},
+)
+
+SECRET_KEY = os.environ.get('SECRET_KEY') or "a_very_secret_key"
+app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
+
+# Dependency to get the current user
+def get_user(request: Request):
+ user = request.session.get('user')
+ if user:
+ return user['name']
+ return None
+
+@app.get('/')
+def public(user: dict = Depends(get_user)):
+ if user:
+ return RedirectResponse(url='/gradio')
+ else:
+ return RedirectResponse(url='/login-demo')
+
+@app.route('/logout')
+async def logout(request: Request):
+ request.session.pop('user', None)
+ return RedirectResponse(url='/')
+
+@app.route('/login')
+async def login(request: Request):
+ redirect_uri = request.url_for('auth')
+ # If your app is running on https, you should ensure that the
+ # `redirect_uri` is https, e.g. uncomment the following lines:
+ #
+ # from urllib.parse import urlparse, urlunparse
+ # redirect_uri = urlunparse(urlparse(str(redirect_uri))._replace(scheme='https'))
+ return await oauth.google.authorize_redirect(request, redirect_uri)
+
+@app.route('/auth')
+async def auth(request: Request):
+ try:
+ access_token = await oauth.google.authorize_access_token(request)
+ except OAuthError:
+ return RedirectResponse(url='/')
+ request.session['user'] = dict(access_token)["userinfo"]
+ return RedirectResponse(url='/')
+
+with gr.Blocks() as login_demo:
+ gr.Button("Login", link="/login")
+
+app = gr.mount_gradio_app(app, login_demo, path="/login-demo")
+
+def greet(request: gr.Request):
+ return f"Welcome to Gradio, {request.username}"
+
+with gr.Blocks() as main_demo:
+ m = gr.Markdown("Welcome to Gradio!")
+ gr.Button("Logout", link="/logout")
+ main_demo.load(greet, None, m)
+
+app = gr.mount_gradio_app(app, main_demo, path="/gradio", auth_dependency=get_user)
+
+if __name__ == '__main__':
+ uvicorn.run(app)
+```
+
+There are actually two separate Gradio apps in this example! One that simply displays a log in button (this demo is accessible to any user), while the other main demo is only accessible to users that are logged in. You can try this example out on [this Space](https://huggingface.co/spaces/gradio/oauth-example).
+
+## MCP Servers
+
+Gradio apps can function as MCP (Model Context Protocol) servers, allowing LLMs to use your app's functions as tools. By simply setting `mcp_server=True` in the `.launch()` method, Gradio automatically converts your app's functions into MCP tools that can be called by MCP clients like Claude Desktop, Cursor, or Cline. The server exposes tools based on your function names, docstrings, and type hints, and can handle file uploads, authentication headers, and progress updates. You can also create MCP-only functions using `gr.api` and expose resources and prompts using decorators. For a comprehensive guide on building MCP servers with Gradio, see [Building an MCP Server with Gradio](https://www.gradio.app/guides/building-mcp-server-with-gradio).
+
+## Rate Limits
+
+When publishing your app publicly, and making it available via API or via MCP server, you might want to set rate limits to prevent users from abusing your app. You can identify users using their IP address (using the `gr.Request` object [as discussed above](#accessing-the-network-request-directly)) or, if they are logged in via Hugging Face OAuth, using their username. To see a complete example of how to set rate limits, please see [this Gradio app](https://github.com/gradio-app/gradio/blob/main/demo/rate_limit/run.py).
+
+## Analytics
+
+By default, Gradio collects certain analytics to help us better understand the usage of the `gradio` library. This includes the following information:
+
+* What environment the Gradio app is running on (e.g. Colab Notebook, Hugging Face Spaces)
+* What input/output components are being used in the Gradio app
+* Whether the Gradio app is utilizing certain advanced features, such as `auth` or `show_error`
+* The IP address which is used solely to measure the number of unique developers using Gradio
+* The version of Gradio that is running
+
+No information is collected from _users_ of your Gradio app. If you'd like to disable analytics altogether, you can do so by setting the `analytics_enabled` parameter to `False` in `gr.Blocks`, `gr.Interface`, or `gr.ChatInterface`. Or, you can set the GRADIO_ANALYTICS_ENABLED environment variable to `"False"` to apply this to all Gradio apps created across your system.
+
+*Note*: this reflects the analytics policy as of `gradio>=4.32.0`.
+
+## Progressive Web App (PWA)
+
+[Progressive Web Apps (PWAs)](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps) are web applications that are regular web pages or websites, but can appear to the user like installable platform-specific applications.
+
+Gradio apps can be easily served as PWAs by setting the `pwa=True` parameter in the `launch()` method. Here's an example:
+
+```python
+import gradio as gr
+
+def greet(name):
+ return "Hello " + name + "!"
+
+demo = gr.Interface(fn=greet, inputs="textbox", outputs="textbox")
+
+demo.launch(pwa=True) # Launch your app as a PWA
+```
+
+This will generate a PWA that can be installed on your device. Here's how it looks:
+
+
+
+When you specify `favicon_path` in the `launch()` method, the icon will be used as the app's icon. Here's an example:
+
+```python
+demo.launch(pwa=True, favicon_path="./hf-logo.svg") # Use a custom icon for your PWA
+```
+
+
diff --git a/guides/04_additional-features/08_file-access.md b/guides/04_additional-features/08_file-access.md
new file mode 100644
index 0000000..9cc2466
--- /dev/null
+++ b/guides/04_additional-features/08_file-access.md
@@ -0,0 +1,97 @@
+# Security and File Access
+
+Sharing your Gradio app with others (by hosting it on Spaces, on your own server, or through temporary share links) **exposes** certain files on your machine to the internet. Files that are exposed can be accessed at a special URL:
+
+```bash
+http:///gradio_api/file=
+```
+
+This guide explains which files are exposed as well as some best practices for making sure the files on your machine are secure.
+
+## Files Gradio allows users to access
+
+- **1. Static files**. You can designate static files or directories using the `gr.set_static_paths` function. Static files are not be copied to the Gradio cache (see below) and will be served directly from your computer. This can help save disk space and reduce the time your app takes to launch but be mindful of possible security implications as any static files are accessible to all useres of your Gradio app.
+
+- **2. Files in the `allowed_paths` parameter in `launch()`**. This parameter allows you to pass in a list of additional directories or exact filepaths you'd like to allow users to have access to. (By default, this parameter is an empty list).
+
+- **3. Files in Gradio's cache**. After you launch your Gradio app, Gradio copies certain files into a temporary cache and makes these files accessible to users. Let's unpack this in more detail below.
+
+
+## The Gradio cache
+
+First, it's important to understand why Gradio has a cache at all. Gradio copies files to a cache directory before returning them to the frontend. This prevents files from being overwritten by one user while they are still needed by another user of your application. For example, if your prediction function returns a video file, then Gradio will move that video to the cache after your prediction function runs and returns a URL the frontend can use to show the video. Any file in the cache is available via URL to all users of your running application.
+
+Tip: You can customize the location of the cache by setting the `GRADIO_TEMP_DIR` environment variable to an absolute path, such as `/home/usr/scripts/project/temp/`.
+
+### Files Gradio moves to the cache
+
+Gradio moves three kinds of files into the cache
+
+1. Files specified by the developer before runtime, e.g. cached examples, default values of components, or files passed into parameters such as the `avatar_images` of `gr.Chatbot`
+
+2. File paths returned by a prediction function in your Gradio application, if they ALSO meet one of the conditions below:
+
+* It is in the `allowed_paths` parameter of the `Blocks.launch` method.
+* It is in the current working directory of the python interpreter.
+* It is in the temp directory obtained by `tempfile.gettempdir()`.
+
+**Note:** files in the current working directory whose name starts with a period (`.`) will not be moved to the cache, even if they are returned from a prediction function, since they often contain sensitive information.
+
+If none of these criteria are met, the prediction function that is returning that file will raise an exception instead of moving the file to cache. Gradio performs this check so that arbitrary files on your machine cannot be accessed.
+
+3. Files uploaded by a user to your Gradio app (e.g. through the `File` or `Image` input components).
+
+Tip: If at any time Gradio blocks a file that you would like it to process, add its path to the `allowed_paths` parameter.
+
+## The files Gradio will not allow others to access
+
+While running, Gradio apps will NOT ALLOW users to access:
+
+- **Files that you explicitly block via the `blocked_paths` parameter in `launch()`**. You can pass in a list of additional directories or exact filepaths to the `blocked_paths` parameter in `launch()`. This parameter takes precedence over the files that Gradio exposes by default, or by the `allowed_paths` parameter or the `gr.set_static_paths` function.
+
+- **Any other paths on the host machine**. Users should NOT be able to access other arbitrary paths on the host.
+
+## Uploading Files
+
+Sharing your Gradio application will also allow users to upload files to your computer or server. You can set a maximum file size for uploads to prevent abuse and to preserve disk space. You can do this with the `max_file_size` parameter of `.launch`. For example, the following two code snippets limit file uploads to 5 megabytes per file.
+
+```python
+import gradio as gr
+
+demo = gr.Interface(lambda x: x, "image", "image")
+
+demo.launch(max_file_size="5mb")
+# or
+demo.launch(max_file_size=5 * gr.FileSize.MB)
+```
+
+## Best Practices
+
+* Set a `max_file_size` for your application.
+* Do not return arbitrary user input from a function that is connected to a file-based output component (`gr.Image`, `gr.File`, etc.). For example, the following interface would allow anyone to move an arbitrary file in your local directory to the cache: `gr.Interface(lambda s: s, "text", "file")`. This is because the user input is treated as an arbitrary file path.
+* Make `allowed_paths` as small as possible. If a path in `allowed_paths` is a directory, any file within that directory can be accessed. Make sure the entires of `allowed_paths` only contains files related to your application.
+* Run your gradio application from the same directory the application file is located in. This will narrow the scope of files Gradio will be allowed to move into the cache. For example, prefer `python app.py` to `python Users/sources/project/app.py`.
+
+
+## Example: Accessing local files
+Both `gr.set_static_paths` and the `allowed_paths` parameter in launch expect absolute paths. Below is a minimal example to display a local `.png` image file in an HTML block.
+
+```txt
+├── assets
+│ └── logo.png
+└── app.py
+```
+For the example directory structure, `logo.png` and any other files in the `assets` folder can be accessed from your Gradio app in `app.py` as follows:
+
+```python
+from pathlib import Path
+
+import gradio as gr
+
+gr.set_static_paths(paths=[Path.cwd().absolute()/"assets"])
+
+with gr.Blocks() as demo:
+ gr.HTML("
")
+
+demo.launch()
+```
diff --git a/guides/04_additional-features/09_multipage-apps.md b/guides/04_additional-features/09_multipage-apps.md
new file mode 100644
index 0000000..ea873b3
--- /dev/null
+++ b/guides/04_additional-features/09_multipage-apps.md
@@ -0,0 +1,129 @@
+# Multipage Apps
+
+Your Gradio app can support multiple pages with the `Blocks.route()` method. Here's what a multipage Gradio app generally looks like:
+
+```python
+with gr.Blocks() as demo: # Main page
+ name = gr.Textbox(label="Name")
+ ...
+with demo.route("Second page", "/second"):
+ num = gr.Number()
+ ...
+
+demo.launch()
+```
+
+This allows you to define links to separate pages, each with a separate URL, which are linked to the top of the Gradio app in an automatically-generated navbar.
+
+Here's a complete example:
+
+$code_multipage
+
+All of these pages will share the same backend, including the same queue.
+
+Note: multipage apps do not support interactions between pages, e.g. an event listener on one page cannot output to a component on another page. Use `gr.Tabs()` for this type of functionality instead of pages.
+
+**Separate Files**
+
+For maintainability, you may want to write the code for different pages in different files. Because any Gradio Blocks can be imported and rendered inside another Blocks using the `.render()` method, you can do this as follows.
+
+Create one main file, say `app.py` and create separate Python files for each page:
+
+```
+- app.py
+- main_page.py
+- second_page.py
+```
+
+The Python file corresponding to each page should consist of a regular Gradio Blocks, Interface, or ChatInterface application, e.g.
+
+`main_page.py`
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ gr.Image()
+
+if __name__ == "__main__":
+ demo.launch()
+```
+
+`second_page.py`
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ t = gr.Textbox()
+ demo.load(lambda : "Loaded", None, t)
+
+if __name__ == "__main__":
+ demo.launch()
+```
+
+In your main `app.py` file, simply import the Gradio demos from the page files and `.render()` them:
+
+`app.py`
+
+```py
+import gradio as gr
+
+import main_page, second_page
+
+with gr.Blocks() as demo:
+ main_page.demo.render()
+with demo.route("Second Page"):
+ second_page.demo.render()
+
+if __name__ == "__main__":
+ demo.launch()
+```
+
+This allows you to run each page as an independent Gradio app for testing, while also creating a single file `app.py` that serves as the entrypoint for the complete multipage app.
+
+## Customizing the Navbar
+
+By default, Gradio automatically generates a navigation bar for multipage apps that displays all your pages with "Home" as the title for the main page. You can customize the navbar behavior using the `gr.Navbar` component.
+
+### Per-Page Navbar Configuration
+
+You can have different navbar configurations for each page of your app:
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ # Navbar for the main page
+ navbar = gr.Navbar(
+ visible=True,
+ main_page_name="Dashboard",
+ value=[("About", "https://example.com/about")]
+ )
+
+ gr.Textbox(label="Main page content")
+
+with demo.route("Settings"):
+ # Different navbar for the Settings page
+ navbar = gr.Navbar(
+ visible=True,
+ main_page_name="Home",
+ value=[("Documentation", "https://docs.example.com")]
+ )
+ gr.Textbox(label="Settings page")
+
+demo.launch()
+```
+
+
+**Important Notes:**
+- You can have one `gr.Navbar` component per page. Each page's navbar configuration is independent.
+- The `main_page_name` parameter customizes the title of the home page link in the navbar.
+- The `value` parameter allows you to add additional links to the navbar, which can be internal pages or external URLs.
+- If no `gr.Navbar` component is present on a page, the default navbar behavior is used (visible with "Home" as the home page title).
+- You can update the navbar properties using standard Gradio event handling, just like with any other component.
+
+Here's an example that demonstrates the last point:
+
+$code_navbar_customization
+
diff --git a/guides/04_additional-features/10_environment-variables.md b/guides/04_additional-features/10_environment-variables.md
new file mode 100644
index 0000000..3e9a780
--- /dev/null
+++ b/guides/04_additional-features/10_environment-variables.md
@@ -0,0 +1,265 @@
+# Environment Variables
+
+Environment variables in Gradio provide a way to customize your applications and launch settings without changing the codebase. In this guide, we'll explore the key environment variables supported in Gradio and how to set them.
+
+## Key Environment Variables
+
+### 1. `GRADIO_SERVER_PORT`
+
+- **Description**: Specifies the port on which the Gradio app will run.
+- **Default**: `7860`
+- **Example**:
+ ```bash
+ export GRADIO_SERVER_PORT=8000
+ ```
+
+### 2. `GRADIO_SERVER_NAME`
+
+- **Description**: Defines the host name for the Gradio server. To make Gradio accessible from any IP address, set this to `"0.0.0.0"`
+- **Default**: `"127.0.0.1"`
+- **Example**:
+ ```bash
+ export GRADIO_SERVER_NAME="0.0.0.0"
+ ```
+
+### 3. `GRADIO_NUM_PORTS`
+
+- **Description**: Defines the number of ports to try when starting the Gradio server.
+- **Default**: `100`
+- **Example**:
+ ```bash
+ export GRADIO_NUM_PORTS=200
+ ```
+
+### 4. `GRADIO_ANALYTICS_ENABLED`
+
+- **Description**: Whether Gradio should provide
+- **Default**: `"True"`
+- **Options**: `"True"`, `"False"`
+- **Example**:
+ ```sh
+ export GRADIO_ANALYTICS_ENABLED="True"
+ ```
+
+### 5. `GRADIO_DEBUG`
+
+- **Description**: Enables or disables debug mode in Gradio. If debug mode is enabled, the main thread does not terminate allowing error messages to be printed in environments such as Google Colab.
+- **Default**: `0`
+- **Example**:
+ ```sh
+ export GRADIO_DEBUG=1
+ ```
+
+### 6. `GRADIO_FLAGGING_MODE`
+
+- **Description**: Controls whether users can flag inputs/outputs in the Gradio interface. See [the Guide on flagging](/guides/using-flagging) for more details.
+- **Default**: `"manual"`
+- **Options**: `"never"`, `"manual"`, `"auto"`
+- **Example**:
+ ```sh
+ export GRADIO_FLAGGING_MODE="never"
+ ```
+
+### 7. `GRADIO_TEMP_DIR`
+
+- **Description**: Specifies the directory where temporary files created by Gradio are stored.
+- **Default**: System default temporary directory
+- **Example**:
+ ```sh
+ export GRADIO_TEMP_DIR="/path/to/temp"
+ ```
+
+### 8. `GRADIO_ROOT_PATH`
+
+- **Description**: Sets the root path for the Gradio application. Useful if running Gradio [behind a reverse proxy](/guides/running-gradio-on-your-web-server-with-nginx).
+- **Default**: `""`
+- **Example**:
+ ```sh
+ export GRADIO_ROOT_PATH="/myapp"
+ ```
+
+### 9. `GRADIO_SHARE`
+
+- **Description**: Enables or disables sharing the Gradio app.
+- **Default**: `"False"`
+- **Options**: `"True"`, `"False"`
+- **Example**:
+ ```sh
+ export GRADIO_SHARE="True"
+ ```
+
+### 10. `GRADIO_ALLOWED_PATHS`
+
+- **Description**: Sets a list of complete filepaths or parent directories that gradio is allowed to serve. Must be absolute paths. Warning: if you provide directories, any files in these directories or their subdirectories are accessible to all users of your app. Multiple items can be specified by separating items with commas.
+- **Default**: `""`
+- **Example**:
+ ```sh
+ export GRADIO_ALLOWED_PATHS="/mnt/sda1,/mnt/sda2"
+ ```
+
+### 11. `GRADIO_BLOCKED_PATHS`
+
+- **Description**: Sets a list of complete filepaths or parent directories that gradio is not allowed to serve (i.e. users of your app are not allowed to access). Must be absolute paths. Warning: takes precedence over `allowed_paths` and all other directories exposed by Gradio by default. Multiple items can be specified by separating items with commas.
+- **Default**: `""`
+- **Example**:
+ ```sh
+ export GRADIO_BLOCKED_PATHS="/users/x/gradio_app/admin,/users/x/gradio_app/keys"
+ ```
+
+### 12. `FORWARDED_ALLOW_IPS`
+
+- **Description**: This is not a Gradio-specific environment variable, but rather one used in server configurations, specifically `uvicorn` which is used by Gradio internally. This environment variable is useful when deploying applications behind a reverse proxy. It defines a list of IP addresses that are trusted to forward traffic to your application. When set, the application will trust the `X-Forwarded-For` header from these IP addresses to determine the original IP address of the user making the request. This means that if you use the `gr.Request` [object's](https://www.gradio.app/docs/gradio/request) `client.host` property, it will correctly get the user's IP address instead of the IP address of the reverse proxy server. Note that only trusted IP addresses (i.e. the IP addresses of your reverse proxy servers) should be added, as any server with these IP addresses can modify the `X-Forwarded-For` header and spoof the client's IP address.
+- **Default**: `"127.0.0.1"`
+- **Example**:
+ ```sh
+ export FORWARDED_ALLOW_IPS="127.0.0.1,192.168.1.100"
+ ```
+
+### 13. `GRADIO_CACHE_EXAMPLES`
+
+- **Description**: Whether or not to cache examples by default in `gr.Interface()`, `gr.ChatInterface()` or in `gr.Examples()` when no explicit argument is passed for the `cache_examples` parameter. You can set this environment variable to either the string "true" or "false".
+- **Default**: `"false"`
+- **Example**:
+ ```sh
+ export GRADIO_CACHE_EXAMPLES="true"
+ ```
+
+
+### 14. `GRADIO_CACHE_MODE`
+
+- **Description**: How to cache examples. Only applies if `cache_examples` is set to `True` either via enviornment variable or by an explicit parameter, AND no no explicit argument is passed for the `cache_mode` parameter in `gr.Interface()`, `gr.ChatInterface()` or in `gr.Examples()`. Can be set to either the strings "lazy" or "eager." If "lazy", examples are cached after their first use for all users of the app. If "eager", all examples are cached at app launch.
+
+- **Default**: `"eager"`
+- **Example**:
+ ```sh
+ export GRADIO_CACHE_MODE="lazy"
+ ```
+
+
+### 15. `GRADIO_EXAMPLES_CACHE`
+
+- **Description**: If you set `cache_examples=True` in `gr.Interface()`, `gr.ChatInterface()` or in `gr.Examples()`, Gradio will run your prediction function and save the results to disk. By default, this is in the `.gradio/cached_examples//` subdirectory within your app's working directory. You can customize the location of cached example files created by Gradio by setting the environment variable `GRADIO_EXAMPLES_CACHE` to an absolute path or a path relative to your working directory.
+- **Default**: `".gradio/cached_examples/"`
+- **Example**:
+ ```sh
+ export GRADIO_EXAMPLES_CACHE="custom_cached_examples/"
+ ```
+
+
+### 16. `GRADIO_SSR_MODE`
+
+- **Description**: Controls whether server-side rendering (SSR) is enabled. When enabled, the initial HTML is rendered on the server rather than the client, which can improve initial page load performance and SEO.
+
+- **Default**: `"False"` (except on Hugging Face Spaces, where this environment variable sets it to `True`)
+- **Options**: `"True"`, `"False"`
+- **Example**:
+ ```sh
+ export GRADIO_SSR_MODE="True"
+ ```
+
+### 17. `GRADIO_NODE_SERVER_NAME`
+
+- **Description**: Defines the host name for the Gradio node server. (Only applies if `ssr_mode` is set to `True`.)
+- **Default**: `GRADIO_SERVER_NAME` if it is set, otherwise `"127.0.0.1"`
+- **Example**:
+ ```sh
+ export GRADIO_NODE_SERVER_NAME="0.0.0.0"
+ ```
+
+### 18. `GRADIO_NODE_NUM_PORTS`
+
+- **Description**: Defines the number of ports to try when starting the Gradio node server. (Only applies if `ssr_mode` is set to `True`.)
+- **Default**: `100`
+- **Example**:
+ ```sh
+ export GRADIO_NODE_NUM_PORTS=200
+ ```
+
+### 19. `GRADIO_RESET_EXAMPLES_CACHE`
+
+- **Description**: If set to "True", Gradio will delete and recreate the examples cache directory when the app starts instead of reusing the cached example if they already exist.
+- **Default**: `"False"`
+- **Options**: `"True"`, `"False"`
+- **Example**:
+ ```sh
+ export GRADIO_RESET_EXAMPLES_CACHE="True"
+ ```
+
+### 20. `GRADIO_CHAT_FLAGGING_MODE`
+
+- **Description**: Controls whether users can flag messages in `gr.ChatInterface` applications. Similar to `GRADIO_FLAGGING_MODE` but specifically for chat interfaces.
+- **Default**: `"never"`
+- **Options**: `"never"`, `"manual"`
+- **Example**:
+ ```sh
+ export GRADIO_CHAT_FLAGGING_MODE="manual"
+ ```
+
+### 21. `GRADIO_WATCH_DIRS`
+
+- **Description**: Specifies directories to watch for file changes when running Gradio in development mode. When files in these directories change, the Gradio app will automatically reload. Multiple directories can be specified by separating them with commas. This is primarily used by the `gradio` CLI command for development workflows.
+- **Default**: `""`
+- **Example**:
+ ```sh
+ export GRADIO_WATCH_DIRS="/path/to/src,/path/to/templates"
+ ```
+
+### 22. `GRADIO_VIBE_MODE`
+
+- **Description**: Enables the Vibe editor mode, which provides an in-browser chat that can be used to write or edit your Gradio app using natural language. When enabled, anyone who can access the Gradio endpoint can modify files and run arbitrary code on the host machine. Use with extreme caution in production environments.
+- **Default**: `""`
+- **Options**: Any non-empty string enables the mode
+- **Example**:
+ ```sh
+ export GRADIO_VIBE_MODE="1"
+ ```
+
+### 23. `GRADIO_MCP_SERVER`
+
+- **Description**: Enables the MCP (Model Context Protocol) server functionality in Gradio. When enabled, the Gradio app will be set up as an MCP server and documented functions will be added as MCP tools that can be used by LLMs. This allows LLMs to interact with your Gradio app's functionality through the MCP protocol.
+- **Default**: `"False"`
+- **Options**: `"True"`, `"False"`
+- **Example**:
+ ```sh
+ export GRADIO_MCP_SERVER="True"
+ ```
+
+
+### 24. `GRADIO_NUM_WORKERS`
+
+- **Description**: Number of multiple workers to launch in the background to offload traffic for file I/O and static assets from the main Gradio server. Only works when SSR mode is set.
+- **Default**: not set.
+- **Options**: Any positive integer.
+- **Example**:
+ ```sh
+ export GRADIO_NUM_WORKERS=4
+ ```
+
+### 25. `GRADIO_HEARTBEAT_INTERVAL`
+
+- **Description**: Sets the interval, in seconds, between heartbeats that keep a client session alive. When a client disconnects, this heartbeat is used to trigger `unload` events and clean up session state. Lowering this value can help detect disconnections faster in environments such as Kubernetes, where the default interval can delay session cleanup.
+- **Default**: `15`
+- **Example**:
+ ```sh
+ export GRADIO_HEARTBEAT_INTERVAL=5
+ ```
+
+## How to Set Environment Variables
+
+To set environment variables in your terminal, use the `export` command followed by the variable name and its value. For example:
+
+```sh
+export GRADIO_SERVER_PORT=8000
+```
+
+If you're using a `.env` file to manage your environment variables, you can add them like this:
+
+```sh
+GRADIO_SERVER_PORT=8000
+GRADIO_SERVER_NAME="localhost"
+```
+
+Then, use a tool like `dotenv` to load these variables when running your application.
+
+
+
diff --git a/guides/04_additional-features/11_resource-cleanup.md b/guides/04_additional-features/11_resource-cleanup.md
new file mode 100644
index 0000000..89529ca
--- /dev/null
+++ b/guides/04_additional-features/11_resource-cleanup.md
@@ -0,0 +1,48 @@
+# Resource Cleanup
+
+Your Gradio application may create resources during its lifetime.
+Examples of resources are `gr.State` variables, any variables you create and explicitly hold in memory, or files you save to disk.
+Over time, these resources can use up all of your server's RAM or disk space and crash your application.
+
+Gradio provides some tools for you to clean up the resources created by your app:
+
+1. Automatic deletion of `gr.State` variables.
+2. Automatic cache cleanup with the `delete_cache` parameter.
+2. The `Blocks.unload` event.
+
+Let's take a look at each of them individually.
+
+## Automatic deletion of `gr.State`
+
+When a user closes their browser tab, Gradio will automatically delete any `gr.State` variables associated with that user session after 60 minutes. If the user connects again within those 60 minutes, no state will be deleted.
+
+You can control the deletion behavior further with the following two parameters of `gr.State`:
+
+1. `delete_callback` - An arbitrary function that will be called when the variable is deleted. This function must take the state value as input. This function is useful for deleting variables from GPU memory.
+2. `time_to_live` - The number of seconds the state should be stored for after it is created or updated. This will delete variables before the session is closed, so it's useful for clearing state for potentially long running sessions.
+
+## Automatic cache cleanup via `delete_cache`
+
+Your Gradio application will save uploaded and generated files to a special directory called the cache directory. Gradio uses a hashing scheme to ensure that duplicate files are not saved to the cache but over time the size of the cache will grow (especially if your app goes viral 😉).
+
+Gradio can periodically clean up the cache for you if you specify the `delete_cache` parameter of `gr.Blocks()`, `gr.Interface()`, or `gr.ChatInterface()`.
+This parameter is a tuple of the form `[frequency, age]` both expressed in number of seconds.
+Every `frequency` seconds, the temporary files created by this Blocks instance will be deleted if more than `age` seconds have passed since the file was created.
+For example, setting this to (86400, 86400) will delete temporary files every day if they are older than a day old.
+Additionally, the cache will be deleted entirely when the server restarts.
+
+## The `unload` event
+
+Additionally, Gradio now includes a `Blocks.unload()` event, allowing you to run arbitrary cleanup functions when users disconnect (this does not have a 60 minute delay).
+Unlike other gradio events, this event does not accept inputs or outptus.
+You can think of the `unload` event as the opposite of the `load` event.
+
+## Putting it all together
+
+The following demo uses all of these features. When a user visits the page, a special unique directory is created for that user.
+As the user interacts with the app, images are saved to disk in that special directory.
+When the user closes the page, the images created in that session are deleted via the `unload` event.
+The state and files in the cache are cleaned up automatically as well.
+
+$code_state_cleanup
+$demo_state_cleanup
\ No newline at end of file
diff --git a/guides/04_additional-features/12_themes.md b/guides/04_additional-features/12_themes.md
new file mode 100644
index 0000000..0b04d04
--- /dev/null
+++ b/guides/04_additional-features/12_themes.md
@@ -0,0 +1,22 @@
+# Gradio Themes
+
+Gradio themes are the easiest way to customize the look and feel of your app. You can choose from a variety of themes, or create your own. To do so, pass the `theme=` kwarg to the `launch()` method of `Interface`, `ChatInterface`, or `Blocks`. For example:
+
+```python
+demo = gr.Interface()
+demo.launch(theme=gr.themes.Monochrome())
+```
+
+or
+
+```python
+with gr.Blocks() as demo:
+ ... # your code here
+demo.launch(theme=gr.themes.Soft())
+ ...
+```
+
+Gradio comes with a set of prebuilt themes which you can load from `gr.themes.*`. You can extend these themes or create your own themes from scratch - see the [theming guide](https://gradio.app/guides/theming-guide) for more details.
+
+For additional styling ability, you can pass any CSS (as well as custom JavaScript) to your Gradio application. This is discussed in more detail in our [custom JS and CSS guide](/guides/custom-CSS-and-JS).
+
diff --git a/guides/04_additional-features/13_client-side-functions.md b/guides/04_additional-features/13_client-side-functions.md
new file mode 100644
index 0000000..bba81e4
--- /dev/null
+++ b/guides/04_additional-features/13_client-side-functions.md
@@ -0,0 +1,71 @@
+# Client Side Functions
+
+Gradio allows you to run certain "simple" functions directly in the browser by setting `js=True` in your event listeners. This will **automatically convert your Python code into JavaScript**, which significantly improves the responsiveness of your app by avoiding a round trip to the server for simple UI updates.
+
+The difference in responsiveness is most noticeable on hosted applications (like Hugging Face Spaces), when the server is under heavy load, with high-latency connections, or when many users are accessing the app simultaneously.
+
+## When to Use Client Side Functions
+
+Client side functions are ideal for updating component properties (like visibility, placeholders, interactive state, or styling).
+
+Here's a basic example:
+
+```py
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Row() as row:
+ btn = gr.Button("Hide this row")
+
+ # This function runs in the browser without a server roundtrip
+ btn.click(
+ lambda: gr.Row(visible=False),
+ None,
+ row,
+ js=True
+ )
+
+demo.launch()
+```
+
+
+## Limitations
+
+Client side functions have some important restrictions:
+* They can only update component properties (not values)
+* They cannot take any inputs
+
+Here are some functions that will work with `js=True`:
+
+```py
+# Simple property updates
+lambda: gr.Textbox(lines=4)
+
+# Multiple component updates
+lambda: [gr.Textbox(lines=4), gr.Button(interactive=False)]
+
+# Using gr.update() for property changes
+lambda: gr.update(visible=True, interactive=False)
+```
+
+We are working to increase the space of functions that can be transpiled to JavaScript so that they can be run in the browser. [Follow the Groovy library for more info](https://github.com/abidlabs/groovy-transpiler).
+
+
+## Complete Example
+
+Here's a more complete example showing how client side functions can improve the user experience:
+
+$code_todo_list_js
+
+
+## Behind the Scenes
+
+When you set `js=True`, Gradio:
+
+1. Transpiles your Python function to JavaScript
+
+2. Runs the function directly in the browser
+
+3. Still sends the request to the server (for consistency and to handle any side effects)
+
+This provides immediate visual feedback while ensuring your application state remains consistent.
diff --git a/guides/04_additional-features/14_view-api-page.md b/guides/04_additional-features/14_view-api-page.md
new file mode 100644
index 0000000..f1f3b34
--- /dev/null
+++ b/guides/04_additional-features/14_view-api-page.md
@@ -0,0 +1,101 @@
+# API Page
+
+You can use almost any Gradio app programmatically via the built-in API! In the footer of any Gradio app, you'll see a "Use via API" link. Clicking on the link opens up a detailed documentation page for the API that Gradio generates based on the function signatures in your Gradio app.
+
+
+
+## Configuring the API Page
+
+**API endpoint names**
+
+When you create a Gradio application, the API endpoint names are automatically generated based on the function names. You can change this by using the `api_name` parameter in `gr.Interface` or `gr.ChatInterface`. If you are using Gradio `Blocks`, you can name each event listener, like this:
+
+```python
+btn.click(add, [num1, num2], output, api_name="addition")
+```
+
+**Controlling API endpoint visibility**
+
+When building a complex Gradio app, you might want to control how API endpoints appear or behave. Use the `api_visibility` parameter in any `Blocks` event listener to control this:
+
+- `"public"` (default): The endpoint is shown in API docs and accessible to all
+- `"undocumented"`: The endpoint is hidden from API docs but still accessible to downstream apps
+- `"private"`: The endpoint is hidden from API docs and not callable by the Gradio client libraries (e.g. `gradio_client` or `@gradio/client`). Note: this does **not** block direct HTTP requests to the endpoint — it should not be relied upon as a security measure.
+
+To hide an API endpoint from the documentation while still allowing programmatic access:
+
+```python
+btn.click(add, [num1, num2], output, api_visibility="undocumented")
+```
+
+**Hiding endpoints from client libraries**
+
+If you want to hide an API endpoint from the API docs and prevent it from being called by the Gradio client libraries, set `api_visibility="private"`:
+
+```python
+btn.click(add, [num1, num2], output, api_visibility="private")
+```
+
+Note: setting `api_visibility="private"` also means that downstream apps will not be able to load your Gradio app using `gr.load()` as this function uses the Gradio API under the hood. However, the underlying HTTP endpoint is still accessible — this setting should not be relied upon for security.
+
+**Adding API endpoints**
+
+You can also add new API routes to your Gradio application that do not correspond to events in your UI.
+
+For example, in this Gradio application, we add a new route that adds numbers and slices a list:
+
+```py
+import gradio as gr
+with gr.Blocks() as demo:
+ with gr.Row():
+ input = gr.Textbox()
+ button = gr.Button("Submit")
+ output = gr.Textbox()
+ def fn(a: int, b: int, c: list[str]) -> tuple[int, str]:
+ return a + b, c[a:b]
+ gr.api(fn, api_name="add_and_slice")
+
+_, url, _ = demo.launch()
+```
+
+This will create a new route `/add_and_slice` which will show up in the "view API" page. It can be programmatically called by the Python or JS Clients (discussed below) like this:
+
+```py
+from gradio_client import Client
+
+client = Client(url)
+result = client.predict(
+ a=3,
+ b=5,
+ c=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+ api_name="/add_and_slice"
+)
+print(result)
+```
+
+## The Clients
+
+This API page not only lists all of the endpoints that can be used to query the Gradio app, but also shows the usage of both [the Gradio Python client](https://gradio.app/guides/getting-started-with-the-python-client/), and [the Gradio JavaScript client](https://gradio.app/guides/getting-started-with-the-js-client/).
+
+For each endpoint, Gradio automatically generates a complete code snippet with the parameters and their types, as well as example inputs, allowing you to immediately test an endpoint. Here's an example showing an image file input and `str` output:
+
+
+
+
+## The API Recorder 🪄
+
+Instead of reading through the view API page, you can also use Gradio's built-in API recorder to generate the relevant code snippet. Simply click on the "API Recorder" button, use your Gradio app via the UI as you would normally, and then the API Recorder will generate the code using the Clients to recreate your all of your interactions programmatically.
+
+
+
+## MCP Server
+
+The API page also includes instructions on how to use the Gradio app as an Model Context Protocol (MCP) server, which is a standardized way to expose functions as tools so that they can be used by LLMs.
+
+
+
+For the MCP sever, each tool, its description, and its parameters are listed, along with instructions on how to integrate with popular MCP Clients. Read more about Gradio's [MCP integration here](https://www.gradio.app/guides/building-mcp-server-with-gradio).
+
+## OpenAPI Specification
+
+You can access the complete OpenAPI (formerly Swagger) specification of your Gradio app's API at the endpoint `/gradio_api/openapi.json`. The OpenAPI specification is a standardized, language-agnostic interface description for REST APIs that enables both humans and computers to discover and understand the capabilities of your service.
diff --git a/guides/04_additional-features/15_internationalization.md b/guides/04_additional-features/15_internationalization.md
new file mode 100644
index 0000000..1cea585
--- /dev/null
+++ b/guides/04_additional-features/15_internationalization.md
@@ -0,0 +1,59 @@
+Tags: internationalization, i18n, language
+Related spaces:
+
+# Internationalization (i18n)
+
+Gradio comes with ready-to-use internationalization (i18n) support:
+
+- Built-in translations: Gradio automatically translates standard UI elements (like "Submit", "Clear", "Cancel") in more than 40 languages based on the user's browser locale.
+- Custom translations: For app-specific text, Gradio provides the I18n class that lets you extend the built-in system with your own translations.
+
+## Setting Up Translations
+
+You can initialize the `I18n` class with multiple language dictionaries to add custom translations:
+
+```python
+import gradio as gr
+
+# Create an I18n instance with translations for multiple languages
+i18n = gr.I18n(
+ en={"greeting": "Hello, welcome to my app!", "submit": "Submit"},
+ es={"greeting": "¡Hola, bienvenido a mi aplicación!", "submit": "Enviar"},
+ fr={"greeting": "Bonjour, bienvenue dans mon application!", "submit": "Soumettre"}
+)
+
+with gr.Blocks() as demo:
+ # Use the i18n method to translate the greeting
+ gr.Markdown(i18n("greeting"))
+ with gr.Row():
+ input_text = gr.Textbox(label="Input")
+ output_text = gr.Textbox(label="Output")
+
+ submit_btn = gr.Button(i18n("submit"))
+
+# Pass the i18n instance to the launch method
+demo.launch(i18n=i18n)
+```
+
+## How It Works
+
+When you use the `i18n` instance with a translation key, Gradio will show the corresponding translation to users based on their browser's language settings or the language they've selected in your app.
+
+If a translation isn't available for the user's locale, the system will fall back to English (if available) or display the key itself.
+
+## Valid Locale Codes
+
+Locale codes should follow the BCP 47 format (e.g., 'en', 'en-US', 'zh-CN'). The `I18n` class will warn you if you use an invalid locale code.
+
+## Supported Component Properties
+
+The following component properties typically support internationalization:
+
+- `description`
+- `info`
+- `title`
+- `placeholder`
+- `value`
+- `label`
+
+Note that support may vary depending on the component, and some properties might have exceptions where internationalization is not applicable. You can check this by referring to the typehint for the parameter and if it contains `I18nData`, then it supports internationalization.
\ No newline at end of file
diff --git a/guides/04_additional-features/16_custom-buttons.md b/guides/04_additional-features/16_custom-buttons.md
new file mode 100644
index 0000000..ca419be
--- /dev/null
+++ b/guides/04_additional-features/16_custom-buttons.md
@@ -0,0 +1,83 @@
+# Custom Buttons
+
+Many Gradio components support custom buttons in their toolbar, allowing you to add interactive buttons that can trigger Python functions, JavaScript functions, or both. Custom buttons appear alongside built-in buttons (like "copy" or "download") in the component's toolbar.
+
+## Basic Usage
+
+To add custom buttons to a component, pass a list of `gr.Button()` instances to the `buttons` parameter:
+
+```python
+import gradio as gr
+
+refresh_btn = gr.Button("Refresh", variant="secondary", size="sm")
+clear_btn = gr.Button("Clear", variant="secondary", size="sm")
+
+textbox = gr.Textbox(
+ value="Sample text",
+ label="Text Input",
+ buttons=[refresh_btn, clear_btn]
+)
+```
+
+You can also mix built-in buttons (as strings) with custom buttons:
+
+```python
+code = gr.Code(
+ value="print('Hello')",
+ language="python",
+ buttons=["copy", "download", refresh_btn, export_btn]
+)
+```
+
+## Connecting Button Events
+
+Custom buttons work just like regular `gr.Button` components. You can connect them to Python functions or JavaScript functions using the `.click()` method:
+
+### Python Functions
+
+```python
+def refresh_data():
+ import random
+ return f"Refreshed: {random.randint(1000, 9999)}"
+
+refresh_btn.click(refresh_data, outputs=textbox)
+```
+
+### JavaScript Functions
+
+```python
+clear_btn.click(
+ None,
+ inputs=[],
+ outputs=textbox,
+ js="() => ''"
+)
+```
+
+### Combined Python and JavaScript
+
+You can use the same button for both Python and JavaScript logic:
+
+```python
+alert_btn.click(
+ None,
+ inputs=textbox,
+ outputs=[],
+ js="(text) => { alert('Text: ' + text); return []; }"
+)
+```
+
+## Complete Example
+
+Here's a complete example showing custom buttons with both Python and JavaScript functions:
+
+$code_textbox_custom_buttons
+
+
+## Notes
+
+- Custom buttons appear in the component's toolbar, typically in the top-right corner
+- Only the `value` of the Button is used, other attributes like `icon` are not used.
+- Buttons are rendered in the order they appear in the `buttons` list
+- Built-in buttons (like "copy", "download") can be hidden by omitting them from the list
+- Custom buttons work with component events in the same way as as regular buttons
diff --git a/guides/04_additional-features/17_caching.md b/guides/04_additional-features/17_caching.md
new file mode 100644
index 0000000..881f7f4
--- /dev/null
+++ b/guides/04_additional-features/17_caching.md
@@ -0,0 +1,151 @@
+# Caching Function Results
+
+ML inference is often expensive: image editing, video classification, or audio transcription can each take seconds, minutes, or longer. If a user submits the same inputs twice, there's no reason to re-run the model. Gradio provides two caching mechanisms: `@gr.cache` for automatic exact-match caching, and `gr.Cache()` for manual cache control inside your functions.
+
+## Automatic caching with `@gr.cache`
+
+Add `@gr.cache` to any function to automatically cache its results. The decorator hashes inputs by their content — two different numpy arrays with the same pixel values will produce a cache hit. Cache hits bypass the Gradio queue entirely.
+
+```python
+import gradio as gr
+
+@gr.cache
+def classify(image):
+ return model.predict(image)
+```
+
+### Generators
+
+For generator functions, `@gr.cache` caches **all yielded values** and replays them on a hit. This is particularly important for streaming media (`gr.Audio` or `gr.Video` with `streaming=True`) where each yield is a chunk of the output:
+
+```python
+@gr.cache
+def stream_response(prompt):
+ response = ""
+ for token in model.generate(prompt):
+ response += token
+ yield response
+```
+
+### Async
+
+Async functions and async generators work identically:
+
+```python
+@gr.cache
+async def transcribe(audio):
+ return await model.transcribe(audio)
+```
+
+### Parameters
+
+The behavior of `@gr.cache()` can be customized with a few parameters, most notably the `key`:
+
+```python
+@gr.cache(
+ key=lambda kw: kw["prompt"], # only cache based on prompt, ignore temperature
+ max_size=256, # max entries (LRU eviction), default 128
+ max_memory="512mb", # max memory before eviction
+ per_session=True, # isolate cache per user session
+)
+def generate(prompt, temperature=0.7):
+ return llm(prompt, temperature=temperature)
+```
+
+- **`key`** — function that takes the kwargs dict and returns what to hash. Useful for ignoring parameters like temperature or seed.
+- **`max_size`** — maximum number of entries. LRU eviction when full. Default 128. Set to 0 for unlimited.
+- **`max_memory`** — maximum memory usage. Accepts strings like `"512mb"`, `"2gb"` or raw bytes. LRU eviction when exceeded.
+- **`per_session`** — when `True`, each user session gets an isolated cache namespace. Prevents one user's cached results from being served to another, clears that session's entries when the client disconnects, and still applies `max_size` and `max_memory` to the shared cache store across all sessions.
+
+
+Access the cache programmatically via `fn.cache`:
+
+```python
+generate.cache.clear()
+print(len(generate.cache))
+```
+
+When a queued event is served from `@gr.cache`, Gradio shows a small `from cache` timing badge in the UI which appears temporarily in the relevant output components.
+
+### Caching intermediate helper calls
+
+You can also apply `gr.cache()` to a callable at runtime to cache an intermediate step inside a larger Gradio callback:
+
+```python
+def embed(text):
+ return embedding_model(text)
+
+def predict(text):
+ embedding = gr.cache(embed, per_session=True)(text)
+ return rerank(embedding)
+```
+
+This is especially useful when only part of your function is deterministic or reusable. Runtime `gr.cache(fn)(...)` uses the same cache store for repeated calls to that helper and shows the same `used cache` badge as `gr.Cache()` (see below) when a hit is reused during a request.
+
+`gr.cache()` must wrap a callable. If you accidentally write `gr.cache(fn(...))`, Gradio raises an error and tells you to use `gr.cache(fn)(...)` instead.
+
+## Manual cache control with `gr.Cache()`
+
+For full control over what gets cached and when, use `gr.Cache()` as an injectable parameter (like `gr.Progress`). Gradio injects the same instance on every call, giving you a thread-safe `get`/`set` interface:
+
+```python
+def my_function(prompt, c=gr.Cache()):
+ hit = c.get(prompt)
+ if hit is not None:
+ return hit["result"]
+ result = expensive_computation(prompt)
+ c.set(prompt, result=result)
+ return result
+```
+
+If a queued function gets a successful hit from `c.get(...)`, Gradio also shows a timing badge in the UI. This badge says `used cache` instead of `from cache`, because the request still ran, but part of its work was reused from `gr.Cache()`.
+
+A minimal example is available in the [`gr.Cache()` manual cache demo](https://github.com/gradio-app/gradio/blob/main/demo/cache_manual_demo/run.py).
+
+### Why use `gr.Cache()` over a plain dict?
+
+- **Thread-safe** — built-in locking for concurrent requests
+- **LRU eviction** + **memory limits** — bounded memory usage (`max_size`, `max_memory`)
+- **Per-session isolation** — `gr.Cache(per_session=True)` partitions the cache by user session, prevents data leakage between users, clears that session's entries when the client disconnects, and still applies `max_size` and `max_memory` across the combined cache entries of all sessions
+- **Content-aware keys** — numpy arrays, PIL images, DataFrames all work as cache keys
+
+### KV Cache Example
+
+You can cache arbitrary intermediate state, not just function outputs. Here's how to cache transformer KV states for prefix reuse:
+
+```python
+def generate(prompt, c=gr.Cache(per_session=True)):
+ best_key = None
+ best_len = 0
+ for cached_key in c.keys():
+ if prompt.startswith(cached_key) and len(cached_key) > best_len:
+ best_key = cached_key
+ best_len = len(cached_key)
+
+ if best_key:
+ past_kv = c.get(best_key)["kv"]
+ output = model.generate(prompt, past_key_values=past_kv)
+ else:
+ output = model.generate(prompt)
+
+ c.set(prompt, kv=model.past_key_values)
+ return output.text
+```
+
+For a full runnable version, see the [`gr.Cache()` KV cache demo](https://github.com/gradio-app/gradio/blob/main/demo/cache_kv_demo/run.py).
+
+
+## When to use caching
+
+`@gr.cache` is most useful for **deterministic** functions where the same input always produces the same output: image classification, audio transcription, embedding computation, structured data extraction.
+
+It is less useful for **non-deterministic** functions like text generation or image generation, where users might want different outputs even for the same input. For those, `gr.Cache()` with manual control may be more appropriate as you can cache intermediate state (like KV caches) without caching the output completely.
+
+
+## Next steps
+
+Take a look at these complete examples and then build your own Gradio app with caching!
+
+- [`@gr.cache()` function types demo](https://github.com/gradio-app/gradio/blob/main/demo/cache_demo/run.py) - sync, async, generator, and async generator caching
+- [`gr.Cache()` manual cache demo](https://github.com/gradio-app/gradio/blob/main/demo/cache_manual_demo/run.py) - normalized manual cache keys with explicit `get` / `set`
+- [`gr.Cache()` KV cache demo](https://github.com/gradio-app/gradio/blob/main/demo/cache_kv_demo/run.py) - transformer prefix reuse with cached KV state
diff --git a/guides/04_additional-features/18_workflows.md b/guides/04_additional-features/18_workflows.md
new file mode 100644
index 0000000..91bf677
--- /dev/null
+++ b/guides/04_additional-features/18_workflows.md
@@ -0,0 +1,178 @@
+# gr.Workflow
+
+`gr.Workflow` is a visual, node-based AI pipeline builder built into Gradio. It lets you chain together Hugging Face Spaces, models, datasets, and your own Python functions on a drag-and-drop canvas:
+
+
+
+
+## Quickstart
+
+The simplest possible Workflow app:
+
+```python
+import gradio as gr
+
+gr.Workflow().launch()
+```
+
+Open the app, drag Spaces and models from the sidebar onto the canvas, connect their ports, and hit **Run**. As you create nodes and edges, a `workflow.json` file will automatically be created in your working directory. You can also use a coding agent to write or edit this file, allowing you to create workflows programmatically.
+
+## Binding Python functions
+
+Pass your own Python functions via `bind=` and they appear as callable nodes on the canvas. Gradio inspects the function signature to auto-generate input/output ports.
+
+```python
+import gradio as gr
+
+def summarize(text: str) -> str:
+ return text[:200]
+
+gr.Workflow(bind=[summarize]).launch()
+```
+
+Use a dict to give nodes explicit names:
+
+```python
+gr.Workflow(bind={"My Summarizer": summarize}).launch()
+```
+
+## Defining edges in code
+
+For pipelines you want to ship with a fixed topology, declare edges programmatically:
+
+```python
+import gradio as gr
+
+def clean(text: str) -> str:
+ return text.strip().lower()
+
+def tag(text: str) -> str:
+ return f"[processed] {text}"
+
+gr.Workflow(
+ bind=[clean, tag],
+ edges=[("clean", "tag")],
+).launch()
+```
+
+Each edge is a `(from_fn, to_fn)` tuple. Use `"fn_name.port_label"` to target a specific port when a node has multiple inputs or outputs.
+
+> **Note:** `edges=` is only applied when no workflow file exists yet. If `workflow.json` already exists, `edges=` is ignored — delete the file to regenerate the topology from `bind` and `edges`.
+
+## Loading from a JSON file
+
+Pass a `graph=` path to load a saved workflow topology. The canvas reads from the file on each page load and autosaves back to it when you make edits.
+
+```python
+gr.Workflow(graph="workflow.json").launch()
+```
+
+If the file doesn't exist yet, it's created on first save. Combine `graph=` with `bind=` to pre-wire Space nodes alongside your Python functions.
+
+## Workflow JSON format
+
+A workflow is a JSON file with three node collections:
+
+```json
+{
+ "schema_version": "2",
+ "name": "My Pipeline",
+ "references": [
+ {
+ "id": "ref_image", "label": "Input Photo", "role": "reference",
+ "asset_type": "image",
+ "inputs": [{"id": "in", "label": "Image", "type": "image"}],
+ "outputs": [{"id": "out","label": "Image", "type": "image"}],
+ "x": 80, "y": 120, "width": 220, "height": 124, "data": {}
+ }
+ ],
+ "operators": [
+ {
+ "id": "op_flux", "label": "FLUX.1", "role": "operator",
+ "kind": "space",
+ "space_id": "black-forest-labs/FLUX.1-schnell",
+ "endpoint": "/infer",
+ "inputs": [{"id": "in_0", "label": "Prompt", "type": "text", "required": true}],
+ "outputs": [{"id": "out_0","label": "Result","type": "image","output_index": 0}],
+ "x": 400, "y": 120, "width": 220, "height": 124, "data": {}
+ }
+ ],
+ "subjects": [
+ {
+ "id": "sub_img", "label": "Output Image", "role": "subject",
+ "asset_type": "image",
+ "inputs": [{"id": "in", "label": "Image", "type": "image"}],
+ "outputs": [{"id": "out","label": "Image","type": "image"}],
+ "x": 700, "y": 120, "width": 220, "height": 107, "data": {}
+ }
+ ],
+ "edges": [
+ {
+ "id": "e1",
+ "from_node_id": "ref_image", "from_port_id": "out",
+ "to_node_id": "op_flux", "to_port_id": "in_0",
+ "type": "image"
+ }
+ ]
+}
+```
+
+| Collection | Role |
+|---|---|
+| `references` | Inputs — uploaded files, editable text, literal values |
+| `operators` | Processing steps — Spaces, models, datasets, Python functions |
+| `subjects` | Outputs — the results being created |
+
+### Operator kinds
+
+| `kind` | What it calls |
+|---|---|
+| `"space"` | Any Gradio Space on the Hub via `gradio_client` |
+| `"model"` | HF Inference API — set `pipeline_tag` to select the task |
+| `"dataset"` | Streams rows from any Hub dataset |
+| `"fn"` | A Python function passed via `bind=` |
+
+## Port types
+
+Ports are typed so the canvas can validate connections. Supported types:
+
+`image` · `audio` · `video` · `text` · `number` · `boolean` · `gallery` · `file` · `json` · `model3d`
+
+## Fan-out pipelines
+
+One reference can feed multiple operators simultaneously — they run in parallel:
+
+```python
+# workflow.json excerpt — one product photo → 4 FLUX Kontext branches
+"edges": [
+ {"from_node_id": "ref_product", ..., "to_node_id": "op_kontext_0", ...},
+ {"from_node_id": "ref_product", ..., "to_node_id": "op_kontext_1", ...},
+ {"from_node_id": "ref_product", ..., "to_node_id": "op_kontext_2", ...},
+ {"from_node_id": "ref_product", ..., "to_node_id": "op_kontext_3", ...}
+]
+```
+
+## Deploying to Spaces
+
+A Workflow app is a standard Gradio app — deploy it to Hugging Face Spaces exactly like any other, by uploading the code to a Space, or by simply running in your terminal:
+
+```
+gradio deploy
+```
+
+On a Space, the canvas authenticates visitors via OAuth. The Space owner gets write access (can edit and save the workflow); visitors get a read-only view and can run the pipeline with their own HF token. As a result, you should set `hf_oauth: true` [in your Space](https://huggingface.co/docs/hub/en/spaces-oauth).
+
+## API access
+
+Every Workflow app is a Gradio app, meaning that it exposes a Gradio REST API endpoint for each output (subject) node. The endpoint name is derived from the subject's label — for example, a subject labelled "Output Image" becomes `/output_image`. Use `client.view_api()` to see the exact names for your workflow:
+
+```python
+from gradio_client import Client
+
+client = Client("your-username/my-workflow")
+client.view_api() # lists available endpoints and their parameters
+
+result = client.predict("a sunset over mountains", api_name="/output_image")
+```
+
+This also means that you can reuse your workflows within larger workflows, making it possible to build modular and complex applications with Gradio Workflows!
diff --git a/guides/05_chatbots/01_creating-a-chatbot-fast.md b/guides/05_chatbots/01_creating-a-chatbot-fast.md
new file mode 100644
index 0000000..39b5f91
--- /dev/null
+++ b/guides/05_chatbots/01_creating-a-chatbot-fast.md
@@ -0,0 +1,414 @@
+# How to Create a Chatbot with Gradio
+
+Tags: LLM, CHATBOT, NLP
+
+## Introduction
+
+Chatbots are a popular application of large language models (LLMs). Using Gradio, you can easily build a chat application and share that with your users, or try it yourself using an intuitive UI.
+
+This tutorial uses `gr.ChatInterface()`, which is a high-level abstraction that allows you to create your chatbot UI fast, often with a _few lines of Python_. It can be easily adapted to support multimodal chatbots, or chatbots that require further customization.
+
+**Prerequisites**: please make sure you are using the latest version of Gradio:
+
+```bash
+$ pip install --upgrade gradio
+```
+
+## Note for OpenAI-API compatible endpoints
+
+If you have a chat server serving an OpenAI-API compatible endpoint (such as Ollama), you can spin up a ChatInterface in a single line of Python. First, also run `pip install openai`. Then, with your own URL, model, and optional token:
+
+```python
+import gradio as gr
+
+gr.load_chat("http://localhost:11434/v1/", model="llama3.2", token="***").launch()
+```
+
+Read about `gr.load_chat` in [the docs](https://www.gradio.app/docs/gradio/load_chat). If you have your own model, keep reading to see how to create an application around any chat model in Python!
+
+## Defining a chat function
+
+To create a chat application with `gr.ChatInterface()`, the first thing you should do is define your **chat function**. In the simplest case, your chat function should accept two arguments: `message` and `history` (the arguments can be named anything, but must be in this order).
+
+- `message`: a `str` representing the user's most recent message.
+- `history`: a list of openai-style dictionaries with `role` and `content` keys, representing the previous conversation history. May also include additional keys representing message metadata.
+
+The `history` would look like this:
+
+```python
+[
+ {"role": "user", "content": [{"type": "text", "text": "What is the capital of France?"}]},
+ {"role": "assistant", "content": [{"type": "text", "text": "Paris"}]}
+]
+```
+
+while the next `message` would be:
+
+```py
+"And what is its largest city?"
+```
+
+Your chat function simply needs to return:
+
+* a `str` value, which is the chatbot's response based on the chat `history` and most recent `message`, for example, in this case:
+
+```
+Paris is also the largest city.
+```
+
+Let's take a look at a few example chat functions:
+
+**Example: a chatbot that randomly responds with yes or no**
+
+Let's write a chat function that responds `Yes` or `No` randomly.
+
+Here's our chat function:
+
+```python
+import random
+
+def random_response(message, history):
+ return random.choice(["Yes", "No"])
+```
+
+Now, we can plug this into `gr.ChatInterface()` and call the `.launch()` method to create the web interface:
+
+```python
+import gradio as gr
+
+gr.ChatInterface(
+ fn=random_response,
+).launch()
+```
+
+That's it! Here's our running demo, try it out:
+
+$demo_chatinterface_random_response
+
+**Example: a chatbot that alternates between agreeing and disagreeing**
+
+Of course, the previous example was very simplistic, it didn't take user input or the previous history into account! Here's another simple example showing how to incorporate a user's input as well as the history.
+
+```python
+import gradio as gr
+
+def alternatingly_agree(message, history):
+ if len([h for h in history if h['role'] == "assistant"]) % 2 == 0:
+ return f"Yes, I do think that: {message}"
+ else:
+ return "I don't think so"
+
+gr.ChatInterface(
+ fn=alternatingly_agree,
+).launch()
+```
+
+We'll look at more realistic examples of chat functions in our next Guide, which shows [examples of using `gr.ChatInterface` with popular LLMs](../guides/chatinterface-examples).
+
+## Streaming chatbots
+
+In your chat function, you can use `yield` to generate a sequence of partial responses, each replacing the previous ones. This way, you'll end up with a streaming chatbot. It's that simple!
+
+```python
+import time
+import gradio as gr
+
+def slow_echo(message, history):
+ for i in range(len(message)):
+ time.sleep(0.3)
+ yield "You typed: " + message[: i+1]
+
+gr.ChatInterface(
+ fn=slow_echo,
+).launch()
+```
+
+While the response is streaming, the "Submit" button turns into a "Stop" button that can be used to stop the generator function.
+
+Tip: Even though you are yielding the latest message at each iteration, Gradio only sends the "diff" of each message from the server to the frontend, which reduces latency and data consumption over your network.
+
+## Customizing the Chat UI
+
+If you're familiar with Gradio's `gr.Interface` class, the `gr.ChatInterface` includes many of the same arguments that you can use to customize the look and feel of your Chatbot. For example, you can:
+
+- add a title and description above your chatbot using `title` and `description` arguments.
+- add a theme or custom css using `theme` and `css` arguments respectively in the `launch()` method.
+- add `examples` and even enable `cache_examples`, which make your Chatbot easier for users to try it out.
+- customize the chatbot (e.g. to change the height or add a placeholder) or textbox (e.g. to add a max number of characters or add a placeholder).
+
+**Adding examples**
+
+You can add preset examples to your `gr.ChatInterface` with the `examples` parameter, which takes a list of string examples. Any examples will appear as "buttons" within the Chatbot before any messages are sent. If you'd like to include images or other files as part of your examples, you can do so by using this dictionary format for each example instead of a string: `{"text": "What's in this image?", "files": ["cheetah.jpg"]}`. Each file will be a separate message that is added to your Chatbot history.
+
+You can change the displayed text for each example by using the `example_labels` argument. You can add icons to each example as well using the `example_icons` argument. Both of these arguments take a list of strings, which should be the same length as the `examples` list.
+
+If you'd like to cache the examples so that they are pre-computed and the results appear instantly, set `cache_examples=True`.
+
+**Customizing the chatbot or textbox component**
+
+If you want to customize the `gr.Chatbot` or `gr.Textbox` that compose the `ChatInterface`, then you can pass in your own chatbot or textbox components. Here's an example of how we to apply the parameters we've discussed in this section:
+
+```python
+import gradio as gr
+
+def yes_man(message, history):
+ if message.endswith("?"):
+ return "Yes"
+ else:
+ return "Ask me anything!"
+
+gr.ChatInterface(
+ yes_man,
+ chatbot=gr.Chatbot(height=300),
+ textbox=gr.Textbox(placeholder="Ask me a yes or no question", container=False, scale=7),
+ title="Yes Man",
+ description="Ask Yes Man any question",
+ examples=["Hello", "Am I cool?", "Are tomatoes vegetables?"],
+ cache_examples=True,
+).launch(theme="ocean")
+```
+
+Here's another example that adds a "placeholder" for your chat interface, which appears before the user has started chatting. The `placeholder` argument of `gr.Chatbot` accepts Markdown or HTML:
+
+```python
+gr.ChatInterface(
+ yes_man,
+ chatbot=gr.Chatbot(placeholder="Your Personal Yes-Man
Ask Me Anything"),
+...
+```
+
+The placeholder appears vertically and horizontally centered in the chatbot.
+
+## Multimodal Chat Interface
+
+You may want to add multimodal capabilities to your chat interface. For example, you may want users to be able to upload images or files to your chatbot and ask questions about them. You can make your chatbot "multimodal" by passing in a single parameter (`multimodal=True`) to the `gr.ChatInterface` class.
+
+When `multimodal=True`, the signature of your chat function changes slightly: the first parameter of your function (what we referred to as `message` above) should accept a dictionary consisting of the submitted text and uploaded files that looks like this:
+
+```py
+{
+ "text": "user input",
+ "files": [
+ "updated_file_1_path.ext",
+ "updated_file_2_path.ext",
+ ...
+ ]
+}
+```
+
+This second parameter of your chat function, `history`, will be in the same openai-style dictionary format as before. However, if the history contains uploaded files, the `content` key will be a dictionary with a "type" key whose value is "file" and the file will be represented as a dictionary. All the files will be grouped in message in the history. So after uploading two files and asking a question, your history might look like this:
+
+```python
+[
+ {"role": "user", "content": [{"type": "file", "file": {"path": "cat1.png"}},
+ {"type": "file", "file": {"path": "cat1.png"}},
+ {"type": "text", "text": "What's the difference between these two images?"}]}
+]
+```
+
+The return type of your chat function does *not change* when setting `multimodal=True` (i.e. in the simplest case, you should still return a string value). We discuss more complex cases, e.g. returning files [below](#returning-complex-responses).
+
+If you are customizing a multimodal chat interface, you should pass in an instance of `gr.MultimodalTextbox` to the `textbox` parameter. You can customize the `MultimodalTextbox` further by passing in the `sources` parameter, which is a list of sources to enable. Here's an example that illustrates how to set up and customize and multimodal chat interface:
+
+
+```python
+import gradio as gr
+
+def count_images(message, history):
+ num_images = len(message["files"])
+ total_images = 0
+ for message in history:
+ for content in message["content"]:
+ if content["type"] == "file":
+ total_images += 1
+ return f"You just uploaded {num_images} images, total uploaded: {total_images+num_images}"
+
+demo = gr.ChatInterface(
+ fn=count_images,
+ examples=[
+ {"text": "No files", "files": []}
+ ],
+ multimodal=True,
+ textbox=gr.MultimodalTextbox(file_count="multiple", file_types=["image"], sources=["upload", "microphone"])
+)
+
+demo.launch()
+```
+
+## Additional Inputs
+
+You may want to add additional inputs to your chat function and expose them to your users through the chat UI. For example, you could add a textbox for a system prompt, or a slider that sets the number of tokens in the chatbot's response. The `gr.ChatInterface` class supports an `additional_inputs` parameter which can be used to add additional input components.
+
+The `additional_inputs` parameters accepts a component or a list of components. You can pass the component instances directly, or use their string shortcuts (e.g. `"textbox"` instead of `gr.Textbox()`). If you pass in component instances, and they have _not_ already been rendered, then the components will appear underneath the chatbot within a `gr.Accordion()`.
+
+Here's a complete example:
+
+$code_chatinterface_system_prompt
+
+If the components you pass into the `additional_inputs` have already been rendered in a parent `gr.Blocks()`, then they will _not_ be re-rendered in the accordion. This provides flexibility in deciding where to lay out the input components. In the example below, we position the `gr.Textbox()` on top of the Chatbot UI, while keeping the slider underneath.
+
+```python
+import gradio as gr
+import time
+
+def echo(message, history, system_prompt, tokens):
+ response = f"System prompt: {system_prompt}\n Message: {message}."
+ for i in range(min(len(response), int(tokens))):
+ time.sleep(0.05)
+ yield response[: i+1]
+
+with gr.Blocks() as demo:
+ system_prompt = gr.Textbox("You are helpful AI.", label="System Prompt")
+ slider = gr.Slider(10, 100, render=False)
+
+ gr.ChatInterface(
+ echo, additional_inputs=[system_prompt, slider],
+ )
+
+demo.launch()
+```
+
+**Examples with additional inputs**
+
+You can also add example values for your additional inputs. Pass in a list of lists to the `examples` parameter, where each inner list represents one sample, and each inner list should be `1 + len(additional_inputs)` long. The first element in the inner list should be the example value for the chat message, and each subsequent element should be an example value for one of the additional inputs, in order. When additional inputs are provided, examples are rendered in a table underneath the chat interface.
+
+If you need to create something even more custom, then its best to construct the chatbot UI using the low-level `gr.Blocks()` API. We have [a dedicated guide for that here](/guides/creating-a-custom-chatbot-with-blocks).
+
+## Additional Outputs
+
+In the same way that you can accept additional inputs into your chat function, you can also return additional outputs. Simply pass in a list of components to the `additional_outputs` parameter in `gr.ChatInterface` and return additional values for each component from your chat function. Here's an example that extracts code and outputs it into a separate `gr.Code` component:
+
+$code_chatinterface_artifacts
+
+**Note:** unlike the case of additional inputs, the components passed in `additional_outputs` must be already defined in your `gr.Blocks` context -- they are not rendered automatically. If you need to render them after your `gr.ChatInterface`, you can set `render=False` when they are first defined and then `.render()` them in the appropriate section of your `gr.Blocks()` as we do in the example above.
+
+## Returning Complex Responses
+
+We mentioned earlier that in the simplest case, your chat function should return a `str` response, which will be rendered as Markdown in the chatbot. However, you can also return more complex responses as we discuss below:
+
+
+**Returning files or Gradio components**
+
+Currently, the following Gradio components can be displayed inside the chat interface:
+* `gr.Image`
+* `gr.Plot`
+* `gr.Audio`
+* `gr.HTML`
+* `gr.Video`
+* `gr.Gallery`
+* `gr.File`
+
+Simply return one of these components from your function to use it with `gr.ChatInterface`. Here's an example that returns an audio file:
+
+```py
+import gradio as gr
+
+def music(message, history):
+ if message.strip():
+ return gr.Audio("https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav")
+ else:
+ return "Please provide the name of an artist"
+
+gr.ChatInterface(
+ music,
+ textbox=gr.Textbox(placeholder="Which artist's music do you want to listen to?", scale=7),
+).launch()
+```
+
+Similarly, you could return image files with `gr.Image`, video files with `gr.Video`, or arbitrary files with the `gr.File` component.
+
+**Returning Multiple Messages**
+
+You can return multiple assistant messages from your chat function simply by returning a `list` of messages, each of which is a valid chat type. This lets you, for example, send a message along with files, as in the following example:
+
+$code_chatinterface_echo_multimodal
+
+
+**Displaying intermediate thoughts or tool usage**
+
+The `gr.ChatInterface` class supports displaying intermediate thoughts or tool usage direct in the chatbot.
+
+
+
+ To do this, you will need to return a `gr.ChatMessage` object from your chat function. Here is the schema of the `gr.ChatMessage` data class as well as two internal typed dictionaries:
+
+ ```py
+MessageContent = Union[str, FileDataDict, FileData, Component]
+
+@dataclass
+class ChatMessage:
+ content: MessageContent | list[MessageContent]
+ metadata: MetadataDict = None
+ options: list[OptionDict] = None
+
+class MetadataDict(TypedDict):
+ title: NotRequired[str]
+ id: NotRequired[int | str]
+ parent_id: NotRequired[int | str]
+ log: NotRequired[str]
+ duration: NotRequired[float]
+ status: NotRequired[Literal["pending", "done"]]
+
+class OptionDict(TypedDict):
+ label: NotRequired[str]
+ value: str
+ ```
+
+As you can see, the `gr.ChatMessage` dataclass is similar to the openai-style message format, e.g. it has a "content" key that refers to the chat message content. But it also includes a "metadata" key whose value is a dictionary. If this dictionary includes a "title" key, the resulting message is displayed as an intermediate thought with the title being displayed on top of the thought. Here's an example showing the usage:
+
+$code_chatinterface_thoughts
+
+You can even show nested thoughts, which is useful for agent demos in which one tool may call other tools. To display nested thoughts, include "id" and "parent_id" keys in the "metadata" dictionary. Read our [dedicated guide on displaying intermediate thoughts and tool usage](/guides/agents-and-tool-usage) for more realistic examples.
+
+**Providing preset responses**
+
+When returning an assistant message, you may want to provide preset options that a user can choose in response. To do this, again, you will again return a `gr.ChatMessage` instance from your chat function. This time, make sure to set the `options` key specifying the preset responses.
+
+As shown in the schema for `gr.ChatMessage` above, the value corresponding to the `options` key should be a list of dictionaries, each with a `value` (a string that is the value that should be sent to the chat function when this response is clicked) and an optional `label` (if provided, is the text displayed as the preset response instead of the `value`).
+
+This example illustrates how to use preset responses:
+
+$code_chatinterface_options
+
+## Modifying the Chatbot Value Directly
+
+You may wish to modify the value of the chatbot with your own events, other than those prebuilt in the `gr.ChatInterface`. For example, you could create a dropdown that prefills the chat history with certain conversations or add a separate button to clear the conversation history. The `gr.ChatInterface` supports these events, but you need to use the `gr.ChatInterface.chatbot_value` as the input or output component in such events. In this example, we use a `gr.Radio` component to prefill the the chatbot with certain conversations:
+
+$code_chatinterface_prefill
+
+## Using Your Chatbot via API
+
+Once you've built your Gradio chat interface and are hosting it on [Hugging Face Spaces](https://hf.space) or somewhere else, then you can query it with a simple API. The API route will be the name of the function you pass to the ChatInterface. So if `gr.ChatInterface(respond)`, then the API route is `/respond`. The endpoint just expects the user's message and will return the response, internally keeping track of the message history.
+
+
+
+To use the endpoint, you should use either the [Gradio Python Client](/guides/getting-started-with-the-python-client) or the [Gradio JS client](/guides/getting-started-with-the-js-client). Or, you can deploy your Chat Interface to other platforms, such as a:
+
+* Slack bot [[tutorial]](../guides/creating-a-slack-bot-from-a-gradio-app)
+* Website widget [[tutorial]](../guides/creating-a-website-widget-from-a-gradio-chatbot)
+
+## Chat History
+
+You can enable persistent chat history for your ChatInterface, allowing users to maintain multiple conversations and easily switch between them. When enabled, conversations are stored locally and privately in the user's browser using local storage. So if you deploy a ChatInterface e.g. on [Hugging Face Spaces](https://hf.space), each user will have their own separate chat history that won't interfere with other users' conversations. This means multiple users can interact with the same ChatInterface simultaneously while maintaining their own private conversation histories.
+
+To enable this feature, simply set `gr.ChatInterface(save_history=True)` (as shown in the example in the next section). Users will then see their previous conversations in a side panel and can continue any previous chat or start a new one.
+
+## Collecting User Feedback
+
+To gather feedback on your chat model, set `gr.ChatInterface(flagging_mode="manual")` and users will be able to thumbs-up or thumbs-down assistant responses. Each flagged response, along with the entire chat history, will get saved in a CSV file in the app working directory (this can be configured via the `flagging_dir` parameter).
+
+You can also change the feedback options via `flagging_options` parameter. The default options are "Like" and "Dislike", which appear as the thumbs-up and thumbs-down icons. Any other options appear under a dedicated flag icon. This example shows a ChatInterface that has both chat history (mentioned in the previous section) and user feedback enabled:
+
+$code_chatinterface_streaming_echo
+
+Note that in this example, we set several flagging options: "Like", "Spam", "Inappropriate", "Other". Because the case-sensitive string "Like" is one of the flagging options, the user will see a thumbs-up icon next to each assistant message. The three other flagging options will appear in a dropdown under the flag icon.
+
+## What's Next?
+
+Now that you've learned about the `gr.ChatInterface` class and how it can be used to create chatbot UIs quickly, we recommend reading one of the following:
+
+* [Our next Guide](../guides/chatinterface-examples) shows examples of how to use `gr.ChatInterface` with popular LLM libraries.
+* If you'd like to build very custom chat applications from scratch, you can build them using the low-level Blocks API, as [discussed in this Guide](../guides/creating-a-custom-chatbot-with-blocks).
+* Once you've deployed your Gradio Chat Interface, its easy to use in other applications because of the built-in API. Here's a tutorial on [how to deploy a Gradio chat interface as a Discord bot](../guides/creating-a-discord-bot-from-a-gradio-app).
+
+
diff --git a/guides/05_chatbots/02_chatinterface-examples.md b/guides/05_chatbots/02_chatinterface-examples.md
new file mode 100644
index 0000000..37786d0
--- /dev/null
+++ b/guides/05_chatbots/02_chatinterface-examples.md
@@ -0,0 +1,76 @@
+# Using Popular LLM libraries and APIs
+
+Tags: LLM, CHATBOT, API
+
+In this Guide, we go through several examples of how to use `gr.ChatInterface` with popular LLM libraries and API providers.
+
+We will cover the following libraries and API providers:
+
+* [Llama Index](#llama-index)
+* [LangChain](#lang-chain)
+* [OpenAI](#open-ai)
+* [Hugging Face `transformers`](#hugging-face-transformers)
+* [SambaNova](#samba-nova)
+* [Hyperbolic](#hyperbolic)
+* [Anthropic's Claude](#anthropics-claude)
+* [MiniMax](#minimax)
+
+For many LLM libraries and providers, there exist community-maintained integration libraries that make it even easier to spin up Gradio apps. We reference these libraries in the appropriate sections below.
+
+## Llama Index
+
+Let's start by using `llama-index` on top of `openai` to build a RAG chatbot on any text or PDF files that you can demo and share in less than 30 lines of code. You'll need to have an OpenAI key for this example (keep reading for the free, open-source equivalent!)
+
+$code_llm_llamaindex
+
+## LangChain
+
+Here's an example using `langchain` on top of `openai` to build a general-purpose chatbot. As before, you'll need to have an OpenAI key for this example.
+
+$code_llm_langchain
+
+Tip: For quick prototyping, the community-maintained langchain-gradio repo makes it even easier to build chatbots on top of LangChain.
+
+## OpenAI
+
+Of course, we could also use the `openai` library directy. Here a similar example to the LangChain , but this time with streaming as well:
+
+Tip: For quick prototyping, the openai-gradio library makes it even easier to build chatbots on top of OpenAI models.
+
+
+## Hugging Face `transformers`
+
+Of course, in many cases you want to run a chatbot locally. Here's the equivalent example using the SmolLM2-135M-Instruct model using the Hugging Face `transformers` library.
+
+$code_llm_hf_transformers
+
+## SambaNova
+
+The SambaNova Cloud API provides access to full-precision open-source models, such as the Llama family. Here's an example of how to build a Gradio app around the SambaNova API
+
+$code_llm_sambanova
+
+Tip: For quick prototyping, the sambanova-gradio library makes it even easier to build chatbots on top of SambaNova models.
+
+## Hyperbolic
+
+The Hyperbolic AI API provides access to many open-source models, such as the Llama family. Here's an example of how to build a Gradio app around the Hyperbolic
+
+$code_llm_hyperbolic
+
+Tip: For quick prototyping, the hyperbolic-gradio library makes it even easier to build chatbots on top of Hyperbolic models.
+
+
+## Anthropic's Claude
+
+Anthropic's Claude model can also be used via API. Here's a simple 20 questions-style game built on top of the Anthropic API:
+
+$code_llm_claude
+
+
+## MiniMax
+
+The MiniMax API exposes the M-series models through an OpenAI-compatible endpoint, so the standard `openai` client works out of the box. Here's an example of how to build a Gradio app around MiniMax:
+
+$code_llm_minimax
+
diff --git a/guides/05_chatbots/03_agents-and-tool-usage.md b/guides/05_chatbots/03_agents-and-tool-usage.md
new file mode 100644
index 0000000..b6fe983
--- /dev/null
+++ b/guides/05_chatbots/03_agents-and-tool-usage.md
@@ -0,0 +1,513 @@
+# Building a UI for an LLM Agent
+
+Tags: LLM, AGENTS, CHAT
+
+The Gradio Chatbot can natively display intermediate thoughts and tool usage in a collapsible accordion next to a chat message. This makes it perfect for creating UIs for LLM agents and chain-of-thought (CoT) or reasoning demos. This guide will show you how to display thoughts and tool usage with `gr.Chatbot` and `gr.ChatInterface`.
+
+
+
+## The `ChatMessage` dataclass
+
+Every element of the chatbot value is a dictionary of `role` and `content` keys. You can always use plain python dictionaries to add new values to the chatbot but Gradio also provides the `ChatMessage` dataclass to help you with IDE autocompletion. The schema of `ChatMessage` is as follows:
+
+ ```py
+MessageContent = Union[str, FileDataDict, FileData, Component]
+
+@dataclass
+class ChatMessage:
+ content: MessageContent | [MessageContent]
+ role: Literal["user", "assistant"]
+ metadata: MetadataDict = None
+ options: list[OptionDict] = None
+
+class MetadataDict(TypedDict):
+ title: NotRequired[str]
+ id: NotRequired[int | str]
+ parent_id: NotRequired[int | str]
+ log: NotRequired[str]
+ duration: NotRequired[float]
+ status: NotRequired[Literal["pending", "done"]]
+
+class OptionDict(TypedDict):
+ label: NotRequired[str]
+ value: str
+ ```
+
+
+For our purposes, the most important key is the `metadata` key, which accepts a dictionary. If this dictionary includes a `title` for the message, it will be displayed in a collapsible accordion representing a thought. It's that simple! Take a look at this example:
+
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ chatbot = gr.Chatbot(
+ value=[
+ gr.ChatMessage(
+ role="user",
+ content="What is the weather in San Francisco?"
+ ),
+ gr.ChatMessage(
+ role="assistant",
+ content="I need to use the weather API tool?",
+ metadata={"title": "🧠 Thinking"}
+ )
+ ]
+ )
+
+demo.launch()
+```
+
+
+
+In addition to `title`, the dictionary provided to `metadata` can take several optional keys:
+
+* `log`: an optional string value to be displayed in a subdued font next to the thought title.
+* `duration`: an optional numeric value representing the duration of the thought/tool usage, in seconds. Displayed in a subdued font next inside parentheses next to the thought title.
+* `status`: if set to `"pending"`, a spinner appears next to the thought title and the accordion is initialized open. If `status` is `"done"`, the thought accordion is initialized closed. If `status` is not provided, the thought accordion is initialized open and no spinner is displayed.
+* `id` and `parent_id`: if these are provided, they can be used to nest thoughts inside other thoughts.
+
+Below, we show several complete examples of using `gr.Chatbot` and `gr.ChatInterface` to display tool use or thinking UIs.
+
+## Building with Agents
+
+### A real example using transformers.agents
+
+We'll create a Gradio application simple agent that has access to a text-to-image tool.
+
+Tip: Make sure you read the [smolagents documentation](https://huggingface.co/docs/smolagents/index) first
+
+We'll start by importing the necessary classes from transformers and gradio.
+
+```python
+import gradio as gr
+from gradio import ChatMessage
+from transformers import Tool, ReactCodeAgent # type: ignore
+from transformers.agents import stream_to_gradio, HfApiEngine # type: ignore
+
+# Import tool from Hub
+image_generation_tool = Tool.from_space(
+ space_id="black-forest-labs/FLUX.1-schnell",
+ name="image_generator",
+ description="Generates an image following your prompt. Returns a PIL Image.",
+ api_name="/infer",
+)
+
+llm_engine = HfApiEngine("Qwen/Qwen2.5-Coder-32B-Instruct")
+# Initialize the agent with both tools and engine
+agent = ReactCodeAgent(tools=[image_generation_tool], llm_engine=llm_engine)
+```
+
+Then we'll build the UI:
+
+```python
+def interact_with_agent(prompt, history):
+ messages = []
+ yield messages
+ for msg in stream_to_gradio(agent, prompt):
+ messages.append(asdict(msg))
+ yield messages
+ yield messages
+
+
+demo = gr.ChatInterface(
+ interact_with_agent,
+ chatbot= gr.Chatbot(
+ label="Agent",
+ avatar_images=(
+ None,
+ "https://em-content.zobj.net/source/twitter/53/robot-face_1f916.png",
+ ),
+ ),
+ examples=[
+ ["Generate an image of an astronaut riding an alligator"],
+ ["I am writing a children's book for my daughter. Can you help me with some illustrations?"],
+ ],
+)
+```
+
+You can see the full demo code [here](https://huggingface.co/spaces/gradio/agent_chatbot/blob/main/app.py).
+
+
+
+
+
+### A real example using langchain agents
+
+We'll create a UI for langchain agent that has access to a search engine.
+
+We'll begin with imports and setting up the langchain agent. Note that you'll need an .env file with the following environment variables set -
+
+```
+SERPAPI_API_KEY=
+HF_TOKEN=
+OPENAI_API_KEY=
+```
+
+```python
+from langchain import hub
+from langchain.agents import AgentExecutor, create_openai_tools_agent, load_tools
+from langchain_openai import ChatOpenAI
+from gradio import ChatMessage
+import gradio as gr
+
+from dotenv import load_dotenv
+
+load_dotenv()
+
+model = ChatOpenAI(temperature=0, streaming=True)
+
+tools = load_tools(["serpapi"])
+
+# Get the prompt to use - you can modify this!
+prompt = hub.pull("hwchase17/openai-tools-agent")
+agent = create_openai_tools_agent(
+ model.with_config({"tags": ["agent_llm"]}), tools, prompt
+)
+agent_executor = AgentExecutor(agent=agent, tools=tools).with_config(
+ {"run_name": "Agent"}
+)
+```
+
+Then we'll create the Gradio UI
+
+```python
+async def interact_with_langchain_agent(prompt, messages):
+ messages.append(ChatMessage(role="user", content=prompt))
+ yield messages
+ async for chunk in agent_executor.astream(
+ {"input": prompt}
+ ):
+ if "steps" in chunk:
+ for step in chunk["steps"]:
+ messages.append(ChatMessage(role="assistant", content=step.action.log,
+ metadata={"title": f"🛠️ Used tool {step.action.tool}"}))
+ yield messages
+ if "output" in chunk:
+ messages.append(ChatMessage(role="assistant", content=chunk["output"]))
+ yield messages
+
+
+with gr.Blocks() as demo:
+ gr.Markdown("# Chat with a LangChain Agent 🦜⛓️ and see its thoughts 💭")
+ chatbot = gr.Chatbot(
+ label="Agent",
+ avatar_images=(
+ None,
+ "https://em-content.zobj.net/source/twitter/141/parrot_1f99c.png",
+ ),
+ )
+ input = gr.Textbox(lines=1, label="Chat Message")
+ input.submit(interact_with_langchain_agent, [input_2, chatbot_2], [chatbot_2])
+
+demo.launch()
+```
+
+
+
+That's it! See our finished langchain demo [here](https://huggingface.co/spaces/gradio/langchain-agent).
+
+
+## Building with Visibly Thinking LLMs
+
+
+The Gradio Chatbot can natively display intermediate thoughts of a _thinking_ LLM. This makes it perfect for creating UIs that show how an AI model "thinks" while generating responses. Below guide will show you how to build a chatbot that displays Gemini AI's thought process in real-time.
+
+
+### A real example using Gemini 2.0 Flash Thinking API
+
+Let's create a complete chatbot that shows its thoughts and responses in real-time. We'll use Google's Gemini API for accessing Gemini 2.0 Flash Thinking LLM and Gradio for the UI.
+
+We'll begin with imports and setting up the gemini client. Note that you'll need to [acquire a Google Gemini API key](https://aistudio.google.com/apikey) first -
+
+```python
+import gradio as gr
+from gradio import ChatMessage
+from typing import Iterator
+import google.generativeai as genai
+
+genai.configure(api_key="your-gemini-api-key")
+model = genai.GenerativeModel("gemini-2.0-flash-thinking-exp-1219")
+```
+
+First, let's set up our streaming function that handles the model's output:
+
+```python
+def stream_gemini_response(user_message: str, messages: list) -> Iterator[list]:
+ """
+ Streams both thoughts and responses from the Gemini model.
+ """
+ # Initialize response from Gemini
+ response = model.generate_content(user_message, stream=True)
+
+ # Initialize buffers
+ thought_buffer = ""
+ response_buffer = ""
+ thinking_complete = False
+
+ # Add initial thinking message
+ messages.append(
+ ChatMessage(
+ role="assistant",
+ content="",
+ metadata={"title": "⏳Thinking: *The thoughts produced by the Gemini2.0 Flash model are experimental"}
+ )
+ )
+
+ for chunk in response:
+ parts = chunk.candidates[0].content.parts
+ current_chunk = parts[0].text
+
+ if len(parts) == 2 and not thinking_complete:
+ # Complete thought and start response
+ thought_buffer += current_chunk
+ messages[-1] = ChatMessage(
+ role="assistant",
+ content=thought_buffer,
+ metadata={"title": "⏳Thinking: *The thoughts produced by the Gemini2.0 Flash model are experimental"}
+ )
+
+ # Add response message
+ messages.append(
+ ChatMessage(
+ role="assistant",
+ content=parts[1].text
+ )
+ )
+ thinking_complete = True
+
+ elif thinking_complete:
+ # Continue streaming response
+ response_buffer += current_chunk
+ messages[-1] = ChatMessage(
+ role="assistant",
+ content=response_buffer
+ )
+
+ else:
+ # Continue streaming thoughts
+ thought_buffer += current_chunk
+ messages[-1] = ChatMessage(
+ role="assistant",
+ content=thought_buffer,
+ metadata={"title": "⏳Thinking: *The thoughts produced by the Gemini2.0 Flash model are experimental"}
+ )
+
+ yield messages
+```
+
+Then, let's create the Gradio interface:
+
+```python
+with gr.Blocks() as demo:
+ gr.Markdown("# Chat with Gemini 2.0 Flash and See its Thoughts 💭")
+
+ chatbot = gr.Chatbot(
+ label="Gemini2.0 'Thinking' Chatbot",
+ render_markdown=True,
+ )
+
+ input_box = gr.Textbox(
+ lines=1,
+ label="Chat Message",
+ placeholder="Type your message here and press Enter..."
+ )
+
+ # Set up event handlers
+ msg_store = gr.State("") # Store for preserving user message
+
+ input_box.submit(
+ lambda msg: (msg, msg, ""), # Store message and clear input
+ inputs=[input_box],
+ outputs=[msg_store, input_box, input_box],
+ queue=False
+ ).then(
+ user_message, # Add user message to chat
+ inputs=[msg_store, chatbot],
+ outputs=[input_box, chatbot],
+ queue=False
+ ).then(
+ stream_gemini_response, # Generate and stream response
+ inputs=[msg_store, chatbot],
+ outputs=chatbot
+ )
+
+demo.launch()
+```
+
+This creates a chatbot that:
+
+- Displays the model's thoughts in a collapsible section
+- Streams the thoughts and final response in real-time
+- Maintains a clean chat history
+
+ That's it! You now have a chatbot that not only responds to users but also shows its thinking process, creating a more transparent and engaging interaction. See our finished Gemini 2.0 Flash Thinking demo [here](https://huggingface.co/spaces/ysharma/Gemini2-Flash-Thinking).
+
+
+ ## Building with Citations
+
+The Gradio Chatbot can display citations from LLM responses, making it perfect for creating UIs that show source documentation and references. This guide will show you how to build a chatbot that displays Claude's citations in real-time.
+
+### A real example using Anthropic's Citations API
+Let's create a complete chatbot that shows both responses and their supporting citations. We'll use Anthropic's Claude API with citations enabled and Gradio for the UI.
+
+We'll begin with imports and setting up the Anthropic client. Note that you'll need an `ANTHROPIC_API_KEY` environment variable set:
+
+```python
+import gradio as gr
+import anthropic
+import base64
+from typing import List, Dict, Any
+
+client = anthropic.Anthropic()
+```
+
+First, let's set up our message formatting functions that handle document preparation:
+
+```python
+def encode_pdf_to_base64(file_obj) -> str:
+ """Convert uploaded PDF file to base64 string."""
+ if file_obj is None:
+ return None
+ with open(file_obj.name, 'rb') as f:
+ return base64.b64encode(f.read()).decode('utf-8')
+
+def format_message_history(
+ history: list,
+ enable_citations: bool,
+ doc_type: str,
+ text_input: str,
+ pdf_file: str
+) -> List[Dict]:
+ """Convert Gradio chat history to Anthropic message format."""
+ formatted_messages = []
+
+ # Add previous messages
+ for msg in history[:-1]:
+ if msg["role"] == "user":
+ formatted_messages.append({"role": "user", "content": msg["content"]})
+
+ # Prepare the latest message with document
+ latest_message = {"role": "user", "content": []}
+
+ if enable_citations:
+ if doc_type == "plain_text":
+ latest_message["content"].append({
+ "type": "document",
+ "source": {
+ "type": "text",
+ "media_type": "text/plain",
+ "data": text_input.strip()
+ },
+ "title": "Text Document",
+ "citations": {"enabled": True}
+ })
+ elif doc_type == "pdf" and pdf_file:
+ pdf_data = encode_pdf_to_base64(pdf_file)
+ if pdf_data:
+ latest_message["content"].append({
+ "type": "document",
+ "source": {
+ "type": "base64",
+ "media_type": "application/pdf",
+ "data": pdf_data
+ },
+ "title": pdf_file.name,
+ "citations": {"enabled": True}
+ })
+
+ # Add the user's question
+ latest_message["content"].append({"type": "text", "text": history[-1]["content"]})
+
+ formatted_messages.append(latest_message)
+ return formatted_messages
+```
+
+Then, let's create our bot response handler that processes citations:
+
+```python
+def bot_response(
+ history: list,
+ enable_citations: bool,
+ doc_type: str,
+ text_input: str,
+ pdf_file: str
+) -> List[Dict[str, Any]]:
+ try:
+ messages = format_message_history(history, enable_citations, doc_type, text_input, pdf_file)
+ response = client.messages.create(model="claude-3-5-sonnet-20241022", max_tokens=1024, messages=messages)
+
+ # Initialize main response and citations
+ main_response = ""
+ citations = []
+
+ # Process each content block
+ for block in response.content:
+ if block.type == "text":
+ main_response += block.text
+ if enable_citations and hasattr(block, 'citations') and block.citations:
+ for citation in block.citations:
+ if citation.cited_text not in citations:
+ citations.append(citation.cited_text)
+
+ # Add main response
+ history.append({"role": "assistant", "content": main_response})
+
+ # Add citations in a collapsible section
+ if enable_citations and citations:
+ history.append({
+ "role": "assistant",
+ "content": "\n".join([f"• {cite}" for cite in citations]),
+ "metadata": {"title": "📚 Citations"}
+ })
+
+ return history
+
+ except Exception as e:
+ history.append({
+ "role": "assistant",
+ "content": "I apologize, but I encountered an error while processing your request."
+ })
+ return history
+```
+
+Finally, let's create the Gradio interface:
+
+```python
+with gr.Blocks() as demo:
+ gr.Markdown("# Chat with Citations")
+
+ with gr.Row(scale=1):
+ with gr.Column(scale=4):
+ chatbot = gr.Chatbot(bubble_full_width=False, show_label=False, scale=1)
+ msg = gr.Textbox(placeholder="Enter your message here...", show_label=False, container=False)
+
+ with gr.Column(scale=1):
+ enable_citations = gr.Checkbox(label="Enable Citations", value=True, info="Toggle citation functionality" )
+ doc_type_radio = gr.Radio( choices=["plain_text", "pdf"], value="plain_text", label="Document Type", info="Choose the type of document to use")
+ text_input = gr.Textbox(label="Document Content", lines=10, info="Enter the text you want to reference")
+ pdf_input = gr.File(label="Upload PDF", file_types=[".pdf"], file_count="single", visible=False)
+
+ # Handle message submission
+ msg.submit(
+ user_message,
+ [msg, chatbot, enable_citations, doc_type_radio, text_input, pdf_input],
+ [msg, chatbot]
+ ).then(
+ bot_response,
+ [chatbot, enable_citations, doc_type_radio, text_input, pdf_input],
+ chatbot
+ )
+
+demo.launch()
+```
+
+This creates a chatbot that:
+- Supports both plain text and PDF documents for Claude to cite from
+- Displays Citations in collapsible sections using our `metadata` feature
+- Shows source quotes directly from the given documents
+
+The citations feature works particularly well with the Gradio Chatbot's `metadata` support, allowing us to create collapsible sections that keep the chat interface clean while still providing easy access to source documentation.
+
+That's it! You now have a chatbot that not only responds to users but also shows its sources, creating a more transparent and trustworthy interaction. See our finished Citations demo [here](https://huggingface.co/spaces/ysharma/anthropic-citations-with-gradio-metadata-key).
+
diff --git a/guides/05_chatbots/04_creating-a-custom-chatbot-with-blocks.md b/guides/05_chatbots/04_creating-a-custom-chatbot-with-blocks.md
new file mode 100644
index 0000000..7de2ea2
--- /dev/null
+++ b/guides/05_chatbots/04_creating-a-custom-chatbot-with-blocks.md
@@ -0,0 +1,92 @@
+# How to Create a Custom Chatbot with Gradio Blocks
+
+Tags: NLP, TEXT, CHAT
+Related spaces: https://huggingface.co/spaces/gradio/chatbot_streaming, https://huggingface.co/spaces/gradio/chatbot_examples,
+
+## Introduction
+
+**Important Note**: if you are getting started, we recommend using the `gr.ChatInterface` to create chatbots -- its a high-level abstraction that makes it possible to create beautiful chatbot applications fast, often with a single line of code. [Read more about it here](/guides/creating-a-chatbot-fast).
+
+This tutorial will show how to make chatbot UIs from scratch with Gradio's low-level Blocks API. This will give you full control over your Chatbot UI. You'll start by first creating a a simple chatbot to display text, a second one to stream text responses, and finally a chatbot that can handle media files as well. The chatbot interface that we create will look something like this:
+
+$demo_chatbot_streaming
+
+**Prerequisite**: We'll be using the `gradio.Blocks` class to build our Chatbot demo.
+You can [read the Guide to Blocks first](https://gradio.app/blocks-and-event-listeners) if you are not already familiar with it. Also please make sure you are using the **latest version** version of Gradio: `pip install --upgrade gradio`.
+
+## A Simple Chatbot Demo
+
+Let's start with recreating the simple demo above. As you may have noticed, our bot simply randomly responds "How are you?", "Today is a great day", or "I'm very hungry" to any input. Here's the code to create this with Gradio:
+
+$code_chatbot_simple
+
+There are three Gradio components here:
+
+- A `Chatbot`, whose value stores the entire history of the conversation, as a list of response pairs between the user and bot.
+- A `Textbox` where the user can type their message, and then hit enter/submit to trigger the chatbot response
+- A `ClearButton` button to clear the Textbox and entire Chatbot history
+
+We have a single function, `respond()`, which takes in the entire history of the chatbot, appends a random message, waits 1 second, and then returns the updated chat history. The `respond()` function also clears the textbox when it returns.
+
+Of course, in practice, you would replace `respond()` with your own more complex function, which might call a pretrained model or an API, to generate a response.
+
+$demo_chatbot_simple
+
+Tip: For better type hinting and auto-completion in your IDE, you can use the `gr.ChatMessage` dataclass:
+
+```python
+from gradio import ChatMessage
+
+def chat_function(message, history):
+ history.append(ChatMessage(role="user", content=message))
+ history.append(ChatMessage(role="assistant", content="Hello, how can I help you?"))
+ return history
+```
+
+## Add Streaming to your Chatbot
+
+There are several ways we can improve the user experience of the chatbot above. First, we can stream responses so the user doesn't have to wait as long for a message to be generated. Second, we can have the user message appear immediately in the chat history, while the chatbot's response is being generated. Here's the code to achieve that:
+
+$code_chatbot_streaming
+
+You'll notice that when a user submits their message, we now _chain_ two event events with `.then()`:
+
+1. The first method `user()` updates the chatbot with the user message and clears the input field. Because we want this to happen instantly, we set `queue=False`, which would skip any queue had it been enabled. The chatbot's history is appended with `{"role": "user", "content": user_message}`.
+
+2. The second method, `bot()` updates the chatbot history with the bot's response. Finally, we construct the message character by character and `yield` the intermediate outputs as they are being constructed. Gradio automatically turns any function with the `yield` keyword [into a streaming output interface](/guides/key-features/#iterative-outputs).
+
+
+Of course, in practice, you would replace `bot()` with your own more complex function, which might call a pretrained model or an API, to generate a response.
+
+
+## Adding Markdown, Images, Audio, or Videos
+
+The `gr.Chatbot` component supports a subset of markdown including bold, italics, and code. For example, we could write a function that responds to a user's message, with a bold **That's cool!**, like this:
+
+```py
+def bot(history):
+ response = {"role": "assistant", "content": "**That's cool!**"}
+ history.append(response)
+ return history
+```
+
+In addition, it can handle media files, such as images, audio, and video. You can use the `MultimodalTextbox` component to easily upload all types of media files to your chatbot. You can customize the `MultimodalTextbox` further by passing in the `sources` parameter, which is a list of sources to enable. To pass in a media file, we must pass in the file a dictionary with a `path` key pointing to a local file and an `alt_text` key. The `alt_text` is optional, so you can also just pass in a tuple with a single element `{"path": "filepath"}`, like this:
+
+```python
+def add_message(history, message):
+ for x in message["files"]:
+ history.append({"role": "user", "content": {"path": x}})
+ if message["text"] is not None:
+ history.append({"role": "user", "content": message["text"]})
+ return history, gr.MultimodalTextbox(value=None, interactive=False, file_types=["image"], sources=["upload", "microphone"])
+```
+
+Putting this together, we can create a _multimodal_ chatbot with a multimodal textbox for a user to submit text and media files. The rest of the code looks pretty much the same as before:
+
+$code_chatbot_multimodal
+$demo_chatbot_multimodal
+
+And you're done! That's all the code you need to build an interface for your chatbot model. Finally, we'll end our Guide with some links to Chatbots that are running on Spaces so that you can get an idea of what else is possible:
+
+- [gradio/chatbot_streaming](https://huggingface.co/spaces/gradio/chatbot_streaming): A streaming chatbot demo built with `gr.Chatbot` and Blocks.
+- [gradio/chatbot_examples](https://huggingface.co/spaces/gradio/chatbot_examples): A chatbot that presents new visitors with a list of multimodal examples they can use to start the conversation.
diff --git a/guides/05_chatbots/05_chatbot-specific-events.md b/guides/05_chatbots/05_chatbot-specific-events.md
new file mode 100644
index 0000000..d1f86c5
--- /dev/null
+++ b/guides/05_chatbots/05_chatbot-specific-events.md
@@ -0,0 +1,188 @@
+# Chatbot-Specific Events
+
+Tags: LLM, CHAT
+
+Users expect modern chatbot UIs to let them easily interact with individual chat messages: for example, users might want to retry message generations, undo messages, or click on a like/dislike button to upvote or downvote a generated message.
+
+Thankfully, the Gradio Chatbot exposes several events, such as `.retry`, `.undo`, `.like`, and `.clear`, to let you build this functionality into your application. As an application developer, you can attach functions to any of these event, allowing you to run arbitrary Python functions e.g. when a user interacts with a message.
+
+In this demo, we'll build a UI that implements these events. You can see our finished demo deployed on Hugging Face spaces here:
+
+$demo_chatbot_retry_undo_like
+
+Tip: `gr.ChatInterface` automatically uses the `retry` and `.undo` events so it's best to start there in order get a fully working application quickly.
+
+
+## The UI
+
+First, we'll build the UI without handling these events and build from there.
+We'll use the Hugging Face InferenceClient in order to get started without setting up
+any API keys.
+
+This is what the first draft of our application looks like:
+
+```python
+from huggingface_hub import InferenceClient
+import gradio as gr
+
+client = InferenceClient()
+
+def respond(
+ prompt: str,
+ history,
+):
+ if not history:
+ history = [{"role": "system", "content": "You are a friendly chatbot"}]
+ history.append({"role": "user", "content": prompt})
+
+ yield history
+
+ response = {"role": "assistant", "content": ""}
+ for message in client.chat_completion( # type: ignore
+ history,
+ temperature=0.95,
+ top_p=0.9,
+ max_tokens=512,
+ stream=True,
+ model="openai/gpt-oss-20b"
+ ):
+ response["content"] += message.choices[0].delta.content or "" if message.choices else ""
+ yield history + [response]
+
+
+with gr.Blocks() as demo:
+ gr.Markdown("# Chat with GPT-OSS 20b 🤗")
+ chatbot = gr.Chatbot(
+ label="Agent",
+ avatar_images=(
+ None,
+ "https://em-content.zobj.net/source/twitter/376/hugging-face_1f917.png",
+ ),
+ )
+ prompt = gr.Textbox(max_lines=1, label="Chat Message")
+ prompt.submit(respond, [prompt, chatbot], [chatbot])
+ prompt.submit(lambda: "", None, [prompt])
+
+if __name__ == "__main__":
+ demo.launch()
+```
+
+## The Undo Event
+
+Our undo event will populate the textbox with the previous user message and also remove all subsequent assistant responses.
+
+In order to know the index of the last user message, we can pass `gr.UndoData` to our event handler function like so:
+
+```python
+def handle_undo(history, undo_data: gr.UndoData):
+ return history[:undo_data.index], history[undo_data.index]['content'][0]["text"]
+```
+
+We then pass this function to the `undo` event!
+
+```python
+ chatbot.undo(handle_undo, chatbot, [chatbot, prompt])
+```
+
+You'll notice that every bot response will now have an "undo icon" you can use to undo the response -
+
+
+
+Tip: You can also access the content of the user message with `undo_data.value`
+
+## The Retry Event
+
+The retry event will work similarly. We'll use `gr.RetryData` to get the index of the previous user message and remove all the subsequent messages from the history. Then we'll use the `respond` function to generate a new response. We could also get the previous prompt via the `value` property of `gr.RetryData`.
+
+```python
+def handle_retry(history, retry_data: gr.RetryData):
+ new_history = history[:retry_data.index]
+ previous_prompt = history[retry_data.index]['content'][0]["text"]
+ yield from respond(previous_prompt, new_history)
+...
+
+chatbot.retry(handle_retry, chatbot, chatbot)
+```
+
+You'll see that the bot messages have a "retry" icon now -
+
+
+
+Tip: The Hugging Face inference API caches responses, so in this demo, the retry button will not generate a new response.
+
+## The Like Event
+
+By now you should hopefully be seeing the pattern!
+To let users like a message, we'll add a `.like` event to our chatbot.
+We'll pass it a function that accepts a `gr.LikeData` object.
+In this case, we'll just print the message that was either liked or disliked.
+
+```python
+def handle_like(data: gr.LikeData):
+ if data.liked:
+ print("You upvoted this response: ", data.value)
+ else:
+ print("You downvoted this response: ", data.value)
+
+chatbot.like(handle_like, None, None)
+```
+
+## The Edit Event
+
+Same idea with the edit listener! with `gr.Chatbot(editable=True)`, you can capture user edits. The `gr.EditData` object tells us the index of the message edited and the new text of the mssage. Below, we use this object to edit the history, and delete any subsequent messages.
+
+```python
+def handle_edit(history, edit_data: gr.EditData):
+ new_history = history[:edit_data.index]
+ new_history[-1]['content'] = [{"text": edit_data.value, "type": "text"}]
+ return new_history
+
+...
+
+chatbot.edit(handle_edit, chatbot, chatbot)
+```
+
+## The Clear Event
+
+As a bonus, we'll also cover the `.clear()` event, which is triggered when the user clicks the clear icon to clear all messages. As a developer, you can attach additional events that should happen when this icon is clicked, e.g. to handle clearing of additional chatbot state:
+
+```python
+from uuid import uuid4
+import gradio as gr
+
+
+def clear():
+ print("Cleared uuid")
+ return uuid4()
+
+
+def chat_fn(user_input, history, uuid):
+ return f"{user_input} with uuid {uuid}"
+
+
+with gr.Blocks() as demo:
+ uuid_state = gr.State(
+ uuid4
+ )
+ chatbot = gr.Chatbot()
+ chatbot.clear(clear, outputs=[uuid_state])
+
+ gr.ChatInterface(
+ chat_fn,
+ additional_inputs=[uuid_state],
+ chatbot=chatbot,
+ )
+
+demo.launch()
+```
+
+In this example, the `clear` function, bound to the `chatbot.clear` event, returns a new UUID into our session state, when the chat history is cleared via the trash icon. This can be seen in the `chat_fn` function, which references the UUID saved in our session state.
+
+This example also shows that you can use these events with `gr.ChatInterface` by passing in a custom `gr.Chatbot` object.
+
+## Conclusion
+
+That's it! You now know how you can implement the retry, undo, like, and clear events for the Chatbot.
+
+
+
diff --git a/guides/05_chatbots/06_creating-a-discord-bot-from-a-gradio-app.md b/guides/05_chatbots/06_creating-a-discord-bot-from-a-gradio-app.md
new file mode 100644
index 0000000..d2d90eb
--- /dev/null
+++ b/guides/05_chatbots/06_creating-a-discord-bot-from-a-gradio-app.md
@@ -0,0 +1,162 @@
+# 🚀 Creating Discord Bots with Gradio 🚀
+
+Tags: CHAT, DEPLOY, DISCORD
+
+You can make your Gradio app available as a Discord bot to let users in your Discord server interact with it directly.
+
+## How does it work?
+
+The Discord bot will listen to messages mentioning it in channels. When it receives a message (which can include text as well as files), it will send it to your Gradio app via Gradio's built-in API. Your bot will reply with the response it receives from the API.
+
+Because Gradio's API is very flexible, you can create Discord bots that support text, images, audio, streaming, chat history, and a wide variety of other features very easily.
+
+
+
+## Prerequisites
+
+* Install the latest version of `gradio` and the `discord.py` libraries:
+
+```
+pip install --upgrade gradio discord.py~=2.0
+```
+
+* Have a running Gradio app. This app can be running locally or on Hugging Face Spaces. In this example, we will be using the [Gradio Playground Space](https://huggingface.co/spaces/abidlabs/gradio-playground-bot), which takes in an image and/or text and generates the code to generate the corresponding Gradio app.
+
+Now, we are ready to get started!
+
+
+### 1. Create a Discord application
+
+First, go to the [Discord apps dashboard](https://discord.com/developers/applications). Look for the "New Application" button and click it. Give your application a name, and then click "Create".
+
+
+
+On the resulting screen, you will see basic information about your application. Under the Settings section, click on the "Bot" option. You can update your bot's username if you would like.
+
+Then click on the "Reset Token" button. A new token will be generated. Copy it as we will need it for the next step.
+
+Scroll down to the section that says "Privileged Gateway Intents". Your bot will need certain permissions to work correctly. In this tutorial, we will only be using the "Message Content Intent" so click the toggle to enable this intent. Save the changes.
+
+
+
+
+
+### 2. Write a Discord bot
+
+Let's start by writing a very simple Discord bot, just to make sure that everything is working. Write the following Python code in a file called `bot.py`, pasting the discord bot token from the previous step:
+
+```python
+# bot.py
+import discord
+
+TOKEN = #PASTE YOUR DISCORD BOT TOKEN HERE
+
+client = discord.Client()
+
+@client.event
+async def on_ready():
+ print(f'{client.user} has connected to Discord!')
+
+client.run(TOKEN)
+```
+
+Now, run this file: `python bot.py`, which should run and print a message like:
+
+```text
+We have logged in as GradioPlaygroundBot#1451
+```
+
+If that is working, we are ready to add Gradio-specific code. We will be using the [Gradio Python Client](https://www.gradio.app/guides/getting-started-with-the-python-client) to query the Gradio Playground Space mentioned above. Here's the updated `bot.py` file:
+
+```python
+import discord
+from gradio_client import Client, handle_file
+import httpx
+import os
+
+TOKEN = #PASTE YOUR DISCORD BOT TOKEN HERE
+
+intents = discord.Intents.default()
+intents.message_content = True
+
+client = discord.Client(intents=intents)
+gradio_client = Client("abidlabs/gradio-playground-bot")
+
+def download_image(attachment):
+ response = httpx.get(attachment.url)
+ image_path = f"./images/{attachment.filename}"
+ os.makedirs("./images", exist_ok=True)
+ with open(image_path, "wb") as f:
+ f.write(response.content)
+ return image_path
+
+@client.event
+async def on_ready():
+ print(f'We have logged in as {client.user}')
+
+@client.event
+async def on_message(message):
+ # Ignore messages from the bot itself
+ if message.author == client.user:
+ return
+
+ # Check if the bot is mentioned in the message and reply
+ if client.user in message.mentions:
+ # Extract the message content without the bot mention
+ clean_message = message.content.replace(f"<@{client.user.id}>", "").strip()
+
+ # Handle images (only the first image is used)
+ files = []
+ if message.attachments:
+ for attachment in message.attachments:
+ if any(attachment.filename.lower().endswith(ext) for ext in ['png', 'jpg', 'jpeg', 'gif', 'webp']):
+ image_path = download_image(attachment)
+ files.append(handle_file(image_path))
+ break
+
+ # Stream the responses to the channel
+ for response in gradio_client.submit(
+ message={"text": clean_message, "files": files},
+ ):
+ await message.channel.send(response[-1])
+
+client.run(TOKEN)
+```
+
+### 3. Add the bot to your Discord Server
+
+Now we are ready to install the bot on our server. Go back to the [Discord apps dashboard](https://discord.com/developers/applications). Under the Settings section, click on the "OAuth2" option. Scroll down to the "OAuth2 URL Generator" box and select the "bot" checkbox:
+
+
+
+
+
+Then in "Bot Permissions" box that pops up underneath, enable the following permissions:
+
+
+
+
+Copy the generated URL that appears underneath, which should look something like:
+
+```text
+https://discord.com/oauth2/authorize?client_id=1319011745452265575&permissions=377957238784&integration_type=0&scope=bot
+```
+
+Paste it into your browser, which should allow you to add the Discord bot to any Discord server that you manage.
+
+
+### 4. That's it!
+
+Now you can mention your bot from any channel in your Discord server, optionally attach an image, and it will respond with generated Gradio app code!
+
+The bot will:
+1. Listen for mentions
+2. Process any attached images
+3. Send the text and images to your Gradio app
+4. Stream the responses back to the Discord channel
+
+ This is just a basic example - you can extend it to handle more types of files, add error handling, or integrate with different Gradio apps.
+
+
+
+If you build a Discord bot from a Gradio app, feel free to share it on X and tag [the Gradio account](https://x.com/Gradio), and we are happy to help you amplify!
\ No newline at end of file
diff --git a/guides/05_chatbots/07_creating-a-slack-bot-from-a-gradio-app.md b/guides/05_chatbots/07_creating-a-slack-bot-from-a-gradio-app.md
new file mode 100644
index 0000000..dedb686
--- /dev/null
+++ b/guides/05_chatbots/07_creating-a-slack-bot-from-a-gradio-app.md
@@ -0,0 +1,144 @@
+# 🚀 Creating a Slack Bot from a Gradio App 🚀
+
+Tags: CHAT, DEPLOY, SLACK
+
+You can make your Gradio app available as a Slack bot to let users in your Slack workspace interact with it directly.
+
+## How does it work?
+
+The Slack bot will listen to messages mentioning it in channels. When it receives a message (which can include text as well as files), it will send it to your Gradio app via Gradio's built-in API. Your bot will reply with the response it receives from the API.
+
+Because Gradio's API is very flexible, you can create Slack bots that support text, images, audio, streaming, chat history, and a wide variety of other features very easily.
+
+
+
+## Prerequisites
+
+* Install the latest version of `gradio` and the `slack-bolt` library:
+
+```bash
+pip install --upgrade gradio slack-bolt~=1.0
+```
+
+* Have a running Gradio app. This app can be running locally or on Hugging Face Spaces. In this example, we will be using the [Gradio Playground Space](https://huggingface.co/spaces/abidlabs/gradio-playground-bot), which takes in an image and/or text and generates the code to generate the corresponding Gradio app.
+
+Now, we are ready to get started!
+
+### 1. Create a Slack App
+
+1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click "Create New App"
+2. Choose "From scratch" and give your app a name
+3. Select the workspace where you want to develop your app
+4. Under "OAuth & Permissions", scroll to "Scopes" and add these Bot Token Scopes:
+ - `app_mentions:read`
+ - `chat:write`
+ - `files:read`
+ - `files:write`
+5. In the same "OAuth & Permissions" page, scroll back up and click the button to install the app to your workspace.
+6. Note the "Bot User OAuth Token" (starts with `xoxb-`) that appears as we'll need it later
+7. Click on "Socket Mode" in the menu bar. When the page loads, click the toggle to "Enable Socket Mode"
+8. Give your token a name, such as `socket-token` and copy the token that is generated (starts with `xapp-`) as we'll need it later.
+9. Finally, go to the "Event Subscription" option in the menu bar. Click the toggle to "Enable Events" and subscribe to the `app_mention` bot event.
+
+### 2. Write a Slack bot
+
+Let's start by writing a very simple Slack bot, just to make sure that everything is working. Write the following Python code in a file called `bot.py`, pasting the two tokens from step 6 and step 8 in the previous section.
+
+```py
+from slack_bolt import App
+from slack_bolt.adapter.socket_mode import SocketModeHandler
+
+SLACK_BOT_TOKEN = # PASTE YOUR SLACK BOT TOKEN HERE
+SLACK_APP_TOKEN = # PASTE YOUR SLACK APP TOKEN HERE
+
+app = App(token=SLACK_BOT_TOKEN)
+
+@app.event("app_mention")
+def handle_app_mention_events(body, say):
+ user_id = body["event"]["user"]
+ say(f"Hi <@{user_id}>! You mentioned me and said: {body['event']['text']}")
+
+if __name__ == "__main__":
+ handler = SocketModeHandler(app, SLACK_APP_TOKEN)
+ handler.start()
+```
+
+If that is working, we are ready to add Gradio-specific code. We will be using the [Gradio Python Client](https://www.gradio.app/guides/getting-started-with-the-python-client) to query the Gradio Playground Space mentioned above. Here's the updated `bot.py` file:
+
+```python
+from slack_bolt import App
+from slack_bolt.adapter.socket_mode import SocketModeHandler
+
+SLACK_BOT_TOKEN = # PASTE YOUR SLACK BOT TOKEN HERE
+SLACK_APP_TOKEN = # PASTE YOUR SLACK APP TOKEN HERE
+
+app = App(token=SLACK_BOT_TOKEN)
+gradio_client = Client("abidlabs/gradio-playground-bot")
+
+def download_image(url, filename):
+ headers = {"Authorization": f"Bearer {SLACK_BOT_TOKEN}"}
+ response = httpx.get(url, headers=headers)
+ image_path = f"./images/{filename}"
+ os.makedirs("./images", exist_ok=True)
+ with open(image_path, "wb") as f:
+ f.write(response.content)
+ return image_path
+
+def slackify_message(message):
+ # Replace markdown links with slack format and remove code language specifier after triple backticks
+ pattern = r'\[(.*?)\]\((.*?)\)'
+ cleaned = re.sub(pattern, r'<\2|\1>', message)
+ cleaned = re.sub(r'```\w+\n', '```', cleaned)
+ return cleaned.strip()
+
+@app.event("app_mention")
+def handle_app_mention_events(body, say):
+ # Extract the message content without the bot mention
+ text = body["event"]["text"]
+ bot_user_id = body["authorizations"][0]["user_id"]
+ clean_message = text.replace(f"<@{bot_user_id}>", "").strip()
+
+ # Handle images if present
+ files = []
+ if "files" in body["event"]:
+ for file in body["event"]["files"]:
+ if file["filetype"] in ["png", "jpg", "jpeg", "gif", "webp"]:
+ image_path = download_image(file["url_private_download"], file["name"])
+ files.append(handle_file(image_path))
+ break
+
+ # Submit to Gradio and send responses back to Slack
+ for response in gradio_client.submit(
+ message={"text": clean_message, "files": files},
+ ):
+ cleaned_response = slackify_message(response[-1])
+ say(cleaned_response)
+
+if __name__ == "__main__":
+ handler = SocketModeHandler(app, SLACK_APP_TOKEN)
+ handler.start()
+```
+### 3. Add the bot to your Slack Workplace
+
+Now, create a new channel or navigate to an existing channel in your Slack workspace where you want to use the bot. Click the "+" button next to "Channels" in your Slack sidebar and follow the prompts to create a new channel.
+
+Finally, invite your bot to the channel:
+1. In your new channel, type `/invite @YourBotName`
+2. Select your bot from the dropdown
+3. Click "Invite to Channel"
+
+### 4. That's it!
+
+Now you can mention your bot in any channel it's in, optionally attach an image, and it will respond with generated Gradio app code!
+
+The bot will:
+1. Listen for mentions
+2. Process any attached images
+3. Send the text and images to your Gradio app
+4. Stream the responses back to the Slack channel
+
+This is just a basic example - you can extend it to handle more types of files, add error handling, or integrate with different Gradio apps!
+
+
+
+If you build a Slack bot from a Gradio app, feel free to share it on X and tag [the Gradio account](https://x.com/Gradio), and we are happy to help you amplify!
\ No newline at end of file
diff --git a/guides/05_chatbots/08_creating-a-website-widget-from-a-gradio-chatbot.md b/guides/05_chatbots/08_creating-a-website-widget-from-a-gradio-chatbot.md
new file mode 100644
index 0000000..56ee898
--- /dev/null
+++ b/guides/05_chatbots/08_creating-a-website-widget-from-a-gradio-chatbot.md
@@ -0,0 +1,211 @@
+# 🚀 Creating a Website Chat Widget with Gradio 🚀
+
+Tags: CHAT, DEPLOY, WEB
+
+You can make your Gradio Chatbot available as an embedded chat widget on your website, similar to popular customer service widgets like Intercom. This is particularly useful for:
+
+- Adding AI assistance to your documentation pages
+- Providing interactive help on your portfolio or product website
+- Creating a custom chatbot interface for your Gradio app
+
+
+
+## How does it work?
+
+The chat widget appears as a small button in the corner of your website. When clicked, it opens a chat interface that communicates with your Gradio app via the JavaScript Client API. Users can ask questions and receive responses directly within the widget.
+
+
+## Prerequisites
+
+* A running Gradio app (local or on Hugging Face Spaces). In this example, we'll use the [Gradio Playground Space](https://huggingface.co/spaces/abidlabs/gradio-playground-bot), which helps generate code for Gradio apps based on natural language descriptions.
+
+### 1. Create and Style the Chat Widget
+
+First, add this HTML and CSS to your website:
+
+```html
+
+
+
+```
+
+### 2. Add the JavaScript
+
+Then, add the following JavaScript code (which uses the Gradio JavaScript Client to connect to the Space) to your website by including this in the `` section of your website:
+
+```html
+
+```
+
+### 3. That's it!
+
+Your website now has a chat widget that connects to your Gradio app! Users can click the chat button to open the widget and start interacting with your app.
+
+### Customization
+
+You can customize the appearance of the widget by modifying the CSS. Some ideas:
+- Change the colors to match your website's theme
+- Adjust the size and position of the widget
+- Add animations for opening/closing
+- Modify the message styling
+
+
+
+If you build a website widget from a Gradio app, feel free to share it on X and tag [the Gradio account](https://x.com/Gradio), and we are happy to help you amplify!
\ No newline at end of file
diff --git a/guides/06_data-science-and-plots/01_creating-plots.md b/guides/06_data-science-and-plots/01_creating-plots.md
new file mode 100644
index 0000000..76a088b
--- /dev/null
+++ b/guides/06_data-science-and-plots/01_creating-plots.md
@@ -0,0 +1,72 @@
+# Creating Plots
+
+Gradio is a great way to create extremely customizable dashboards. Gradio comes with three native Plot components: `gr.LinePlot`, `gr.ScatterPlot` and `gr.BarPlot`. All these plots have the same API. Let's take a look how to set them up.
+
+## Creating a Plot with a pd.Dataframe
+
+Plots accept a pandas Dataframe as their value. The plot also takes `x` and `y` which represent the names of the columns that represent the x and y axes respectively. Here's a simple example:
+
+$code_plot_guide_line
+$demo_plot_guide_line
+
+All plots have the same API, so you could swap this out with a `gr.ScatterPlot`:
+
+$code_plot_guide_scatter
+$demo_plot_guide_scatter
+
+The y axis column in the dataframe should have a numeric type, but the x axis column can be anything from strings, numbers, categories, or datetimes.
+
+$code_plot_guide_scatter_nominal
+$demo_plot_guide_scatter_nominal
+
+## Breaking out Series by Color
+
+You can break out your plot into series using the `color` argument.
+
+$code_plot_guide_series_nominal
+$demo_plot_guide_series_nominal
+
+If you wish to assign series specific colors, use the `color_map` arg, e.g. `gr.ScatterPlot(..., color_map={'white': '#FF9988', 'asian': '#88EEAA', 'black': '#333388'})`
+
+The color column can be numeric type as well.
+
+$code_plot_guide_series_quantitative
+$demo_plot_guide_series_quantitative
+
+## Aggregating Values
+
+You can aggregate values into groups using the `x_bin` and `y_aggregate` arguments. If your x-axis is numeric, providing an `x_bin` will create a histogram-style binning:
+
+$code_plot_guide_aggregate_quantitative
+$demo_plot_guide_aggregate_quantitative
+
+If your x-axis is a string type instead, they will act as the category bins automatically:
+
+$code_plot_guide_aggregate_nominal
+$demo_plot_guide_aggregate_nominal
+
+## Selecting Regions
+
+You can use the `.select` listener to select regions of a plot. Click and drag on the plot below to select part of the plot.
+
+$code_plot_guide_selection
+$demo_plot_guide_selection
+
+You can combine this and the `.double_click` listener to create some zoom in/out effects by changing `x_lim` which sets the bounds of the x-axis:
+
+$code_plot_guide_zoom
+$demo_plot_guide_zoom
+
+If you had multiple plots with the same x column, your event listeners could target the x limits of all other plots so that the x-axes stay in sync.
+
+$code_plot_guide_zoom_sync
+$demo_plot_guide_zoom_sync
+
+## Making an Interactive Dashboard
+
+Take a look how you can have an interactive dashboard where the plots are functions of other Components.
+
+$code_plot_guide_interactive
+$demo_plot_guide_interactive
+
+It's that simple to filter and control the data presented in your visualization!
\ No newline at end of file
diff --git a/guides/06_data-science-and-plots/02_time-plots.md b/guides/06_data-science-and-plots/02_time-plots.md
new file mode 100644
index 0000000..4396ca4
--- /dev/null
+++ b/guides/06_data-science-and-plots/02_time-plots.md
@@ -0,0 +1,59 @@
+# Time Plots
+
+Creating visualizations with a time x-axis is a common use case. Let's dive in!
+
+## Creating a Plot with a pd.Dataframe
+
+Time plots need a datetime column on the x-axis. Here's a simple example with some flight data:
+
+$code_plot_guide_temporal
+$demo_plot_guide_temporal
+
+## Aggregating by Time
+
+You may wish to bin data by time buckets. Use `x_bin` to do so, using a string suffix with "s", "m", "h" or "d", such as "15m" or "1d".
+
+$code_plot_guide_aggregate_temporal
+$demo_plot_guide_aggregate_temporal
+
+## DateTime Components
+
+You can use `gr.DateTime` to accept input datetime data. This works well with plots for defining the x-axis range for the data.
+
+$code_plot_guide_datetime
+$demo_plot_guide_datetime
+
+Note how `gr.DateTime` can accept a full datetime string, or a shorthand using `now - [0-9]+[smhd]` format to refer to a past time.
+
+You will often have many time plots in which case you'd like to keep the x-axes in sync. The `DateTimeRange` custom component keeps a set of datetime plots in sync, and also uses the `.select` listener of plots to allow you to zoom into plots while keeping plots in sync.
+
+Because it is a custom component, you first need to `pip install gradio_datetimerange`. Then run the following:
+
+$code_plot_guide_datetimerange
+$demo_plot_guide_datetimerange
+
+Try zooming around in the plots and see how DateTimeRange updates. All the plots updates their `x_lim` in sync. You also have a "Back" link in the component to allow you to quickly zoom in and out.
+
+## RealTime Data
+
+In many cases, you're working with live, realtime date, not a static dataframe. In this case, you'd update the plot regularly with a `gr.Timer()`. Assuming there's a `get_data` method that gets the latest dataframe:
+
+```python
+with gr.Blocks() as demo:
+ timer = gr.Timer(5)
+ plot1 = gr.BarPlot(x="time", y="price")
+ plot2 = gr.BarPlot(x="time", y="price", color="origin")
+
+ timer.tick(lambda: [get_data(), get_data()], outputs=[plot1, plot2])
+```
+
+You can also use the `every` shorthand to attach a `Timer` to a component that has a function value:
+
+```python
+with gr.Blocks() as demo:
+ timer = gr.Timer(5)
+ plot1 = gr.BarPlot(get_data, x="time", y="price", every=timer)
+ plot2 = gr.BarPlot(get_data, x="time", y="price", color="origin", every=timer)
+```
+
+
diff --git a/guides/06_data-science-and-plots/03_filters-tables-and-stats.md b/guides/06_data-science-and-plots/03_filters-tables-and-stats.md
new file mode 100644
index 0000000..dc943dc
--- /dev/null
+++ b/guides/06_data-science-and-plots/03_filters-tables-and-stats.md
@@ -0,0 +1,21 @@
+# Filters, Tables and Stats
+
+Your dashboard will likely consist of more than just plots. Let's take a look at some of the other common components of a dashboard.
+
+## Filters
+
+Use any of the standard Gradio form components to filter your data. You can do this via event listeners or function-as-value syntax. Let's look at the event listener approach first:
+
+$code_plot_guide_filters_events
+$demo_plot_guide_filters_events
+
+And this would be the function-as-value approach for the same demo.
+
+$code_plot_guide_filters
+
+## Tables and Stats
+
+Add `gr.DataFrame` and `gr.Label` to your dashboard for some hard numbers.
+
+$code_plot_guide_tables_stats
+$demo_plot_guide_tables_stats
diff --git a/guides/06_data-science-and-plots/04_connecting-to-a-database.md b/guides/06_data-science-and-plots/04_connecting-to-a-database.md
new file mode 100644
index 0000000..ddeb5a9
--- /dev/null
+++ b/guides/06_data-science-and-plots/04_connecting-to-a-database.md
@@ -0,0 +1,47 @@
+# Connecting to a Database
+
+The data you wish to visualize may be stored in a database. Let's use SQLAlchemy to quickly extract database content into pandas Dataframe format so we can use it in gradio.
+
+First install `pip install sqlalchemy` and then let's see some examples.
+
+## SQLite
+
+```python
+from sqlalchemy import create_engine
+import pandas as pd
+
+engine = create_engine('sqlite:///your_database.db')
+
+with gr.Blocks() as demo:
+ gr.LinePlot(pd.read_sql_query("SELECT time, price from flight_info;", engine), x="time", y="price")
+```
+
+Let's see a a more interactive plot involving filters that modify your SQL query:
+
+```python
+from sqlalchemy import create_engine
+import pandas as pd
+
+engine = create_engine('sqlite:///your_database.db')
+
+with gr.Blocks() as demo:
+ origin = gr.Dropdown(["DFW", "DAL", "HOU"], value="DFW", label="Origin")
+
+ gr.LinePlot(lambda origin: pd.read_sql_query(f"SELECT time, price from flight_info WHERE origin = {origin};", engine), inputs=origin, x="time", y="price")
+```
+
+## Postgres, mySQL, and other databases
+
+If you're using a different database format, all you have to do is swap out the engine, e.g.
+
+```python
+engine = create_engine('postgresql://username:password@host:port/database_name')
+```
+
+```python
+engine = create_engine('mysql://username:password@host:port/database_name')
+```
+
+```python
+engine = create_engine('oracle://username:password@host:port/database_name')
+```
\ No newline at end of file
diff --git a/guides/07_streaming/01_streaming-ai-generated-audio.md b/guides/07_streaming/01_streaming-ai-generated-audio.md
new file mode 100644
index 0000000..23e75cf
--- /dev/null
+++ b/guides/07_streaming/01_streaming-ai-generated-audio.md
@@ -0,0 +1,153 @@
+# Streaming AI Generated Audio
+
+Tags: AUDIO, STREAMING
+
+In this guide, we'll build a novel AI application to showcase Gradio's audio output streaming. We're going to a build a talking [Magic 8 Ball](https://en.wikipedia.org/wiki/Magic_8_Ball) 🎱
+
+A Magic 8 Ball is a toy that answers any question after you shake it. Our application will do the same but it will also speak its response!
+
+We won't cover all the implementation details in this blog post but the code is freely available on [Hugging Face Spaces](https://huggingface.co/spaces/gradio/magic-8-ball).
+
+## The Overview
+
+Just like the classic Magic 8 Ball, a user should ask it a question orally and then wait for a response. Under the hood, we'll use Whisper to transcribe the audio and then use an LLM to generate a magic-8-ball-style answer. Finally, we'll use Parler TTS to read the response aloud.
+
+## The UI
+
+First let's define the UI and put placeholders for all the python logic.
+
+```python
+import gradio as gr
+
+with gr.Blocks() as block:
+ gr.HTML(
+ f"""
+ Magic 8 Ball 🎱
+ Ask a question and receive wisdom
+ Powered by Parler-TTS
+ """
+ )
+ with gr.Group():
+ with gr.Row():
+ audio_out = gr.Audio(label="Spoken Answer", streaming=True, autoplay=True)
+ answer = gr.Textbox(label="Answer")
+ state = gr.State()
+ with gr.Row():
+ audio_in = gr.Audio(label="Speak your question", sources="microphone", type="filepath")
+
+ audio_in.stop_recording(generate_response, audio_in, [state, answer, audio_out])\
+ .then(fn=read_response, inputs=state, outputs=[answer, audio_out])
+
+block.launch()
+```
+
+We're placing the output Audio and Textbox components and the input Audio component in separate rows. In order to stream the audio from the server, we'll set `streaming=True` in the output Audio component. We'll also set `autoplay=True` so that the audio plays as soon as it's ready.
+We'll be using the Audio input component's `stop_recording` event to trigger our application's logic when a user stops recording from their microphone.
+
+We're separating the logic into two parts. First, `generate_response` will take the recorded audio, transcribe it and generate a response with an LLM. We're going to store the response in a `gr.State` variable that then gets passed to the `read_response` function that generates the audio.
+
+We're doing this in two parts because only `read_response` will require a GPU. Our app will run on Hugging Faces [ZeroGPU](https://huggingface.co/zero-gpu-explorers) which has time-based quotas. Since generating the response can be done with Hugging Face's Inference API, we shouldn't include that code in our GPU function as it will needlessly use our GPU quota.
+
+## The Logic
+
+As mentioned above, we'll use [Hugging Face's Inference API](https://huggingface.co/docs/huggingface_hub/guides/inference) to transcribe the audio and generate a response from an LLM. After instantiating the client, I use the `automatic_speech_recognition` method (this automatically uses Whisper running on Hugging Face's Inference Servers) to transcribe the audio. Then I pass the question to an LLM (Mistal-7B-Instruct) to generate a response. We are prompting the LLM to act like a magic 8 ball with the system message.
+
+Our `generate_response` function will also send empty updates to the output textbox and audio components (returning `None`).
+This is because I want the Gradio progress tracker to be displayed over the components but I don't want to display the answer until the audio is ready.
+
+
+```python
+from huggingface_hub import InferenceClient
+
+client = InferenceClient(token=os.getenv("HF_TOKEN"))
+
+def generate_response(audio):
+ gr.Info("Transcribing Audio", duration=5)
+ question = client.automatic_speech_recognition(audio).text
+
+ messages = [{"role": "system", "content": ("You are a magic 8 ball."
+ "Someone will present to you a situation or question and your job "
+ "is to answer with a cryptic adage or proverb such as "
+ "'curiosity killed the cat' or 'The early bird gets the worm'."
+ "Keep your answers short and do not include the phrase 'Magic 8 Ball' in your response. If the question does not make sense or is off-topic, say 'Foolish questions get foolish answers.'"
+ "For example, 'Magic 8 Ball, should I get a dog?', 'A dog is ready for you but are you ready for the dog?'")},
+ {"role": "user", "content": f"Magic 8 Ball please answer this question - {question}"}]
+
+ response = client.chat_completion(messages, max_tokens=64, seed=random.randint(1, 5000),
+ model="mistralai/Mistral-7B-Instruct-v0.3")
+
+ response = response.choices[0].message.content.replace("Magic 8 Ball", "").replace(":", "")
+ return response, None, None
+```
+
+
+Now that we have our text response, we'll read it aloud with Parler TTS. The `read_response` function will be a python generator that yields the next chunk of audio as it's ready.
+
+
+We'll be using the [Mini v0.1](https://huggingface.co/parler-tts/parler_tts_mini_v0.1) for the feature extraction but the [Jenny fine tuned version](https://huggingface.co/parler-tts/parler-tts-mini-jenny-30H) for the voice. This is so that the voice is consistent across generations.
+
+
+Streaming audio with transformers requires a custom Streamer class. You can see the implementation [here](https://huggingface.co/spaces/gradio/magic-8-ball/blob/main/streamer.py). Additionally, we'll convert the output to bytes so that it can be streamed faster from the backend.
+
+
+```python
+from streamer import ParlerTTSStreamer
+from transformers import AutoTokenizer, AutoFeatureExtractor, set_seed
+import numpy as np
+import spaces
+import torch
+from threading import Thread
+
+
+device = "cuda:0" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
+torch_dtype = torch.float16 if device != "cpu" else torch.float32
+
+repo_id = "parler-tts/parler_tts_mini_v0.1"
+
+jenny_repo_id = "ylacombe/parler-tts-mini-jenny-30H"
+
+model = ParlerTTSForConditionalGeneration.from_pretrained(
+ jenny_repo_id, torch_dtype=torch_dtype, low_cpu_mem_usage=True
+).to(device)
+
+tokenizer = AutoTokenizer.from_pretrained(repo_id)
+feature_extractor = AutoFeatureExtractor.from_pretrained(repo_id)
+
+sampling_rate = model.audio_encoder.config.sampling_rate
+frame_rate = model.audio_encoder.config.frame_rate
+
+@spaces.GPU
+def read_response(answer):
+
+ play_steps_in_s = 2.0
+ play_steps = int(frame_rate * play_steps_in_s)
+
+ description = "Jenny speaks at an average pace with a calm delivery in a very confined sounding environment with clear audio quality."
+ description_tokens = tokenizer(description, return_tensors="pt").to(device)
+
+ streamer = ParlerTTSStreamer(model, device=device, play_steps=play_steps)
+ prompt = tokenizer(answer, return_tensors="pt").to(device)
+
+ generation_kwargs = dict(
+ input_ids=description_tokens.input_ids,
+ prompt_input_ids=prompt.input_ids,
+ streamer=streamer,
+ do_sample=True,
+ temperature=1.0,
+ min_new_tokens=10,
+ )
+
+ set_seed(42)
+ thread = Thread(target=model.generate, kwargs=generation_kwargs)
+ thread.start()
+
+ for new_audio in streamer:
+ print(f"Sample of length: {round(new_audio.shape[0] / sampling_rate, 2)} seconds")
+ yield answer, numpy_to_mp3(new_audio, sampling_rate=sampling_rate)
+```
+
+## Conclusion
+
+You can see our final application [here](https://huggingface.co/spaces/gradio/magic-8-ball)!
+
+
diff --git a/guides/07_streaming/02_object-detection-from-webcam-with-webrtc.md b/guides/07_streaming/02_object-detection-from-webcam-with-webrtc.md
new file mode 100644
index 0000000..8884cab
--- /dev/null
+++ b/guides/07_streaming/02_object-detection-from-webcam-with-webrtc.md
@@ -0,0 +1,103 @@
+# Real Time Object Detection from a Webcam Stream with FastRTC
+
+Tags: VISION, STREAMING, WEBCAM
+
+In this guide, we'll use YOLOv10 to perform real-time object detection in Gradio from a user's webcam feed. We'll utilize [FastRTC](https://fastrtc.org/) a companion library from the gradio team for building low latency streaming web applications. You can see the finished product in action below:
+
+
+
+## Setting up
+
+Start by installing all the dependencies. Add the following lines to a `requirements.txt` file and run `pip install -r requirements.txt`:
+
+```bash
+opencv-python
+fastrtc
+onnxruntime-gpu
+```
+
+We'll use the ONNX runtime to speed up YOLOv10 inference. This guide assumes you have access to a GPU. If you don't, change `onnxruntime-gpu` to `onnxruntime`. Without a GPU, the model will run slower, resulting in a laggy demo.
+
+We'll use OpenCV for image manipulation and the [WebRTC](https://webrtc.org/) protocol to achieve near-zero latency.
+
+**Note**: If you want to deploy this app on any cloud provider, you'll need to use your Hugging Face token to connect to a TURN server. Learn more in this [guide](https://fastrtc.org/deployment/). If you're not familiar with TURN servers, consult this [guide](https://www.twilio.com/docs/stun-turn/faq#faq-what-is-nat).
+
+## The Inference Function
+
+We'll download the YOLOv10 model from the Hugging Face hub and instantiate a custom inference class to use this model.
+
+The implementation of the inference class isn't covered in this guide, but you can find the source code [here](https://huggingface.co/spaces/freddyaboulton/webrtc-yolov10n/blob/main/inference.py#L9) if you're interested. This implementation borrows heavily from this [github repository](https://github.com/ibaiGorordo/ONNX-YOLOv8-Object-Detection).
+
+We're using the `yolov10-n` variant because it has the lowest latency. See the [Performance](https://github.com/THU-MIG/yolov10?tab=readme-ov-file#performance) section of the README in the YOLOv10 GitHub repository.
+
+```python
+from huggingface_hub import hf_hub_download
+from inference import YOLOv10
+
+model_file = hf_hub_download(
+ repo_id="onnx-community/yolov10n", filename="onnx/model.onnx"
+)
+
+model = YOLOv10(model_file)
+
+def detection(image, conf_threshold=0.3):
+ image = cv2.resize(image, (model.input_width, model.input_height))
+ new_image = model.detect_objects(image, conf_threshold)
+ return new_image
+```
+
+Our inference function, `detection`, accepts a numpy array from the webcam and a desired confidence threshold. Object detection models like YOLO identify many objects and assign a confidence score to each. The lower the confidence, the higher the chance of a false positive. We'll let users adjust the confidence threshold.
+
+The function returns a numpy array corresponding to the same input image with all detected objects in bounding boxes.
+
+## The Gradio Demo
+
+The Gradio demo is straightforward, but we'll implement a few specific features:
+
+1. Use the `WebRTC` custom component to ensure input and output are sent to/from the server with WebRTC.
+2. The [WebRTC](https://github.com/freddyaboulton/gradio-webrtc) component will serve as both an input and output component.
+3. Utilize the `time_limit` parameter of the `stream` event. This parameter sets a processing time for each user's stream. In a multi-user setting, such as on Spaces, we'll stop processing the current user's stream after this period and move on to the next.
+
+We'll also apply custom CSS to center the webcam and slider on the page.
+
+```python
+import gradio as gr
+from fastrtc import WebRTC
+
+css = """.my-group {max-width: 600px !important; max-height: 600px !important;}
+ .my-column {display: flex !important; justify-content: center !important; align-items: center !important;}"""
+
+with gr.Blocks(css=css) as demo:
+ gr.HTML(
+ """
+
+ YOLOv10 Webcam Stream (Powered by WebRTC ⚡️)
+
+ """
+ )
+ with gr.Column(elem_classes=["my-column"]):
+ with gr.Group(elem_classes=["my-group"]):
+ image = WebRTC(label="Stream", rtc_configuration=rtc_configuration)
+ conf_threshold = gr.Slider(
+ label="Confidence Threshold",
+ minimum=0.0,
+ maximum=1.0,
+ step=0.05,
+ value=0.30,
+ )
+
+ image.stream(
+ fn=detection, inputs=[image, conf_threshold], outputs=[image], time_limit=10
+ )
+
+if __name__ == "__main__":
+ demo.launch()
+```
+
+## Conclusion
+
+Our app is hosted on Hugging Face Spaces [here](https://huggingface.co/spaces/freddyaboulton/webrtc-yolov10n).
+
+You can use this app as a starting point to build real-time image applications with Gradio. Don't hesitate to open issues in the space or in the [FastRTC GitHub repo](https://github.com/gradio-app/fastrtc) if you have any questions or encounter problems.
\ No newline at end of file
diff --git a/guides/07_streaming/03_object-detection-from-video.md b/guides/07_streaming/03_object-detection-from-video.md
new file mode 100644
index 0000000..17ac016
--- /dev/null
+++ b/guides/07_streaming/03_object-detection-from-video.md
@@ -0,0 +1,184 @@
+# Streaming Object Detection from Video
+
+Tags: VISION, STREAMING, VIDEO
+
+In this guide we'll use the [RT-DETR](https://huggingface.co/docs/transformers/en/model_doc/rt_detr) model to detect objects in a user uploaded video. We'll stream the results from the server using the new video streaming features introduced in Gradio 5.0.
+
+
+
+## Setting up the Model
+
+First, we'll install the following requirements in our system:
+
+```
+opencv-python
+torch
+transformers>=4.43.0
+spaces
+```
+
+Then, we'll download the model from the Hugging Face Hub:
+
+```python
+from transformers import RTDetrForObjectDetection, RTDetrImageProcessor
+
+image_processor = RTDetrImageProcessor.from_pretrained("PekingU/rtdetr_r50vd")
+model = RTDetrForObjectDetection.from_pretrained("PekingU/rtdetr_r50vd").to("cuda")
+```
+We're moving the model to the GPU. We'll be deploying our model to Hugging Face Spaces and running the inference in the [free ZeroGPU cluster](https://huggingface.co/zero-gpu-explorers).
+
+
+## The Inference Function
+
+Our inference function will accept a video and a desired confidence threshold.
+Object detection models identify many objects and assign a confidence score to each object. The lower the confidence, the higher the chance of a false positive. So we will let our users set the confidence threshold.
+
+Our function will iterate over the frames in the video and run the RT-DETR model over each frame.
+We will then draw the bounding boxes for each detected object in the frame and save the frame to a new output video.
+The function will yield each output video in chunks of two seconds.
+
+In order to keep inference times as low as possible on ZeroGPU (there is a time-based quota),
+we will halve the original frames-per-second in the output video and resize the input frames to be half the original
+size before running the model.
+
+The code for the inference function is below - we'll go over it piece by piece.
+
+```python
+import spaces
+import cv2
+from PIL import Image
+import torch
+import time
+import numpy as np
+import uuid
+
+from draw_boxes import draw_bounding_boxes
+
+SUBSAMPLE = 2
+
+@spaces.GPU
+def stream_object_detection(video, conf_threshold):
+ cap = cv2.VideoCapture(video)
+
+ # This means we will output mp4 videos
+ video_codec = cv2.VideoWriter_fourcc(*"mp4v") # type: ignore
+ fps = int(cap.get(cv2.CAP_PROP_FPS))
+
+ desired_fps = fps // SUBSAMPLE
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) // 2
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) // 2
+
+ iterating, frame = cap.read()
+
+ n_frames = 0
+
+ # Use UUID to create a unique video file
+ output_video_name = f"output_{uuid.uuid4()}.mp4"
+
+ # Output Video
+ output_video = cv2.VideoWriter(output_video_name, video_codec, desired_fps, (width, height)) # type: ignore
+ batch = []
+
+ while iterating:
+ frame = cv2.resize( frame, (0,0), fx=0.5, fy=0.5)
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
+ if n_frames % SUBSAMPLE == 0:
+ batch.append(frame)
+ if len(batch) == 2 * desired_fps:
+ inputs = image_processor(images=batch, return_tensors="pt").to("cuda")
+
+ with torch.no_grad():
+ outputs = model(**inputs)
+
+ boxes = image_processor.post_process_object_detection(
+ outputs,
+ target_sizes=torch.tensor([(height, width)] * len(batch)),
+ threshold=conf_threshold)
+
+ for i, (array, box) in enumerate(zip(batch, boxes)):
+ pil_image = draw_bounding_boxes(Image.fromarray(array), box, model, conf_threshold)
+ frame = np.array(pil_image)
+ # Convert RGB to BGR
+ frame = frame[:, :, ::-1].copy()
+ output_video.write(frame)
+
+ batch = []
+ output_video.release()
+ yield output_video_name
+ output_video_name = f"output_{uuid.uuid4()}.mp4"
+ output_video = cv2.VideoWriter(output_video_name, video_codec, desired_fps, (width, height)) # type: ignore
+
+ iterating, frame = cap.read()
+ n_frames += 1
+```
+
+1. **Reading from the Video**
+
+One of the industry standards for creating videos in python is OpenCV so we will use it in this app.
+
+The `cap` variable is how we will read from the input video. Whenever we call `cap.read()`, we are reading the next frame in the video.
+
+In order to stream video in Gradio, we need to yield a different video file for each "chunk" of the output video.
+We create the next video file to write to with the `output_video = cv2.VideoWriter(output_video_name, video_codec, desired_fps, (width, height))` line. The `video_codec` is how we specify the type of video file. Only "mp4" and "ts" files are supported for video sreaming at the moment.
+
+
+2. **The Inference Loop**
+
+For each frame in the video, we will resize it to be half the size. OpenCV reads files in `BGR` format, so will convert to the expected `RGB` format of transfomers. That's what the first two lines of the while loop are doing.
+
+We take every other frame and add it to a `batch` list so that the output video is half the original FPS. When the batch covers two seconds of video, we will run the model. The two second threshold was chosen to keep the processing time of each batch small enough so that video is smoothly displayed in the server while not requiring too many separate forward passes. In order for video streaming to work properly in Gradio, the batch size should be at least 1 second.
+
+We run the forward pass of the model and then use the `post_process_object_detection` method of the model to scale the detected bounding boxes to the size of the input frame.
+
+We make use of a custom function to draw the bounding boxes (source [here](https://huggingface.co/spaces/gradio/rt-detr-object-detection/blob/main/draw_boxes.py#L14)). We then have to convert from `RGB` to `BGR` before writing back to the output video.
+
+Once we have finished processing the batch, we create a new output video file for the next batch.
+
+## The Gradio Demo
+
+The UI code is pretty similar to other kinds of Gradio apps.
+We'll use a standard two-column layout so that users can see the input and output videos side by side.
+
+In order for streaming to work, we have to set `streaming=True` in the output video. Setting the video
+to autoplay is not necessary but it's a better experience for users.
+
+```python
+import gradio as gr
+
+with gr.Blocks() as app:
+ gr.HTML(
+ """
+
+ Video Object Detection with RT-DETR
+
+ """)
+ with gr.Row():
+ with gr.Column():
+ video = gr.Video(label="Video Source")
+ conf_threshold = gr.Slider(
+ label="Confidence Threshold",
+ minimum=0.0,
+ maximum=1.0,
+ step=0.05,
+ value=0.30,
+ )
+ with gr.Column():
+ output_video = gr.Video(label="Processed Video", streaming=True, autoplay=True)
+
+ video.upload(
+ fn=stream_object_detection,
+ inputs=[video, conf_threshold],
+ outputs=[output_video],
+ )
+
+
+```
+
+
+## Conclusion
+
+You can check out our demo hosted on Hugging Face Spaces [here](https://huggingface.co/spaces/gradio/rt-detr-object-detection).
+
+It is also embedded on this page below
+
+$demo_rt-detr-object-detection
\ No newline at end of file
diff --git a/guides/07_streaming/04_conversational-chatbot.md b/guides/07_streaming/04_conversational-chatbot.md
new file mode 100644
index 0000000..0246acc
--- /dev/null
+++ b/guides/07_streaming/04_conversational-chatbot.md
@@ -0,0 +1,189 @@
+# Building Conversational Chatbots with Gradio
+
+Tags: AUDIO, STREAMING, CHATBOTS
+
+## Introduction
+
+The next generation of AI user interfaces is moving towards audio-native experiences. Users will be able to speak to chatbots and receive spoken responses in return. Several models have been built under this paradigm, including GPT-4o and [mini omni](https://github.com/gpt-omni/mini-omni).
+
+In this guide, we'll walk you through building your own conversational chat application using mini omni as an example. You can see a demo of the finished app below:
+
+
+
+## Application Overview
+
+Our application will enable the following user experience:
+
+1. Users click a button to start recording their message
+2. The app detects when the user has finished speaking and stops recording
+3. The user's audio is passed to the omni model, which streams back a response
+4. After omni mini finishes speaking, the user's microphone is reactivated
+5. All previous spoken audio, from both the user and omni, is displayed in a chatbot component
+
+Let's dive into the implementation details.
+
+## Processing User Audio
+
+We'll stream the user's audio from their microphone to the server and determine if the user has stopped speaking on each new chunk of audio.
+
+Here's our `process_audio` function:
+
+```python
+import numpy as np
+from utils import determine_pause
+
+def process_audio(audio: tuple, state: AppState):
+ if state.stream is None:
+ state.stream = audio[1]
+ state.sampling_rate = audio[0]
+ else:
+ state.stream = np.concatenate((state.stream, audio[1]))
+
+ pause_detected = determine_pause(state.stream, state.sampling_rate, state)
+ state.pause_detected = pause_detected
+
+ if state.pause_detected and state.started_talking:
+ return gr.Audio(recording=False), state
+ return None, state
+```
+
+This function takes two inputs:
+1. The current audio chunk (a tuple of `(sampling_rate, numpy array of audio)`)
+2. The current application state
+
+We'll use the following `AppState` dataclass to manage our application state:
+
+```python
+from dataclasses import dataclass
+
+@dataclass
+class AppState:
+ stream: np.ndarray | None = None
+ sampling_rate: int = 0
+ pause_detected: bool = False
+ stopped: bool = False
+ conversation: list = []
+```
+
+The function concatenates new audio chunks to the existing stream and checks if the user has stopped speaking. If a pause is detected, it returns an update to stop recording. Otherwise, it returns `None` to indicate no changes.
+
+The implementation of the `determine_pause` function is specific to the omni-mini project and can be found [here](https://huggingface.co/spaces/gradio/omni-mini/blob/eb027808c7bfe5179b46d9352e3fa1813a45f7c3/app.py#L98).
+
+## Generating the Response
+
+After processing the user's audio, we need to generate and stream the chatbot's response. Here's our `response` function:
+
+```python
+import io
+import tempfile
+from pydub import AudioSegment
+
+def response(state: AppState):
+ if not state.pause_detected and not state.started_talking:
+ return None, AppState()
+
+ audio_buffer = io.BytesIO()
+
+ segment = AudioSegment(
+ state.stream.tobytes(),
+ frame_rate=state.sampling_rate,
+ sample_width=state.stream.dtype.itemsize,
+ channels=(1 if len(state.stream.shape) == 1 else state.stream.shape[1]),
+ )
+ segment.export(audio_buffer, format="wav")
+
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
+ f.write(audio_buffer.getvalue())
+
+ state.conversation.append({"role": "user",
+ "content": {"path": f.name,
+ "mime_type": "audio/wav"}})
+
+ output_buffer = b""
+
+ for mp3_bytes in speaking(audio_buffer.getvalue()):
+ output_buffer += mp3_bytes
+ yield mp3_bytes, state
+
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
+ f.write(output_buffer)
+
+ state.conversation.append({"role": "assistant",
+ "content": {"path": f.name,
+ "mime_type": "audio/mp3"}})
+ yield None, AppState(conversation=state.conversation)
+```
+
+This function:
+1. Converts the user's audio to a WAV file
+2. Adds the user's message to the conversation history
+3. Generates and streams the chatbot's response using the `speaking` function
+4. Saves the chatbot's response as an MP3 file
+5. Adds the chatbot's response to the conversation history
+
+Note: The implementation of the `speaking` function is specific to the omni-mini project and can be found [here](https://huggingface.co/spaces/gradio/omni-mini/blob/main/app.py#L116).
+
+## Building the Gradio App
+
+Now let's put it all together using Gradio's Blocks API:
+
+```python
+import gradio as gr
+
+def start_recording_user(state: AppState):
+ if not state.stopped:
+ return gr.Audio(recording=True)
+
+with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column():
+ input_audio = gr.Audio(
+ label="Input Audio", sources="microphone", type="numpy"
+ )
+ with gr.Column():
+ chatbot = gr.Chatbot(label="Conversation")
+ output_audio = gr.Audio(label="Output Audio", streaming=True, autoplay=True)
+ state = gr.State(value=AppState())
+
+ stream = input_audio.stream(
+ process_audio,
+ [input_audio, state],
+ [input_audio, state],
+ stream_every=0.5,
+ time_limit=30,
+ )
+ respond = input_audio.stop_recording(
+ response,
+ [state],
+ [output_audio, state]
+ )
+ respond.then(lambda s: s.conversation, [state], [chatbot])
+
+ restart = output_audio.stop(
+ start_recording_user,
+ [state],
+ [input_audio]
+ )
+ cancel = gr.Button("Stop Conversation", variant="stop")
+ cancel.click(lambda: (AppState(stopped=True), gr.Audio(recording=False)), None,
+ [state, input_audio], cancels=[respond, restart])
+
+if __name__ == "__main__":
+ demo.launch()
+```
+
+This setup creates a user interface with:
+- An input audio component for recording user messages
+- A chatbot component to display the conversation history
+- An output audio component for the chatbot's responses
+- A button to stop and reset the conversation
+
+The app streams user audio in 0.5-second chunks, processes it, generates responses, and updates the conversation history accordingly.
+
+## Conclusion
+
+This guide demonstrates how to build a conversational chatbot application using Gradio and the mini omni model. You can adapt this framework to create various audio-based chatbot demos. To see the full application in action, visit the Hugging Face Spaces demo: https://huggingface.co/spaces/gradio/omni-mini
+
+Feel free to experiment with different models, audio processing techniques, or user interface designs to create your own unique conversational AI experiences!
\ No newline at end of file
diff --git a/guides/07_streaming/05_real-time-speech-recognition.md b/guides/07_streaming/05_real-time-speech-recognition.md
new file mode 100644
index 0000000..c3bdcbc
--- /dev/null
+++ b/guides/07_streaming/05_real-time-speech-recognition.md
@@ -0,0 +1,70 @@
+# Real Time Speech Recognition
+
+Tags: ASR, SPEECH, STREAMING
+
+## Introduction
+
+Automatic speech recognition (ASR), the conversion of spoken speech to text, is a very important and thriving area of machine learning. ASR algorithms run on practically every smartphone, and are becoming increasingly embedded in professional workflows, such as digital assistants for nurses and doctors. Because ASR algorithms are designed to be used directly by customers and end users, it is important to validate that they are behaving as expected when confronted with a wide variety of speech patterns (different accents, pitches, and background audio conditions).
+
+Using `gradio`, you can easily build a demo of your ASR model and share that with a testing team, or test it yourself by speaking through the microphone on your device.
+
+This tutorial will show how to take a pretrained speech-to-text model and deploy it with a Gradio interface. We will start with a **_full-context_** model, in which the user speaks the entire audio before the prediction runs. Then we will adapt the demo to make it **_streaming_**, meaning that the audio model will convert speech as you speak.
+
+### Prerequisites
+
+Make sure you have the `gradio` Python package already [installed](/getting_started). You will also need a pretrained speech recognition model. In this tutorial, we will build demos from 2 ASR libraries:
+
+- Transformers (for this, `pip install torch transformers torchaudio`)
+
+Make sure you have at least one of these installed so that you can follow along the tutorial. You will also need `ffmpeg` [installed on your system](https://www.ffmpeg.org/download.html), if you do not already have it, to process files from the microphone.
+
+Here's how to build a real time speech recognition (ASR) app:
+
+1. [Set up the Transformers ASR Model](#1-set-up-the-transformers-asr-model)
+2. [Create a Full-Context ASR Demo with Transformers](#2-create-a-full-context-asr-demo-with-transformers)
+3. [Create a Streaming ASR Demo with Transformers](#3-create-a-streaming-asr-demo-with-transformers)
+
+## 1. Set up the Transformers ASR Model
+
+First, you will need to have an ASR model that you have either trained yourself or you will need to download a pretrained model. In this tutorial, we will start by using a pretrained ASR model from the model, `whisper`.
+
+Here is the code to load `whisper` from Hugging Face `transformers`.
+
+```python
+from transformers import pipeline
+
+p = pipeline("automatic-speech-recognition", model="openai/whisper-base.en")
+```
+
+That's it!
+
+## 2. Create a Full-Context ASR Demo with Transformers
+
+We will start by creating a _full-context_ ASR demo, in which the user speaks the full audio before using the ASR model to run inference. This is very easy with Gradio -- we simply create a function around the `pipeline` object above.
+
+We will use `gradio`'s built in `Audio` component, configured to take input from the user's microphone and return a filepath for the recorded audio. The output component will be a plain `Textbox`.
+
+$code_asr
+$demo_asr
+
+The `transcribe` function takes a single parameter, `audio`, which is a numpy array of the audio the user recorded. The `pipeline` object expects this in float32 format, so we convert it first to float32, and then extract the transcribed text.
+
+## 3. Create a Streaming ASR Demo with Transformers
+
+To make this a *streaming* demo, we need to make these changes:
+
+1. Set `streaming=True` in the `Audio` component
+2. Set `live=True` in the `Interface`
+3. Add a `state` to the interface to store the recorded audio of a user
+
+Tip: You can also set `time_limit` and `stream_every` parameters in the interface. The `time_limit` caps the amount of time each user's stream can take. The default is 30 seconds so users won't be able to stream audio for more than 30 seconds. The `stream_every` parameter controls how frequently data is sent to your function. By default it is 0.5 seconds.
+
+Take a look below.
+
+$code_stream_asr
+
+Notice that we now have a state variable because we need to track all the audio history. `transcribe` gets called whenever there is a new small chunk of audio, but we also need to keep track of all the audio spoken so far in the state. As the interface runs, the `transcribe` function gets called, with a record of all the previously spoken audio in the `stream` and the new chunk of audio as `new_chunk`. We return the new full audio to be stored back in its current state, and we also return the transcription. Here, we naively append the audio together and call the `transcriber` object on the entire audio. You can imagine more efficient ways of handling this, such as re-processing only the last 5 seconds of audio whenever a new chunk of audio is received.
+
+$demo_stream_asr
+
+Now the ASR model will run inference as you speak!
diff --git a/guides/07_streaming/06_automatic-voice-detection.md b/guides/07_streaming/06_automatic-voice-detection.md
new file mode 100644
index 0000000..a6e1496
--- /dev/null
+++ b/guides/07_streaming/06_automatic-voice-detection.md
@@ -0,0 +1,260 @@
+# Multimodal Gradio App Powered by Groq with Automatic Speech Detection
+
+Tags: AUDIO, STREAMING, CHATBOTS, VOICE
+
+## Introduction
+Modern voice applications should feel natural and responsive, moving beyond the traditional "click-to-record" pattern. By combining Groq's fast inference capabilities with automatic speech detection, we can create a more intuitive interaction model where users can simply start talking whenever they want to engage with the AI.
+
+> Credits: VAD and Gradio code inspired by [WillHeld's Diva-audio-chat](https://huggingface.co/spaces/WillHeld/diva-audio-chat/tree/main).
+
+In this tutorial, you will learn how to create a multimodal Gradio and Groq app that has automatic speech detection. You can also watch the full video tutorial which includes a demo of the application:
+
+
+
+## Background
+
+Many voice apps currently work by the user clicking record, speaking, then stopping the recording. While this can be a powerful demo, the most natural mode of interaction with voice requires the app to dynamically detect when the user is speaking, so they can talk back and forth without having to continually click a record button.
+
+Creating a natural interaction with voice and text requires a dynamic and low-latency response. Thus, we need both automatic voice detection and fast inference. With @ricky0123/vad-web powering speech detection and Groq powering the LLM, both of these requirements are met. Groq provides a lightning fast response, and Gradio allows for easy creation of impressively functional apps.
+
+This tutorial shows you how to build a calorie tracking app where you speak to an AI that automatically detects when you start and stop your response, and provides its own text response back to guide you with questions that allow it to give a calorie estimate of your last meal.
+
+## Key Components
+
+- **Gradio**: Provides the web interface and audio handling capabilities
+- **@ricky0123/vad-web**: Handles voice activity detection
+- **Groq**: Powers fast LLM inference for natural conversations
+- **Whisper**: Transcribes speech to text
+
+### Setting Up the Environment
+
+First, let’s install and import our essential libraries and set up a client for using the Groq API. Here’s how to do it:
+
+`requirements.txt`
+```
+gradio
+groq
+numpy
+soundfile
+librosa
+spaces
+xxhash
+datasets
+```
+
+`app.py`
+```python
+import groq
+import gradio as gr
+import soundfile as sf
+from dataclasses import dataclass, field
+import os
+
+# Initialize Groq client securely
+api_key = os.environ.get("GROQ_API_KEY")
+if not api_key:
+ raise ValueError("Please set the GROQ_API_KEY environment variable.")
+client = groq.Client(api_key=api_key)
+```
+
+Here, we’re pulling in key libraries to interact with the Groq API, build a sleek UI with Gradio, and handle audio data. We’re accessing the Groq API key securely with a key stored in an environment variable, which is a security best practice for avoiding leaking the API key.
+
+---
+
+### State Management for Seamless Conversations
+
+We need a way to keep track of our conversation history, so the chatbot remembers past interactions, and manage other states like whether recording is currently active. To do this, let’s create an `AppState` class:
+
+```python
+@dataclass
+class AppState:
+ conversation: list = field(default_factory=list)
+ stopped: bool = False
+ model_outs: Any = None
+```
+
+Our `AppState` class is a handy tool for managing conversation history and tracking whether recording is on or off. Each instance will have its own fresh list of conversations, making sure chat history is isolated to each session.
+
+---
+
+### Transcribing Audio with Whisper on Groq
+
+Next, we’ll create a function to transcribe the user’s audio input into text using Whisper, a powerful transcription model hosted on Groq. This transcription will also help us determine whether there’s meaningful speech in the input. Here’s how:
+
+```python
+def transcribe_audio(client, file_name):
+ if file_name is None:
+ return None
+
+ try:
+ with open(file_name, "rb") as audio_file:
+ response = client.audio.transcriptions.with_raw_response.create(
+ model="whisper-large-v3-turbo",
+ file=("audio.wav", audio_file),
+ response_format="verbose_json",
+ )
+ completion = process_whisper_response(response.parse())
+ return completion
+ except Exception as e:
+ print(f"Error in transcription: {e}")
+ return f"Error in transcription: {str(e)}"
+```
+
+This function opens the audio file and sends it to Groq’s Whisper model for transcription, requesting detailed JSON output. verbose_json is needed to get information to determine if speech was included in the audio. We also handle any potential errors so our app doesn’t fully crash if there’s an issue with the API request.
+
+```python
+def process_whisper_response(completion):
+ """
+ Process Whisper transcription response and return text or null based on no_speech_prob
+
+ Args:
+ completion: Whisper transcription response object
+
+ Returns:
+ str or None: Transcribed text if no_speech_prob <= 0.7, otherwise None
+ """
+ if completion.segments and len(completion.segments) > 0:
+ no_speech_prob = completion.segments[0].get('no_speech_prob', 0)
+ print("No speech prob:", no_speech_prob)
+
+ if no_speech_prob > 0.7:
+ return None
+
+ return completion.text.strip()
+
+ return None
+```
+
+We also need to interpret the audio data response. The process_whisper_response function takes the resulting completion from Whisper and checks if the audio was just background noise or had actual speaking that was transcribed. It uses a threshold of 0.7 to interpret the no_speech_prob, and will return None if there was no speech. Otherwise, it will return the text transcript of the conversational response from the human.
+
+
+---
+
+### Adding Conversational Intelligence with LLM Integration
+
+Our chatbot needs to provide intelligent, friendly responses that flow naturally. We’ll use a Groq-hosted Llama-3.2 for this:
+
+```python
+def generate_chat_completion(client, history):
+ messages = []
+ messages.append(
+ {
+ "role": "system",
+ "content": "In conversation with the user, ask questions to estimate and provide (1) total calories, (2) protein, carbs, and fat in grams, (3) fiber and sugar content. Only ask *one question at a time*. Be conversational and natural.",
+ }
+ )
+
+ for message in history:
+ messages.append(message)
+
+ try:
+ completion = client.chat.completions.create(
+ model="llama-3.2-11b-vision-preview",
+ messages=messages,
+ )
+ return completion.choices[0].message.content
+ except Exception as e:
+ return f"Error in generating chat completion: {str(e)}"
+```
+
+We’re defining a system prompt to guide the chatbot’s behavior, ensuring it asks one question at a time and keeps things conversational. This setup also includes error handling to ensure the app gracefully manages any issues.
+
+---
+
+### Voice Activity Detection for Hands-Free Interaction
+
+To make our chatbot hands-free, we’ll add Voice Activity Detection (VAD) to automatically detect when someone starts or stops speaking. Here’s how to implement it using ONNX in JavaScript:
+
+```javascript
+async function main() {
+ const script1 = document.createElement("script");
+ script1.src = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.14.0/dist/ort.js";
+ document.head.appendChild(script1)
+ const script2 = document.createElement("script");
+ script2.onload = async () => {
+ console.log("vad loaded");
+ var record = document.querySelector('.record-button');
+ record.textContent = "Just Start Talking!"
+
+ const myvad = await vad.MicVAD.new({
+ onSpeechStart: () => {
+ var record = document.querySelector('.record-button');
+ var player = document.querySelector('#streaming-out')
+ if (record != null && (player == null || player.paused)) {
+ record.click();
+ }
+ },
+ onSpeechEnd: (audio) => {
+ var stop = document.querySelector('.stop-button');
+ if (stop != null) {
+ stop.click();
+ }
+ }
+ })
+ myvad.start()
+ }
+ script2.src = "https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.7/dist/bundle.min.js";
+}
+```
+
+This script loads our VAD model and sets up functions to start and stop recording automatically. When the user starts speaking, it triggers the recording, and when they stop, it ends the recording.
+
+---
+
+### Building a User Interface with Gradio
+
+Now, let’s create an intuitive and visually appealing user interface with Gradio. This interface will include an audio input for capturing voice, a chat window for displaying responses, and state management to keep things synchronized.
+
+```python
+with gr.Blocks() as demo:
+ with gr.Row():
+ input_audio = gr.Audio(
+ label="Input Audio",
+ sources=["microphone"],
+ type="numpy",
+ streaming=False,
+ waveform_options=gr.WaveformOptions(waveform_color="#B83A4B"),
+ )
+ with gr.Row():
+ chatbot = gr.Chatbot(label="Conversation")
+ state = gr.State(value=AppState())
+demo.launch(theme=theme, js=js)
+```
+
+In this code block, we’re using Gradio’s `Blocks` API to create an interface with an audio input, a chat display, and an application state manager. The color customization for the waveform adds a nice visual touch.
+
+---
+
+### Handling Recording and Responses
+
+Finally, let’s link the recording and response components to ensure the app reacts smoothly to user inputs and provides responses in real-time.
+
+```python
+ stream = input_audio.start_recording(
+ process_audio,
+ [input_audio, state],
+ [input_audio, state],
+ )
+ respond = input_audio.stop_recording(
+ response, [state, input_audio], [state, chatbot]
+ )
+```
+
+These lines set up event listeners for starting and stopping the recording, processing the audio input, and generating responses. By linking these events, we create a cohesive experience where users can simply talk, and the chatbot handles the rest.
+
+---
+
+## Summary
+
+1. When you open the app, the VAD system automatically initializes and starts listening for speech
+2. As soon as you start talking, it triggers the recording automatically
+3. When you stop speaking, the recording ends and:
+ - The audio is transcribed using Whisper
+ - The transcribed text is sent to the LLM
+ - The LLM generates a response about calorie tracking
+ - The response is displayed in the chat interface
+4. This creates a natural back-and-forth conversation where you can simply talk about your meals and get instant feedback on nutritional content
+
+This app demonstrates how to create a natural voice interface that feels responsive and intuitive. By combining Groq's fast inference with automatic speech detection, we've eliminated the need for manual recording controls while maintaining high-quality interactions. The result is a practical calorie tracking assistant that users can simply talk to as naturally as they would to a human nutritionist.
+
+Link to GitHub repository: [Groq Gradio Basics](https://github.com/bklieger-groq/gradio-groq-basics/tree/main/calorie-tracker)
\ No newline at end of file
diff --git a/guides/08_custom-components/01_custom-components-in-five-minutes.md b/guides/08_custom-components/01_custom-components-in-five-minutes.md
new file mode 100644
index 0000000..23fa14b
--- /dev/null
+++ b/guides/08_custom-components/01_custom-components-in-five-minutes.md
@@ -0,0 +1,123 @@
+# Custom Components in 5 minutes
+
+Gradio includes the ability for developers to create their own custom components and use them in Gradio apps. You can publish your components as Python packages so that other users can use them as well.
+
+Users will be able to use all of Gradio's existing functions, such as `gr.Blocks`, `gr.Interface`, API usage, themes, etc. with Custom Components. This guide will cover how to get started making custom components.
+
+## Installation
+
+You will need to have:
+
+* Python 3.10+ (install here)
+* pip 21.3+ (`python -m pip install --upgrade pip`)
+* Node.js 20+ (install here)
+* npm 9+ (install here)
+* Gradio 5+ (`pip install --upgrade gradio`)
+
+## The Workflow
+
+The Custom Components workflow consists of 4 steps: create, dev, build, and publish.
+
+1. create: creates a template for you to start developing a custom component.
+2. dev: launches a development server with a sample app & hot reloading allowing you to easily develop your custom component
+3. build: builds a python package containing to your custom component's Python and JavaScript code -- this makes things official!
+4. publish: uploads your package to [PyPi](https://pypi.org/) and/or a sample app to [HuggingFace Spaces](https://hf.co/spaces).
+
+Each of these steps is done via the Custom Component CLI. You can invoke it with `gradio cc` or `gradio component`
+
+Tip: Run `gradio cc --help` to get a help menu of all available commands. There are some commands that are not covered in this guide. You can also append `--help` to any command name to bring up a help page for that command, e.g. `gradio cc create --help`.
+
+## 1. create
+
+Bootstrap a new template by running the following in any working directory:
+
+```bash
+gradio cc create MyComponent --template SimpleTextbox
+```
+
+Instead of `MyComponent`, give your component any name.
+
+Instead of `SimpleTextbox`, you can use any Gradio component as a template. `SimpleTextbox` is actually a special component that a stripped-down version of the `Textbox` component that makes it particularly useful when creating your first custom component.
+Some other components that are good if you are starting out: `SimpleDropdown`, `SimpleImage`, or `File`.
+
+Tip: Run `gradio cc show` to get a list of available component templates.
+
+The `create` command will:
+
+1. Create a directory with your component's name in lowercase with the following structure:
+```directory
+- backend/ <- The python code for your custom component
+- frontend/ <- The javascript code for your custom component
+- demo/ <- A sample app using your custom component. Modify this to develop your component!
+- pyproject.toml <- Used to build the package and specify package metadata.
+```
+
+2. Install the component in development mode
+
+Each of the directories will have the code you need to get started developing!
+
+## 2. dev
+
+Once you have created your new component, you can start a development server by `entering the directory` and running
+
+```bash
+gradio cc dev
+```
+
+You'll see several lines that are printed to the console.
+The most important one is the one that says:
+
+> Frontend Server (Go here): http://localhost:7861/
+
+The port number might be different for you.
+Click on that link to launch the demo app in hot reload mode.
+Now, you can start making changes to the backend and frontend you'll see the results reflected live in the sample app!
+We'll go through a real example in a later guide.
+
+Tip: You don't have to run dev mode from your custom component directory. The first argument to `dev` mode is the path to the directory. By default it uses the current directory.
+
+## 3. build
+
+Once you are satisfied with your custom component's implementation, you can `build` it to use it outside of the development server.
+
+From your component directory, run:
+
+```bash
+gradio cc build
+```
+
+This will create a `tar.gz` and `.whl` file in a `dist/` subdirectory.
+If you or anyone installs that `.whl` file (`pip install `) they will be able to use your custom component in any gradio app!
+
+The `build` command will also generate documentation for your custom component. This takes the form of an interactive space and a static `README.md`. You can disable this by passing `--no-generate-docs`. You can read more about the documentation generator in [the dedicated guide](https://gradio.app/guides/documenting-custom-components).
+
+## 4. publish
+
+Right now, your package is only available on a `.whl` file on your computer.
+You can share that file with the world with the `publish` command!
+
+Simply run the following command from your component directory:
+
+```bash
+gradio cc publish
+```
+
+This will guide you through the following process:
+
+1. Upload your distribution files to PyPi. This makes it easier to upload the demo to Hugging Face spaces. Otherwise your package must be at a publicly available url. If you decide to upload to PyPi, you will need a PyPI username and password. You can get one [here](https://pypi.org/account/register/).
+2. Upload a demo of your component to hugging face spaces. This is also optional.
+
+
+Here is an example of what publishing looks like:
+
+
+
+
+## Conclusion
+
+Now that you know the high-level workflow of creating custom components, you can go in depth in the next guides!
+After reading the guides, check out this [collection](https://huggingface.co/collections/gradio/custom-components-65497a761c5192d981710b12) of custom components on the HuggingFace Hub so you can learn from other's code.
+
+Tip: If you want to start off from someone else's custom component see this [guide](./frequently-asked-questions#do-i-always-need-to-start-my-component-from-scratch).
diff --git a/guides/08_custom-components/02_key-component-concepts.md b/guides/08_custom-components/02_key-component-concepts.md
new file mode 100644
index 0000000..35ea217
--- /dev/null
+++ b/guides/08_custom-components/02_key-component-concepts.md
@@ -0,0 +1,124 @@
+# Gradio Components: The Key Concepts
+
+In this section, we discuss a few important concepts when it comes to components in Gradio.
+It's important to understand these concepts when developing your own component.
+Otherwise, your component may behave very different to other Gradio components!
+
+Tip: You can skip this section if you are familiar with the internals of the Gradio library, such as each component's preprocess and postprocess methods.
+
+## Interactive vs Static
+
+Every component in Gradio comes in a `static` variant, and most come in an `interactive` version as well.
+The `static` version is used when a component is displaying a value, and the user can **NOT** change that value by interacting with it.
+The `interactive` version is used when the user is able to change the value by interacting with the Gradio UI.
+
+Let's see some examples:
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ gr.Textbox(value="Hello", interactive=True)
+ gr.Textbox(value="Hello", interactive=False)
+
+demo.launch()
+
+```
+This will display two textboxes.
+The only difference: you'll be able to edit the value of the Gradio component on top, and you won't be able to edit the variant on the bottom (i.e. the textbox will be disabled).
+
+Perhaps a more interesting example is with the `Image` component:
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ gr.Image(interactive=True)
+ gr.Image(interactive=False)
+
+demo.launch()
+```
+
+The interactive version of the component is much more complex -- you can upload images or snap a picture from your webcam -- while the static version can only be used to display images.
+
+Not every component has a distinct interactive version. For example, the `gr.AnnotatedImage` only appears as a static version since there's no way to interactively change the value of the annotations or the image.
+
+### What you need to remember
+
+* Gradio will use the interactive version (if available) of a component if that component is used as the **input** to any event; otherwise, the static version will be used.
+
+* When you design custom components, you **must** accept the boolean interactive keyword in the constructor of your Python class. In the frontend, you **may** accept the `interactive` property, a `bool` which represents whether the component should be static or interactive. If you do not use this property in the frontend, the component will appear the same in interactive or static mode.
+
+## The value and how it is preprocessed/postprocessed
+
+The most important attribute of a component is its `value`.
+Every component has a `value`.
+The value that is typically set by the user in the frontend (if the component is interactive) or displayed to the user (if it is static).
+It is also this value that is sent to the backend function when a user triggers an event, or returned by the user's function e.g. at the end of a prediction.
+
+So this value is passed around quite a bit, but sometimes the format of the value needs to change between the frontend and backend.
+Take a look at this example:
+
+```python
+import numpy as np
+import gradio as gr
+
+def sepia(input_img):
+ sepia_filter = np.array([
+ [0.393, 0.769, 0.189],
+ [0.349, 0.686, 0.168],
+ [0.272, 0.534, 0.131]
+ ])
+ sepia_img = input_img.dot(sepia_filter.T)
+ sepia_img /= sepia_img.max()
+ return sepia_img
+
+demo = gr.Interface(sepia, gr.Image(width=200, height=200), "image")
+demo.launch()
+```
+
+This will create a Gradio app which has an `Image` component as the input and the output.
+In the frontend, the Image component will actually **upload** the file to the server and send the **filepath** but this is converted to a `numpy` array before it is sent to a user's function.
+Conversely, when the user returns a `numpy` array from their function, the numpy array is converted to a file so that it can be sent to the frontend and displayed by the `Image` component.
+
+Tip: By default, the `Image` component sends numpy arrays to the python function because it is a common choice for machine learning engineers, though the Image component also supports other formats using the `type` parameter. Read the `Image` docs [here](https://www.gradio.app/docs/image) to learn more.
+
+Each component does two conversions:
+
+1. `preprocess`: Converts the `value` from the format sent by the frontend to the format expected by the python function. This usually involves going from a web-friendly **JSON** structure to a **python-native** data structure, like a `numpy` array or `PIL` image. The `Audio`, `Image` components are good examples of `preprocess` methods.
+
+2. `postprocess`: Converts the value returned by the python function to the format expected by the frontend. This usually involves going from a **python-native** data-structure, like a `PIL` image to a **JSON** structure.
+
+### What you need to remember
+
+* Every component must implement `preprocess` and `postprocess` methods. In the rare event that no conversion needs to happen, simply return the value as-is. `Textbox` and `Number` are examples of this.
+
+* As a component author, **YOU** control the format of the data displayed in the frontend as well as the format of the data someone using your component will receive. Think of an ergonomic data-structure a **python** developer will find intuitive, and control the conversion from a **Web-friendly JSON** data structure (and vice-versa) with `preprocess` and `postprocess.`
+
+## The "Example Version" of a Component
+
+Gradio apps support providing example inputs -- and these are very useful in helping users get started using your Gradio app.
+In `gr.Interface`, you can provide examples using the `examples` keyword, and in `Blocks`, you can provide examples using the special `gr.Examples` component.
+
+At the bottom of this screenshot, we show a miniature example image of a cheetah that, when clicked, will populate the same image in the input Image component:
+
+
+
+
+To enable the example view, you must have the following two files in the top of the `frontend` directory:
+
+* `Example.svelte`: this corresponds to the "example version" of your component
+* `Index.svelte`: this corresponds to the "regular version"
+
+In the backend, you typically don't need to do anything. The user-provided example `value` is processed using the same `.postprocess()` method described earlier. If you'd like to do process the data differently (for example, if the `.postprocess()` method is computationally expensive), then you can write your own `.process_example()` method for your custom component, which will be used instead.
+
+The `Example.svelte` file and `process_example()` method will be covered in greater depth in the dedicated [frontend](./frontend) and [backend](./backend) guides respectively.
+
+### What you need to remember
+
+* If you expect your component to be used as input, it is important to define an "Example" view.
+* If you don't, Gradio will use a default one but it won't be as informative as it can be!
+
+## Conclusion
+
+Now that you know the most important pieces to remember about Gradio components, you can start to design and build your own!
diff --git a/guides/08_custom-components/03_configuration.md b/guides/08_custom-components/03_configuration.md
new file mode 100644
index 0000000..234cae3
--- /dev/null
+++ b/guides/08_custom-components/03_configuration.md
@@ -0,0 +1,100 @@
+# Configuring Your Custom Component
+
+The custom components workflow focuses on [convention over configuration](https://en.wikipedia.org/wiki/Convention_over_configuration) to reduce the number of decisions you as a developer need to make when developing your custom component.
+That being said, you can still configure some aspects of the custom component package and directory.
+This guide will cover how.
+
+## The Package Name
+
+By default, all custom component packages are called `gradio_` where `component-name` is the name of the component's python class in lowercase.
+
+As an example, let's walkthrough changing the name of a component from `gradio_mytextbox` to `supertextbox`.
+
+1. Modify the `name` in the `pyproject.toml` file.
+
+```bash
+[project]
+name = "supertextbox"
+```
+
+2. Change all occurrences of `gradio_` in `pyproject.toml` to ``
+
+```bash
+[tool.hatch.build]
+artifacts = ["/backend/supertextbox/templates", "*.pyi"]
+
+[tool.hatch.build.targets.wheel]
+packages = ["/backend/supertextbox"]
+```
+
+3. Rename the `gradio_` directory in `backend/` to ``
+
+```bash
+mv backend/gradio_mytextbox backend/supertextbox
+```
+
+
+Tip: Remember to change the import statement in `demo/app.py`!
+
+## Top Level Python Exports
+
+By default, only the custom component python class is a top level export.
+This means that when users type `from gradio_ import ...`, the only class that will be available is the custom component class.
+To add more classes as top level exports, modify the `__all__` property in `__init__.py`
+
+```python
+from .mytextbox import MyTextbox
+from .mytextbox import AdditionalClass, additional_function
+
+__all__ = ['MyTextbox', 'AdditionalClass', 'additional_function']
+```
+
+## Python Dependencies
+
+You can add python dependencies by modifying the `dependencies` key in `pyproject.toml`
+
+```bash
+dependencies = ["gradio", "numpy", "PIL"]
+```
+
+
+Tip: Remember to run `gradio cc install` when you add dependencies!
+
+## Javascript Dependencies
+
+You can add JavaScript dependencies by modifying the `"dependencies"` key in `frontend/package.json`
+
+```json
+"dependencies": {
+ "@gradio/atoms": "0.2.0-beta.4",
+ "@gradio/statustracker": "0.3.0-beta.6",
+ "@gradio/utils": "0.2.0-beta.4",
+ "your-npm-package": ""
+}
+```
+
+## Directory Structure
+
+By default, the CLI will place the Python code in `backend` and the JavaScript code in `frontend`.
+It is not recommended to change this structure since it makes it easy for a potential contributor to look at your source code and know where everything is.
+However, if you did want to this is what you would have to do:
+
+1. Place the Python code in the subdirectory of your choosing. Remember to modify the `[tool.hatch.build]` `[tool.hatch.build.targets.wheel]` in the `pyproject.toml` to match!
+
+2. Place the JavaScript code in the subdirectory of your choosing.
+
+2. Add the `FRONTEND_DIR` property on the component python class. It must be the relative path from the file where the class is defined to the location of the JavaScript directory.
+
+```python
+class SuperTextbox(Component):
+ FRONTEND_DIR = "../../frontend/"
+```
+
+The JavaScript and Python directories must be under the same common directory!
+
+## Conclusion
+
+
+Sticking to the defaults will make it easy for others to understand and contribute to your custom component.
+After all, the beauty of open source is that anyone can help improve your code!
+But if you ever need to deviate from the defaults, you know how!
\ No newline at end of file
diff --git a/guides/08_custom-components/04_backend.md b/guides/08_custom-components/04_backend.md
new file mode 100644
index 0000000..f5d52ef
--- /dev/null
+++ b/guides/08_custom-components/04_backend.md
@@ -0,0 +1,227 @@
+# The Backend 🐍
+
+This guide will cover everything you need to know to implement your custom component's backend processing.
+
+## Which Class to Inherit From
+
+All components inherit from one of three classes `Component`, `FormComponent`, or `BlockContext`.
+You need to inherit from one so that your component behaves like all other gradio components.
+When you start from a template with `gradio cc create --template`, you don't need to worry about which one to choose since the template uses the correct one.
+For completeness, and in the event that you need to make your own component from scratch, we explain what each class is for.
+
+* `FormComponent`: Use this when you want your component to be grouped together in the same `Form` layout with other `FormComponents`. The `Slider`, `Textbox`, and `Number` components are all `FormComponents`.
+* `BlockContext`: Use this when you want to place other components "inside" your component. This enabled `with MyComponent() as component:` syntax.
+* `Component`: Use this for all other cases.
+
+Tip: If your component supports streaming output, inherit from the `StreamingOutput` class.
+
+Tip: If you inherit from `BlockContext`, you also need to set the metaclass to be `ComponentMeta`. See example below.
+
+```python
+from gradio.blocks import BlockContext
+from gradio.component_meta import ComponentMeta
+
+
+
+
+@document()
+class Row(BlockContext, metaclass=ComponentMeta):
+ pass
+```
+
+## The methods you need to implement
+
+When you inherit from any of these classes, the following methods must be implemented.
+Otherwise the Python interpreter will raise an error when you instantiate your component!
+
+### `preprocess` and `postprocess`
+
+Explained in the [Key Concepts](./key-component-concepts#the-value-and-how-it-is-preprocessed-postprocessed) guide.
+They handle the conversion from the data sent by the frontend to the format expected by the python function.
+
+```python
+ def preprocess(self, x: Any) -> Any:
+ """
+ Convert from the web-friendly (typically JSON) value in the frontend to the format expected by the python function.
+ """
+ return x
+
+ def postprocess(self, y):
+ """
+ Convert from the data returned by the python function to the web-friendly (typically JSON) value expected by the frontend.
+ """
+ return y
+```
+
+### `process_example`
+
+Takes in the original Python value and returns the modified value that should be displayed in the examples preview in the app.
+If not provided, the `.postprocess()` method is used instead. Let's look at the following example from the `SimpleDropdown` component.
+
+```python
+def process_example(self, input_data):
+ return next((c[0] for c in self.choices if c[1] == input_data), None)
+```
+
+Since `self.choices` is a list of tuples corresponding to (`display_name`, `value`), this converts the value that a user provides to the display value (or if the value is not present in `self.choices`, it is converted to `None`).
+
+
+### `api_info`
+
+A JSON-schema representation of the value that the `preprocess` expects.
+This powers api usage via the gradio clients.
+You do **not** need to implement this yourself if you components specifies a `data_model`.
+The `data_model` in the following section.
+
+```python
+def api_info(self) -> dict[str, list[str]]:
+ """
+ A JSON-schema representation of the value that the `preprocess` expects and the `postprocess` returns.
+ """
+ pass
+```
+
+### `example_payload`
+
+An example payload for your component, e.g. something that can be passed into the `.preprocess()` method
+of your component. The example input is displayed in the `View API` page of a Gradio app that uses your custom component.
+Must be JSON-serializable. If your component expects a file, it is best to use a publicly accessible URL.
+
+```python
+def example_payload(self) -> Any:
+ """
+ The example inputs for this component for API usage. Must be JSON-serializable.
+ """
+ pass
+```
+
+### `example_value`
+
+An example value for your component, e.g. something that can be passed into the `.postprocess()` method
+of your component. This is used as the example value in the default app that is created in custom component development.
+
+```python
+def example_payload(self) -> Any:
+ """
+ The example inputs for this component for API usage. Must be JSON-serializable.
+ """
+ pass
+```
+
+### `flag`
+
+Write the component's value to a format that can be stored in the `csv` or `json` file used for flagging.
+You do **not** need to implement this yourself if you components specifies a `data_model`.
+The `data_model` in the following section.
+
+```python
+def flag(self, x: Any | GradioDataModel, flag_dir: str | Path = "") -> str:
+ pass
+```
+
+### `read_from_flag`
+Convert from the format stored in the `csv` or `json` file used for flagging to the component's python `value`.
+You do **not** need to implement this yourself if you components specifies a `data_model`.
+The `data_model` in the following section.
+
+```python
+def read_from_flag(
+ self,
+ x: Any,
+) -> GradioDataModel | Any:
+ """
+ Convert the data from the csv or jsonl file into the component state.
+ """
+ return x
+```
+
+## The `data_model`
+
+The `data_model` is how you define the expected data format your component's value will be stored in the frontend.
+It specifies the data format your `preprocess` method expects and the format the `postprocess` method returns.
+It is not necessary to define a `data_model` for your component but it greatly simplifies the process of creating a custom component.
+If you define a custom component you only need to implement four methods - `preprocess`, `postprocess`, `example_payload`, and `example_value`!
+
+You define a `data_model` by defining a [pydantic model](https://docs.pydantic.dev/latest/concepts/models/#basic-model-usage) that inherits from either `GradioModel` or `GradioRootModel`.
+
+This is best explained with an example. Let's look at the core `Video` component, which stores the video data as a JSON object with two keys `video` and `subtitles` which point to separate files.
+
+```python
+from gradio.data_classes import FileData, GradioModel
+
+class VideoData(GradioModel):
+ video: FileData
+ subtitles: Optional[FileData] = None
+
+class Video(Component):
+ data_model = VideoData
+```
+
+By adding these four lines of code, your component automatically implements the methods needed for API usage, the flagging methods, and example caching methods!
+It also has the added benefit of self-documenting your code.
+Anyone who reads your component code will know exactly the data it expects.
+
+Tip: If your component expects files to be uploaded from the frontend, your must use the `FileData` model! It will be explained in the following section.
+
+Tip: Read the pydantic docs [here](https://docs.pydantic.dev/latest/concepts/models/#basic-model-usage).
+
+The difference between a `GradioModel` and a `GradioRootModel` is that the `RootModel` will not serialize the data to a dictionary.
+For example, the `Names` model will serialize the data to `{'names': ['freddy', 'pete']}` whereas the `NamesRoot` model will serialize it to `['freddy', 'pete']`.
+
+```python
+from typing import List
+
+class Names(GradioModel):
+ names: List[str]
+
+class NamesRoot(GradioRootModel):
+ root: List[str]
+```
+
+Even if your component does not expect a "complex" JSON data structure it can be beneficial to define a `GradioRootModel` so that you don't have to worry about implementing the API and flagging methods.
+
+Tip: Use classes from the Python typing library to type your models. e.g. `List` instead of `list`.
+
+## Handling Files
+
+If your component expects uploaded files as input, or returns saved files to the frontend, you **MUST** use the `FileData` to type the files in your `data_model`.
+
+When you use the `FileData`:
+
+* Gradio knows that it should allow serving this file to the frontend. Gradio automatically blocks requests to serve arbitrary files in the computer running the server.
+
+* Gradio will automatically place the file in a cache so that duplicate copies of the file don't get saved.
+
+* The client libraries will automatically know that they should upload input files prior to sending the request. They will also automatically download files.
+
+If you do not use the `FileData`, your component will not work as expected!
+
+
+## Adding Event Triggers To Your Component
+
+The events triggers for your component are defined in the `EVENTS` class attribute.
+This is a list that contains the string names of the events.
+Adding an event to this list will automatically add a method with that same name to your component!
+
+You can import the `Events` enum from `gradio.events` to access commonly used events in the core gradio components.
+
+For example, the following code will define `text_submit`, `file_upload` and `change` methods in the `MyComponent` class.
+
+```python
+from gradio.events import Events
+from gradio.components import FormComponent
+
+class MyComponent(FormComponent):
+
+ EVENTS = [
+ "text_submit",
+ "file_upload",
+ Events.change
+ ]
+```
+
+
+Tip: Don't forget to also handle these events in the JavaScript code!
+
+## Conclusion
+
diff --git a/guides/08_custom-components/05_frontend.md b/guides/08_custom-components/05_frontend.md
new file mode 100644
index 0000000..1ce51fc
--- /dev/null
+++ b/guides/08_custom-components/05_frontend.md
@@ -0,0 +1,369 @@
+# The Frontend 🌐⭐️
+
+This guide will cover everything you need to know to implement your custom component's frontend.
+
+Tip: Gradio components use Svelte. Writing Svelte is fun! If you're not familiar with it, we recommend checking out their interactive [guide](https://learn.svelte.dev/tutorial/welcome-to-svelte).
+
+## The directory structure
+
+The frontend code should have, at minimum, three files:
+
+* `Index.svelte`: This is the main export and where your component's layout and logic should live.
+* `Example.svelte`: This is where the example view of the component is defined.
+
+Feel free to add additional files and subdirectories.
+If you want to export any additional modules, remember to modify the `package.json` file
+
+```json
+"exports": {
+ ".": "./Index.svelte",
+ "./example": "./Example.svelte",
+ "./package.json": "./package.json"
+},
+```
+
+## The Index.svelte file
+
+Your component should expose the following props that will be passed down from the parent Gradio application.
+
+```typescript
+import type { LoadingStatus } from "@gradio/statustracker";
+import type { Gradio } from "@gradio/utils";
+
+export let gradio: Gradio<{
+ event_1: never;
+ event_2: never;
+}>;
+
+export let elem_id = "";
+export let elem_classes: string[] = [];
+export let scale: number | null = null;
+export let min_width: number | undefined = undefined;
+export let loading_status: LoadingStatus | undefined = undefined;
+export let mode: "static" | "interactive";
+```
+
+* `elem_id` and `elem_classes` allow Gradio app developers to target your component with custom CSS and JavaScript from the Python `Blocks` class.
+
+* `scale` and `min_width` allow Gradio app developers to control how much space your component takes up in the UI.
+
+* `loading_status` is used to display a loading status over the component when it is the output of an event.
+
+* `mode` is how the parent Gradio app tells your component whether the `interactive` or `static` version should be displayed.
+
+* `gradio`: The `gradio` object is created by the parent Gradio app. It stores some application-level configuration that will be useful in your component, like internationalization. You must use it to dispatch events from your component.
+
+A minimal `Index.svelte` file would look like:
+
+```svelte
+
+
+
+ {#if loading_status}
+
+ {/if}
+ {value}
+
+```
+
+## The Example.svelte file
+
+The `Example.svelte` file should expose the following props:
+
+```typescript
+ export let value: string;
+ export let type: "gallery" | "table";
+ export let selected = false;
+ export let index: number;
+```
+
+* `value`: The example value that should be displayed.
+
+* `type`: This is a variable that can be either `"gallery"` or `"table"` depending on how the examples are displayed. The `"gallery"` form is used when the examples correspond to a single input component, while the `"table"` form is used when a user has multiple input components, and the examples need to populate all of them.
+
+* `selected`: You can also adjust how the examples are displayed if a user "selects" a particular example by using the selected variable.
+
+* `index`: The current index of the selected value.
+
+* Any additional props your "non-example" component takes!
+
+This is the `Example.svelte` file for the code `Radio` component:
+
+```svelte
+
+
+
+ {value}
+
+
+
+```
+
+## Handling Files
+
+If your component deals with files, these files **should** be uploaded to the backend server.
+The `@gradio/client` npm package provides the `upload` and `prepare_files` utility functions to help you do this.
+
+The `prepare_files` function will convert the browser's `File` datatype to gradio's internal `FileData` type.
+You should use the `FileData` data in your component to keep track of uploaded files.
+
+The `upload` function will upload an array of `FileData` values to the server.
+
+Here's an example of loading files from an `` element when its value changes.
+
+
+```svelte
+
+
+
+```
+
+The component exposes a prop named `root`.
+This is passed down by the parent gradio app and it represents the base url that the files will be uploaded to and fetched from.
+
+For WASM support, you should get the upload function from the `Context` and pass that as the third parameter of the `upload` function.
+
+```typescript
+
+```
+
+## Leveraging Existing Gradio Components
+
+Most of Gradio's frontend components are published on [npm](https://www.npmjs.com/), the javascript package repository.
+This means that you can use them to save yourself time while incorporating common patterns in your component, like uploading files.
+For example, the `@gradio/upload` package has `Upload` and `ModifyUpload` components for properly uploading files to the Gradio server.
+Here is how you can use them to create a user interface to upload and display PDF files.
+
+```svelte
+
+
+
+{#if value === null && interactive}
+
+
+
+{:else if value !== null}
+ {#if interactive}
+
+ {/if}
+
+{:else}
+
+{/if}
+```
+
+You can also combine existing Gradio components to create entirely unique experiences.
+Like rendering a gallery of chatbot conversations.
+The possibilities are endless, please read the documentation on our javascript packages [here](https://gradio.app/main/docs/js).
+We'll be adding more packages and documentation over the coming weeks!
+
+## Matching Gradio Core's Design System
+
+You can explore our component library via Storybook. You'll be able to interact with our components and see them in their various states.
+
+For those interested in design customization, we provide the CSS variables consisting of our color palette, radii, spacing, and the icons we use - so you can easily match up your custom component with the style of our core components. This Storybook will be regularly updated with any new additions or changes.
+
+[Storybook Link](https://gradio.app/main/docs/js/storybook)
+
+## Custom configuration
+
+If you want to make use of the vast vite ecosystem, you can use the `gradio.config.js` file to configure your component's build process. This allows you to make use of tools like tailwindcss, mdsvex, and more.
+
+Currently, it is possible to configure the following:
+
+Vite options:
+- `plugins`: A list of vite plugins to use.
+
+Svelte options:
+- `preprocess`: A list of svelte preprocessors to use.
+- `extensions`: A list of file extensions to compile to `.svelte` files.
+- `build.target`: The target to build for, this may be necessary to support newer javascript features. See the [esbuild docs](https://esbuild.github.io/api/#target) for more information.
+
+The `gradio.config.js` file should be placed in the root of your component's `frontend` directory. A default config file is created for you when you create a new component. But you can also create your own config file, if one doesn't exist, and use it to customize your component's build process.
+
+### Example for a Vite plugin
+
+Custom components can use Vite plugins to customize the build process. Check out the [Vite Docs](https://vitejs.dev/guide/using-plugins.html) for more information.
+
+Here we configure [TailwindCSS](https://tailwindcss.com), a utility-first CSS framework. Setup is easiest using the version 4 prerelease.
+
+```
+npm install tailwindcss@next @tailwindcss/vite@next
+```
+
+In `gradio.config.js`:
+
+```typescript
+import tailwindcss from "@tailwindcss/vite";
+export default {
+ plugins: [tailwindcss()]
+};
+```
+
+Then create a `style.css` file with the following content:
+
+```css
+@import "tailwindcss";
+```
+
+Import this file into `Index.svelte`. Note, that you need to import the css file containing `@import` and cannot just use a `
+```
+
+Now import `PdfUploadText.svelte` in your `
+
+
+
+
+
+
+```
+
+
+Tip: Exercise for the reader - reduce the code duplication between `Index.svelte` and `Example.svelte` 😊
+
+
+You will not be able to render examples until we make some changes to the backend code in the next step!
+
+## Step 9: The backend
+
+The backend changes needed are smaller.
+We're almost done!
+
+What we're going to do is:
+* Add `change` and `upload` events to our component.
+* Add a `height` property to let users control the height of the PDF.
+* Set the `data_model` of our component to be `FileData`. This is so that Gradio can automatically cache and safely serve any files that are processed by our component.
+* Modify the `preprocess` method to return a string corresponding to the path of our uploaded PDF.
+* Modify the `postprocess` to turn a path to a PDF created in an event handler to a `FileData`.
+
+When all is said an done, your component's backend code should look like this:
+
+```python
+from __future__ import annotations
+from typing import Any, Callable, TYPE_CHECKING
+
+from gradio.components.base import Component
+from gradio.data_classes import FileData
+from gradio import processing_utils
+if TYPE_CHECKING:
+ from gradio.components import Timer
+
+class PDF(Component):
+
+ EVENTS = ["change", "upload"]
+
+ data_model = FileData
+
+ def __init__(self, value: Any = None, *,
+ height: int | None = None,
+ label: str | I18nData | None = None,
+ info: str | I18nData | None = None,
+ show_label: bool | None = None,
+ container: bool = True,
+ scale: int | None = None,
+ min_width: int | None = None,
+ interactive: bool | None = None,
+ visible: bool = True,
+ elem_id: str | None = None,
+ elem_classes: list[str] | str | None = None,
+ render: bool = True,
+ load_fn: Callable[..., Any] | None = None,
+ every: Timer | float | None = None):
+ super().__init__(value, label=label, info=info,
+ show_label=show_label, container=container,
+ scale=scale, min_width=min_width,
+ interactive=interactive, visible=visible,
+ elem_id=elem_id, elem_classes=elem_classes,
+ render=render, load_fn=load_fn, every=every)
+ self.height = height
+
+ def preprocess(self, payload: FileData) -> str:
+ return payload.path
+
+ def postprocess(self, value: str | None) -> FileData:
+ if not value:
+ return None
+ return FileData(path=value)
+
+ def example_payload(self):
+ return "https://gradio-builds.s3.amazonaws.com/assets/pdf-guide/fw9.pdf"
+
+ def example_value(self):
+ return "https://gradio-builds.s3.amazonaws.com/assets/pdf-guide/fw9.pdf"
+```
+
+## Step 10: Add a demo and publish!
+
+To test our backend code, let's add a more complex demo that performs Document Question and Answering with huggingface transformers.
+
+In our `demo` directory, create a `requirements.txt` file with the following packages
+
+```
+torch
+transformers
+pdf2image
+pytesseract
+```
+
+
+Tip: Remember to install these yourself and restart the dev server! You may need to install extra non-python dependencies for `pdf2image`. See [here](https://pypi.org/project/pdf2image/). Feel free to write your own demo if you have trouble.
+
+
+```python
+import gradio as gr
+from gradio_pdf import PDF
+from pdf2image import convert_from_path
+from transformers import pipeline
+from pathlib import Path
+
+dir_ = Path(__file__).parent
+
+p = pipeline(
+ "document-question-answering",
+ model="impira/layoutlm-document-qa",
+)
+
+def qa(question: str, doc: str) -> str:
+ img = convert_from_path(doc)[0]
+ output = p(img, question)
+ return sorted(output, key=lambda x: x["score"], reverse=True)[0]['answer']
+
+
+demo = gr.Interface(
+ qa,
+ [gr.Textbox(label="Question"), PDF(label="Document")],
+ gr.Textbox(),
+)
+
+demo.launch()
+```
+
+See our demo in action below!
+
+
+
+Finally lets build our component with `gradio cc build` and publish it with the `gradio cc publish` command!
+This will guide you through the process of uploading your component to [PyPi](https://pypi.org/) and [HuggingFace Spaces](https://huggingface.co/spaces).
+
+
+Tip: You may need to add the following lines to the `Dockerfile` of your HuggingFace Space.
+
+```Dockerfile
+RUN mkdir -p /tmp/cache/
+RUN chmod a+rwx -R /tmp/cache/
+RUN apt-get update && apt-get install -y poppler-utils tesseract-ocr
+
+ENV TRANSFORMERS_CACHE=/tmp/cache/
+```
+
+## Conclusion
+
+In order to use our new component in **any** gradio 4.0 app, simply install it with pip, e.g. `pip install gradio-pdf`. Then you can use it like the built-in `gr.File()` component (except that it will only accept and display PDF files).
+
+Here is a simple demo with the Blocks api:
+
+```python
+import gradio as gr
+from gradio_pdf import PDF
+
+with gr.Blocks() as demo:
+ pdf = PDF(label="Upload a PDF", interactive=True)
+ name = gr.Textbox()
+ pdf.upload(lambda f: f, pdf, name)
+
+demo.launch()
+```
+
+
+I hope you enjoyed this tutorial!
+The complete source code for our component is [here](https://huggingface.co/spaces/freddyaboulton/gradio_pdf/tree/main/src).
+Please don't hesitate to reach out to the gradio community on the [HuggingFace Discord](https://discord.gg/hugging-face-879548962464493619) if you get stuck.
diff --git a/guides/08_custom-components/08_multimodal-chatbot-part1.md b/guides/08_custom-components/08_multimodal-chatbot-part1.md
new file mode 100644
index 0000000..a5dd23f
--- /dev/null
+++ b/guides/08_custom-components/08_multimodal-chatbot-part1.md
@@ -0,0 +1,358 @@
+# Build a Custom Multimodal Chatbot - Part 1
+
+This is the first in a two part series where we build a custom Multimodal Chatbot component.
+In part 1, we will modify the Gradio Chatbot component to display text and media files (video, audio, image) in the same message.
+In part 2, we will build a custom Textbox component that will be able to send multimodal messages (text and media files) to the chatbot.
+
+You can follow along with the author of this post as he implements the chatbot component in the following YouTube video!
+
+
+
+Here's a preview of what our multimodal chatbot component will look like:
+
+
+
+
+## Part 1 - Creating our project
+
+For this demo we will be tweaking the existing Gradio `Chatbot` component to display text and media files in the same message.
+Let's create a new custom component directory by templating off of the `Chatbot` component source code.
+
+```bash
+gradio cc create MultimodalChatbot --template Chatbot
+```
+
+And we're ready to go!
+
+Tip: Make sure to modify the `Author` key in the `pyproject.toml` file.
+
+## Part 2a - The backend data_model
+
+Open up the `multimodalchatbot.py` file in your favorite code editor and let's get started modifying the backend of our component.
+
+The first thing we will do is create the `data_model` of our component.
+The `data_model` is the data format that your python component will receive and send to the javascript client running the UI.
+You can read more about the `data_model` in the [backend guide](./backend).
+
+For our component, each chatbot message will consist of two keys: a `text` key that displays the text message and an optional list of media files that can be displayed underneath the text.
+
+Import the `FileData` and `GradioModel` classes from `gradio.data_classes` and modify the existing `ChatbotData` class to look like the following:
+
+```python
+class FileMessage(GradioModel):
+ file: FileData
+ alt_text: Optional[str] = None
+
+
+class MultimodalMessage(GradioModel):
+ text: Optional[str] = None
+ files: Optional[List[FileMessage]] = None
+
+
+class ChatbotData(GradioRootModel):
+ root: List[Tuple[Optional[MultimodalMessage], Optional[MultimodalMessage]]]
+
+
+class MultimodalChatbot(Component):
+ ...
+ data_model = ChatbotData
+```
+
+
+Tip: The `data_model`s are implemented using `Pydantic V2`. Read the documentation [here](https://docs.pydantic.dev/latest/).
+
+We've done the hardest part already!
+
+## Part 2b - The pre and postprocess methods
+
+For the `preprocess` method, we will keep it simple and pass a list of `MultimodalMessage`s to the python functions that use this component as input.
+This will let users of our component access the chatbot data with `.text` and `.files` attributes.
+This is a design choice that you can modify in your implementation!
+We can return the list of messages with the `root` property of the `ChatbotData` like so:
+
+```python
+def preprocess(
+ self,
+ payload: ChatbotData | None,
+) -> List[MultimodalMessage] | None:
+ if payload is None:
+ return payload
+ return payload.root
+```
+
+
+Tip: Learn about the reasoning behind the `preprocess` and `postprocess` methods in the [key concepts guide](./key-component-concepts)
+
+In the `postprocess` method we will coerce each message returned by the python function to be a `MultimodalMessage` class.
+We will also clean up any indentation in the `text` field so that it can be properly displayed as markdown in the frontend.
+
+We can leave the `postprocess` method as is and modify the `_postprocess_chat_messages`
+
+```python
+def _postprocess_chat_messages(
+ self, chat_message: MultimodalMessage | dict | None
+) -> MultimodalMessage | None:
+ if chat_message is None:
+ return None
+ if isinstance(chat_message, dict):
+ chat_message = MultimodalMessage(**chat_message)
+ chat_message.text = inspect.cleandoc(chat_message.text or "")
+ for file_ in chat_message.files:
+ file_.file.mime_type = client_utils.get_mimetype(file_.file.path)
+ return chat_message
+```
+
+Before we wrap up with the backend code, let's modify the `example_value` and `example_payload` method to return a valid dictionary representation of the `ChatbotData`:
+
+```python
+def example_value(self) -> Any:
+ return [[{"text": "Hello!", "files": []}, None]]
+
+def example_payload(self) -> Any:
+ return [[{"text": "Hello!", "files": []}, None]]
+```
+
+Congrats - the backend is complete!
+
+## Part 3a - The Index.svelte file
+
+The frontend for the `Chatbot` component is divided into two parts - the `Index.svelte` file and the `shared/Chatbot.svelte` file.
+The `Index.svelte` file applies some processing to the data received from the server and then delegates the rendering of the conversation to the `shared/Chatbot.svelte` file.
+First we will modify the `Index.svelte` file to apply processing to the new data type the backend will return.
+
+Let's begin by porting our custom types from our python `data_model` to typescript.
+Open `frontend/shared/utils.ts` and add the following type definitions at the top of the file:
+
+```ts
+export type FileMessage = {
+ file: FileData;
+ alt_text?: string;
+};
+
+
+export type MultimodalMessage = {
+ text: string;
+ files?: FileMessage[];
+}
+```
+
+Now let's import them in `Index.svelte` and modify the type annotations for `value` and `_value`.
+
+```ts
+import type { FileMessage, MultimodalMessage } from "./shared/utils";
+
+export let value: [
+ MultimodalMessage | null,
+ MultimodalMessage | null
+][] = [];
+
+let _value: [
+ MultimodalMessage | null,
+ MultimodalMessage | null
+][];
+```
+
+We need to normalize each message to make sure each file has a proper URL to fetch its contents from.
+We also need to format any embedded file links in the `text` key.
+Let's add a `process_message` utility function and apply it whenever the `value` changes.
+
+```ts
+function process_message(msg: MultimodalMessage | null): MultimodalMessage | null {
+ if (msg === null) {
+ return msg;
+ }
+ msg.text = redirect_src_url(msg.text);
+ msg.files = msg.files.map(normalize_messages);
+ return msg;
+}
+
+$: _value = value
+ ? value.map(([user_msg, bot_msg]) => [
+ process_message(user_msg),
+ process_message(bot_msg)
+ ])
+ : [];
+```
+
+## Part 3b - the Chatbot.svelte file
+
+Let's begin similarly to the `Index.svelte` file and let's first modify the type annotations.
+Import `Mulimodal` message at the top of the `
+```
+
+Be sure to add this to the `` of your HTML. This will install the latest version but we advise hardcoding the version in production. You can find all available versions [here](https://www.jsdelivr.com/package/npm/@gradio/client). This approach is ideal for experimental or prototying purposes, though has some limitations. A complete example would look like this:
+
+```html
+
+
+
+
+
+
+```
+
+## Connecting to a running Gradio App
+
+Start by connecting instantiating a `client` instance and connecting it to a Gradio app that is running on Hugging Face Spaces or generally anywhere on the web.
+
+## Connecting to a Hugging Face Space
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("abidlabs/en2fr"); // a Space that translates from English to French
+```
+
+You can also connect to private Spaces by passing in your HF token with the `token` property of the options parameter. You can get your HF token here: https://huggingface.co/settings/tokens
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("abidlabs/my-private-space", { token: "hf_..." })
+```
+
+## Duplicating a Space for private use
+
+While you can use any public Space as an API, you may get rate limited by Hugging Face if you make too many requests. For unlimited usage of a Space, simply duplicate the Space to create a private Space, and then use it to make as many requests as you'd like! You'll need to pass in your [Hugging Face token](https://huggingface.co/settings/tokens)).
+
+`Client.duplicate` is almost identical to `Client.connect`, the only difference is under the hood:
+
+```js
+import { Client, handle_file } from "@gradio/client";
+
+const response = await fetch(
+ "https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3"
+);
+const audio_file = await response.blob();
+
+const app = await Client.duplicate("abidlabs/whisper", { token: "hf_..." });
+const transcription = await app.predict("/predict", [handle_file(audio_file)]);
+```
+
+If you have previously duplicated a Space, re-running `Client.duplicate` will _not_ create a new Space. Instead, the client will attach to the previously-created Space. So it is safe to re-run the `Client.duplicate` method multiple times with the same space.
+
+**Note:** if the original Space uses GPUs, your private Space will as well, and your Hugging Face account will get billed based on the price of the GPU. To minimize charges, your Space will automatically go to sleep after 5 minutes of inactivity. You can also set the hardware using the `hardware` and `timeout` properties of `duplicate`'s options object like this:
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.duplicate("abidlabs/whisper", {
+ token: "hf_...",
+ timeout: 60,
+ hardware: "a10g-small"
+});
+```
+
+## Connecting a general Gradio app
+
+If your app is running somewhere else, just provide the full URL instead, including the "http://" or "https://". Here's an example of making predictions to a Gradio app that is running on a share URL:
+
+```js
+import { Client } from "@gradio/client";
+
+const app = Client.connect("https://bec81a83-5b5c-471e.gradio.live");
+```
+
+## Connecting to a Gradio app with auth
+
+If the Gradio application you are connecting to [requires a username and password](/guides/sharing-your-app#authentication), then provide them as a tuple to the `auth` argument of the `Client` class:
+
+```js
+import { Client } from "@gradio/client";
+
+Client.connect(
+ space_name,
+ { auth: [username, password] }
+)
+```
+
+
+## Inspecting the API endpoints
+
+Once you have connected to a Gradio app, you can view the APIs that are available to you by calling the `Client`'s `view_api` method.
+
+For the Whisper Space, we can do this:
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("abidlabs/whisper");
+
+const app_info = await app.view_api();
+
+console.log(app_info);
+```
+
+And we will see the following:
+
+```json
+{
+ "named_endpoints": {
+ "/predict": {
+ "parameters": [
+ {
+ "label": "text",
+ "component": "Textbox",
+ "type": "string"
+ }
+ ],
+ "returns": [
+ {
+ "label": "output",
+ "component": "Textbox",
+ "type": "string"
+ }
+ ]
+ }
+ },
+ "unnamed_endpoints": {}
+}
+```
+
+This shows us that we have 1 API endpoint in this space, and shows us how to use the API endpoint to make a prediction: we should call the `.predict()` method (which we will explore below), providing a parameter `input_audio` of type `string`, which is a url to a file.
+
+We should also provide the `api_name='/predict'` argument to the `predict()` method. Although this isn't necessary if a Gradio app has only 1 named endpoint, it does allow us to call different endpoints in a single app if they are available. If an app has unnamed API endpoints, these can also be displayed by running `.view_api(all_endpoints=True)`.
+
+## The "View API" Page
+
+As an alternative to running the `.view_api()` method, you can click on the "Use via API" link in the footer of the Gradio app, which shows us the same information, along with example usage.
+
+
+
+The View API page also includes an "API Recorder" that lets you interact with the Gradio UI normally and converts your interactions into the corresponding code to run with the JS Client.
+
+
+## Making a prediction
+
+The simplest way to make a prediction is simply to call the `.predict()` method with the appropriate arguments:
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("abidlabs/en2fr");
+const result = await app.predict("/predict", ["Hello"]);
+```
+
+If there are multiple parameters, then you should pass them as an array to `.predict()`, like this:
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("gradio/calculator");
+const result = await app.predict("/predict", [4, "add", 5]);
+```
+
+For certain inputs, such as images, you should pass in a `Buffer`, `Blob` or `File` depending on what is most convenient. In node, this would be a `Buffer` or `Blob`; in a browser environment, this would be a `Blob` or `File`.
+
+```js
+import { Client, handle_file } from "@gradio/client";
+
+const response = await fetch(
+ "https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3"
+);
+const audio_file = await response.blob();
+
+const app = await Client.connect("abidlabs/whisper");
+const result = await app.predict("/predict", [handle_file(audio_file)]);
+```
+
+## Using events
+
+If the API you are working with can return results over time, or you wish to access information about the status of a job, you can use the iterable interface for more flexibility. This is especially useful for iterative endpoints or generator endpoints that will produce a series of values over time as discrete responses.
+
+```js
+import { Client } from "@gradio/client";
+
+function log_result(payload) {
+ const {
+ data: [translation]
+ } = payload;
+
+ console.log(`The translated result is: ${translation}`);
+}
+
+const app = await Client.connect("abidlabs/en2fr");
+const job = app.submit("/predict", ["Hello"]);
+
+for await (const message of job) {
+ log_result(message);
+}
+```
+
+## Status
+
+The event interface also allows you to get the status of the running job by instantiating the client with the `events` options passing `status` and `data` as an array:
+
+
+```ts
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("abidlabs/en2fr", {
+ events: ["status", "data"]
+});
+```
+
+This ensures that status messages are also reported to the client.
+
+`status`es are returned as an object with the following attributes: `status` (a human readbale status of the current job, `"pending" | "generating" | "complete" | "error"`), `code` (the detailed gradio code for the job), `position` (the current position of this job in the queue), `queue_size` (the total queue size), `eta` (estimated time this job will complete), `success` (a boolean representing whether the job completed successfully), and `time` ( as `Date` object detailing the time that the status was generated).
+
+```js
+import { Client } from "@gradio/client";
+
+function log_status(status) {
+ console.log(
+ `The current status for this job is: ${JSON.stringify(status, null, 2)}.`
+ );
+}
+
+const app = await Client.connect("abidlabs/en2fr", {
+ events: ["status", "data"]
+});
+const job = app.submit("/predict", ["Hello"]);
+
+for await (const message of job) {
+ if (message.type === "status") {
+ log_status(message);
+ }
+}
+```
+
+## Cancelling Jobs
+
+The job instance also has a `.cancel()` method that cancels jobs that have been queued but not started. For example, if you run:
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("abidlabs/en2fr");
+const job_one = app.submit("/predict", ["Hello"]);
+const job_two = app.submit("/predict", ["Friends"]);
+
+job_one.cancel();
+job_two.cancel();
+```
+
+If the first job has started processing, then it will not be canceled but the client will no longer listen for updates (throwing away the job). If the second job has not yet started, it will be successfully canceled and removed from the queue.
+
+## Generator Endpoints
+
+Some Gradio API endpoints do not return a single value, rather they return a series of values. You can listen for these values in real time using the iterable interface:
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("gradio/count_generator");
+const job = app.submit(0, [9]);
+
+for await (const message of job) {
+ console.log(message.data);
+}
+```
+
+This will log out the values as they are generated by the endpoint.
+
+You can also cancel jobs that that have iterative outputs, in which case the job will finish immediately.
+
+```js
+import { Client } from "@gradio/client";
+
+const app = await Client.connect("gradio/count_generator");
+const job = app.submit(0, [9]);
+
+for await (const message of job) {
+ console.log(message.data);
+}
+
+setTimeout(() => {
+ job.cancel();
+}, 3000);
+```
diff --git a/guides/09_gradio-clients-and-lite/03_querying-gradio-apps-with-curl.md b/guides/09_gradio-clients-and-lite/03_querying-gradio-apps-with-curl.md
new file mode 100644
index 0000000..e7fdc07
--- /dev/null
+++ b/guides/09_gradio-clients-and-lite/03_querying-gradio-apps-with-curl.md
@@ -0,0 +1,303 @@
+# Querying Gradio Apps with Curl
+
+Tags: CURL, API, SPACES
+
+It is possible to use any Gradio app as an API using cURL, the command-line tool that is pre-installed on many operating systems. This is particularly useful if you are trying to query a Gradio app from an environment other than Python or Javascript (since specialized Gradio clients exist for both [Python](/guides/getting-started-with-the-python-client) and [Javascript](/guides/getting-started-with-the-js-client)).
+
+As an example, consider this Gradio demo that translates text from English to French: https://abidlabs-en2fr.hf.space/.
+
+Using `curl`, we can translate text programmatically.
+
+Here's the code to do it:
+
+```bash
+$ curl -X POST https://abidlabs-en2fr.hf.space/call/predict -H "Content-Type: application/json" -d '{
+ "data": ["Hello, my friend."]
+}'
+
+>> {"event_id": $EVENT_ID}
+```
+
+```bash
+$ curl -N https://abidlabs-en2fr.hf.space/call/predict/$EVENT_ID
+
+>> event: complete
+>> data: ["Bonjour, mon ami."]
+```
+
+
+Note: making a prediction and getting a result requires two `curl` requests: a `POST` and a `GET`. The `POST` request returns an `EVENT_ID` and prints it to the console, which is used in the second `GET` request to fetch the results. You can combine these into a single command using `awk` and `read` to parse the results of the first command and pipe into the second, like this:
+
+```bash
+$ curl -X POST https://abidlabs-en2fr.hf.space/call/predict -H "Content-Type: application/json" -d '{
+ "data": ["Hello, my friend."]
+}' \
+ | awk -F'"' '{ print $4}' \
+ | read EVENT_ID; curl -N https://abidlabs-en2fr.hf.space/call/predict/$EVENT_ID
+
+>> event: complete
+>> data: ["Bonjour, mon ami."]
+```
+
+In the rest of this Guide, we'll explain these two steps in more detail and provide additional examples of querying Gradio apps with `curl`.
+
+
+**Prerequisites**: For this Guide, you do _not_ need to know how to build Gradio apps in great detail. However, it is helpful to have general familiarity with Gradio's concepts of input and output components.
+
+## Installation
+
+You generally don't need to install cURL, as it comes pre-installed on many operating systems. Run:
+
+```bash
+curl --version
+```
+
+to confirm that `curl` is installed. If it is not already installed, you can install it by visiting https://curl.se/download.html.
+
+
+## Step 0: Get the URL for your Gradio App
+
+To query a Gradio app, you'll need its full URL. This is usually just the URL that the Gradio app is hosted on, for example: https://bec81a83-5b5c-471e.gradio.live
+
+
+**Hugging Face Spaces**
+
+However, if you are querying a Gradio on Hugging Face Spaces, you will need to use the URL of the embedded Gradio app, not the URL of the Space webpage. For example:
+
+```bash
+❌ Space URL: https://huggingface.co/spaces/abidlabs/en2fr
+✅ Gradio app URL: https://abidlabs-en2fr.hf.space/
+```
+
+You can get the Gradio app URL by clicking the "view API" link at the bottom of the page. Or, you can right-click on the page and then click on "View Frame Source" or the equivalent in your browser to view the URL of the embedded Gradio app.
+
+While you can use any public Space as an API, you may get rate limited by Hugging Face if you make too many requests. For unlimited usage of a Space, simply duplicate the Space to create a private Space,
+and then use it to make as many requests as you'd like!
+
+Note: to query private Spaces, you will need to pass in your Hugging Face (HF) token. You can get your HF token here: https://huggingface.co/settings/tokens. In this case, you will need to include an additional header in both of your `curl` calls that we'll discuss below:
+
+```bash
+-H "Authorization: Bearer $HF_TOKEN"
+```
+
+Now, we are ready to make the two `curl` requests.
+
+## Step 1: Make a Prediction (POST)
+
+The first of the two `curl` requests is `POST` request that submits the input payload to the Gradio app.
+
+The syntax of the `POST` request is as follows:
+
+```bash
+$ curl -X POST $URL/call/$API_NAME -H "Content-Type: application/json" -d '{
+ "data": $PAYLOAD
+}'
+```
+
+Here:
+
+* `$URL` is the URL of the Gradio app as obtained in Step 0
+* `$API_NAME` is the name of the API endpoint for the event that you are running. You can get the API endpoint names by clicking the "view API" link at the bottom of the page.
+* `$PAYLOAD` is a valid JSON data list containing the input payload, one element for each input component.
+
+When you make this `POST` request successfully, you will get an event id that is printed to the terminal in this format:
+
+```bash
+>> {"event_id": $EVENT_ID}
+```
+
+This `EVENT_ID` will be needed in the subsequent `curl` request to fetch the results of the prediction.
+
+Here are some examples of how to make the `POST` request
+
+**Basic Example**
+
+Revisiting the example at the beginning of the page, here is how to make the `POST` request for a simple Gradio application that takes in a single input text component:
+
+```bash
+$ curl -X POST https://abidlabs-en2fr.hf.space/call/predict -H "Content-Type: application/json" -d '{
+ "data": ["Hello, my friend."]
+}'
+```
+
+**Multiple Input Components**
+
+This [Gradio demo](https://huggingface.co/spaces/gradio/hello_world_3) accepts three inputs: a string corresponding to the `gr.Textbox`, a boolean value corresponding to the `gr.Checkbox`, and a numerical value corresponding to the `gr.Slider`. Here is the `POST` request:
+
+```bash
+curl -X POST https://gradio-hello-world-3.hf.space/call/predict -H "Content-Type: application/json" -d '{
+ "data": ["Hello", true, 5]
+}'
+```
+
+**Private Spaces**
+
+As mentioned earlier, if you are making a request to a private Space, you will need to pass in a [Hugging Face token](https://huggingface.co/settings/tokens) that has read access to the Space. The request will look like this:
+
+```bash
+$ curl -X POST https://private-space.hf.space/call/predict -H "Content-Type: application/json" -H "Authorization: Bearer $HF_TOKEN" -d '{
+ "data": ["Hello, my friend."]
+}'
+```
+
+**Files**
+
+If you are using `curl` to query a Gradio application that requires file inputs, the files *need* to be provided as URLs, and The URL needs to be enclosed in a dictionary in this format:
+
+```bash
+{"path": $URL}
+```
+
+Here is an example `POST` request:
+
+```bash
+$ curl -X POST https://gradio-image-mod.hf.space/call/predict -H "Content-Type: application/json" -d '{
+ "data": [{"path": "https://raw.githubusercontent.com/gradio-app/gradio/main/test/test_files/bus.png"}]
+}'
+```
+
+
+**Stateful Demos**
+
+If your Gradio demo [persists user state](/guides/interface-state) across multiple interactions (e.g. is a chatbot), you can pass in a `session_hash` alongside the `data`. Requests with the same `session_hash` are assumed to be part of the same user session. Here's how that might look:
+
+```bash
+# These two requests will share a session
+
+curl -X POST https://gradio-chatinterface-random-response.hf.space/call/chat -H "Content-Type: application/json" -d '{
+ "data": ["Are you sentient?"],
+ "session_hash": "randomsequence1234"
+}'
+
+curl -X POST https://gradio-chatinterface-random-response.hf.space/call/chat -H "Content-Type: application/json" -d '{
+ "data": ["Really?"],
+ "session_hash": "randomsequence1234"
+}'
+
+# This request will be treated as a new session
+
+curl -X POST https://gradio-chatinterface-random-response.hf.space/call/chat -H "Content-Type: application/json" -d '{
+ "data": ["Are you sentient?"],
+ "session_hash": "newsequence5678"
+}'
+```
+
+
+
+## Step 2: GET the result
+
+Once you have received the `EVENT_ID` corresponding to your prediction, you can stream the results. Gradio stores these results in a least-recently-used cache in the Gradio app. By default, the cache can store 2,000 results (across all users and endpoints of the app).
+
+To stream the results for your prediction, make a `GET` request with the following syntax:
+
+```bash
+$ curl -N $URL/call/$API_NAME/$EVENT_ID
+```
+
+
+Tip: If you are fetching results from a private Space, include a header with your HF token like this: `-H "Authorization: Bearer $HF_TOKEN"` in the `GET` request.
+
+This should produce a stream of responses in this format:
+
+```bash
+event: ...
+data: ...
+event: ...
+data: ...
+...
+```
+
+Here: `event` can be one of the following:
+* `generating`: indicating an intermediate result
+* `complete`: indicating that the prediction is complete and the final result
+* `error`: indicating that the prediction was not completed successfully
+* `heartbeat`: sent every 15 seconds to keep the request alive
+
+The `data` is in the same format as the input payload: valid JSON data list containing the output result, one element for each output component.
+
+Here are some examples of what results you should expect if a request is completed successfully:
+
+**Basic Example**
+
+Revisiting the example at the beginning of the page, we would expect the result to look like this:
+
+```bash
+event: complete
+data: ["Bonjour, mon ami."]
+```
+
+**Multiple Outputs**
+
+If your endpoint returns multiple values, they will appear as elements of the `data` list:
+
+```bash
+event: complete
+data: ["Good morning Hello. It is 5 degrees today", -15.0]
+```
+
+**Streaming Example**
+
+If your Gradio app [streams a sequence of values](/guides/streaming-outputs), then they will be streamed directly to your terminal, like this:
+
+```bash
+event: generating
+data: ["Hello, w!"]
+event: generating
+data: ["Hello, wo!"]
+event: generating
+data: ["Hello, wor!"]
+event: generating
+data: ["Hello, worl!"]
+event: generating
+data: ["Hello, world!"]
+event: complete
+data: ["Hello, world!"]
+```
+
+**File Example**
+
+If your Gradio app returns a file, the file will be represented as a dictionary in this format (including potentially some additional keys):
+
+```python
+{
+ "orig_name": "example.jpg",
+ "path": "/path/in/server.jpg",
+ "url": "https:/example.com/example.jpg",
+ "meta": {"_type": "gradio.FileData"}
+}
+```
+
+In your terminal, it may appear like this:
+
+```bash
+event: complete
+data: [{"path": "/tmp/gradio/359933dc8d6cfe1b022f35e2c639e6e42c97a003/image.webp", "url": "https://gradio-image-mod.hf.space/c/file=/tmp/gradio/359933dc8d6cfe1b022f35e2c639e6e42c97a003/image.webp", "size": null, "orig_name": "image.webp", "mime_type": null, "is_stream": false, "meta": {"_type": "gradio.FileData"}}]
+```
+
+## Authentication
+
+What if your Gradio application has [authentication enabled](/guides/sharing-your-app#authentication)? In that case, you'll need to make an additional `POST` request with cURL to authenticate yourself before you make any queries. Here are the complete steps:
+
+First, login with a `POST` request supplying a valid username and password:
+
+```bash
+curl -X POST $URL/login \
+ -d "username=$USERNAME&password=$PASSWORD" \
+ -c cookies.txt
+```
+
+If the credentials are correct, you'll get `{"success":true}` in response and the cookies will be saved in `cookies.txt`.
+
+Next, you'll need to include these cookies when you make the original `POST` request, like this:
+
+```bash
+$ curl -X POST $URL/call/$API_NAME -b cookies.txt -H "Content-Type: application/json" -d '{
+ "data": $PAYLOAD
+}'
+```
+
+Finally, you'll need to `GET` the results, again supplying the cookies from the file:
+
+```bash
+curl -N $URL/call/$API_NAME/$EVENT_ID -b cookies.txt
+```
diff --git a/guides/09_gradio-clients-and-lite/04_gradio-and-llm-agents.md b/guides/09_gradio-clients-and-lite/04_gradio-and-llm-agents.md
new file mode 100644
index 0000000..e79639f
--- /dev/null
+++ b/guides/09_gradio-clients-and-lite/04_gradio-and-llm-agents.md
@@ -0,0 +1,139 @@
+# Gradio & LLM Agents 🤝
+
+Large Language Models (LLMs) are very impressive but they can be made even more powerful if we could give them skills to accomplish specialized tasks.
+
+The [gradio_tools](https://github.com/freddyaboulton/gradio-tools) library can turn any [Gradio](https://github.com/gradio-app/gradio) application into a [tool](https://python.langchain.com/en/latest/modules/agents/tools.html) that an [agent](https://docs.langchain.com/docs/components/agents/agent) can use to complete its task. For example, an LLM could use a Gradio tool to transcribe a voice recording it finds online and then summarize it for you. Or it could use a different Gradio tool to apply OCR to a document on your Google Drive and then answer questions about it.
+
+This guide will show how you can use `gradio_tools` to grant your LLM Agent access to the cutting edge Gradio applications hosted in the world. Although `gradio_tools` are compatible with more than one agent framework, we will focus on [Langchain Agents](https://docs.langchain.com/docs/components/agents/) in this guide.
+
+## Some background
+
+### What are agents?
+
+A [LangChain agent](https://docs.langchain.com/docs/components/agents/agent) is a Large Language Model (LLM) that takes user input and reports an output based on using one of many tools at its disposal.
+
+### What is Gradio?
+
+[Gradio](https://github.com/gradio-app/gradio) is the defacto standard framework for building Machine Learning Web Applications and sharing them with the world - all with just python! 🐍
+
+## gradio_tools - An end-to-end example
+
+To get started with `gradio_tools`, all you need to do is import and initialize your tools and pass them to the langchain agent!
+
+In the following example, we import the `StableDiffusionPromptGeneratorTool` to create a good prompt for stable diffusion, the
+`StableDiffusionTool` to create an image with our improved prompt, the `ImageCaptioningTool` to caption the generated image, and
+the `TextToVideoTool` to create a video from a prompt.
+
+We then tell our agent to create an image of a dog riding a skateboard, but to please improve our prompt ahead of time. We also ask
+it to caption the generated image and create a video for it. The agent can decide which tool to use without us explicitly telling it.
+
+```python
+import os
+
+if not os.getenv("OPENAI_API_KEY"):
+ raise ValueError("OPENAI_API_KEY must be set")
+
+from langchain.agents import initialize_agent
+from langchain.llms import OpenAI
+from gradio_tools import (StableDiffusionTool, ImageCaptioningTool, StableDiffusionPromptGeneratorTool,
+ TextToVideoTool)
+
+from langchain.memory import ConversationBufferMemory
+
+llm = OpenAI(temperature=0)
+memory = ConversationBufferMemory(memory_key="chat_history")
+tools = [StableDiffusionTool().langchain, ImageCaptioningTool().langchain,
+ StableDiffusionPromptGeneratorTool().langchain, TextToVideoTool().langchain]
+
+
+agent = initialize_agent(tools, llm, memory=memory, agent="conversational-react-description", verbose=True)
+output = agent.run(input=("Please create a photo of a dog riding a skateboard "
+ "but improve my prompt prior to using an image generator."
+ "Please caption the generated image and create a video for it using the improved prompt."))
+```
+
+You'll note that we are using some pre-built tools that come with `gradio_tools`. Please see this [doc](https://github.com/freddyaboulton/gradio-tools#gradio-tools-gradio--llm-agents) for a complete list of the tools that come with `gradio_tools`.
+If you would like to use a tool that's not currently in `gradio_tools`, it is very easy to add your own. That's what the next section will cover.
+
+## gradio_tools - creating your own tool
+
+The core abstraction is the `GradioTool`, which lets you define a new tool for your LLM as long as you implement a standard interface:
+
+```python
+class GradioTool(BaseTool):
+
+ def __init__(self, name: str, description: str, src: str) -> None:
+
+ @abstractmethod
+ def create_job(self, query: str) -> Job:
+ pass
+
+ @abstractmethod
+ def postprocess(self, output: Tuple[Any] | Any) -> str:
+ pass
+```
+
+The requirements are:
+
+1. The name for your tool
+2. The description for your tool. This is crucial! Agents decide which tool to use based on their description. Be precise and be sure to include example of what the input and the output of the tool should look like.
+3. The url or space id, e.g. `freddyaboulton/calculator`, of the Gradio application. Based on this value, `gradio_tool` will create a [gradio client](https://github.com/gradio-app/gradio/blob/main/client/python/README.md) instance to query the upstream application via API. Be sure to click the link and learn more about the gradio client library if you are not familiar with it.
+4. create_job - Given a string, this method should parse that string and return a job from the client. Most times, this is as simple as passing the string to the `submit` function of the client. More info on creating jobs [here](https://github.com/gradio-app/gradio/blob/main/client/python/README.md#making-a-prediction)
+5. postprocess - Given the result of the job, convert it to a string the LLM can display to the user.
+6. _Optional_ - Some libraries, e.g. [MiniChain](https://github.com/srush/MiniChain/tree/main), may need some info about the underlying gradio input and output types used by the tool. By default, this will return gr.Textbox() but
+ if you'd like to provide more accurate info, implement the `_block_input(self, gr)` and `_block_output(self, gr)` methods of the tool. The `gr` variable is the gradio module (the result of `import gradio as gr`). It will be
+ automatically imported by the `GradiTool` parent class and passed to the `_block_input` and `_block_output` methods.
+
+And that's it!
+
+Once you have created your tool, open a pull request to the `gradio_tools` repo! We welcome all contributions.
+
+## Example tool - Stable Diffusion
+
+Here is the code for the StableDiffusion tool as an example:
+
+```python
+from gradio_tool import GradioTool
+import os
+
+class StableDiffusionTool(GradioTool):
+ """Tool for calling stable diffusion from llm"""
+
+ def __init__(
+ self,
+ name="StableDiffusion",
+ description=(
+ "An image generator. Use this to generate images based on "
+ "text input. Input should be a description of what the image should "
+ "look like. The output will be a path to an image file."
+ ),
+ src="gradio-client-demos/stable-diffusion",
+ token=None,
+ ) -> None:
+ super().__init__(name, description, src, token)
+
+ def create_job(self, query: str) -> Job:
+ return self.client.submit(query, "", 9, fn_index=1)
+
+ def postprocess(self, output: str) -> str:
+ return [os.path.join(output, i) for i in os.listdir(output) if not i.endswith("json")][0]
+
+ def _block_input(self, gr) -> "gr.components.Component":
+ return gr.Textbox()
+
+ def _block_output(self, gr) -> "gr.components.Component":
+ return gr.Image()
+```
+
+Some notes on this implementation:
+
+1. All instances of `GradioTool` have an attribute called `client` that is a pointed to the underlying [gradio client](https://github.com/gradio-app/gradio/tree/main/client/python#gradio_client-use-a-gradio-app-as-an-api----in-3-lines-of-python). That is what you should use
+ in the `create_job` method.
+2. `create_job` just passes the query string to the `submit` function of the client with some other parameters hardcoded, i.e. the negative prompt string and the guidance scale. We could modify our tool to also accept these values from the input string in a subsequent version.
+3. The `postprocess` method simply returns the first image from the gallery of images created by the stable diffusion space. We use the `os` module to get the full path of the image.
+
+## Conclusion
+
+You now know how to extend the abilities of your LLM with the 1000s of gradio spaces running in the wild!
+Again, we welcome any contributions to the [gradio_tools](https://github.com/freddyaboulton/gradio-tools) library.
+We're excited to see the tools you all build!
diff --git a/guides/09_gradio-clients-and-lite/07_fastapi-app-with-the-gradio-client.md b/guides/09_gradio-clients-and-lite/07_fastapi-app-with-the-gradio-client.md
new file mode 100644
index 0000000..d5cb24f
--- /dev/null
+++ b/guides/09_gradio-clients-and-lite/07_fastapi-app-with-the-gradio-client.md
@@ -0,0 +1,197 @@
+# Building a Web App with the Gradio Python Client
+
+Tags: CLIENT, API, WEB APP
+
+In this guide, we will demonstrate how to use the `gradio_client` [Python library](getting-started-with-the-python-client/), which enables developers to make requests to a Gradio app programmatically, by creating an end-to-end example web app using FastAPI. The web app we will be building is called "Acapellify," and it will allow users to upload video files as input and return a version of that video without instrumental music. It will also display a gallery of generated videos.
+
+**Prerequisites**
+
+Before we begin, make sure you are running Python 3.9 or later, and have the following libraries installed:
+
+- `gradio_client`
+- `fastapi`
+- `uvicorn`
+
+You can install these libraries from `pip`:
+
+```bash
+$ pip install gradio_client fastapi uvicorn
+```
+
+You will also need to have ffmpeg installed. You can check to see if you already have ffmpeg by running in your terminal:
+
+```bash
+$ ffmpeg version
+```
+
+Otherwise, install ffmpeg [by following these instructions](https://www.hostinger.com/tutorials/how-to-install-ffmpeg).
+
+## Step 1: Write the Video Processing Function
+
+Let's start with what seems like the most complex bit -- using machine learning to remove the music from a video.
+
+Luckily for us, there's an existing Space we can use to make this process easier: [https://huggingface.co/spaces/abidlabs/music-separation](https://huggingface.co/spaces/abidlabs/music-separation). This Space takes an audio file and produces two separate audio files: one with the instrumental music and one with all other sounds in the original clip. Perfect to use with our client!
+
+Open a new Python file, say `main.py`, and start by importing the `Client` class from `gradio_client` and connecting it to this Space:
+
+```py
+from gradio_client import Client, handle_file
+
+client = Client("abidlabs/music-separation")
+
+def acapellify(audio_path):
+ result = client.predict(handle_file(audio_path), api_name="/predict")
+ return result[0]
+```
+
+That's all the code that's needed -- notice that the API endpoints returns two audio files (one without the music, and one with just the music) in a list, and so we just return the first element of the list.
+
+---
+
+**Note**: since this is a public Space, there might be other users using this Space as well, which might result in a slow experience. You can duplicate this Space with your own [Hugging Face token](https://huggingface.co/settings/tokens) and create a private Space that only you have will have access to and bypass the queue. To do that, simply replace the first two lines above with:
+
+```py
+from gradio_client import Client
+
+client = Client.duplicate("abidlabs/music-separation", token=YOUR_HF_TOKEN)
+```
+
+Everything else remains the same!
+
+---
+
+Now, of course, we are working with video files, so we first need to extract the audio from the video files. For this, we will be using the `ffmpeg` library, which does a lot of heavy lifting when it comes to working with audio and video files. The most common way to use `ffmpeg` is through the command line, which we'll call via Python's `subprocess` module:
+
+Our video processing workflow will consist of three steps:
+
+1. First, we start by taking in a video filepath and extracting the audio using `ffmpeg`.
+2. Then, we pass in the audio file through the `acapellify()` function above.
+3. Finally, we combine the new audio with the original video to produce a final acapellified video.
+
+Here's the complete code in Python, which you can add to your `main.py` file:
+
+```python
+import subprocess
+
+def process_video(video_path):
+ old_audio = os.path.basename(video_path).split(".")[0] + ".m4a"
+ subprocess.run(['ffmpeg', '-y', '-i', video_path, '-vn', '-acodec', 'copy', old_audio])
+
+ new_audio = acapellify(old_audio)
+
+ new_video = f"acap_{video_path}"
+ subprocess.call(['ffmpeg', '-y', '-i', video_path, '-i', new_audio, '-map', '0:v', '-map', '1:a', '-c:v', 'copy', '-c:a', 'aac', '-strict', 'experimental', f"static/{new_video}"])
+ return new_video
+```
+
+You can read up on [ffmpeg documentation](https://ffmpeg.org/ffmpeg.html) if you'd like to understand all of the command line parameters, as they are beyond the scope of this tutorial.
+
+## Step 2: Create a FastAPI app (Backend Routes)
+
+Next up, we'll create a simple FastAPI app. If you haven't used FastAPI before, check out [the great FastAPI docs](https://fastapi.tiangolo.com/). Otherwise, this basic template, which we add to `main.py`, will look pretty familiar:
+
+```python
+import os
+from fastapi import FastAPI, File, UploadFile, Request
+from fastapi.responses import HTMLResponse, RedirectResponse
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+
+app = FastAPI()
+os.makedirs("static", exist_ok=True)
+app.mount("/static", StaticFiles(directory="static"), name="static")
+templates = Jinja2Templates(directory="templates")
+
+videos = []
+
+@app.get("/", response_class=HTMLResponse)
+async def home(request: Request):
+ return templates.TemplateResponse(
+ "home.html", {"request": request, "videos": videos})
+
+@app.post("/uploadvideo/")
+async def upload_video(video: UploadFile = File(...)):
+ video_path = video.filename
+ with open(video_path, "wb+") as fp:
+ fp.write(video.file.read())
+
+ new_video = process_video(video.filename)
+ videos.append(new_video)
+ return RedirectResponse(url='/', status_code=303)
+```
+
+In this example, the FastAPI app has two routes: `/` and `/uploadvideo/`.
+
+The `/` route returns an HTML template that displays a gallery of all uploaded videos.
+
+The `/uploadvideo/` route accepts a `POST` request with an `UploadFile` object, which represents the uploaded video file. The video file is "acapellified" via the `process_video()` method, and the output video is stored in a list which stores all of the uploaded videos in memory.
+
+Note that this is a very basic example and if this were a production app, you will need to add more logic to handle file storage, user authentication, and security considerations.
+
+## Step 3: Create a FastAPI app (Frontend Template)
+
+Finally, we create the frontend of our web application. First, we create a folder called `templates` in the same directory as `main.py`. We then create a template, `home.html` inside the `templates` folder. Here is the resulting file structure:
+
+```csv
+├── main.py
+├── templates
+│ └── home.html
+```
+
+Write the following as the contents of `home.html`:
+
+```html
+<!DOCTYPE html> <html> <head> <title>Video Gallery</title>
+<style> body { font-family: sans-serif; margin: 0; padding: 0;
+background-color: #f5f5f5; } h1 { text-align: center; margin-top: 30px;
+margin-bottom: 20px; } .gallery { display: flex; flex-wrap: wrap;
+justify-content: center; gap: 20px; padding: 20px; } .video { border: 2px solid
+#ccc; box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); border-radius: 5px; overflow:
+hidden; width: 300px; margin-bottom: 20px; } .video video { width: 100%; height:
+200px; } .video p { text-align: center; margin: 10px 0; } form { margin-top:
+20px; text-align: center; } input[type="file"] { display: none; } .upload-btn {
+display: inline-block; background-color: #3498db; color: #fff; padding: 10px
+20px; font-size: 16px; border: none; border-radius: 5px; cursor: pointer; }
+.upload-btn:hover { background-color: #2980b9; } .file-name { margin-left: 10px;
+} </style> </head> <body> <h1>Video Gallery</h1> {% if videos %}
+<div class="gallery"> {% for video in videos %} <div class="video">
+<video controls> <source src="{{ url_for('static', path=video) }}"
+type="video/mp4"> Your browser does not support the video tag. </video>
+<p>{{ video }}</p> </div> {% endfor %} </div> {% else %} <p>No
+videos uploaded yet.</p> {% endif %} <form action="/uploadvideo/"
+method="post" enctype="multipart/form-data"> <label for="video-upload"
+class="upload-btn">Choose video file</label> <input type="file"
+name="video" id="video-upload"> <span class="file-name"></span> <button
+type="submit" class="upload-btn">Upload</button> </form> <script> //
+Display selected file name in the form const fileUpload =
+document.getElementById("video-upload"); const fileName =
+document.querySelector(".file-name"); fileUpload.addEventListener("change", (e)
+=> { fileName.textContent = e.target.files[0].name; }); </script> </body>
+</html>
+```
+
+## Step 4: Run your FastAPI app
+
+Finally, we are ready to run our FastAPI app, powered by the Gradio Python Client!
+
+Open up a terminal and navigate to the directory containing `main.py`. Then run the following command in the terminal:
+
+```bash
+$ uvicorn main:app
+```
+
+You should see an output that looks like this:
+
+```csv
+Loaded as API: https://abidlabs-music-separation.hf.space ✔
+INFO: Started server process [1360]
+INFO: Waiting for application startup.
+INFO: Application startup complete.
+INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
+```
+
+And that's it! Start uploading videos and you'll get some "acapellified" videos in response (might take seconds to minutes to process depending on the length of your videos). Here's how the UI looks after uploading two videos:
+
+
+
+If you'd like to learn more about how to use the Gradio Python Client in your projects, [read the dedicated Guide](/guides/getting-started-with-the-python-client/).
diff --git a/guides/09_gradio-clients-and-lite/08_server-mode.md b/guides/09_gradio-clients-and-lite/08_server-mode.md
new file mode 100644
index 0000000..0476966
--- /dev/null
+++ b/guides/09_gradio-clients-and-lite/08_server-mode.md
@@ -0,0 +1,137 @@
+# Server mode
+
+Tags: API, MCP, FASTAPI, SERVER
+
+In this post, we will demonstrate how to build a completely custom frontend for your Gradio application, while still utilizing Gradio's backend, which means you still get an API server with queuing and streaming, MCP tool support, ZeroGPU support, and hosting on Hugging Face Spaces.
+
+To do this, you use **Server mode**: instantiate `gradio.Server` directly. The `gradio.Server` class is a FastAPI server with Gradio's API engine built in, so you get all the backend benefits with complete flexibility on what kind of frontend (e.g. a React app, a simple HTML page, or any vibe-coded frontend), if any, you'd like to launch alongside the backend server.
+
+## When to use `gradio.Server`
+
+Use `gradio.Server` instead of `gr.Blocks` when any of the following apply:
+
+- You want a **completely custom (potentially vibe-coded) UI** (your own HTML, React, Svelte, etc.) powered by Gradio's backend
+- You want **full FastAPI control** (custom GET/POST routes, middleware, dependency injection) alongside Gradio API endpoints
+- You're building a service to **host on Spaces** with or without ZeroGPU but don't need Gradio components
+
+If you're happy with Gradio's built-in UI components, use `gr.Blocks`, `gr.ChatInterface`, or `gr.Interface` instead.
+
+## Installation
+
+`gradio.Server` is included in the main Gradio package. If you want MCP support, install the extra:
+
+```bash
+pip install "gradio[mcp]"
+```
+
+## A Minimal Example
+
+Here's the simplest possible Server mode app — a single API endpoint with no UI:
+
+```python
+from gradio import Server
+
+app = Server()
+
+@app.api(name="hello")
+def hello(name: str) -> str:
+ return f"Hello, {name}!"
+
+app.launch()
+```
+
+That's it. When you run this script, you get:
+
+- A Gradio API endpoint at `/gradio_api/call/hello` with queuing and SSE streaming
+- Auto-generated API docs at `/gradio_api/info`
+- A Python and JavaScript client that can call `/hello` by name
+
+You can test it with the Gradio Python client:
+
+```python
+from gradio_client import Client
+
+client = Client("http://localhost:7860")
+result = client.predict("World", api_name="/hello")
+print(result) # "Hello, World!"
+```
+
+## Custom Routes
+
+Since `gradio.Server` inherits from FastAPI, you can add any route directly:
+
+```python
+from gradio import Server
+from fastapi.responses import HTMLResponse
+
+app = Server()
+
+@app.api(name="hello")
+def hello(name: str) -> str:
+ return f"Hello, {name}!"
+
+@app.get("/", response_class=HTMLResponse)
+async def homepage():
+ return "Welcome to my API
"
+
+@app.get("/health")
+async def health():
+ return {"status": "ok"}
+
+app.launch()
+```
+
+Your custom routes take priority over Gradio's default routes. For example, your `GET /` replaces Gradio's default UI page.
+
+You can also use all standard FastAPI features — `app.add_middleware()`, `app.include_router()`, dependency injection, exception handlers, and so on.
+
+## MCP Tools
+
+To expose your API endpoints as MCP tools, add the `@app.mcp.tool()` decorator and pass `mcp_server=True` to `launch()`:
+
+```python
+from gradio import Server
+
+app = Server()
+
+@app.mcp.tool(name="hello")
+@app.api(name="hello")
+def hello(name: str) -> str:
+ """Greet someone by name."""
+ return f"Hello, {name}!"
+
+app.launch(mcp_server=True)
+```
+
+The `@app.mcp.tool()` and `@app.api()` decorators are independent — you can have API-only endpoints or MCP-only tools. Stack both when you want a function available through both.
+
+## A Complete Example with the JavaScript Client
+
+This example combines everything: custom HTML served at `/`, Gradio API endpoints with concurrency limits, MCP tools, and a custom REST endpoint, and two connected via [the Gradio JavaScript client](/guides/getting-started-with-the-js-client).
+
+$code_server_app
+
+Run it with:
+
+```bash
+python run.py
+```
+
+Then open `http://localhost:7860` in your browser. The custom HTML page uses the `@gradio/client` JavaScript library to call the Gradio API endpoints. Meanwhile, the same endpoints are available as MCP tools and through the REST API at `/gradio_api/call/add` and `/gradio_api/call/multiply`.
+
+Note: if your `Server` app uses ZeroGPU, you _must_ call Gradio API endpoints through `@gradio/client` from the browser. The JavaScript client forwards the Hugging Face iframe auth headers needed for ZeroGPU quota handling.
+
+## Concurrency and Streaming
+
+`app.api()` supports all of the same concurrency and streaming options as `gr.api()`:
+
+```python
+@app.api(name="generate", concurrency_limit=2, stream_every=0.5)
+async def generate(prompt: str):
+ for token in model.generate(prompt):
+ yield token
+```
+
+Generator functions automatically stream results via SSE, just like in a regular Gradio app. The `concurrency_limit` parameter controls how many concurrent calls to this endpoint are allowed. By default, this is set to 1, since many ML workloads that run on GPU can only support a single user at a time. However, you can increase this, or set to `None` to use FastAPI defaults, if you are e.g. calling an external API.
+
+For the full API reference, see the [`Server` documentation](/docs/gradio/server).
diff --git a/guides/10_mcp/01_building-mcp-server-with-gradio.md b/guides/10_mcp/01_building-mcp-server-with-gradio.md
new file mode 100644
index 0000000..4472f47
--- /dev/null
+++ b/guides/10_mcp/01_building-mcp-server-with-gradio.md
@@ -0,0 +1,431 @@
+# Building an MCP Server with Gradio
+
+Tags: MCP, TOOL, LLM, SERVER
+
+In this guide, we will describe how to launch your Gradio app so that it functions as an MCP Server.
+
+Punchline: it's as simple as setting `mcp_server=True` in `.launch()`.
+
+### Prerequisites
+
+If not already installed, please install Gradio with the MCP extra:
+
+```bash
+pip install "gradio[mcp]"
+```
+
+This will install the necessary dependencies, including the `mcp` package. Also, you will need an LLM application that supports tool calling using the MCP protocol, such as Claude Desktop, Cursor, or Cline (these are known as "MCP Clients").
+
+## What is an MCP Server?
+
+An MCP (Model Control Protocol) server is a standardized way to expose tools so that they can be used by LLMs. A tool can provide an LLM functionality that it does not have natively, such as the ability to generate images or calculate the prime factors of a number.
+
+## Example: Counting Letters in a Word
+
+LLMs are famously not great at counting the number of letters in a word (e.g. the number of "r"-s in "strawberry"). But what if we equip them with a tool to help? Let's start by writing a simple Gradio app that counts the number of letters in a word or phrase:
+
+$code_letter_counter
+
+Notice that we have: (1) included a detailed docstring for our function, and (2) set `mcp_server=True` in `.launch()`. This is all that's needed for your Gradio app to serve as an MCP server! Now, when you run this app, it will:
+
+1. Start the regular Gradio web interface
+2. Start the MCP server
+3. Print the MCP server URL in the console
+
+The MCP server will be accessible at:
+```
+http://your-server:port/gradio_api/mcp/
+```
+
+Gradio automatically converts the `letter_counter` function into an MCP tool that can be used by LLMs. The docstring of the function and the type hints of arguments will be used to generate the description of the tool and its parameters. The name of the function will be used as the name of your tool. Any initial values you provide to your input components (e.g. "strawberry" and "r" in the `gr.Textbox` components above) will be used as the default values if your LLM doesn't specify a value for that particular input parameter.
+
+Now, all you need to do is add this URL endpoint to your MCP Client (e.g. Claude Desktop, Cursor, or Cline), which typically means pasting this config in the settings:
+
+```
+{
+ "mcpServers": {
+ "gradio": {
+ "url": "http://your-server:port/gradio_api/mcp/"
+ }
+ }
+}
+```
+
+(By the way, you can find the exact config to copy-paste by going to the "View API" link in the footer of your Gradio app, and then clicking on "MCP").
+
+
+
+## Key features of the Gradio <> MCP Integration
+
+1. **Tool Conversion**: Each API endpoint in your Gradio app is automatically converted into an MCP tool with a corresponding name, description, and input schema. To view the tools and schemas, visit http://your-server:port/gradio_api/mcp/schema or go to the "View API" link in the footer of your Gradio app, and then click on "MCP".
+
+
+2. **Environment variable support**. There are two ways to enable the MCP server functionality:
+
+* Using the `mcp_server` parameter, as shown above:
+ ```python
+ demo.launch(mcp_server=True)
+ ```
+
+* Using environment variables:
+ ```bash
+ export GRADIO_MCP_SERVER=True
+ ```
+
+3. **File Handling**: The Gradio MCP server automatically handles file data conversions, including:
+ - Processing image files and returning them in the correct format
+ - Managing temporary file storage
+
+ By default, the Gradio MCP server accepts input images and files as full URLs ("http://..." or "https:/..."). For convenience, an additional STDIO-based MCP server is also generated, which can be used to upload files to any remote Gradio app and which returns a URL that can be used for subsequent tool calls.
+
+4. **Hosted MCP Servers on 🤗 Spaces**: You can publish your Gradio application for free on Hugging Face Spaces, which will allow you to have a free hosted MCP server. Here's an example of such a Space: https://huggingface.co/spaces/abidlabs/mcp-tools. Notice that you can add this config to your MCP Client to start using the tools from this Space immediately:
+
+```
+{
+ "mcpServers": {
+ "gradio": {
+ "url": "https://abidlabs-mcp-tools.hf.space/gradio_api/mcp/"
+ }
+ }
+}
+```
+
+
+
+Tip: To minimize latency and increase throughput by as much as 10 times, set queue=False in the event handlers of your Gradio app. However, this disables progress notifications so its recommended that long running events set queue=True
+
+## Converting an Existing Space
+
+If there's an existing Space that you'd like to use an MCP server, you'll need to do three things:
+
+1. First, [duplicate the Space](https://huggingface.co/docs/hub/en/spaces-more-ways-to-create#duplicating-a-space) if it is not your own Space. This will allow you to make changes to the app. If the Space requires a GPU, set the hardware of the duplicated Space to be same as the original Space. You can make it either a public Space or a private Space, since it is possible to use either as an MCP server, as described below.
+2. Then, add docstrings to the functions that you'd like the LLM to be able to call as a tool. The docstring should be in the same format as the example code above.
+3. Finally, add `mcp_server=True` in `.launch()`.
+
+That's it!
+
+## Private Spaces
+
+You can use either a public Space or a private Space as an MCP server. If you'd like to use a private Space as an MCP server (or a ZeroGPU Space with your own quota), then you will need to provide your [Hugging Face token](https://huggingface.co/settings/token) when you make your request. To do this, simply add it as a header in your config like this:
+
+```
+{
+ "mcpServers": {
+ "gradio": {
+ "url": "https://abidlabs-mcp-tools.hf.space/gradio_api/mcp/",
+ "headers": {
+ "Authorization": "Bearer "
+ }
+ }
+ }
+}
+```
+
+## Authentication and Credentials
+
+You may wish to authenticate users more precisely or let them provide other kinds of credentials or tokens in order to provide a custom experience for different users.
+
+Gradio allows you to access the underlying `starlette.Request` that has made the tool call, which means that you can access headers, originating IP address, or any other information that is part of the network request. To do this, simply add a parameter in your function of the type `gr.Request`, and Gradio will automatically inject the request object as the parameter.
+
+Here's an example:
+
+```py
+import gradio as gr
+
+def echo_headers(x, request: gr.Request):
+ return str(dict(request.headers))
+
+gr.Interface(echo_headers, "textbox", "textbox").launch(mcp_server=True)
+```
+
+This MCP server will simply ignore the user's input and echo back all of the headers from a user's request. One can build more complex apps using the same idea. See the [docs on `gr.Request`](https://www.gradio.app/main/docs/gradio/request) for more information (note that only the core Starlette attributes of the `gr.Request` object will be present, attributes such as Gradio's `.session_hash` will not be present).
+
+### Using the gr.Header class
+
+A common pattern in MCP server development is to use authentication headers to call services on behalf of your users. Instead of using a `gr.Request` object like in the example above, you can use a `gr.Header` argument. Gradio will automatically extract that header from the incoming request (if it exists) and pass it to your function.
+
+In the example below, the `X-API-Token` header is extracted from the incoming request and passed in as the `x_api_token` argument to `make_api_request_on_behalf_of_user`.
+
+The benefit of using `gr.Header` is that the MCP connection docs will automatically display the headers you need to supply when connecting to the server! See the image below:
+
+```python
+import gradio as gr
+
+def make_api_request_on_behalf_of_user(prompt: str, x_api_token: gr.Header):
+ """Make a request to everyone's favorite API.
+ Args:
+ prompt: The prompt to send to the API.
+ Returns:
+ The response from the API.
+ Raises:
+ AssertionError: If the API token is not valid.
+ """
+ return "Hello from the API" if not x_api_token else "Hello from the API with token!"
+
+
+demo = gr.Interface(
+ make_api_request_on_behalf_of_user,
+ [
+ gr.Textbox(label="Prompt"),
+ ],
+ gr.Textbox(label="Response"),
+)
+
+demo.launch(mcp_server=True)
+```
+
+
+
+### Sending Progress Updates
+
+The Gradio MCP server automatically sends progress updates to your MCP Client based on the queue in the Gradio application. If you'd like to send custom progress updates, you can do so using the same mechanism as you would use to display progress updates in the UI of your Gradio app: by using the `gr.Progress` class!
+
+Here's an example of how to do this:
+
+$code_mcp_progress
+
+[Here are the docs](https://www.gradio.app/docs/gradio/progress) for the `gr.Progress` class, which can also automatically track `tqdm` calls.
+
+Note: by default, progress notifications are enabled for all MCP tools, even if the corresponding Gradio functions do not include a `gr.Progress`. However, this can add some overhead to the MCP tool (typically ~500ms). To disable progress notification, you can set `queue=False` in your Gradio event handler to skip the overhead related to subscribing to the queue's progress updates.
+
+
+## Modifying Tool Descriptions
+
+Gradio automatically sets the tool name based on the name of your function, and the description from the docstring of your function. But you may want to change how the description appears to your LLM. You can do this by using the `api_description` parameter in `Interface`, `ChatInterface`, or any event listener. This parameter takes three different kinds of values:
+
+* `None` (default): the tool description is automatically created from the docstring of the function (or its parent's docstring if it does not have a docstring but inherits from a method that does.)
+* `False`: no tool description appears to the LLM.
+* `str`: an arbitrary string to use as the tool description.
+
+In addition to modifying the tool descriptions, you can also toggle which tools appear to the LLM. You can do this by setting the `show_api` parameter, which is by default `True`. Setting it to `False` hides the endpoint from the API docs and from the MCP server. If you expose multiple tools, users of your app will also be able to toggle which tools they'd like to add to their MCP server by checking boxes in the "view MCP or API" panel.
+
+Here's an example that shows the `api_description` and `show_api` parameters in actions:
+
+$code_mcp_tools
+
+
+
+## MCP Resources and Prompts
+
+In addition to tools (which execute functions generally and are the default for any function exposed through the Gradio MCP integration), MCP supports two other important primitives: **resources** (for exposing data) and **prompts** (for defining reusable templates). Gradio provides decorators to easily create MCP servers with all three capabilities.
+
+
+### Creating MCP Resources
+
+Use the `@gr.mcp.resource` decorator on any function to expose data through your Gradio app. Resources can be static (always available at a fixed URI) or templated (with parameters in the URI).
+
+$code_mcp_resources_and_prompts
+
+In this example:
+- The `get_greeting` function is exposed as a resource with a URI template `greeting://{name}`
+- When an MCP client requests `greeting://Alice`, it receives "Hello, Alice!"
+- Resources can also return images and other types of files or binary data. In order to return non-text data, you should specify the `mime_type` parameter in `@gr.mcp.resource()` and return a Base64 string from your function.
+
+### Creating MCP Prompts
+
+Prompts help standardize how users interact with your tools. They're especially useful for complex workflows that require specific formatting or multiple steps.
+
+The `greet_user` function in the example above is decorated with `@gr.mcp.prompt()`, which:
+- Makes it available as a prompt template in MCP clients
+- Accepts parameters (`name` and `style`) to customize the output
+- Returns a structured prompt that guides the LLM's behavior
+
+
+## Adding MCP-Only Functions
+
+So far, all of our MCP tools, resources, or prompts have corresponded to event listeners in the UI. This works well for functions that directly update the UI, but may not work if you wish to expose a "pure logic" function that should return raw data (e.g. a JSON object) without directly causing a UI update.
+
+In order to expose such an MCP tool, you can create a pure Gradio API endpoint using `gr.api` (see [full docs here](https://www.gradio.app/main/docs/gradio/api)). Here's an example of creating an MCP tool that slices a list:
+
+$code_mcp_tool_only
+
+Note that if you use this approach, your function signature must be fully typed, including the return value, as these signature are used to determine the typing information for the MCP tool.
+
+## Gradio with FastMCP
+
+In some cases, you may decide not to use Gradio's built-in integration and instead manually create an FastMCP Server that calls a Gradio app. This approach is useful when you want to:
+
+- Store state / identify users between calls instead of treating every tool call completely independently
+- Start the Gradio app MCP server when a tool is called (if you are running multiple Gradio apps locally and want to save memory / GPU)
+
+This is very doable thanks to the [Gradio Python Client](https://www.gradio.app/guides/getting-started-with-the-python-client) and the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)'s `FastMCP` class. Here's an example of creating a custom MCP server that connects to various Gradio apps hosted on [HuggingFace Spaces](https://huggingface.co/spaces) using the `stdio` protocol:
+
+```python
+from mcp.server.fastmcp import FastMCP
+from gradio_client import Client
+import sys
+import io
+import json
+
+mcp = FastMCP("gradio-spaces")
+
+clients = {}
+
+def get_client(space_id: str) -> Client:
+ """Get or create a Gradio client for the specified space."""
+ if space_id not in clients:
+ clients[space_id] = Client(space_id)
+ return clients[space_id]
+
+
+@mcp.tool()
+async def generate_image(prompt: str, space_id: str = "ysharma/SanaSprint") -> str:
+ """Generate an image using Flux.
+
+ Args:
+ prompt: Text prompt describing the image to generate
+ space_id: HuggingFace Space ID to use
+ """
+ client = get_client(space_id)
+ result = client.predict(
+ prompt=prompt,
+ model_size="1.6B",
+ seed=0,
+ randomize_seed=True,
+ width=1024,
+ height=1024,
+ guidance_scale=4.5,
+ num_inference_steps=2,
+ api_name="/infer"
+ )
+ return result
+
+
+@mcp.tool()
+async def run_dia_tts(prompt: str, space_id: str = "ysharma/Dia-1.6B") -> str:
+ """Text-to-Speech Synthesis.
+
+ Args:
+ prompt: Text prompt describing the conversation between speakers S1, S2
+ space_id: HuggingFace Space ID to use
+ """
+ client = get_client(space_id)
+ result = client.predict(
+ text_input=f"""{prompt}""",
+ audio_prompt_input=None,
+ max_new_tokens=3072,
+ cfg_scale=3,
+ temperature=1.3,
+ top_p=0.95,
+ cfg_filter_top_k=30,
+ speed_factor=0.94,
+ api_name="/generate_audio"
+ )
+ return result
+
+
+if __name__ == "__main__":
+ import sys
+ import io
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
+
+ mcp.run(transport='stdio')
+```
+
+This server exposes two tools:
+1. `run_dia_tts` - Generates a conversation for the given transcript in the form of `[S1]first-sentence. [S2]second-sentence. [S1]...`
+2. `generate_image` - Generates images using a fast text-to-image model
+
+To use this MCP Server with Claude Desktop (as MCP Client):
+
+1. Save the code to a file (e.g., `gradio_mcp_server.py`)
+2. Install the required dependencies: `pip install mcp gradio-client`
+3. Configure Claude Desktop to use your server by editing the configuration file at `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
+
+```json
+{
+ "mcpServers": {
+ "gradio-spaces": {
+ "command": "python",
+ "args": [
+ "/absolute/path/to/gradio_mcp_server.py"
+ ]
+ }
+ }
+}
+```
+
+4. Restart Claude Desktop
+
+Now, when you ask Claude about generating an image or transcribing audio, it can use your Gradio-powered tools to accomplish these tasks.
+
+
+## Troubleshooting your MCP Servers
+
+The MCP protocol is still in its infancy and you might see issues connecting to an MCP Server that you've built. We generally recommend using the [MCP Inspector Tool](https://github.com/modelcontextprotocol/inspector) to try connecting and debugging your MCP Server.
+
+Here are some things that may help:
+
+**1. Ensure that you've provided type hints and valid docstrings for your functions**
+
+As mentioned earlier, Gradio reads the docstrings for your functions and the type hints of input arguments to generate the description of the tool and parameters. A valid function and docstring looks like this (note the "Args:" block with indented parameter names underneath):
+
+```py
+def image_orientation(image: Image.Image) -> str:
+ """
+ Returns whether image is portrait or landscape.
+
+ Args:
+ image (Image.Image): The image to check.
+ """
+ return "Portrait" if image.height > image.width else "Landscape"
+```
+
+Note: You can preview the schema that is created for your MCP server by visiting the `http://your-server:port/gradio_api/mcp/schema` URL.
+
+**2. Try accepting input arguments as `str`**
+
+Some MCP Clients do not recognize parameters that are numeric or other complex types, but all of the MCP Clients that we've tested accept `str` input parameters. When in doubt, change your input parameter to be a `str` and then cast to a specific type in the function, as in this example:
+
+```py
+def prime_factors(n: str):
+ """
+ Compute the prime factorization of a positive integer.
+
+ Args:
+ n (str): The integer to factorize. Must be greater than 1.
+ """
+ n_int = int(n)
+ if n_int <= 1:
+ raise ValueError("Input must be an integer greater than 1.")
+
+ factors = []
+ while n_int % 2 == 0:
+ factors.append(2)
+ n_int //= 2
+
+ divisor = 3
+ while divisor * divisor <= n_int:
+ while n_int % divisor == 0:
+ factors.append(divisor)
+ n_int //= divisor
+ divisor += 2
+
+ if n_int > 1:
+ factors.append(n_int)
+
+ return factors
+```
+
+**3. Ensure that your MCP Client Supports Streamable HTTP**
+
+Some MCP Clients do not yet support streamable HTTP-based MCP Servers. In those cases, you can use a tool such as [mcp-remote](https://github.com/geelen/mcp-remote). First install [Node.js](https://nodejs.org/en/download/). Then, add the following to your own MCP Client config:
+
+```
+{
+ "mcpServers": {
+ "gradio": {
+ "command": "npx",
+ "args": [
+ "mcp-remote",
+ "http://your-server:port/gradio_api/mcp/"
+ ]
+ }
+ }
+}
+```
+
+**4. Restart your MCP Client and MCP Server**
+
+Some MCP Clients require you to restart them every time you update the MCP configuration. Other times, if the connection between the MCP Client and servers breaks, you might need to restart the MCP server. If all else fails, try restarting both your MCP Client and MCP Servers!
+
diff --git a/guides/10_mcp/02_file-upload-mcp.md b/guides/10_mcp/02_file-upload-mcp.md
new file mode 100644
index 0000000..e47741c
--- /dev/null
+++ b/guides/10_mcp/02_file-upload-mcp.md
@@ -0,0 +1,48 @@
+# The File Upload MCP Server
+
+Tags: MCP, TOOL, LLM, SERVER, DOCS
+
+If you've tried to to use a remote Gradio MCP server that takes a file as input (image, video, audio), you've probably run into this error:
+
+
+
+The reason is that since the Gradio server is hosted on a different machine, any input files must be available via a public URL so that they can downloaded in the remote machine.
+
+There are many ways to host files on the internet, but they all require adding a manual step to your workflow. In the age of LLM agents, shouldn't we expect them to handle this step for you?
+
+In this post, we'll show how you can connect your LLM to the "File Upload" MCP server so that it can handle the file uploading for you when appropriate!
+
+## Using the File Upload MCP Server
+
+As of version 5.36.0, Gradio now comes with a built-in MCP server that can upload files to a running Gradio application. In the `View API` page of the server, you should see the following code snippet if any of the tools require file inputs:
+
+
+
+The command to start the MCP server takes two arguments:
+
+- The URL (or Hugging Face space id) of the gradio application to upload the files to. In this case, `http://127.0.0.1:7860`.
+- The local directory on your computer with which the server is allowed to upload files from (``). For security, please make this directory as narrow as possible to prevent unintended file uploads.
+
+As stated in the image, you need to install [uv](https://docs.astral.sh/uv/getting-started/installation/) (a python package manager that can run python scripts) before connecting from your MCP client.
+
+If you have gradio installed locally and you don't want to install uv, you can replace the `uvx` command with the path to gradio binary. It should look like this:
+
+```json
+"upload-files": {
+ "command": "",
+ "args": [
+ "upload-mcp",
+ "http://localhost:7860/",
+ "/Users/freddyboulton/Pictures"
+ ]
+}
+```
+
+After connecting to the upload server, your LLM agent will know when to upload files for you automatically!
+
+
+
+## Conclusion
+
+In this guide, we've covered how you can connect to the Upload File MCP Server so that your agent can upload files before using Gradio MCP servers. Remember to set the `` as small as possible to prevent unintended file uploads!
+
diff --git a/guides/10_mcp/03_building-an-mcp-client-with-gradio.md b/guides/10_mcp/03_building-an-mcp-client-with-gradio.md
new file mode 100644
index 0000000..cda3e22
--- /dev/null
+++ b/guides/10_mcp/03_building-an-mcp-client-with-gradio.md
@@ -0,0 +1,415 @@
+# Using the Gradio Chatbot as an MCP Client
+
+This guide will walk you through a Model Context Protocol (MCP) Client and Server implementation with Gradio. You'll build a Gradio Chatbot that uses Anthropic's Claude API to respond to user messages, but also, as an MCP Client, generates images (by connecting to an MCP Server, which is a separate Gradio app).
+
+
+
+## What is MCP?
+
+The Model Context Protocol (MCP) standardizes how applications provide context to LLMs. It allows Claude to interact with external tools, like image generators, file systems, or APIs, etc.
+
+## Prerequisites
+
+- Python 3.10+
+- An Anthropic API key
+- Basic understanding of Python programming
+
+## Setup
+
+First, install the required packages:
+
+```bash
+pip install gradio anthropic mcp
+```
+
+Create a `.env` file in your project directory and add your Anthropic API key:
+
+```
+ANTHROPIC_API_KEY=your_api_key_here
+```
+
+## Part 1: Building the MCP Server
+
+The server provides tools that Claude can use. In this example, we'll create a server that generates images through [a HuggingFace space](https://huggingface.co/spaces/ysharma/SanaSprint).
+
+Create a file named `gradio_mcp_server.py`:
+
+```python
+from mcp.server.fastmcp import FastMCP
+import json
+import sys
+import io
+import time
+from gradio_client import Client
+
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
+
+mcp = FastMCP("huggingface_spaces_image_display")
+
+@mcp.tool()
+async def generate_image(prompt: str, width: int = 512, height: int = 512) -> str:
+ """Generate an image using SanaSprint model.
+
+ Args:
+ prompt: Text prompt describing the image to generate
+ width: Image width (default: 512)
+ height: Image height (default: 512)
+ """
+ client = Client("https://ysharma-sanasprint.hf.space/")
+
+ try:
+ result = client.predict(
+ prompt,
+ "0.6B",
+ 0,
+ True,
+ width,
+ height,
+ 4.0,
+ 2,
+ api_name="/infer"
+ )
+
+ if isinstance(result, list) and len(result) >= 1:
+ image_data = result[0]
+ if isinstance(image_data, dict) and "url" in image_data:
+ return json.dumps({
+ "type": "image",
+ "url": image_data["url"],
+ "message": f"Generated image for prompt: {prompt}"
+ })
+
+ return json.dumps({
+ "type": "error",
+ "message": "Failed to generate image"
+ })
+
+ except Exception as e:
+ return json.dumps({
+ "type": "error",
+ "message": f"Error generating image: {str(e)}"
+ })
+
+if __name__ == "__main__":
+ mcp.run(transport='stdio')
+```
+
+### What this server does:
+
+1. It creates an MCP server that exposes a `generate_image` tool
+2. The tool connects to the SanaSprint model hosted on HuggingFace Spaces
+3. It handles the asynchronous nature of image generation by polling for results
+4. When an image is ready, it returns the URL in a structured JSON format
+
+## Part 2: Building the MCP Client with Gradio
+
+Now let's create a Gradio chat interface as MCP Client that connects Claude to our MCP server.
+
+Create a file named `app.py`:
+
+```python
+import asyncio
+import os
+import json
+from typing import List, Dict, Any, Union
+from contextlib import AsyncExitStack
+
+import gradio as gr
+from gradio.components.chatbot import ChatMessage
+from mcp import ClientSession, StdioServerParameters
+from mcp.client.stdio import stdio_client
+from anthropic import Anthropic
+from dotenv import load_dotenv
+
+load_dotenv()
+
+loop = asyncio.new_event_loop()
+asyncio.set_event_loop(loop)
+
+class MCPClientWrapper:
+ def __init__(self):
+ self.session = None
+ self.exit_stack = None
+ self.anthropic = Anthropic()
+ self.tools = []
+
+ def connect(self, server_path: str) -> str:
+ return loop.run_until_complete(self._connect(server_path))
+
+ async def _connect(self, server_path: str) -> str:
+ if self.exit_stack:
+ await self.exit_stack.aclose()
+
+ self.exit_stack = AsyncExitStack()
+
+ is_python = server_path.endswith('.py')
+ command = "python" if is_python else "node"
+
+ server_params = StdioServerParameters(
+ command=command,
+ args=[server_path],
+ env={"PYTHONIOENCODING": "utf-8", "PYTHONUNBUFFERED": "1"}
+ )
+
+ stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
+ self.stdio, self.write = stdio_transport
+
+ self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
+ await self.session.initialize()
+
+ response = await self.session.list_tools()
+ self.tools = [{
+ "name": tool.name,
+ "description": tool.description,
+ "input_schema": tool.inputSchema
+ } for tool in response.tools]
+
+ tool_names = [tool["name"] for tool in self.tools]
+ return f"Connected to MCP server. Available tools: {', '.join(tool_names)}"
+
+ def process_message(self, message: str, history: List[Union[Dict[str, Any], ChatMessage]]) -> tuple:
+ if not self.session:
+ return history + [
+ {"role": "user", "content": message},
+ {"role": "assistant", "content": "Please connect to an MCP server first."}
+ ], gr.Textbox(value="")
+
+ new_messages = loop.run_until_complete(self._process_query(message, history))
+ return history + [{"role": "user", "content": message}] + new_messages, gr.Textbox(value="")
+
+ async def _process_query(self, message: str, history: List[Union[Dict[str, Any], ChatMessage]]):
+ claude_messages = []
+ for msg in history:
+ if isinstance(msg, ChatMessage):
+ role, content = msg.role, msg.content
+ else:
+ role, content = msg.get("role"), msg.get("content")
+
+ if role in ["user", "assistant", "system"]:
+ claude_messages.append({"role": role, "content": content})
+
+ claude_messages.append({"role": "user", "content": message})
+
+ response = self.anthropic.messages.create(
+ model="claude-3-5-sonnet-20241022",
+ max_tokens=1000,
+ messages=claude_messages,
+ tools=self.tools
+ )
+
+ result_messages = []
+
+ for content in response.content:
+ if content.type == 'text':
+ result_messages.append({
+ "role": "assistant",
+ "content": content.text
+ })
+
+ elif content.type == 'tool_use':
+ tool_name = content.name
+ tool_args = content.input
+
+ result_messages.append({
+ "role": "assistant",
+ "content": f"I'll use the {tool_name} tool to help answer your question.",
+ "metadata": {
+ "title": f"Using tool: {tool_name}",
+ "log": f"Parameters: {json.dumps(tool_args, ensure_ascii=True)}",
+ "status": "pending",
+ "id": f"tool_call_{tool_name}"
+ }
+ })
+
+ result_messages.append({
+ "role": "assistant",
+ "content": "```json\n" + json.dumps(tool_args, indent=2, ensure_ascii=True) + "\n```",
+ "metadata": {
+ "parent_id": f"tool_call_{tool_name}",
+ "id": f"params_{tool_name}",
+ "title": "Tool Parameters"
+ }
+ })
+
+ result = await self.session.call_tool(tool_name, tool_args)
+
+ if result_messages and "metadata" in result_messages[-2]:
+ result_messages[-2]["metadata"]["status"] = "done"
+
+ result_messages.append({
+ "role": "assistant",
+ "content": "Here are the results from the tool:",
+ "metadata": {
+ "title": f"Tool Result for {tool_name}",
+ "status": "done",
+ "id": f"result_{tool_name}"
+ }
+ })
+
+ result_content = result.content
+ if isinstance(result_content, list):
+ result_content = "\n".join(str(item) for item in result_content)
+
+ try:
+ result_json = json.loads(result_content)
+ if isinstance(result_json, dict) and "type" in result_json:
+ if result_json["type"] == "image" and "url" in result_json:
+ result_messages.append({
+ "role": "assistant",
+ "content": {"path": result_json["url"], "alt_text": result_json.get("message", "Generated image")},
+ "metadata": {
+ "parent_id": f"result_{tool_name}",
+ "id": f"image_{tool_name}",
+ "title": "Generated Image"
+ }
+ })
+ else:
+ result_messages.append({
+ "role": "assistant",
+ "content": "```\n" + result_content + "\n```",
+ "metadata": {
+ "parent_id": f"result_{tool_name}",
+ "id": f"raw_result_{tool_name}",
+ "title": "Raw Output"
+ }
+ })
+ except:
+ result_messages.append({
+ "role": "assistant",
+ "content": "```\n" + result_content + "\n```",
+ "metadata": {
+ "parent_id": f"result_{tool_name}",
+ "id": f"raw_result_{tool_name}",
+ "title": "Raw Output"
+ }
+ })
+
+ claude_messages.append({"role": "user", "content": f"Tool result for {tool_name}: {result_content}"})
+ next_response = self.anthropic.messages.create(
+ model="claude-3-5-sonnet-20241022",
+ max_tokens=1000,
+ messages=claude_messages,
+ )
+
+ if next_response.content and next_response.content[0].type == 'text':
+ result_messages.append({
+ "role": "assistant",
+ "content": next_response.content[0].text
+ })
+
+ return result_messages
+
+client = MCPClientWrapper()
+
+def gradio_interface():
+ with gr.Blocks(title="MCP Weather Client") as demo:
+ gr.Markdown("# MCP Weather Assistant")
+ gr.Markdown("Connect to your MCP weather server and chat with the assistant")
+
+ with gr.Row(equal_height=True):
+ with gr.Column(scale=4):
+ server_path = gr.Textbox(
+ label="Server Script Path",
+ placeholder="Enter path to server script (e.g., weather.py)",
+ value="gradio_mcp_server.py"
+ )
+ with gr.Column(scale=1):
+ connect_btn = gr.Button("Connect")
+
+ status = gr.Textbox(label="Connection Status", interactive=False)
+
+ chatbot = gr.Chatbot(
+ value=[],
+ height=500,
+ show_copy_button=True,
+ avatar_images=("👤", "🤖")
+ )
+
+ with gr.Row(equal_height=True):
+ msg = gr.Textbox(
+ label="Your Question",
+ placeholder="Ask about weather or alerts (e.g., What's the weather in New York?)",
+ scale=4
+ )
+ clear_btn = gr.Button("Clear Chat", scale=1)
+
+ connect_btn.click(client.connect, inputs=server_path, outputs=status)
+ msg.submit(client.process_message, [msg, chatbot], [chatbot, msg])
+ clear_btn.click(lambda: [], None, chatbot)
+
+ return demo
+
+if __name__ == "__main__":
+ if not os.getenv("ANTHROPIC_API_KEY"):
+ print("Warning: ANTHROPIC_API_KEY not found in environment. Please set it in your .env file.")
+
+ interface = gradio_interface()
+ interface.launch(debug=True)
+```
+
+### What this MCP Client does:
+
+- Creates a friendly Gradio chat interface for user interaction
+- Connects to the MCP server you specify
+- Handles conversation history and message formatting
+- Makes call to Claude API with tool definitions
+- Processes tool usage requests from Claude
+- Displays images and other tool outputs in the chat
+- Sends tool results back to Claude for interpretation
+
+## Running the Application
+
+To run your MCP application:
+
+- Start a terminal window and run the MCP Client:
+ ```bash
+ python app.py
+ ```
+- Open the Gradio interface at the URL shown (typically http://127.0.0.1:7860)
+- In the Gradio interface, you'll see a field for the MCP Server path. It should default to `gradio_mcp_server.py`.
+- Click "Connect" to establish the connection to the MCP server.
+- You should see a message indicating the server connection was successful.
+
+## Example Usage
+
+Now you can chat with Claude and it will be able to generate images based on your descriptions.
+
+Try prompts like:
+- "Can you generate an image of a mountain landscape at sunset?"
+- "Create an image of a cool tabby cat"
+- "Generate a picture of a panda wearing sunglasses"
+
+Claude will recognize these as image generation requests and automatically use the `generate_image` tool from your MCP server.
+
+
+## How it Works
+
+Here's the high-level flow of what happens during a chat session:
+
+1. Your prompt enters the Gradio interface
+2. The client forwards your prompt to Claude
+3. Claude analyzes the prompt and decides to use the `generate_image` tool
+4. The client sends the tool call to the MCP server
+5. The server calls the external image generation API
+6. The image URL is returned to the client
+7. The client sends the image URL back to Claude
+8. Claude provides a response that references the generated image
+9. The Gradio chat interface displays both Claude's response and the image
+
+
+## Next Steps
+
+Now that you have a working MCP system, here are some ideas to extend it:
+
+- Add more tools to your server
+- Improve error handling
+- Add private Huggingface Spaces with authentication for secure tool access
+- Create custom tools that connect to your own APIs or services
+- Implement streaming responses for better user experience
+
+## Conclusion
+
+Congratulations! You've successfully built an MCP Client and Server that allows Claude to generate images based on text prompts. This is just the beginning of what you can do with Gradio and MCP. This guide enables you to build complex AI applications that can use Claude or any other powerful LLM to interact with virtually any external tool or service.
+
+Read our other Guide on using [Gradio apps as MCP Servers](./building-mcp-server-with-gradio).
diff --git a/guides/10_mcp/04_using-docs-mcp.md b/guides/10_mcp/04_using-docs-mcp.md
new file mode 100644
index 0000000..07f0cfd
--- /dev/null
+++ b/guides/10_mcp/04_using-docs-mcp.md
@@ -0,0 +1,83 @@
+# Using the Gradio Docs MCP Server
+
+Tags: MCP, TOOL, LLM, SERVER, DOCS
+
+In this guide, we will describe how to use the official Gradio Docs MCP Server.
+
+### Prerequisites
+
+You will need an LLM application that supports tool calling using the MCP protocol, such as Claude Desktop, Cursor, or Cline (these are known as "MCP Clients").
+
+## Why an MCP Server?
+
+If you're using LLMs in your workflow, adding this server will augment them with just the right context on gradio - which makes your experience a lot faster and smoother.
+
+
+
+The server is running on Spaces and was launched entirely using Gradio, you can see all the code [here](https://huggingface.co/spaces/gradio/docs-mcp). For more on building an mcp server with gradio, see the [previous guide](./building-an-mcp-client-with-gradio).
+
+## Installing in the Clients
+
+For clients that support streamable HTTP (e.g. Cursor, Windsurf, Cline), simply add the following configuration to your MCP config:
+
+```json
+{
+ "mcpServers": {
+ "gradio": {
+ "url": "https://gradio-docs-mcp.hf.space/gradio_api/mcp/"
+ }
+ }
+}
+```
+
+We've included step-by-step instructions for Cursor below, but you can consult the docs for Windsurf [here](https://docs.windsurf.com/windsurf/mcp), and Cline [here](https://docs.cline.bot/mcp-servers/configuring-mcp-servers) which are similar to set up.
+
+
+
+### Cursor
+
+1. Make sure you're using the latest version of Cursor, and go to Cursor > Settings > Cursor Settings > MCP
+2. Click on '+ Add new global MCP server'
+3. Copy paste this json into the file that opens and then save it.
+```json
+{
+ "mcpServers": {
+ "gradio": {
+ "url": "https://gradio-docs-mcp.hf.space/gradio_api/mcp/"
+ }
+ }
+}
+```
+4. That's it! You should see the tools load and the status go green in the settings page. You may have to click the refresh icon or wait a few seconds.
+
+
+
+### Claude Desktop
+
+1. Since Claude Desktop only supports stdio, you will need to [install Node.js](https://nodejs.org/en/download/) to get this to work.
+2. Make sure you're using the latest version of Claude Desktop, and go to Claude > Settings > Developer > Edit Config
+3. Open the file with your favorite editor and copy paste this json, then save the file.
+```json
+{
+ "mcpServers": {
+ "gradio": {
+ "command": "npx",
+ "args": [
+ "mcp-remote",
+ "https://gradio-docs-mcp.hf.space/gradio_api/mcp/"
+ ]
+ }
+ }
+}
+```
+4. Quit and re-open Claude Desktop, and you should be good to go. You should see it loaded in the Search and Tools icon or on the developer settings page.
+
+
+
+## Tools
+
+There are currently only two tools in the server: `gradio_docs_mcp_load_gradio_docs` and `gradio_docs_mcp_search_gradio_docs`.
+
+1. `gradio_docs_mcp_load_gradio_docs`: This tool takes no arguments and will load an /llms.txt style summary of Gradio's latest, full documentation. Very useful context the LLM can parse before answering questions or generating code.
+
+2. `gradio_docs_mcp_search_gradio_docs`: This tool takes a query as an argument and will run embedding search on Gradio's docs, guides, and demos to return the most useful context for the LLM to parse.
\ No newline at end of file
diff --git a/guides/10_mcp/05_building-chatgpt-apps-with-gradio.md b/guides/10_mcp/05_building-chatgpt-apps-with-gradio.md
new file mode 100644
index 0000000..a75893d
--- /dev/null
+++ b/guides/10_mcp/05_building-chatgpt-apps-with-gradio.md
@@ -0,0 +1,210 @@
+# Building ChatGPT Apps with Gradio and Apps SDK
+
+[Apps in ChatGPT](https://openai.com/index/introducing-apps-in-chatgpt/) are a great way to let users try your machine learning models or other kinds of apps entirely by chatting in familiar chat application. OpenAI has released the [Apps SDK](https://developers.openai.com/apps-sdk/quickstart) for developers to build complete applications, but you can use Gradio to build ChatGPT apps very quickly, based off of your Gradio MCP server. We will also see how Gradio's built-in [share links](https://www.gradio.app/guides/sharing-your-app#sharing-demos) make it especially easy to iterate on your ChatGPT app!
+
+### Introduction
+
+Building a ChatGPT app requires doing two things:
+
+* Building a Gradio MCP server with at least one tool exposed. If you're not already familiar with building a Gradio MCP server, we recommend reading [this guide first](https://www.gradio.app/guides/building-mcp-server-with-gradio).
+
+* Building a custom UI with HTML, JavaScript, and CSS that will be displayed when your tool is called, an exposing that as an MCP resource.
+
+We will walk through the steps in more detail below.
+
+### Prerequisites
+
+* You will need to enable "developer mode" in ChatGPT under Settings → Apps & Connectors → Advanced settings in ChatGPT. This currently requires a paid ChatGPT account.
+* You need to have `gradio>=6.0` installed with the `mcp` add-on:
+
+```bash
+pip install --upgrade gradio[mcp]
+```
+
+Now, let's walk through two examples of how you can build build ChatGPT apps with Gradio.
+
+### Example 1: Letter Counter App
+
+The first example is an ChatGPT app that counts the occurrence of letters in a word and displays a card with the word and specified letters highlighted, like this:
+
+
+
+
+So how do we build this? You can find the complete code for the letter counter app [in a single file here](https://github.com/gradio-app/gradio/blob/main/demo/mcp_letter_counter_app/run.py), or follow the steps below:
+
+1. Start by writing your Python function. In our case, the function is simply a letter counter:
+
+```py
+def letter_counter(word: str, letter: str) -> int:
+ """
+ Count the number of letters in a word or phrase.
+
+ Parameters:
+ word (str): The word or phrase to count the letters of.
+ letter (str): The letter to count the occurrences of.
+ """
+ return word.count(letter)
+```
+
+2. Then, wrap your Python function with a Gradio UI, something along these lines:
+
+```py
+with gr.Blocks() as demo:
+ with gr.Row():
+ with gr.Column():
+ word = gr.Textbox(label="Word")
+ letter = gr.Textbox(label="Letter")
+ btn = gr.Button("Count Letters")
+ with gr.Column():
+ count = gr.Number(label="Count")
+
+ btn.click(letter_counter, inputs=[word, letter], outputs=count)
+```
+
+3. Now, launch your Gradio app with the MCP server enabled, i.e. with `mcp_server=True`
+
+```py
+ demo.launch(mcp_server=True)
+```
+
+As covered in [earlier guides](https://www.gradio.app/guides/building-mcp-server-with-gradio), you will now be able to test the tool using any MCP Client, such as the MCP Inspector tool. Test it and confirm that it behaves as you expect.
+
+4. Create a UI for your ChatGPT app and expose it as a resource. This part requires writing some frontend code and may be unfamiliar at first, but a few examples will help you create an app that works well for your use case. In our case, we'll create a card with HTML, Javascript, and CSS. Inside the card, we'll display the word presented by the user, highlighting each occurrence of the specified letter. Note that we access the user's tool input using `window.openai?.toolInput?.word` and `window.openai?.toolInput?.letter`. The `window.openai` object is automatically inserted by ChatGPT with the data from the user's tool call. This is what the complete function looks like:
+
+```py
+@gr.mcp.resource("ui://widget/app.html", mime_type="text/html+skybridge")
+def app_html():
+ visual = """
+
+
+ """
+ return visual
+```
+
+Note that we've provided a URI for the `gr.mcp.resource` at `ui://widget/app.html`. This is arbitrary, but we'll need to use the same URI later on. We also need to specify the mimetype of the resource to be `mime_type="text/html+skybridge"`. Finally, note that we attached an event listener in the JavaScript for "openai:set_globals", which is generally a good practice as it allows the widget to update whenever a new tool call is triggered.
+
+5. Create an event in your Gradio app corresponding to the resource function. This is necessary because your Gradio app only picks up MCP tools, resources, prompts, etc. if they are associated with a Gradio event. Typically, the convention is to simply display the code for your MCP resource in a `gr.Code` component, e.g. like this:
+
+```py
+ html = gr.Code(language="html", max_lines=20)
+
+ # ... the rest of your Gradio app
+
+ btn.click(app_html, outputs=html)
+```
+
+6. Add `_meta` attributes to your MCP tool. We need to connect the MCP tool that we created to the UI that we created for our app. We can do this by adding this decorator to our MCP tool function:
+
+```py
+@gr.mcp.tool(
+ _meta={
+ "openai/outputTemplate": "ui://widget/app.html",
+ "openai/resultCanProduceWidget": True,
+ "openai/widgetAccessible": True,
+ }
+)
+```
+
+The key thing to observe is that the `"openai/outputTemplate"` must match the URI of the MCP resource that we created earlier.
+
+7. Relaunch your Gradio app with `share=True`. This will make it very easy to test within ChatGPT. Note the MCP server URL that is printed to your terminal, e.g. `https://2e879c6066d729b11b.gradio.live/gradio_api/mcp/`.
+
+```py
+ demo.launch(share=True, mcp_server=True)
+```
+
+This will print a public URL that your Gradio app will be running on.
+
+8. Now, navigate to ChatGPT (https://chat.com/). As mentioned earlier, you need to enable "developer mode" in ChatGPT under Settings → Apps & Connectors → Advanced settings in ChatGPT. Then, navigate to Settings → Apps & Connectors and click the "Create" button. Give your connector a name, a description (optional), and paste in the MCP server URL that was printed to your terminal. Choose "No authentication" and create.
+
+
+
+And that's it! Once the Connector has been created, you can start prompting it by saying something like, "Use @letter-counter to count the number of r's in Gradio."
+
+### Example 2: An Image Brightener
+
+Next, let's see a more complex ChatGPT app for image enhancement. The ChatGPT app includes a "Brighten" button that lets the user call the tool directly from the app UI.
+
+
+
+Here's the complete code for this app:
+
+$code_mcp_image_app
+
+We won't break down the code in as much detail since many of the pieces are the same. But note the following differences from the earlier example:
+
+* Calling tools from the widget: The app uses `window.openai.callTool()` to invoke the MCP tool directly from a button click, without requiring ChatGPT to call it:
+
+```javascript
+const result = await window.openai.callTool('power_law_image', {
+ input_path: imageEl.src
+});
+```
+
+* Parsing tool call results: The result from `callTool()` contains a `content` array that needs to be parsed to extract data:
+
+```javascript
+function extractImageUrl(data) {
+ if (data?.content) {
+ for (const item of data.content) {
+ if (item.type === 'text' && item.text?.startsWith('Image URL: ')) {
+ return item.text.substring('Image URL: '.length).trim();
+ }
+ }
+ }
+}
+```
+
+* Updating UI based on tool results: After calling the tool, the app immediately updates the displayed image with the new result:
+
+```javascript
+const newUrl = extractImageUrl(result);
+if (newUrl) imageEl.src = newUrl;
+```
+
+With these examples, you've seen how to build both simple reactive widgets and more advanced interactive apps that can call tools directly from the UI. By combining Gradio's MCP server capabilities with the OpenAI Apps SDK, it's time to start create richer ChatGPT integrations that enhance the conversational experience with custom visualizations and user interactions!
diff --git a/guides/11_other-tutorials/01_using-hugging-face-integrations.md b/guides/11_other-tutorials/01_using-hugging-face-integrations.md
new file mode 100644
index 0000000..3616b3d
--- /dev/null
+++ b/guides/11_other-tutorials/01_using-hugging-face-integrations.md
@@ -0,0 +1,134 @@
+# Using Hugging Face Integrations
+
+Related spaces: https://huggingface.co/spaces/gradio/en2es
+Tags: HUB, SPACES, EMBED
+
+Contributed by Omar Sanseviero 🦙
+
+## Introduction
+
+The Hugging Face Hub is a central platform that has hundreds of thousands of [models](https://huggingface.co/models), [datasets](https://huggingface.co/datasets) and [demos](https://huggingface.co/spaces) (also known as Spaces).
+
+Gradio has multiple features that make it extremely easy to leverage existing models and Spaces on the Hub. This guide walks through these features.
+
+
+## Demos with the Hugging Face Inference Endpoints
+
+Hugging Face has a service called [Serverless Inference Endpoints](https://huggingface.co/docs/api-inference/index), which allows you to send HTTP requests to models on the Hub. The API includes a generous free tier, and you can switch to [dedicated Inference Endpoints](https://huggingface.co/inference-endpoints/dedicated) when you want to use it in production. Gradio integrates directly with Serverless Inference Endpoints so that you can create a demo simply by specifying a model's name (e.g. `Helsinki-NLP/opus-mt-en-es`), like this:
+
+```python
+import gradio as gr
+
+demo = gr.load("Helsinki-NLP/opus-mt-en-es", src="models")
+
+demo.launch()
+```
+
+For any Hugging Face model supported in Inference Endpoints, Gradio automatically infers the expected input and output and make the underlying server calls, so you don't have to worry about defining the prediction function.
+
+Notice that we just put specify the model name and state that the `src` should be `models` (Hugging Face's Model Hub). There is no need to install any dependencies (except `gradio`) since you are not loading the model on your computer.
+
+You might notice that the first inference takes a little bit longer. This happens since the Inference Endpoints is loading the model in the server. You get some benefits afterward:
+
+- The inference will be much faster.
+- The server caches your requests.
+- You get built-in automatic scaling.
+
+## Hosting your Gradio demos on Spaces
+
+[Hugging Face Spaces](https://hf.co/spaces) allows anyone to host their Gradio demos freely, and uploading your Gradio demos take a couple of minutes. You can head to [hf.co/new-space](https://huggingface.co/new-space), select the Gradio SDK, create an `app.py` file, and voila! You have a demo you can share with anyone else. To learn more, read [this guide how to host on Hugging Face Spaces using the website](https://huggingface.co/blog/gradio-spaces).
+
+Alternatively, you can create a Space programmatically, making use of the [huggingface_hub client library](https://huggingface.co/docs/huggingface_hub/index) library. Here's an example:
+
+```python
+from huggingface_hub import (
+ create_repo,
+ get_full_repo_name,
+ upload_file,
+)
+create_repo(name=target_space_name, token=hf_token, repo_type="space", space_sdk="gradio")
+repo_name = get_full_repo_name(model_id=target_space_name, token=hf_token)
+file_url = upload_file(
+ path_or_fileobj="file.txt",
+ path_in_repo="app.py",
+ repo_id=repo_name,
+ repo_type="space",
+ token=hf_token,
+)
+```
+
+Here, `create_repo` creates a gradio repo with the target name under a specific account using that account's Write Token. `repo_name` gets the full repo name of the related repo. Finally `upload_file` uploads a file inside the repo with the name `app.py`.
+
+
+## Loading demos from Spaces
+
+You can also use and remix existing Gradio demos on Hugging Face Spaces. For example, you could take two existing Gradio demos on Spaces and put them as separate tabs and create a new demo. You can run this new demo locally, or upload it to Spaces, allowing endless possibilities to remix and create new demos!
+
+Here's an example that does exactly that:
+
+```python
+import gradio as gr
+
+with gr.Blocks() as demo:
+ with gr.Tab("Translate to Spanish"):
+ gr.load("gradio/en2es", src="spaces")
+ with gr.Tab("Translate to French"):
+ gr.load("abidlabs/en2fr", src="spaces")
+
+demo.launch()
+```
+
+Notice that we use `gr.load()`, the same method we used to load models using Inference Endpoints. However, here we specify that the `src` is `spaces` (Hugging Face Spaces).
+
+Note: loading a Space in this way may result in slight differences from the original Space. In particular, any attributes that apply to the entire Blocks, such as the theme or custom CSS/JS, will not be loaded. You can copy these properties from the Space you are loading into your own `Blocks` object.
+
+## Demos with the `Pipeline` in `transformers`
+
+Hugging Face's popular `transformers` library has a very easy-to-use abstraction, [`pipeline()`](https://huggingface.co/docs/transformers/v4.16.2/en/main_classes/pipelines#transformers.pipeline) that handles most of the complex code to offer a simple API for common tasks. By specifying the task and an (optional) model, you can build a demo around an existing model with few lines of Python:
+
+```python
+import gradio as gr
+
+from transformers import pipeline
+
+pipe = pipeline("translation", model="Helsinki-NLP/opus-mt-en-es")
+
+def predict(text):
+ return pipe(text)[0]["translation_text"]
+
+demo = gr.Interface(
+ fn=predict,
+ inputs='text',
+ outputs='text',
+)
+
+demo.launch()
+```
+
+But `gradio` actually makes it even easier to convert a `pipeline` to a demo, simply by using the `gradio.Interface.from_pipeline` methods, which skips the need to specify the input and output components:
+
+```python
+from transformers import pipeline
+import gradio as gr
+
+pipe = pipeline("translation", model="Helsinki-NLP/opus-mt-en-es")
+
+demo = gr.Interface.from_pipeline(pipe)
+demo.launch()
+```
+
+The previous code produces the following interface, which you can try right here in your browser:
+
+
+
+
+## Recap
+
+That's it! Let's recap the various ways Gradio and Hugging Face work together:
+
+1. You can build a demo around Inference Endpoints without having to load the model, by using `gr.load()`.
+2. You host your Gradio demo on Hugging Face Spaces, either using the GUI or entirely in Python.
+3. You can load demos from Hugging Face Spaces to remix and create new Gradio demos using `gr.load()`.
+4. You can convert a `transformers` pipeline into a Gradio demo using `from_pipeline()`.
+
+🤗
diff --git a/guides/11_other-tutorials/Gradio-and-Comet.md b/guides/11_other-tutorials/Gradio-and-Comet.md
new file mode 100644
index 0000000..cc809bb
--- /dev/null
+++ b/guides/11_other-tutorials/Gradio-and-Comet.md
@@ -0,0 +1,270 @@
+# Using Gradio and Comet
+
+Tags: COMET, SPACES
+Contributed by the Comet team
+
+## Introduction
+
+In this guide we will demonstrate some of the ways you can use Gradio with Comet. We will cover the basics of using Comet with Gradio and show you some of the ways that you can leverage Gradio's advanced features such as [Embedding with iFrames](https://www.gradio.app/guides/sharing-your-app/#embedding-with-iframes) and [State](https://www.gradio.app/docs/#state) to build some amazing model evaluation workflows.
+
+Here is a list of the topics covered in this guide.
+
+1. Logging Gradio UI's to your Comet Experiments
+2. Embedding Gradio Applications directly into your Comet Projects
+3. Embedding Hugging Face Spaces directly into your Comet Projects
+4. Logging Model Inferences from your Gradio Application to Comet
+
+## What is Comet?
+
+[Comet](https://www.comet.com?utm_source=gradio&utm_medium=referral&utm_campaign=gradio-integration&utm_content=gradio-docs) is an MLOps Platform that is designed to help Data Scientists and Teams build better models faster! Comet provides tooling to Track, Explain, Manage, and Monitor your models in a single place! It works with Jupyter Notebooks and Scripts and most importantly it's 100% free!
+
+## Setup
+
+First, install the dependencies needed to run these examples
+
+```shell
+pip install comet_ml torch torchvision transformers gradio shap requests Pillow
+```
+
+Next, you will need to [sign up for a Comet Account](https://www.comet.com/signup?utm_source=gradio&utm_medium=referral&utm_campaign=gradio-integration&utm_content=gradio-docs). Once you have your account set up, [grab your API Key](https://www.comet.com/docs/v2/guides/getting-started/quickstart/#get-an-api-key?utm_source=gradio&utm_medium=referral&utm_campaign=gradio-integration&utm_content=gradio-docs) and configure your Comet credentials
+
+If you're running these examples as a script, you can either export your credentials as environment variables
+
+```shell
+export COMET_API_KEY=""
+export COMET_WORKSPACE=""
+export COMET_PROJECT_NAME=""
+```
+
+or set them in a `.comet.config` file in your working directory. You file should be formatted in the following way.
+
+```shell
+[comet]
+api_key=
+workspace=
+project_name=
+```
+
+If you are using the provided Colab Notebooks to run these examples, please run the cell with the following snippet before starting the Gradio UI. Running this cell allows you to interactively add your API key to the notebook.
+
+```python
+import comet_ml
+comet_ml.init()
+```
+
+## 1. Logging Gradio UI's to your Comet Experiments
+
+[](https://colab.research.google.com/github/comet-ml/comet-examples/blob/master/integrations/model-evaluation/gradio/notebooks/Gradio_and_Comet.ipynb)
+
+In this example, we will go over how to log your Gradio Applications to Comet and interact with them using the Gradio Custom Panel.
+
+Let's start by building a simple Image Classification example using `resnet18`.
+
+```python
+import comet_ml
+
+import requests
+import torch
+from PIL import Image
+from torchvision import transforms
+
+torch.hub.download_url_to_file("https://github.com/pytorch/hub/raw/master/images/dog.jpg", "dog.jpg")
+
+if torch.cuda.is_available():
+ device = "cuda"
+else:
+ device = "cpu"
+
+model = torch.hub.load("pytorch/vision:v0.6.0", "resnet18", pretrained=True).eval()
+model = model.to(device)
+
+# Download human-readable labels for ImageNet.
+response = requests.get("https://git.io/JJkYN")
+labels = response.text.split("\n")
+
+
+def predict(inp):
+ inp = Image.fromarray(inp.astype("uint8"), "RGB")
+ inp = transforms.ToTensor()(inp).unsqueeze(0)
+ with torch.no_grad():
+ prediction = torch.nn.functional.softmax(model(inp.to(device))[0], dim=0)
+ return {labels[i]: float(prediction[i]) for i in range(1000)}
+
+
+inputs = gr.Image()
+outputs = gr.Label(num_top_classes=3)
+
+io = gr.Interface(
+ fn=predict, inputs=inputs, outputs=outputs, examples=["dog.jpg"]
+)
+io.launch(inline=False, share=True)
+
+experiment = comet_ml.Experiment()
+experiment.add_tag("image-classifier")
+
+io.integrate(comet_ml=experiment)
+```
+
+The last line in this snippet will log the URL of the Gradio Application to your Comet Experiment. You can find the URL in the Text Tab of your Experiment.
+
+
+
+Add the Gradio Panel to your Experiment to interact with your application.
+
+
+
+## 2. Embedding Gradio Applications directly into your Comet Projects
+
+
+
+If you are permanently hosting your Gradio application, you can embed the UI using the Gradio Panel Extended custom Panel.
+
+Go to your Comet Project page, and head over to the Panels tab. Click the `+ Add` button to bring up the Panels search page.
+
+
+
+Next, search for Gradio Panel Extended in the Public Panels section and click `Add`.
+
+
+
+Once you have added your Panel, click `Edit` to access to the Panel Options page and paste in the URL of your Gradio application.
+
+
+
+
+
+## 3. Embedding Hugging Face Spaces directly into your Comet Projects
+
+
+
+You can also embed Gradio Applications that are hosted on Hugging Faces Spaces into your Comet Projects using the Hugging Face Spaces Panel.
+
+Go to your Comet Project page, and head over to the Panels tab. Click the `+ Add` button to bring up the Panels search page. Next, search for the Hugging Face Spaces Panel in the Public Panels section and click `Add`.
+
+
+
+Once you have added your Panel, click Edit to access to the Panel Options page and paste in the path of your Hugging Face Space e.g. `pytorch/ResNet`
+
+
+
+## 4. Logging Model Inferences to Comet
+
+
+
+[](https://colab.research.google.com/github/comet-ml/comet-examples/blob/master/integrations/model-evaluation/gradio/notebooks/Logging_Model_Inferences_with_Comet_and_Gradio.ipynb)
+
+In the previous examples, we demonstrated the various ways in which you can interact with a Gradio application through the Comet UI. Additionally, you can also log model inferences, such as SHAP plots, from your Gradio application to Comet.
+
+In the following snippet, we're going to log inferences from a Text Generation model. We can persist an Experiment across multiple inference calls using Gradio's [State](https://www.gradio.app/docs/#state) object. This will allow you to log multiple inferences from a model to a single Experiment.
+
+```python
+import comet_ml
+import gradio as gr
+import shap
+import torch
+from transformers import AutoModelForCausalLM, AutoTokenizer
+
+if torch.cuda.is_available():
+ device = "cuda"
+else:
+ device = "cpu"
+
+MODEL_NAME = "gpt2"
+
+model = AutoModelForCausalLM.from_pretrained(MODEL_NAME)
+
+# set model decoder to true
+model.config.is_decoder = True
+# set text-generation params under task_specific_params
+model.config.task_specific_params["text-generation"] = {
+ "do_sample": True,
+ "max_length": 50,
+ "temperature": 0.7,
+ "top_k": 50,
+ "no_repeat_ngram_size": 2,
+}
+model = model.to(device)
+
+tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
+explainer = shap.Explainer(model, tokenizer)
+
+
+def start_experiment():
+ """Returns an APIExperiment object that is thread safe
+ and can be used to log inferences to a single Experiment
+ """
+ try:
+ api = comet_ml.API()
+ workspace = api.get_default_workspace()
+ project_name = comet_ml.config.get_config()["comet.project_name"]
+
+ experiment = comet_ml.APIExperiment(
+ workspace=workspace, project_name=project_name
+ )
+ experiment.log_other("Created from", "gradio-inference")
+
+ message = f"Started Experiment: [{experiment.name}]({experiment.url})"
+
+ return (experiment, message)
+
+ except Exception as e:
+ return None, None
+
+
+def predict(text, state, message):
+ experiment = state
+
+ shap_values = explainer([text])
+ plot = shap.plots.text(shap_values, display=False)
+
+ if experiment is not None:
+ experiment.log_other("message", message)
+ experiment.log_html(plot)
+
+ return plot
+
+
+with gr.Blocks() as demo:
+ start_experiment_btn = gr.Button("Start New Experiment")
+ experiment_status = gr.Markdown()
+
+ # Log a message to the Experiment to provide more context
+ experiment_message = gr.Textbox(label="Experiment Message")
+ experiment = gr.State()
+
+ input_text = gr.Textbox(label="Input Text", lines=5, interactive=True)
+ submit_btn = gr.Button("Submit")
+
+ output = gr.HTML(interactive=True)
+
+ start_experiment_btn.click(
+ start_experiment, outputs=[experiment, experiment_status]
+ )
+ submit_btn.click(
+ predict, inputs=[input_text, experiment, experiment_message], outputs=[output]
+ )
+```
+
+Inferences from this snippet will be saved in the HTML tab of your experiment.
+
+
+
+## Conclusion
+
+We hope you found this guide useful and that it provides some inspiration to help you build awesome model evaluation workflows with Comet and Gradio.
+
+## How to contribute Gradio demos on HF spaces on the Comet organization
+
+- Create an account on Hugging Face [here](https://huggingface.co/join).
+- Add Gradio Demo under your username, see this [course](https://huggingface.co/course/chapter9/4?fw=pt) for setting up Gradio Demo on Hugging Face.
+- Request to join the Comet organization [here](https://huggingface.co/Comet).
+
+## Additional Resources
+
+- [Comet Documentation](https://www.comet.com/docs/v2/?utm_source=gradio&utm_medium=referral&utm_campaign=gradio-integration&utm_content=gradio-docs)
diff --git a/guides/11_other-tutorials/Gradio-and-ONNX-on-Hugging-Face.md b/guides/11_other-tutorials/Gradio-and-ONNX-on-Hugging-Face.md
new file mode 100644
index 0000000..9893285
--- /dev/null
+++ b/guides/11_other-tutorials/Gradio-and-ONNX-on-Hugging-Face.md
@@ -0,0 +1,140 @@
+# Gradio and ONNX on Hugging Face
+
+Related spaces: https://huggingface.co/spaces/onnx/EfficientNet-Lite4
+Tags: ONNX, SPACES
+Contributed by Gradio and the ONNX team
+
+## Introduction
+
+In this Guide, we'll walk you through:
+
+- Introduction of ONNX, ONNX model zoo, Gradio, and Hugging Face Spaces
+- How to setup a Gradio demo for EfficientNet-Lite4
+- How to contribute your own Gradio demos for the ONNX organization on Hugging Face
+
+Here's an [example](https://onnx-efficientnet-lite4.hf.space/) of an ONNX model.
+
+## What is the ONNX Model Zoo?
+
+Open Neural Network Exchange ([ONNX](https://onnx.ai/)) is an open standard format for representing machine learning models. ONNX is supported by a community of partners who have implemented it in many frameworks and tools. For example, if you have trained a model in TensorFlow or PyTorch, you can convert it to ONNX easily, and from there run it on a variety of devices using an engine/compiler like ONNX Runtime.
+
+The [ONNX Model Zoo](https://github.com/onnx/models) is a collection of pre-trained, state-of-the-art models in the ONNX format contributed by community members. Accompanying each model are Jupyter notebooks for model training and running inference with the trained model. The notebooks are written in Python and include links to the training dataset as well as references to the original paper that describes the model architecture.
+
+## What are Hugging Face Spaces & Gradio?
+
+### Gradio
+
+Gradio lets users demo their machine learning models as a web app all in python code. Gradio wraps a python function into a user interface and the demos can be launched inside jupyter notebooks, colab notebooks, as well as embedded in your own website and hosted on Hugging Face Spaces for free.
+
+Get started [here](https://gradio.app/getting_started)
+
+### Hugging Face Spaces
+
+Hugging Face Spaces is a free hosting option for Gradio demos. Spaces comes with 3 SDK options: Gradio, Streamlit and Static HTML demos. Spaces can be public or private and the workflow is similar to github repos. There are over 2000+ spaces currently on Hugging Face. Learn more about spaces [here](https://huggingface.co/spaces/launch).
+
+### Hugging Face Models
+
+Hugging Face Model Hub also supports ONNX models and ONNX models can be filtered through the [ONNX tag](https://huggingface.co/models?library=onnx&sort=downloads)
+
+## How did Hugging Face help the ONNX Model Zoo?
+
+There are a lot of Jupyter notebooks in the ONNX Model Zoo for users to test models. Previously, users needed to download the models themselves and run those notebooks locally for testing. With Hugging Face, the testing process can be much simpler and more user-friendly. Users can easily try certain ONNX Model Zoo model on Hugging Face Spaces and run a quick demo powered by Gradio with ONNX Runtime, all on cloud without downloading anything locally. Note, there are various runtimes for ONNX, e.g., [ONNX Runtime](https://github.com/microsoft/onnxruntime), [MXNet](https://github.com/apache/incubator-mxnet).
+
+## What is the role of ONNX Runtime?
+
+ONNX Runtime is a cross-platform inference and training machine-learning accelerator. It makes live Gradio demos with ONNX Model Zoo model on Hugging Face possible.
+
+ONNX Runtime inference can enable faster customer experiences and lower costs, supporting models from deep learning frameworks such as PyTorch and TensorFlow/Keras as well as classical machine learning libraries such as scikit-learn, LightGBM, XGBoost, etc. ONNX Runtime is compatible with different hardware, drivers, and operating systems, and provides optimal performance by leveraging hardware accelerators where applicable alongside graph optimizations and transforms. For more information please see the [official website](https://onnxruntime.ai/).
+
+## Setting up a Gradio Demo for EfficientNet-Lite4
+
+EfficientNet-Lite 4 is the largest variant and most accurate of the set of EfficientNet-Lite models. It is an integer-only quantized model that produces the highest accuracy of all of the EfficientNet models. It achieves 80.4% ImageNet top-1 accuracy, while still running in real-time (e.g. 30ms/image) on a Pixel 4 CPU. To learn more read the [model card](https://github.com/onnx/models/tree/main/vision/classification/efficientnet-lite4)
+
+Here we walk through setting up a example demo for EfficientNet-Lite4 using Gradio
+
+First we import our dependencies and download and load the efficientnet-lite4 model from the onnx model zoo. Then load the labels from the labels_map.txt file. We then setup our preprocessing functions, load the model for inference, and setup the inference function. Finally, the inference function is wrapped into a gradio interface for a user to interact with. See the full code below.
+
+```python
+import numpy as np
+import math
+import matplotlib.pyplot as plt
+import cv2
+import json
+import gradio as gr
+from huggingface_hub import hf_hub_download
+from onnx import hub
+import onnxruntime as ort
+
+# loads ONNX model from ONNX Model Zoo
+model = hub.load("efficientnet-lite4")
+# loads the labels text file
+labels = json.load(open("labels_map.txt", "r"))
+
+# sets image file dimensions to 224x224 by resizing and cropping image from center
+def pre_process_edgetpu(img, dims):
+ output_height, output_width, _ = dims
+ img = resize_with_aspectratio(img, output_height, output_width, inter_pol=cv2.INTER_LINEAR)
+ img = center_crop(img, output_height, output_width)
+ img = np.asarray(img, dtype='float32')
+ # converts jpg pixel value from [0 - 255] to float array [-1.0 - 1.0]
+ img -= [127.0, 127.0, 127.0]
+ img /= [128.0, 128.0, 128.0]
+ return img
+
+# resizes the image with a proportional scale
+def resize_with_aspectratio(img, out_height, out_width, scale=87.5, inter_pol=cv2.INTER_LINEAR):
+ height, width, _ = img.shape
+ new_height = int(100. * out_height / scale)
+ new_width = int(100. * out_width / scale)
+ if height > width:
+ w = new_width
+ h = int(new_height * height / width)
+ else:
+ h = new_height
+ w = int(new_width * width / height)
+ img = cv2.resize(img, (w, h), interpolation=inter_pol)
+ return img
+
+# crops the image around the center based on given height and width
+def center_crop(img, out_height, out_width):
+ height, width, _ = img.shape
+ left = int((width - out_width) / 2)
+ right = int((width + out_width) / 2)
+ top = int((height - out_height) / 2)
+ bottom = int((height + out_height) / 2)
+ img = img[top:bottom, left:right]
+ return img
+
+
+sess = ort.InferenceSession(model)
+
+def inference(img):
+ img = cv2.imread(img)
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
+
+ img = pre_process_edgetpu(img, (224, 224, 3))
+
+ img_batch = np.expand_dims(img, axis=0)
+
+ results = sess.run(["Softmax:0"], {"images:0": img_batch})[0]
+ result = reversed(results[0].argsort()[-5:])
+ resultdic = {}
+ for r in result:
+ resultdic[labels[str(r)]] = float(results[0][r])
+ return resultdic
+
+title = "EfficientNet-Lite4"
+description = "EfficientNet-Lite 4 is the largest variant and most accurate of the set of EfficientNet-Lite model. It is an integer-only quantized model that produces the highest accuracy of all of the EfficientNet models. It achieves 80.4% ImageNet top-1 accuracy, while still running in real-time (e.g. 30ms/image) on a Pixel 4 CPU."
+examples = [['catonnx.jpg']]
+gr.Interface(inference, gr.Image(type="filepath"), "label", title=title, description=description, examples=examples).launch()
+```
+
+## How to contribute Gradio demos on HF spaces using ONNX models
+
+- Add model to the [onnx model zoo](https://github.com/onnx/models/blob/main/.github/PULL_REQUEST_TEMPLATE.md)
+- Create an account on Hugging Face [here](https://huggingface.co/join).
+- See list of models left to add to ONNX organization, please refer to the table with the [Models list](https://github.com/onnx/models#models)
+- Add Gradio Demo under your username, see this [blog post](https://huggingface.co/blog/gradio-spaces) for setting up Gradio Demo on Hugging Face.
+- Request to join ONNX Organization [here](https://huggingface.co/onnx).
+- Once approved transfer model from your username to ONNX organization
+- Add a badge for model in model table, see examples in [Models list](https://github.com/onnx/models#models)
diff --git a/guides/11_other-tutorials/Gradio-and-Wandb-Integration.md b/guides/11_other-tutorials/Gradio-and-Wandb-Integration.md
new file mode 100644
index 0000000..0c3d233
--- /dev/null
+++ b/guides/11_other-tutorials/Gradio-and-Wandb-Integration.md
@@ -0,0 +1,276 @@
+# Gradio and W&B Integration
+
+Related spaces: https://huggingface.co/spaces/akhaliq/JoJoGAN
+Tags: WANDB, SPACES
+Contributed by Gradio team
+
+## Introduction
+
+In this Guide, we'll walk you through:
+
+- Introduction of Gradio, and Hugging Face Spaces, and Wandb
+- How to setup a Gradio demo using the Wandb integration for JoJoGAN
+- How to contribute your own Gradio demos after tracking your experiments on wandb to the Wandb organization on Hugging Face
+
+
+## What is Wandb?
+
+Weights and Biases (W&B) allows data scientists and machine learning scientists to track their machine learning experiments at every stage, from training to production. Any metric can be aggregated over samples and shown in panels in a customizable and searchable dashboard, like below:
+
+
+
+## What are Hugging Face Spaces & Gradio?
+
+### Gradio
+
+Gradio lets users demo their machine learning models as a web app, all in a few lines of Python. Gradio wraps any Python function (such as a machine learning model's inference function) into a user interface and the demos can be launched inside jupyter notebooks, colab notebooks, as well as embedded in your own website and hosted on Hugging Face Spaces for free.
+
+Get started [here](https://gradio.app/getting_started)
+
+### Hugging Face Spaces
+
+Hugging Face Spaces is a free hosting option for Gradio demos. Spaces comes with 3 SDK options: Gradio, Streamlit and Static HTML demos. Spaces can be public or private and the workflow is similar to github repos. There are over 2000+ spaces currently on Hugging Face. Learn more about spaces [here](https://huggingface.co/spaces/launch).
+
+## Setting up a Gradio Demo for JoJoGAN
+
+Now, let's walk you through how to do this on your own. We'll make the assumption that you're new to W&B and Gradio for the purposes of this tutorial.
+
+Let's get started!
+
+1. Create a W&B account
+
+ Follow [these quick instructions](https://app.wandb.ai/login) to create your free account if you don’t have one already. It shouldn't take more than a couple minutes. Once you're done (or if you've already got an account), next, we'll run a quick colab.
+
+2. Open Colab Install Gradio and W&B
+
+ We'll be following along with the colab provided in the JoJoGAN repo with some minor modifications to use Wandb and Gradio more effectively.
+
+ [](https://colab.research.google.com/github/mchong6/JoJoGAN/blob/main/stylize.ipynb)
+
+ Install Gradio and Wandb at the top:
+
+ ```sh
+ pip install gradio wandb
+ ```
+
+3. Finetune StyleGAN and W&B experiment tracking
+
+ This next step will open a W&B dashboard to track your experiments and a gradio panel showing pretrained models to choose from a drop down menu from a Gradio Demo hosted on Huggingface Spaces. Here's the code you need for that:
+
+ ```python
+ alpha = 1.0
+ alpha = 1-alpha
+
+ preserve_color = True
+ num_iter = 100
+ log_interval = 50
+
+ samples = []
+ column_names = ["Reference (y)", "Style Code(w)", "Real Face Image(x)"]
+
+ wandb.init(project="JoJoGAN")
+ config = wandb.config
+ config.num_iter = num_iter
+ config.preserve_color = preserve_color
+ wandb.log(
+ {"Style reference": [wandb.Image(transforms.ToPILImage()(target_im))]},
+ step=0)
+
+ # load discriminator for perceptual loss
+ discriminator = Discriminator(1024, 2).eval().to(device)
+ ckpt = torch.load('models/stylegan2-ffhq-config-f.pt', map_location=lambda storage, loc: storage)
+ discriminator.load_state_dict(ckpt["d"], strict=False)
+
+ # reset generator
+ del generator
+ generator = deepcopy(original_generator)
+
+ g_optim = optim.Adam(generator.parameters(), lr=2e-3, betas=(0, 0.99))
+
+ # Which layers to swap for generating a family of plausible real images -> fake image
+ if preserve_color:
+ id_swap = [9,11,15,16,17]
+ else:
+ id_swap = list(range(7, generator.n_latent))
+
+ for idx in tqdm(range(num_iter)):
+ mean_w = generator.get_latent(torch.randn([latents.size(0), latent_dim]).to(device)).unsqueeze(1).repeat(1, generator.n_latent, 1)
+ in_latent = latents.clone()
+ in_latent[:, id_swap] = alpha*latents[:, id_swap] + (1-alpha)*mean_w[:, id_swap]
+
+ img = generator(in_latent, input_is_latent=True)
+
+ with torch.no_grad():
+ real_feat = discriminator(targets)
+ fake_feat = discriminator(img)
+
+ loss = sum([F.l1_loss(a, b) for a, b in zip(fake_feat, real_feat)])/len(fake_feat)
+
+ wandb.log({"loss": loss}, step=idx)
+ if idx % log_interval == 0:
+ generator.eval()
+ my_sample = generator(my_w, input_is_latent=True)
+ generator.train()
+ my_sample = transforms.ToPILImage()(utils.make_grid(my_sample, normalize=True, range=(-1, 1)))
+ wandb.log(
+ {"Current stylization": [wandb.Image(my_sample)]},
+ step=idx)
+ table_data = [
+ wandb.Image(transforms.ToPILImage()(target_im)),
+ wandb.Image(img),
+ wandb.Image(my_sample),
+ ]
+ samples.append(table_data)
+
+ g_optim.zero_grad()
+ loss.backward()
+ g_optim.step()
+
+ out_table = wandb.Table(data=samples, columns=column_names)
+ wandb.log({"Current Samples": out_table})
+ ```
+4. Save, Download, and Load Model
+
+ Here's how to save and download your model.
+
+ ```python
+ from PIL import Image
+ import torch
+ torch.backends.cudnn.benchmark = True
+ from torchvision import transforms, utils
+ from util import *
+ import math
+ import random
+ import numpy as np
+ from torch import nn, autograd, optim
+ from torch.nn import functional as F
+ from tqdm import tqdm
+ import lpips
+ from model import *
+ from e4e_projection import projection as e4e_projection
+
+ from copy import deepcopy
+ import imageio
+
+ import os
+ import sys
+ import torchvision.transforms as transforms
+ from argparse import Namespace
+ from e4e.models.psp import pSp
+ from util import *
+ from huggingface_hub import hf_hub_download
+ from google.colab import files
+
+ torch.save({"g": generator.state_dict()}, "your-model-name.pt")
+
+ files.download('your-model-name.pt')
+
+ latent_dim = 512
+ device="cuda"
+ model_path_s = hf_hub_download(repo_id="akhaliq/jojogan-stylegan2-ffhq-config-f", filename="stylegan2-ffhq-config-f.pt")
+ original_generator = Generator(1024, latent_dim, 8, 2).to(device)
+ ckpt = torch.load(model_path_s, map_location=lambda storage, loc: storage)
+ original_generator.load_state_dict(ckpt["g_ema"], strict=False)
+ mean_latent = original_generator.mean_latent(10000)
+
+ generator = deepcopy(original_generator)
+
+ ckpt = torch.load("/content/JoJoGAN/your-model-name.pt", map_location=lambda storage, loc: storage)
+ generator.load_state_dict(ckpt["g"], strict=False)
+ generator.eval()
+
+ plt.rcParams['figure.dpi'] = 150
+
+ transform = transforms.Compose(
+ [
+ transforms.Resize((1024, 1024)),
+ transforms.ToTensor(),
+ transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
+ ]
+ )
+
+ def inference(img):
+ img.save('out.jpg')
+ aligned_face = align_face('out.jpg')
+
+ my_w = e4e_projection(aligned_face, "out.pt", device).unsqueeze(0)
+ with torch.no_grad():
+ my_sample = generator(my_w, input_is_latent=True)
+
+ npimage = my_sample[0].cpu().permute(1, 2, 0).detach().numpy()
+ imageio.imwrite('filename.jpeg', npimage)
+ return 'filename.jpeg'
+ ````
+
+5. Build a Gradio Demo
+
+ ```python
+ import gradio as gr
+
+ title = "JoJoGAN"
+ description = "Gradio Demo for JoJoGAN: One Shot Face Stylization. To use it, simply upload your image, or click one of the examples to load them. Read more at the links below."
+
+ demo = gr.Interface(
+ inference,
+ gr.Image(type="pil"),
+ gr.Image(type="file"),
+ title=title,
+ description=description
+ )
+
+ demo.launch(share=True)
+ ```
+
+6. Integrate Gradio into your W&B Dashboard
+
+ The last step—integrating your Gradio demo with your W&B dashboard—is just one extra line:
+
+ ```python
+ demo.integrate(wandb=wandb)
+ ```
+
+ Once you call integrate, a demo will be created and you can integrate it into your dashboard or report.
+
+ Outside of W&B with Web components, using the `gradio-app` tags, anyone can embed Gradio demos on HF spaces directly into their blogs, websites, documentation, etc.:
+
+ ```html
+
+ ```
+
+7. (Optional) Embed W&B plots in your Gradio App
+
+ It's also possible to embed W&B plots within Gradio apps. To do so, you can create a W&B Report of your plots and
+ embed them within your Gradio app within a `gr.HTML` block.
+
+ The Report will need to be public and you will need to wrap the URL within an iFrame like this:
+
+ ```python
+ import gradio as gr
+
+ def wandb_report(url):
+ iframe = f'