Repository: huggingface/huggingface_hub
Pull request #444 (2021-10-27 20:17:41)

Add get started guide for SageMaker
This PR adds a quick start guide for SageMaker, showing a basic end-to-end example of fine-tuning and deploying a pretrained model on SageMaker. Also replaces the old FAQs section on the main page with a link to the Reference.


Patch:
diff --git a/docs/hub/adding-a-library.md b/docs/hub/adding-a-library.md
index 85ed4402..59bdffc7 100644
--- a/docs/hub/adding-a-library.md
+++ b/docs/hub/adding-a-library.md
@@ -95,7 +95,7 @@ Add a code snippet by updating the [Libraries Typescript file](https://github.co
 const asteroid = (model: ModelData) =>
 `from asteroid.models import BaseModel
   
-model = BaseModel.from_pretrained("${model.modelId}")`;
+model = BaseModel.from_pretrained("${model.id}")`;
 ```
 
 This will also add a tag to your model so users can quickly identify models from your library.
diff --git a/docs/hub/spaces.md b/docs/hub/spaces.md
index 99482c8d..9c934f84 100644
--- a/docs/hub/spaces.md
+++ b/docs/hub/spaces.md
@@ -4,29 +4,21 @@ title: Documentation for Spaces
 
 <h1>How to get started with Spaces</h1>
 
-<div class="p-4 border border-gray-200 rounded-lg">
-	⚠️ This feature is currently in private beta, <a style="font-weight: bold !important" href="/spaces">sign up to the waiting list</a> or <a href="#how-can-i-contact-you">reach out to us</a> if you'd like to try it out ⚠️
-</div>
-
 ## What are Spaces?
 
-Spaces are a simple way to host ML demo apps directly on your profile or your organization’s  profile.
+Spaces are a simple way to host ML demo apps directly on your profile or your organization’s  profile. This allows you to create your ML portfolio, showcase your projects at conferences or to stakeholders, and work collaboratively with other people in the ML ecosystem.
 
 We support two awesome SDKs that let you build cool apps in Python in a matter of minutes: **[Streamlit](https://streamlit.io/)** and **[Gradio](https://gradio.app/)**.
 
-**To get started**, simply click on [New Space](https://huggingface.co/new-space) in the top navigation menu, create a new repo of type `Space`, and pick your SDK:
-
-![/docs/assets/hub/new-space.gif](/docs/assets/hub/new-space.gif)
-
-Under the hood, we store your code inside a git repository just as done for model and dataset repositories. Thanks to this, the same tools you're already used to (`git` and `git-lfs`) will also work for Spaces.
+<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/3bSVKNKb_PY" title="Spaces intro" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
 
-Each time you commit, we deploy a containerized version of your code on our Infra. More details below!
+**To get started**, simply click on [New Space](https://huggingface.co/new-space) in the top navigation menu, create a new repo of type `Space`, and pick your SDK.
 
-<!-- TODO(have someone record a Youtube demo of Spaces showcasing some cool apps already running, etc) -->
+Under the hood, Spaces stores your code inside a git repository, just like the model and dataset repositories. Thanks to this, the same tools you're already used to (`git` and `git-lfs`) also work for Spaces.
 
 ## Should I use Streamlit or Gradio?
 
-We recommend you try both as they're both really awesome! 😎
+We recommend you try both because they're really awesome! 😎
 
 Streamlit's documentation is at https://docs.streamlit.io/ and Gradio's doc is at https://gradio.app/getting_started.
 
@@ -78,26 +70,35 @@ We deploy a containerized version of your code on our Infra, each time you commi
 
 If your app needs any secret keys or tokens to run, you do not want to hardcode them inside your code! Instead, head over to the settings page of your Space repo, and you'll be able to input key/secret pairs.
 
-Those secrets will be exposed to your app using the [Streamlit Secrets](https://blog.streamlit.io/secrets-in-sharing-apps/) feature if it's a Streamlit app, or as env variables in other cases.
+Secrets will be exposed to your app using the [Streamlit Secrets](https://blog.streamlit.io/secrets-in-sharing-apps/) feature if it's a Streamlit app, or as environment variables in other cases.
 
-## Streamlit advanced features
 
-We support those Streamlit features transparently:
+## I am having issues with Streamlit versions!
 
-- `st.experimental_get_query_params()` and `st.experimental_set_query_params(**parameter)` to manage app state in the url
-- if something doesn't work, please [reach out](#how-can-i-contact-you).
+The Streamlit version is not configured in the `requirements.txt` file, but rather in the README metadata config through the `sdk_version` setting. Not all Streamlit versions are supported. Refer to the [reference section](#reference) for more information about which versions are supported.
 
-## I am having issues with Streamlit versions!
+## Can I use my own HTML instead of Streamlit or Gradio?
 
-The Streamlit version is not configured in the `requirements.txt` file, but in the README metadata config through the `sdk_version` setting. Note that we do not support all Streamlit versions, please refer to the [reference section](#reference) for more info on this.
+Although we strongly encourage you to use Streamlit and Gradio, you can also use your own HTML
+code by defining `sdk: static` and having the HTML within an `index.html` file. Here are some examples:
 
-## How can I contact you?
+* [Smarter NPC](https://huggingface.co/spaces/mishig/smarter_npc): Display a PlayCanvas project with an iframe.
+* [Huggingfab](https://huggingface.co/spaces/pierreant-p/huggingfab): Display a Sketchfab model in Spaces.
 
-Feel free to ask question in the [Forum](https://discuss.huggingface.co/) if it's suitable for the community.
+Please [get in touch](#how-can-i-contact-you) if you have an idea for cool static Spaces.
 
-If you're interested in infra challenges, custom demos, GPU or something else, please reach out to us by sending an email to website at huggingface.co.
 
-You can also tag us [on Twitter](https://twitter.com/huggingface)!
+## Building an organization card
+
+Create an organization card to help users learn more about what your organization is working on and how users can use your libraries, models, datasets, and Spaces. Build an organization card by creating a static README Space with HTML. As an example, take a look at the [Amazon](https://huggingface.co/spaces/amazon/README/blob/main/README.md) and [spaCy](https://huggingface.co/spaces/spacy/README/blob/main/README.md) organization cards.
+
+* https://huggingface.co/spaces/spacy/README/blob/main/README.md
+* https://huggingface.co/spaces/amazon/README/blob/main/README.md
+
+
+## Can I use Bokeh?
+
+Streamlit has built-in support for Bokeh with the `st.bokeh_chart` component.
 
 ## How should I link my Spaces demo in my GitHub repo?
 
@@ -157,7 +158,7 @@ Color for Thumbnail gradient (red, yellow, green, blue, indigo, purple, pink, gr
 Color for Thumbnail gradient (red, yellow, green, blue, indigo, purple, pink, gray)
 
 **`sdk`** : _string_  
-Can be either `gradio` or `streamlit`
+Can be either `gradio`, `streamlit` or `static`
 
 **`sdk_version`** : _string_  
 Only applicable for `streamlit` SDK. Currently available versions are :  
@@ -170,10 +171,9 @@ Path is relative to the root of the repository.
 **`pinned`** : _boolean_  
 Whether the Space stays on top of your list.
 
-
 ## How can I manage my app through Github
 
-You can keep your app in sync with your GitHub repository by leveraging GitHub Actions:
+Keep your app in sync with your GitHub repository with GitHub Actions:
 
 - We require Git LFS for files above 10MB so you may need to review your files if you don't want to use Git LFS. This includes your history. You can use handy tools such as [BFG Repo-Cleaner](https://rtyley.github.io/bfg-repo-cleaner/) to remove the large files from your history (keep a local copy of your repository for backup).
 - Set your GitHub repository and your Spaces app initially in sync: to add your Spaces app as an additional remote to your existing git repository, you can use the command `git remote add space https://huggingface.co/spaces/FULL_SPACE_NAME`. You can then force-push to sync everything for the first time: `git push --force space main`
@@ -225,6 +225,14 @@ jobs:
 
 ```
 
+## How can I contact you?
+
+Feel free to ask questions on the [forum](https://discuss.huggingface.co/) if it's suitable for the community.
+
+If you're interested in infra challenges, custom demos, GPUs, or something else, please reach out to us by sending an email to **website at huggingface.co**.
+
+You can also tag us [on Twitter](https://twitter.com/huggingface)!
+
 ## Changelog
 
 #### [2021-10-20] - Add support for Streamlit 1.0
diff --git a/docs/hub/tutorial-add-library.md b/docs/hub/tutorial-add-library.md
index dc936ee6..7223ef9f 100644
--- a/docs/hub/tutorial-add-library.md
+++ b/docs/hub/tutorial-add-library.md
@@ -95,7 +95,7 @@ Add a code snippet by updating the [Libraries Typescript file](https://github.co
 const asteroid = (model: ModelData) =>
 `from asteroid.models import BaseModel
   
-model = BaseModel.from_pretrained("${model.modelId}")`;
+model = BaseModel.from_pretrained("${model.id}")`;
 ```
 
 This will also add a tag to your model so users can quickly identify models from your library.
diff --git a/docs/sagemaker/_sections.yml b/docs/sagemaker/_sections.yml
index aae42fb0..daf66e13 100644
--- a/docs/sagemaker/_sections.yml
+++ b/docs/sagemaker/_sections.yml
@@ -1,3 +1,6 @@
+- local: getting-started
+  title: Get started
+
 - local: main
   title: Hugging Face on Amazon SageMaker
 
@@ -7,9 +10,5 @@
 - local: inference
   title: Deploy models to Amazon SageMaker
 
-# TODO not sure if needed
-# - local: getting-started
-#   title: End-to-End Example with training and deployment
-
 - local: reference
-  title: Reference
+  title: Reference
\ No newline at end of file
diff --git a/docs/sagemaker/getting-started.md b/docs/sagemaker/getting-started.md
index 97e86602..32a7cb92 100644
--- a/docs/sagemaker/getting-started.md
+++ b/docs/sagemaker/getting-started.md
@@ -1,7 +1,161 @@
 ---
-title: End-to-End Example with training and deployment
+title: Get started
 ---
 
-<h1>End-to-End Example with training and deployment</h1>
+<h1>Train and deploy Hugging Face on Amazon SageMaker</h1>
 
-`Not implemented yet`
\ No newline at end of file
+The get started guide will show you how to quickly use Hugging Face on Amazon SageMaker. Learn how to fine-tune and deploy a pretrained 🤗 Transformers model on SageMaker for a binary text classification task.
+
+💡 If you are new to Hugging Face, we recommend first reading the 🤗 Transformers [quick tour](https://huggingface.co/transformers/quicktour.html).
+
+<iframe width="560" height="315" src="https://www.youtube.com/embed/pYqjCzoyWyo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+
+📓 Open the [notebook](https://github.com/huggingface/notebooks/blob/master/sagemaker/01_getting_started_pytorch/sagemaker-notebook.ipynb) to follow along!
+
+## Installation and setup
+
+Get started by installing the necessary Hugging Face libraries and SageMaker. You will also need to install [PyTorch](https://pytorch.org/get-started/locally/) and [TensorFlow](https://www.tensorflow.org/install/pip#tensorflow-2-packages-are-available) if you don't already have it installed.
+
+```python
+pip install "sagemaker>=2.48.0" "transformers==4.6.1" "datasets[s3]==1.6.2" --upgrade
+```
+
+If you want to run this example in [SageMaker Studio](https://docs.aws.amazon.com/sagemaker/latest/dg/studio.html), upgrade [ipywidgets](https://ipywidgets.readthedocs.io/en/latest/) for the 🤗 Datasets library and restart the kernel:
+
+```python
+%%capture
+import IPython
+!conda install -c conda-forge ipywidgets -y
+IPython.Application.instance().kernel.do_shutdown(True)
+```
+
+Next, you should set up your environment: a SageMaker session and an S3 bucket. The S3 bucket will store data, models, and logs. You will need access to an [IAM execution role](https://docs.aws.amazon.com/sagemaker/latest/dg/sagemaker-roles.html) with the required permissions.
+
+If you are planning on using SageMaker in a local environment, you need to provide the `role` yourself. Learn more about how to set this up [here](https://huggingface.co/docs/sagemaker/train#installation-and-setup).
+
+⚠️ The execution role is only available when you run a notebook within SageMaker. If you try to run `get_execution_role` in a notebook not on SageMaker, you will get a region error.
+
+```python
+import sagemaker
+
+sess = sagemaker.Session()
+sagemaker_session_bucket = None
+if sagemaker_session_bucket is None and sess is not None:
+    sagemaker_session_bucket = sess.default_bucket()
+
+role = sagemaker.get_execution_role()
+sess = sagemaker.Session(default_bucket=sagemaker_session_bucket)
+```
+
+## Preprocess
+
+The 🤗 Datasets library makes it easy to download and preprocess a dataset for training. Download and tokenize the [IMDb](https://huggingface.co/datasets/imdb) dataset:
+
+```python
+from datasets import load_dataset
+from transformers import AutoTokenizer
+
+# load dataset
+train_dataset, test_dataset = load_dataset("imdb", split=["train", "test"])
+
+# load tokenizer
+tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
+
+# create tokenization function
+def tokenize(batch):
+    return tokenizer(batch["text"], padding="max_length", truncation=True)
+
+# tokenize train and test datasets
+train_dataset = train_dataset.map(tokenize, batched=True)
+test_dataset = test.dataset.map(tokenize, batched=True)
+
+# set dataset format for PyTorch
+train_dataset =  train_dataset.rename_column("label", "labels")
+train_dataset.set_format("torch", columns=["input_ids", "attention_mask", "labels"])
+test_dataset = test_dataset.rename_column("label", "labels")
+test_dataset.set_format("torch", columns=["input_ids", "attention_mask", "labels"])
+```
+
+## Upload dataset to S3 bucket
+
+Next, upload the preprocessed dataset to your S3 session bucket with 🤗 Datasets S3 [filesystem](https://huggingface.co/docs/datasets/filesystems.html) implementation:
+
+```python
+import botocore
+from datasets.filesystems import S3FileSystem
+
+s3_prefix = 'samples/datasets/imdb'
+s3 = S3FileSystem()
+
+# save train_dataset to S3
+training_input_path = f's3://{sess.default_bucket()}/{s3_prefix}/train'
+train_dataset.save_to_disk(training_input_path,fs=s3)
+
+# save test_dataset to S3
+test_input_path = f's3://{sess.default_bucket()}/{s3_prefix}/test'
+test_dataset.save_to_disk(test_input_path,fs=s3)
+```
+
+## Start a training job
+
+Create a Hugging Face Estimator to handle end-to-end SageMaker training and deployment. The most important parameters to pay attention to are:
+
+* `entry_point` refers to the fine-tuning script which you can find [here](https://github.com/huggingface/notebooks/blob/master/sagemaker/01_getting_started_pytorch/scripts/train.py).
+* `instance_type` refers to the SageMaker instance that will be launched. Take a look [here](https://aws.amazon.com/sagemaker/pricing/) for a complete list of instance types.
+* `hyperparameters` refers to the training hyperparameters the model will be fine-tuned with.
+
+```python
+from sagemaker.huggingface import HuggingFace
+
+hyperparameters={
+    "epochs": 1,                            # number of training epochs
+    "train_batch_size": 32,                 # training batch size
+    "model_name":"distilbert-base-uncased"  # name of pretrained model
+}
+
+huggingface_estimator = HuggingFace(
+    entry_point="train.py",                 # fine-tuning script to use in training job
+    source_dir="./scripts",                 # directory where fine-tuning script is stored
+    instance_type="ml.p3.2xlarge",          # instance type
+    instance_count=1,                       # number of instances
+    role=role,                              # IAM role used in training job to acccess AWS resources (S3)
+    transformers_version="4.6",             # Transformers version
+    pytorch_version="1.7",                  # PyTorch version
+    py_version="py36",                      # Python version
+    hyperparameters=hyperparameters         # hyperparameters to use in training job
+)
+```
+
+Begin training with one line of code:
+
+```python
+huggingface_estimator.fit({"train": training_input_path, "test": test_input_path})
+```
+
+## Deploy model
+
+Once the training job is complete, deploy your fine-tuned model by calling `deploy()` with the number of instances and instance type:
+
+```python
+predictor = huggingface_estimator.deploy(initial_instance_count=1,"ml.g4dn.xlarge")
+```
+
+Call `predict()` on your data:
+
+```python
+sentiment_input = {"inputs": "It feels like a curtain closing...there was an elegance in the way they moved toward conclusion. No fan is going to watch and feel short-changed."}
+
+predictor.predict(sentiment_input)
+```
+
+After running your request, delete the endpoint:
+
+```python
+predictor.delete_endpoint()
+```
+
+## What's next?
+
+Congratulations, you've just fine-tuned and deployed a pretrained 🤗 Transformers model on SageMaker! 🎉
+
+For your next steps, keep reading our documentation for more details about training and deployment. There are many interesting features such as [distributed training](/docs/sagemaker/train#distributed-training) and [Spot instances](/docs/sagemaker/train#spot-instances).
\ No newline at end of file
diff --git a/docs/sagemaker/main.md b/docs/sagemaker/main.md
index c89451ca..c7904191 100644
--- a/docs/sagemaker/main.md
+++ b/docs/sagemaker/main.md
@@ -65,7 +65,7 @@ Take a look at our published blog posts, videos, documentation, sample notebooks
 
 - [Run training on Amazon SageMaker](/docs/sagemaker/train)
 - [Deploy models to Amazon SageMaker](/docs/sagemaker/inference)
-- [Frequently Asked Questions](/docs/sagemaker/faq)
+- [Reference](/docs/sagemaker/reference)
 - [Amazon SageMaker documentation for Hugging Face](https://docs.aws.amazon.com/sagemaker/latest/dg/hugging-face.html)
 - [Python SDK SageMaker documentation for Hugging Face](https://sagemaker.readthedocs.io/en/stable/frameworks/huggingface/index.html)
 - [Deep Learning Container](https://github.com/aws/deep-learning-containers/blob/master/available_images.md#huggingface-training-containers)
@@ -74,9 +74,9 @@ Take a look at our published blog posts, videos, documentation, sample notebooks
 
 ### Sample notebooks
 
-- [all Notebooks](https://github.com/huggingface/notebooks/tree/master/sagemaker)
-- [Getting Started Pytorch](https://github.com/huggingface/notebooks/blob/master/sagemaker/01_getting_started_pytorch/sagemaker-notebook.ipynb)
-- [Getting Started Tensorflow](https://github.com/huggingface/notebooks/blob/master/sagemaker/02_getting_started_tensorflow/sagemaker-notebook.ipynb)
+- [All notebooks](https://github.com/huggingface/notebooks/tree/master/sagemaker)
+- [Getting Started with Pytorch](https://github.com/huggingface/notebooks/blob/master/sagemaker/01_getting_started_pytorch/sagemaker-notebook.ipynb)
+- [Getting Started with Tensorflow](https://github.com/huggingface/notebooks/blob/master/sagemaker/02_getting_started_tensorflow/sagemaker-notebook.ipynb)
 - [Distributed Training Data Parallelism](https://github.com/huggingface/notebooks/blob/master/sagemaker/03_distributed_training_data_parallelism/sagemaker-notebook.ipynb)
 - [Distributed Training Model Parallelism](https://github.com/huggingface/notebooks/blob/master/sagemaker/04_distributed_training_model_parallelism/sagemaker-notebook.ipynb)
 - [Spot Instances and continue training](https://github.com/huggingface/notebooks/blob/master/sagemaker/05_spot_instances/sagemaker-notebook.ipynb)
diff --git a/src/huggingface_hub/__init__.py b/src/huggingface_hub/__init__.py
index 95c53235..6c501998 100644
--- a/src/huggingface_hub/__init__.py
+++ b/src/huggingface_hub/__init__.py
@@ -16,7 +16,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-__version__ = "0.0.20.dev0"
+__version__ = "0.1.0.dev0"
 
 from .commands.user import notebook_login
 from .constants import (
diff --git a/src/huggingface_hub/commands/user.py b/src/huggingface_hub/commands/user.py
index 078b163d..a337d6e8 100644
--- a/src/huggingface_hub/commands/user.py
+++ b/src/huggingface_hub/commands/user.py
@@ -18,7 +18,11 @@ from getpass import getpass
 from typing import List, Union
 
 from huggingface_hub.commands import BaseHuggingfaceCLICommand
-from huggingface_hub.constants import REPO_TYPES, REPO_TYPES_URL_PREFIXES
+from huggingface_hub.constants import (
+    REPO_TYPES,
+    REPO_TYPES_URL_PREFIXES,
+    SPACES_SDK_TYPES,
+)
 from huggingface_hub.hf_api import HfApi, HfFolder
 from requests.exceptions import HTTPError
 
@@ -68,6 +72,12 @@ class UserCommands(BaseHuggingfaceCLICommand):
         repo_create_parser.add_argument(
             "--organization", type=str, help="Optional: organization namespace."
         )
+        repo_create_parser.add_argument(
+            "--space_sdk",
+            type=str,
+            help='Optional: Hugging Face Spaces SDK type. Required when --type is set to "space".',
+            choices=SPACES_SDK_TYPES,
+        )
         repo_create_parser.add_argument(
             "-y",
             "--yes",
@@ -263,6 +273,7 @@ class RepoCreateCommand(BaseUserCommand):
                 token=token,
                 organization=self.args.organization,
                 repo_type=self.args.type,
+                space_sdk=self.args.space_sdk,
             )
         except HTTPError as e:
             print(e)
diff --git a/src/huggingface_hub/constants.py b/src/huggingface_hub/constants.py
index f6c8ff0e..85536073 100644
--- a/src/huggingface_hub/constants.py
+++ b/src/huggingface_hub/constants.py
@@ -32,6 +32,7 @@ HUGGINGFACE_CO_URL_TEMPLATE = ENDPOINT + "/{repo_id}/resolve/{revision}/{filenam
 REPO_TYPE_DATASET = "dataset"
 REPO_TYPE_SPACE = "space"
 REPO_TYPES = [None, REPO_TYPE_DATASET, REPO_TYPE_SPACE]
+SPACES_SDK_TYPES = ["gradio", "streamlit", "static"]
 
 REPO_TYPES_URL_PREFIXES = {
     REPO_TYPE_DATASET: "datasets/",
diff --git a/src/huggingface_hub/hf_api.py b/src/huggingface_hub/hf_api.py
index fdbdf0e0..6264994e 100644
--- a/src/huggingface_hub/hf_api.py
+++ b/src/huggingface_hub/hf_api.py
@@ -26,7 +26,13 @@ from typing import IO, Dict, Iterable, List, Optional, Tuple, Union
 import requests
 from requests.exceptions import HTTPError
 
-from .constants import ENDPOINT, REPO_TYPES, REPO_TYPES_MAPPING, REPO_TYPES_URL_PREFIXES
+from .constants import (
+    ENDPOINT,
+    REPO_TYPES,
+    REPO_TYPES_MAPPING,
+    REPO_TYPES_URL_PREFIXES,
+    SPACES_SDK_TYPES,
+)
 
 
 if sys.version_info >= (3, 8):
@@ -35,7 +41,7 @@ else:
     from typing_extensions import Literal
 
 
-REMOTE_FILEPATH_REGEX = re.compile(r"^\w[\w\/]*(\.\w+)?$")
+REMOTE_FILEPATH_REGEX = re.compile(r"^\w[\w\/\-]*(\.\w+)?$")
 # ^^ No trailing slash, no backslash, no spaces, no relative parts ("." or "..")
 #    Only word characters and an optional extension
 
@@ -584,13 +590,24 @@ class HfApi:
         self,
         repo_id: str,
         revision: Optional[str] = None,
+        repo_type: Optional[str] = None,
         token: Optional[str] = None,
         timeout: Optional[float] = None,
-    ) -> ModelInfo:
+    ) -> List[str]:
         """
         Get the list of files in a given repo.
         """
-        info = self.model_info(repo_id, revision=revision, token=token, timeout=timeout)
+        if repo_type is None:
+            info = self.model_info(
+                repo_id, revision=revision, token=token, timeout=timeout
+            )
+        elif repo_type == "dataset":
+            info = self.dataset_info(
+                repo_id, revision=revision, token=token, timeout=timeout
+            )
+        else:
+            raise ValueError("Spaces are not available yet.")
+
         return [f.rfilename for f in info.siblings]
 
     def list_repos_objs(
@@ -664,6 +681,7 @@ class HfApi:
         repo_type: Optional[str] = None,
         exist_ok=False,
         lfsmultipartthresh: Optional[int] = None,
+        space_sdk: Optional[str] = None,
     ) -> str:
         """
         HuggingFace git-based system, used for models, datasets, and spaces.
@@ -679,6 +697,8 @@ class HfApi:
 
             lfsmultipartthresh: Optional: internal param for testing purposes.
 
+            space_sdk: Choice of SDK to use if repo_type is "space". Can be "streamlit", "gradio", or "static".
+
         Returns:
             URL to the newly created repo.
         """
@@ -707,6 +727,22 @@ class HfApi:
         json = {"name": name, "organization": organization, "private": private}
         if repo_type is not None:
             json["type"] = repo_type
+            if repo_type == "space":
+                if space_sdk is None:
+                    raise ValueError(
+                        "No space_sdk provided. `create_repo` expects space_sdk to be one of "
+                        f"{SPACES_SDK_TYPES} when repo_type is 'space'`"
+                    )
+                if space_sdk not in SPACES_SDK_TYPES:
+                    raise ValueError(
+                        f"Invalid space_sdk. Please choose one of {SPACES_SDK_TYPES}."
+                    )
+                json["sdk"] = space_sdk
+        if space_sdk is not None and repo_type != "space":
+            warnings.warn(
+                "Ignoring provided space_sdk because repo_type is not 'space'."
+            )
+
         if lfsmultipartthresh is not None:
             json["lfsmultipartthresh"] = lfsmultipartthresh
         r = requests.post(
@@ -821,6 +857,7 @@ class HfApi:
             path_prefix += REPO_TYPES_URL_PREFIXES[repo_type]
 
         path = "{}{}/{}/settings".format(path_prefix, namespace, name)
+
         json = {"private": private}
 
         r = requests.put(
diff --git a/src/huggingface_hub/repository.py b/src/huggingface_hub/repository.py
index c0eeae5f..961ea36a 100644
--- a/src/huggingface_hub/repository.py
+++ b/src/huggingface_hub/repository.py
@@ -29,6 +29,7 @@ class CommandInProgress:
         is_done_method: Callable,
         status_method: Callable,
         process: subprocess.Popen,
+        post_method: Optional[Callable] = None,
     ):
         self.title = title
         self._is_done = is_done_method
@@ -36,13 +37,19 @@ class CommandInProgress:
         self._process = process
         self._stderr = ""
         self._stdout = ""
+        self._post_method = post_method
 
     @property
     def is_done(self) -> bool:
         """
         Whether the process is done.
         """
-        return self._is_done()
+        result = self._is_done()
+
+        if result and self._post_method is not None:
+            self._post_method()
+
+        return result
 
     @property
     def status(self) -> int:
@@ -840,6 +847,27 @@ class Repository:
 
         return files_to_be_tracked_with_lfs
 
+    def lfs_prune(self, recent=False):
+        """
+        git lfs prune
+        """
+        args = "git lfs prune".split()
+        if recent:
+            args.append("--recent")
+        try:
+            with lfs_log_progress():
+                result = subprocess.run(
+                    args,
+                    stderr=subprocess.PIPE,
+                    stdout=subprocess.PIPE,
+                    check=True,
+                    encoding="utf-8",
+                    cwd=self.local_dir,
+                )
+                logger.info(result.stdout)
+        except subprocess.CalledProcessError as exc:
+            raise EnvironmentError(exc.stderr)
+
     def git_pull(self, rebase: Optional[bool] = False):
         """
         git pull
@@ -914,6 +942,7 @@ class Repository:
         self,
         upstream: Optional[str] = None,
         blocking: Optional[bool] = True,
+        auto_lfs_prune: Optional[bool] = False,
     ) -> Union[str, Tuple[str, CommandInProgress]]:
         """
         git push
@@ -931,6 +960,8 @@ class Repository:
                 Setting this to `False` will return an `CommandInProgress` object
                 which has an `is_done` property. This property will be set to
                 `True` when the push is finished.
+            auto_lfs_prune (`bool`, defaults to `False`):
+                Whether to automatically prune files once they have been pushed to the remote.
         """
         command = "git push"
 
@@ -986,12 +1017,16 @@ class Repository:
                 is_done_method=lambda: process.poll() is not None,
                 status_method=status_method,
                 process=process,
+                post_method=self.lfs_prune if auto_lfs_prune else None,
             )
 
             self.command_queue.append(command)
 
             return self.git_head_commit_url(), command
 
+        if auto_lfs_prune:
+            self.lfs_prune()
+
         return self.git_head_commit_url()
 
     def git_checkout(self, revision: str, create_branch_ok: Optional[bool] = False):
@@ -1172,6 +1207,7 @@ class Repository:
         commit_message: Optional[str] = "commit files to HF hub",
         blocking: Optional[bool] = True,
         clean_ok: Optional[bool] = False,
+        auto_lfs_prune: Optional[bool] = False,
     ) -> Optional[str]:
         """
         Helper to add, commit, and push files to remote repository on the HuggingFace Hub.
@@ -1185,6 +1221,8 @@ class Repository:
             clean_ok (`bool`, `optional`, defaults to `False`):
                 If True, this function will return None if the repo is untouched.
                 Default behavior is to fail because the git command fails.
+            auto_lfs_prune (`bool`, defaults to `False`):
+                Whether to automatically prune files once they have been pushed to the remote.
         """
         if clean_ok and self.is_repo_clean():
             logger.info("Repo currently clean.  Ignoring push_to_hub")
@@ -1192,7 +1230,9 @@ class Repository:
         self.git_add(auto_lfs_track=True)
         self.git_commit(commit_message)
         return self.git_push(
-            upstream=f"origin {self.current_branch}", blocking=blocking
+            upstream=f"origin {self.current_branch}",
+            blocking=blocking,
+            auto_lfs_prune=auto_lfs_prune,
         )
 
     @contextmanager
@@ -1202,6 +1242,7 @@ class Repository:
         branch: Optional[str] = None,
         track_large_files: Optional[bool] = True,
         blocking: Optional[bool] = True,
+        auto_lfs_prune: Optional[bool] = False,
     ):
         """
         Context manager utility to handle committing to a repository. This automatically tracks large files (>10Mb)
@@ -1216,6 +1257,8 @@ class Repository:
                 Whether to automatically track large files or not. Will do so by default.
             blocking (`bool`, `optional`, defaults to `True`):
                 Whether the function should return only when the `git push` has finished.
+            auto_lfs_prune (`bool`, defaults to `True`):
+                Whether to automatically prune files once they have been pushed to the remote.
 
         Examples:
 
@@ -1270,7 +1313,9 @@ class Repository:
 
             try:
                 self.git_push(
-                    upstream=f"origin {self.current_branch}", blocking=blocking
+                    upstream=f"origin {self.current_branch}",
+                    blocking=blocking,
+                    auto_lfs_prune=auto_lfs_prune,
                 )
             except OSError as e:
                 # If no changes are detected, there is nothing to commit.
diff --git a/widgets/src/lib/InferenceWidget/shared/WidgetDropzone/WidgetDropzone.svelte b/widgets/src/lib/InferenceWidget/shared/WidgetDropzone/WidgetDropzone.svelte
index 0fd7545c..ae0ed766 100644
--- a/widgets/src/lib/InferenceWidget/shared/WidgetDropzone/WidgetDropzone.svelte
+++ b/widgets/src/lib/InferenceWidget/shared/WidgetDropzone/WidgetDropzone.svelte
@@ -1,6 +1,6 @@
 <script>
 	import IconSpin from "../../../Icons/IconSpin.svelte";
-	import { proxify } from "../../shared/helpers";
+	import { getBlobFromUrl } from "../../shared/helpers";
 
 	export let accept = "image/*";
 	export let classNames = "";
@@ -38,9 +38,7 @@
 			const url = await new Promise<string>((resolve) =>
 				uriItem.getAsString((s) => resolve(s))
 			);
-			const proxiedUrl = proxify(url);
-			const res = await fetch(proxiedUrl);
-			const file = await res.blob();
+			const file = await getBlobFromUrl(url);
 
 			onSelectFile(file);
 		} else if (fileItem) {
diff --git a/widgets/src/lib/InferenceWidget/shared/WidgetInputSamples/WidgetInputSamples.svelte b/widgets/src/lib/InferenceWidget/shared/WidgetInputSamples/WidgetInputSamples.svelte
index 12708167..8b5bca08 100644
--- a/widgets/src/lib/InferenceWidget/shared/WidgetInputSamples/WidgetInputSamples.svelte
+++ b/widgets/src/lib/InferenceWidget/shared/WidgetInputSamples/WidgetInputSamples.svelte
@@ -1,21 +1,132 @@
 <script>
+	import { slide } from "svelte/transition";
+
 	export let inputSamples: Record<string, any>[];
-	export let applyInputSample: (sample: Record<string, any>[]) => void;
+	export let applyInputSample: (sample: Record<string, any>) => void;
+	export let previewInputSample: (sample: Record<string, any>) => void;
+
+	let containerEl: HTMLElement;
+	let isOptionsVisible = false;
+	let isTouchOptionClicked = false;
+	let touchSelectedIdx: number;
+	let title = "Examples";
 
-	function onChange(e: Event) {
-		const { value } = e.target as HTMLOptionElement;
-		const sample = inputSamples[value];
+	function _applyInputSample(idx: number) {
+		hideOptions();
+		const sample = inputSamples[idx];
+		title = sample.example_title;
 		applyInputSample(sample);
 	}
+
+	function _previewInputSample(idx: number, isTocuh = false) {
+		const sample = inputSamples[idx];
+		if (isTocuh) {
+			isTouchOptionClicked = true;
+			touchSelectedIdx = idx;
+		}
+		previewInputSample(sample);
+	}
+
+	function toggleOptionsVisibility() {
+		isOptionsVisible = !isOptionsVisible;
+	}
+
+	function onClick(e: MouseEvent | TouchEvent) {
+		let targetElement = e.target;
+		do {
+			if (targetElement == containerEl) {
+				// This is a click inside. Do nothing, just return.
+				return;
+			}
+			targetElement = (targetElement as HTMLElement).parentElement;
+		} while (targetElement);
+		// This is a click outside
+		hideOptions();
+	}
+
+	function hideOptions() {
+		isOptionsVisible = false;
+		isTouchOptionClicked = false;
+	}
 </script>
 
-<!-- svelte-ignore a11y-no-onchange -->
-<select
-	class="border-none text-sm w-28 truncate dark:bg-gray-950"
-	on:change={onChange}
->
-	<option selected disabled>Examples</option>
-	{#each inputSamples as { example_title }, i}
-		<option value={i}>{example_title}</option>
-	{/each}
-</select>
+<svelte:window on:click={onClick} />
+
+<div class="relative z-10 ml-2" bind:this={containerEl}>
+	<div
+		class="no-hover:hidden inline-flex justify-between w-32 lg:w-44 rounded-md border border-gray-100 px-4 py-1"
+		on:click={toggleOptionsVisibility}
+	>
+		<p class="text-sm truncate">{title}</p>
+		<svg
+			class="-mr-1 ml-2 h-5 w-5 transition ease-in-out transform {isOptionsVisible &&
+				'-rotate-180'}"
+			xmlns="http://www.w3.org/2000/svg"
+			viewBox="0 0 20 20"
+			fill="currentColor"
+			aria-hidden="true"
+		>
+			<path
+				fill-rule="evenodd"
+				d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
+				clip-rule="evenodd"
+			/>
+		</svg>
+	</div>
+	{#if !isTouchOptionClicked}
+		<div
+			class="with-hover:hidden inline-flex justify-between w-32 lg:w-44 rounded-md border border-gray-100 px-4 py-1"
+			on:click={toggleOptionsVisibility}
+		>
+			<p class="text-sm truncate">{title}</p>
+			<svg
+				class="-mr-1 ml-2 h-5 w-5 transition ease-in-out transform {isOptionsVisible &&
+					'-rotate-180'}"
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				aria-hidden="true"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+	{:else}
+		<!-- Better UX for mobile/table through CSS breakpoints -->
+		<div
+			class="with-hover:hidden inline-flex justify-center w-32 lg:w-44 rounded-md border border-green-500 px-4 py-1"
+			on:click={() => _applyInputSample(touchSelectedIdx)}
+		>
+			<p class="text-green-500">Confirm</p>
+		</div>
+	{/if}
+
+	{#if isOptionsVisible}
+		<div
+			class="origin-top-right absolute right-0 mt-1 w-full rounded-md ring-1 ring-black ring-opacity-10"
+			transition:slide
+		>
+			<div class="py-1 bg-white rounded-md" role="none">
+				{#each inputSamples as { example_title }, i}
+					<p
+						class="no-hover:hidden px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-200"
+						on:mouseover={() => _previewInputSample(i)}
+						on:click={() => _applyInputSample(i)}
+					>
+						{example_title}
+					</p>
+					<!-- Better UX for mobile/table through CSS breakpoints -->
+					<p
+						class="with-hover:hidden px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-200"
+						on:click={() => _previewInputSample(i, true)}
+					>
+						{example_title}
+					</p>
+				{/each}
+			</div>
+		</div>
+	{/if}
+</div>
diff --git a/widgets/src/lib/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte b/widgets/src/lib/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte
index de384aeb..ad64096a 100644
--- a/widgets/src/lib/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte
+++ b/widgets/src/lib/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte
@@ -20,8 +20,10 @@
 	};
 	export let noTitle = false;
 	export let outputJson: string;
-	export let applyInputSample: (sample: Record<string, any>[]) => void =
-		([]) => {};
+	export let applyInputSample: (sample: Record<string, any>) => void =
+		({}) => {};
+	export let previewInputSample: (sample: Record<string, any>) => void =
+		({}) => {};
 
 	let isMaximized = false;
 	let modelStatus: LoadingStatus = "unknown";
@@ -35,7 +37,7 @@
 		.slice(0, 5);
 
 	onMount(() => {
-		getModelStatus(apiUrl, model.modelId).then((status) => {
+		getModelStatus(apiUrl, model.id).then((status) => {
 			modelStatus = status;
 		});
 	});
@@ -55,13 +57,19 @@
 		</button>
 	{/if}
 	<WidgetHeader {noTitle} pipeline={model.pipeline_tag}>
-		{#if model.pipeline_tag === "fill-mask"}
-			Mask token: <code>{model.mask_token}</code>
-		{/if}
-		{#if inputSamples.length > 1}
-			<!-- Show samples selector when there are more than one sample -->
-			<WidgetInputSamples {inputSamples} {applyInputSample} />
-		{/if}
+		<div class="flex items-center">
+			{#if model.pipeline_tag === "fill-mask"}
+				Mask token: <code>{model.mask_token}</code>
+			{/if}
+			{#if inputSamples.length > 1}
+				<!-- Show samples selector when there are more than one sample -->
+				<WidgetInputSamples
+					{inputSamples}
+					{applyInputSample}
+					{previewInputSample}
+				/>
+			{/if}
+		</div>
 	</WidgetHeader>
 	<slot name="top" />
 	<WidgetInfo {computeTime} {error} {modelStatus} />
diff --git a/widgets/src/lib/InferenceWidget/shared/helpers.ts b/widgets/src/lib/InferenceWidget/shared/helpers.ts
index 000c48a2..e0954fcb 100644
--- a/widgets/src/lib/InferenceWidget/shared/helpers.ts
+++ b/widgets/src/lib/InferenceWidget/shared/helpers.ts
@@ -40,16 +40,24 @@ export function updateUrl(obj: Record<string, string>) {
 }
 
 // Run through our own proxy to bypass CORS:
-export function proxify(url: string): string {
+function proxify(url: string): string {
 	return url.startsWith(`http://localhost`)
 		|| new URL(url).host === window.location.host
 		? url
 		: `https://widgets-cors-proxy.huggingface.co/proxy?url=${url}`;
 }
 
+// Get BLOB from a given URL after proxifying the URL
+export async function getBlobFromUrl(url: string): Promise<Blob>{
+	const proxiedUrl = proxify(url);
+	const res = await fetch(proxiedUrl);
+	const blob = await res.blob();
+	return blob;
+}
+
 async function callApi(
 	url: string, 
-	modelId: string, 
+	repoId: string, 
 	requestBody: Record<string, any>, 
 	apiToken = '',
 	waitForModel = false, // If true, the server will only respond once the model has been loaded on the inference API,
@@ -76,7 +84,7 @@ async function callApi(
 		: JSON.stringify(requestBody);
 	
 	return await fetch(
-		`${url}/models/${modelId}`,
+		`${url}/models/${repoId}`,
 		{
 			method: "POST",
 			body,
@@ -87,7 +95,7 @@ async function callApi(
 
 export async function getResponse<T>(
 	url: string, 
-	modelId: string, 
+	repoId: string, 
 	requestBody: Record<string, any>, 
 	apiToken = '',
 	outputParsingFn: (x: unknown) =>  T,
@@ -109,7 +117,7 @@ export async function getResponse<T>(
 }>  {
 	const response = await callApi(
 		url,
-		modelId,
+		repoId,
 		requestBody,
 		apiToken,
 		waitForModel,
@@ -157,8 +165,8 @@ export async function getResponse<T>(
 }
 
 
-export async function getModelStatus(url: string, modelId: string): Promise<LoadingStatus> {
-	const response = await fetch(`${url}/status/${modelId}`);
+export async function getModelStatus(url: string, repoId: string): Promise<LoadingStatus> {
+	const response = await fetch(`${url}/status/${repoId}`);
 	const output = await response.json();
 	if (response.ok && typeof output === 'object' && output.loaded !== undefined) {
 		return output.loaded ? 'loaded' : 'unknown';
diff --git a/widgets/src/lib/InferenceWidget/shared/types.ts b/widgets/src/lib/InferenceWidget/shared/types.ts
index ec91a0be..bee18ad4 100644
--- a/widgets/src/lib/InferenceWidget/shared/types.ts
+++ b/widgets/src/lib/InferenceWidget/shared/types.ts
@@ -16,9 +16,16 @@ export type TableData = Record<string, (string | number)[]>;
 
 export type HighlightCoordinates = Record<string, string>;
 
-export type Box = {
+type Box = {
 	xmin: number;
 	ymin: number;
 	xmax: number;
 	ymax: number;
 };
+
+export type DetectedObject = {
+	box: Box;
+	label: string;
+	score: number;
+	color?: string;
+}
diff --git a/widgets/src/lib/InferenceWidget/widgets/AudioClassificationWidget/AudioClassificationWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/AudioClassificationWidget/AudioClassificationWidget.svelte
index 4e3f8738..3481dd32 100644
--- a/widgets/src/lib/InferenceWidget/widgets/AudioClassificationWidget/AudioClassificationWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/AudioClassificationWidget/AudioClassificationWidget.svelte
@@ -4,18 +4,16 @@
 	import WidgetAudioTrack from "../../shared/WidgetAudioTrack/WidgetAudioTrack.svelte";
 	import WidgetFileInput from "../../shared/WidgetFileInput/WidgetFileInput.svelte";
 	import WidgetOutputChart from "../../shared/WidgetOutputChart/WidgetOutputChart.svelte";
-	import WidgetRadio from "../../shared/WidgetRadio/WidgetRadio.svelte";
 	import WidgetRecorder from "../../shared/WidgetRecorder/WidgetRecorder.svelte";
 	import WidgetSubmitBtn from "../../shared/WidgetSubmitBtn/WidgetSubmitBtn.svelte";
 	import WidgetWrapper from "../../shared/WidgetWrapper/WidgetWrapper.svelte";
-	import { getResponse, proxify } from "../../shared/helpers";
+	import { getResponse, getBlobFromUrl } from "../../shared/helpers";
 
 	export let apiToken: WidgetProps["apiToken"];
 	export let apiUrl: WidgetProps["apiUrl"];
 	export let model: WidgetProps["model"];
 	export let noTitle: WidgetProps["noTitle"];
 
-	let areSamplesVisible = true;
 	let computeTime = "";
 	let error: string = "";
 	let file: Blob | File | null = null;
@@ -30,15 +28,9 @@
 	let output: Array<{ label: string; score: number }> = [];
 	let outputJson: string;
 	let selectedSampleUrl = "";
-
-	function onChangeRadio() {
-		file = null;
-		filename = "";
-		fileUrl = "";
-	}
+	let shouldAudioAutoplay = true;
 
 	function onRecordStart() {
-		areSamplesVisible = false;
 		file = null;
 		filename = "";
 		fileUrl = "";
@@ -50,7 +42,7 @@
 	}
 
 	function onSelectFile(updatedFile: Blob | File) {
-		areSamplesVisible = false;
+		shouldAudioAutoplay = false;
 		isRecording = false;
 		selectedSampleUrl = "";
 
@@ -75,9 +67,7 @@
 		}
 
 		if (!file && selectedSampleUrl) {
-			const proxiedUrl = proxify(selectedSampleUrl);
-			const res = await fetch(proxiedUrl);
-			file = await res.blob();
+			file = await getBlobFromUrl(selectedSampleUrl);
 		}
 
 		const requestBody = { file };
@@ -86,7 +76,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -135,7 +125,19 @@
 	}
 
 	function applyInputSample(sample: Record<string, any>) {
+		shouldAudioAutoplay = false;
+		filename = sample.example_title;
+		fileUrl = sample.src;
+		selectedSampleUrl = sample.src;
+		getOutput();
+	}
+
+	function previewInputSample(sample: Record<string, any>) {
+		shouldAudioAutoplay = true;
+		filename = sample.example_title;
 		fileUrl = sample.src;
+		output = [];
+		outputJson = "";
 	}
 </script>
 
@@ -148,6 +150,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
@@ -166,29 +169,12 @@
 				/>
 			</div>
 			{#if fileUrl}
-				<WidgetAudioTrack classNames="mt-3" label={filename} src={fileUrl} />
-			{/if}
-			{#if model.widgetData}
-				<details
-					open={areSamplesVisible}
-					class="text-gray-500 text-sm mt-4 mb-2"
-				>
-					<summary class="mb-2">Or pick a sample audio file</summary>
-					<div class="mt-4 space-y-5">
-						<!-- Shouldnt this be an option ? -->
-						{#each model.widgetData as widgetData}
-							<WidgetAudioTrack classNames="mt-3" src={String(widgetData.src)}>
-								<WidgetRadio
-									bind:group={selectedSampleUrl}
-									classNames="mb-1.5"
-									label={String(widgetData.label)}
-									onChange={onChangeRadio}
-									value={String(widgetData.src)}
-								/>
-							</WidgetAudioTrack>
-						{/each}
-					</div>
-				</details>
+				<WidgetAudioTrack
+					classNames="mt-3"
+					autoplay={shouldAudioAutoplay}
+					label={filename}
+					src={fileUrl}
+				/>
 			{/if}
 			<WidgetSubmitBtn
 				classNames="mt-2"
diff --git a/widgets/src/lib/InferenceWidget/widgets/AudioToAudioWidget/AudioToAudioWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/AudioToAudioWidget/AudioToAudioWidget.svelte
index ccf9692f..3376417d 100644
--- a/widgets/src/lib/InferenceWidget/widgets/AudioToAudioWidget/AudioToAudioWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/AudioToAudioWidget/AudioToAudioWidget.svelte
@@ -3,18 +3,16 @@
 
 	import WidgetAudioTrack from "../../shared/WidgetAudioTrack/WidgetAudioTrack.svelte";
 	import WidgetFileInput from "../../shared/WidgetFileInput/WidgetFileInput.svelte";
-	import WidgetRadio from "../../shared/WidgetRadio/WidgetRadio.svelte";
 	import WidgetRecorder from "../../shared/WidgetRecorder/WidgetRecorder.svelte";
 	import WidgetSubmitBtn from "../../shared/WidgetSubmitBtn/WidgetSubmitBtn.svelte";
 	import WidgetWrapper from "../../shared/WidgetWrapper/WidgetWrapper.svelte";
-	import { getResponse, proxify } from "../../shared/helpers";
+	import { getResponse, getBlobFromUrl } from "../../shared/helpers";
 
 	export let apiToken: WidgetProps["apiToken"];
 	export let apiUrl: WidgetProps["apiUrl"];
 	export let model: WidgetProps["model"];
 	export let noTitle: WidgetProps["noTitle"];
 
-	let areSamplesVisible = true;
 	let computeTime = "";
 	let error: string = "";
 	let file: Blob | File | null = null;
@@ -29,6 +27,7 @@
 	let output: AudioItem[] = [];
 	let outputJson: string;
 	let selectedSampleUrl = "";
+	let shouldAudioAutoplay = true;
 
 	interface AudioItem {
 		blob: string;
@@ -37,14 +36,7 @@
 		"content-type": string;
 	}
 
-	function onChangeRadio() {
-		file = null;
-		filename = "";
-		fileUrl = "";
-	}
-
 	function onRecordStart() {
-		areSamplesVisible = false;
 		file = null;
 		filename = "";
 		fileUrl = "";
@@ -56,7 +48,7 @@
 	}
 
 	function onSelectFile(updatedFile: Blob | File) {
-		areSamplesVisible = false;
+		shouldAudioAutoplay = false;
 		isRecording = false;
 		selectedSampleUrl = "";
 		if (updatedFile.size !== 0) {
@@ -78,9 +70,7 @@
 		}
 
 		if (!file && selectedSampleUrl) {
-			const proxiedUrl = proxify(selectedSampleUrl);
-			const res = await fetch(proxiedUrl);
-			file = await res.blob();
+			file = await getBlobFromUrl(selectedSampleUrl);
 		}
 		const requestBody = { file };
 
@@ -88,7 +78,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -141,7 +131,19 @@
 	}
 
 	function applyInputSample(sample: Record<string, any>) {
+		shouldAudioAutoplay = false;
+		filename = sample.example_title;
 		fileUrl = sample.src;
+		selectedSampleUrl = sample.src;
+		getOutput();
+	}
+
+	function previewInputSample(sample: Record<string, any>) {
+		shouldAudioAutoplay = true;
+		filename = sample.example_title;
+		fileUrl = sample.src;
+		output = [];
+		outputJson = "";
 	}
 </script>
 
@@ -154,6 +156,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
@@ -172,28 +175,12 @@
 				/>
 			</div>
 			{#if fileUrl}
-				<WidgetAudioTrack classNames="mt-3" label={filename} src={fileUrl} />
-			{/if}
-			{#if model.widgetData}
-				<details
-					open={areSamplesVisible}
-					class="text-gray-500 text-sm mt-4 mb-2"
-				>
-					<summary class="mb-2">Or pick a sample audio file</summary>
-					<div class="mt-4 space-y-5">
-						{#each model.widgetData as widgetData}
-							<WidgetAudioTrack classNames="mt-3" src={String(widgetData.src)}>
-								<WidgetRadio
-									bind:group={selectedSampleUrl}
-									classNames="mb-1.5"
-									label={String(widgetData.label)}
-									onChange={onChangeRadio}
-									value={String(widgetData.src)}
-								/>
-							</WidgetAudioTrack>
-						{/each}
-					</div>
-				</details>
+				<WidgetAudioTrack
+					classNames="mt-3"
+					autoplay={shouldAudioAutoplay}
+					label={filename}
+					src={fileUrl}
+				/>
 			{/if}
 			<WidgetSubmitBtn
 				classNames="mt-2"
diff --git a/widgets/src/lib/InferenceWidget/widgets/AutomaticSpeechRecognitionWidget/AutomaticSpeechRecognitionWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/AutomaticSpeechRecognitionWidget/AutomaticSpeechRecognitionWidget.svelte
index ae43bb33..1a813fb3 100644
--- a/widgets/src/lib/InferenceWidget/widgets/AutomaticSpeechRecognitionWidget/AutomaticSpeechRecognitionWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/AutomaticSpeechRecognitionWidget/AutomaticSpeechRecognitionWidget.svelte
@@ -4,18 +4,16 @@
 	import WidgetAudioTrack from "../../shared/WidgetAudioTrack/WidgetAudioTrack.svelte";
 	import WidgetFileInput from "../../shared/WidgetFileInput/WidgetFileInput.svelte";
 	import WidgetOutputText from "../../shared/WidgetOutputText/WidgetOutputText.svelte";
-	import WidgetRadio from "../../shared/WidgetRadio/WidgetRadio.svelte";
 	import WidgetRecorder from "../../shared/WidgetRecorder/WidgetRecorder.svelte";
 	import WidgetSubmitBtn from "../../shared/WidgetSubmitBtn/WidgetSubmitBtn.svelte";
 	import WidgetWrapper from "../../shared/WidgetWrapper/WidgetWrapper.svelte";
-	import { getResponse, proxify } from "../../shared/helpers";
+	import { getResponse, getBlobFromUrl } from "../../shared/helpers";
 
 	export let apiToken: WidgetProps["apiToken"];
 	export let apiUrl: WidgetProps["apiUrl"];
 	export let model: WidgetProps["model"];
 	export let noTitle: WidgetProps["noTitle"];
 
-	let areSamplesVisible = true;
 	let computeTime = "";
 	let error: string = "";
 	let file: Blob | File | null = null;
@@ -30,15 +28,9 @@
 	let output = "";
 	let outputJson: string;
 	let selectedSampleUrl = "";
-
-	function onChangeRadio() {
-		file = null;
-		filename = "";
-		fileUrl = "";
-	}
+	let shouldAudioAutoplay = true;
 
 	function onRecordStart() {
-		areSamplesVisible = false;
 		file = null;
 		filename = "";
 		fileUrl = "";
@@ -50,7 +42,7 @@
 	}
 
 	function onSelectFile(updatedFile: Blob | File) {
-		areSamplesVisible = false;
+		shouldAudioAutoplay = false;
 		isRecording = false;
 		selectedSampleUrl = "";
 
@@ -75,9 +67,7 @@
 		}
 
 		if (!file && selectedSampleUrl) {
-			const proxiedUrl = proxify(selectedSampleUrl);
-			const res = await fetch(proxiedUrl);
-			file = await res.blob();
+			file = await getBlobFromUrl(selectedSampleUrl);
 		}
 
 		const requestBody = { file };
@@ -86,7 +76,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -124,7 +114,19 @@
 	}
 
 	function applyInputSample(sample: Record<string, any>) {
+		shouldAudioAutoplay = false;
+		filename = sample.example_title;
+		fileUrl = sample.src;
+		selectedSampleUrl = sample.src;
+		getOutput();
+	}
+
+	function previewInputSample(sample: Record<string, any>) {
+		shouldAudioAutoplay = true;
+		filename = sample.example_title;
 		fileUrl = sample.src;
+		output = "";
+		outputJson = "";
 	}
 </script>
 
@@ -137,6 +139,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
@@ -155,29 +158,12 @@
 				/>
 			</div>
 			{#if fileUrl}
-				<WidgetAudioTrack classNames="mt-3" label={filename} src={fileUrl} />
-			{/if}
-			{#if model.widgetData}
-				<details
-					open={areSamplesVisible}
-					class="text-gray-500 text-sm mt-4 mb-2"
-				>
-					<summary class="mb-2">Or pick a sample audio file</summary>
-					<div class="mt-4 space-y-5">
-						<!-- Shouldnt this be an option ? -->
-						{#each model.widgetData as widgetData}
-							<WidgetAudioTrack classNames="mt-3" src={String(widgetData.src)}>
-								<WidgetRadio
-									bind:group={selectedSampleUrl}
-									classNames="mb-1.5"
-									label={String(widgetData.label)}
-									onChange={onChangeRadio}
-									value={String(widgetData.src)}
-								/>
-							</WidgetAudioTrack>
-						{/each}
-					</div>
-				</details>
+				<WidgetAudioTrack
+					classNames="mt-3"
+					autoplay={shouldAudioAutoplay}
+					label={filename}
+					src={fileUrl}
+				/>
 			{/if}
 			<WidgetSubmitBtn
 				classNames="mt-2"
diff --git a/widgets/src/lib/InferenceWidget/widgets/ConversationalWidget/ConversationalWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/ConversationalWidget/ConversationalWidget.svelte
index 175f048f..64aae03a 100644
--- a/widgets/src/lib/InferenceWidget/widgets/ConversationalWidget/ConversationalWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/ConversationalWidget/ConversationalWidget.svelte
@@ -90,7 +90,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -153,8 +153,13 @@
 		);
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -167,9 +172,10 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
-		<WidgetOutputConvo modelId={model.modelId} {output} />
+		<WidgetOutputConvo modelId={model.id} {output} />
 		<form>
 			<WidgetQuickInput
 				bind:value={text}
diff --git a/widgets/src/lib/InferenceWidget/widgets/FeatureExtractionWidget/FeatureExtractionWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/FeatureExtractionWidget/FeatureExtractionWidget.svelte
index 2737314e..a1f1616f 100644
--- a/widgets/src/lib/InferenceWidget/widgets/FeatureExtractionWidget/FeatureExtractionWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/FeatureExtractionWidget/FeatureExtractionWidget.svelte
@@ -66,7 +66,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -121,8 +121,13 @@
 		return Math.ceil(total_elems / SINGLE_DIM_COLS);
 	};
 
+	function previewInputSample(sample: Record<string, any>) {
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -135,6 +140,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
diff --git a/widgets/src/lib/InferenceWidget/widgets/FillMaskWidget/FillMaskWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/FillMaskWidget/FillMaskWidget.svelte
index d53b7f44..8a7bb8fc 100644
--- a/widgets/src/lib/InferenceWidget/widgets/FillMaskWidget/FillMaskWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/FillMaskWidget/FillMaskWidget.svelte
@@ -66,7 +66,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -114,8 +114,13 @@
 		throw new TypeError("Invalid output: output must be of type Array");
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -128,6 +133,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
diff --git a/widgets/src/lib/InferenceWidget/widgets/ImageClassificationWidget/ImageClassificationWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/ImageClassificationWidget/ImageClassificationWidget.svelte
index 4fc4b79c..58fc3b65 100644
--- a/widgets/src/lib/InferenceWidget/widgets/ImageClassificationWidget/ImageClassificationWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/ImageClassificationWidget/ImageClassificationWidget.svelte
@@ -5,7 +5,7 @@
 	import WidgetDropzone from "../../shared/WidgetDropzone/WidgetDropzone.svelte";
 	import WidgetOutputChart from "../../shared/WidgetOutputChart/WidgetOutputChart.svelte";
 	import WidgetWrapper from "../../shared/WidgetWrapper/WidgetWrapper.svelte";
-	import { getResponse } from "../../shared/helpers";
+	import { getResponse, getBlobFromUrl } from "../../shared/helpers";
 
 	export let apiToken: WidgetProps["apiToken"];
 	export let apiUrl: WidgetProps["apiUrl"];
@@ -39,7 +39,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -87,8 +87,16 @@
 		);
 	}
 
-	function applyInputSample(sample: Record<string, any>) {
+	async function applyInputSample(sample: Record<string, any>) {
 		imgSrc = sample.src;
+		const blob = await getBlobFromUrl(imgSrc);
+		getOutput(blob);
+	}
+
+	function previewInputSample(sample: Record<string, any>) {
+		imgSrc = sample.src;
+		output = [];
+		outputJson = "";
 	}
 </script>
 
@@ -101,6 +109,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
@@ -122,7 +131,9 @@
 			<!-- Better UX for mobile/table through CSS breakpoints -->
 			{#if imgSrc}
 				{#if imgSrc}
-					<div class="mb-2 flex justify-center bg-gray-50 with-hover:hidden">
+					<div
+						class="mb-2 flex justify-center bg-gray-50 dark:bg-gray-900 with-hover:hidden"
+					>
 						<img src={imgSrc} class="pointer-events-none max-h-44" alt="" />
 					</div>
 				{/if}
diff --git a/widgets/src/lib/InferenceWidget/widgets/ObjectDetectionWidget/ObjectDetectionWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/ObjectDetectionWidget/ObjectDetectionWidget.svelte
index c2509ded..079c151b 100644
--- a/widgets/src/lib/InferenceWidget/widgets/ObjectDetectionWidget/ObjectDetectionWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/ObjectDetectionWidget/ObjectDetectionWidget.svelte
@@ -1,5 +1,5 @@
 <script>
-	import type { WidgetProps, Box } from "../../shared/types";
+	import type { WidgetProps, DetectedObject } from "../../shared/types";
 	import { mod } from "../../shared/ViewUtils";
 
 	import BoundingBoxes from "./SvgBoundingBoxes.svelte";
@@ -7,7 +7,7 @@
 	import WidgetDropzone from "../../shared/WidgetDropzone/WidgetDropzone.svelte";
 	import WidgetOutputChart from "../../shared/WidgetOutputChart/WidgetOutputChart.svelte";
 	import WidgetWrapper from "../../shared/WidgetWrapper/WidgetWrapper.svelte";
-	import { getResponse } from "../../shared/helpers";
+	import { getResponse, getBlobFromUrl } from "../../shared/helpers";
 
 	export let apiToken: WidgetProps["apiToken"];
 	export let apiUrl: WidgetProps["apiUrl"];
@@ -23,11 +23,7 @@
 		isLoading: false,
 		estimatedTime: 0,
 	};
-	let output: Array<{
-		label: string;
-		score: number;
-		box: Box;
-	}> = [];
+	let output: DetectedObject[] = [];
 	let outputJson: string;
 	let highlightIndex = -1;
 
@@ -42,12 +38,6 @@
 		"lime",
 	] as const;
 
-	$: outputWithColor = output.map((val, index) => {
-		const hash = mod(index, COLORS.length);
-		const color = COLORS[hash];
-		return { ...val, color };
-	});
-
 	function onSelectFile(file: File | Blob) {
 		imgSrc = URL.createObjectURL(file);
 		getOutput(file);
@@ -71,7 +61,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -84,6 +74,7 @@
 		if (res.status === "success") {
 			computeTime = res.computeTime;
 			output = res.output;
+			output = output.map((o, idx) => addOutputColor(o, idx));
 			outputJson = res.outputJson;
 			if (output.length === 0) {
 				warning = "No object was detected";
@@ -99,9 +90,13 @@
 		}
 	}
 
-	function isValidOutput(
-		arg: any
-	): arg is { label: string; score: number; box: Box }[] {
+	function addOutputColor(detObj: DetectedObject, idx: number) {
+		const hash = mod(idx, COLORS.length);
+		const color = COLORS[hash];
+		return { ...detObj, color };
+	}
+
+	function isValidOutput(arg: any): arg is DetectedObject[] {
 		return (
 			Array.isArray(arg) &&
 			arg.every(
@@ -116,9 +111,7 @@
 		);
 	}
 
-	function parseOutput(
-		body: unknown
-	): Array<{ label: string; score: number; box: Box }> {
+	function parseOutput(body: unknown): DetectedObject[] {
 		if (isValidOutput(body)) {
 			return body;
 		}
@@ -135,8 +128,16 @@
 		highlightIndex = index;
 	}
 
-	function applyInputSample(sample: Record<string, any>) {
+	async function applyInputSample(sample: Record<string, any>) {
 		imgSrc = sample.src;
+		const blob = await getBlobFromUrl(imgSrc);
+		getOutput(blob);
+	}
+
+	function previewInputSample(sample: Record<string, any>) {
+		imgSrc = sample.src;
+		output = [];
+		outputJson = "";
 	}
 </script>
 
@@ -149,6 +150,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
@@ -164,7 +166,7 @@
 						{imgSrc}
 						{mouseover}
 						{mouseout}
-						output={outputWithColor}
+						{output}
 						{highlightIndex}
 					/>
 				{/if}
@@ -176,7 +178,7 @@
 					{imgSrc}
 					{mouseover}
 					{mouseout}
-					output={outputWithColor}
+					{output}
 					{highlightIndex}
 				/>
 			{/if}
@@ -195,7 +197,7 @@
 	<svelte:fragment slot="bottom">
 		<WidgetOutputChart
 			classNames="mt-4"
-			output={outputWithColor}
+			{output}
 			{highlightIndex}
 			{mouseover}
 			{mouseout}
diff --git a/widgets/src/lib/InferenceWidget/widgets/ObjectDetectionWidget/SvgBoundingBoxes.svelte b/widgets/src/lib/InferenceWidget/widgets/ObjectDetectionWidget/SvgBoundingBoxes.svelte
index 307edfc2..7cb09a9f 100644
--- a/widgets/src/lib/InferenceWidget/widgets/ObjectDetectionWidget/SvgBoundingBoxes.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/ObjectDetectionWidget/SvgBoundingBoxes.svelte
@@ -10,7 +10,7 @@ text-cyan-400
 text-lime-400
  -->
 <script>
-	import type { Box } from "../../shared/types";
+	import type { DetectedObject } from "../../shared/types";
 
 	import { afterUpdate } from "svelte";
 
@@ -29,7 +29,7 @@ text-lime-400
 	export let classNames = "";
 	export let highlightIndex = -1;
 	export let imgSrc: string;
-	export let output: Array<{ box: Box; color: string }> = [];
+	export let output: DetectedObject[] = [];
 	export let mouseover: (index: number) => void = () => {};
 	export let mouseout: () => void = () => {};
 
diff --git a/widgets/src/lib/InferenceWidget/widgets/QuestionAnsweringWidget/QuestionAnsweringWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/QuestionAnsweringWidget/QuestionAnsweringWidget.svelte
index dddeb8e5..0754fad2 100644
--- a/widgets/src/lib/InferenceWidget/widgets/QuestionAnsweringWidget/QuestionAnsweringWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/QuestionAnsweringWidget/QuestionAnsweringWidget.svelte
@@ -84,7 +84,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -128,9 +128,15 @@
 		);
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		question = sample.text;
+		context = sample.context;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		question = sample.text;
 		context = sample.context;
+		getOutput();
 	}
 </script>
 
@@ -143,6 +149,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form class="space-y-2">
diff --git a/widgets/src/lib/InferenceWidget/widgets/SentenceSimilarityWidget/SentenceSimilarityWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/SentenceSimilarityWidget/SentenceSimilarityWidget.svelte
index 3c9bc3f8..3021c8ec 100644
--- a/widgets/src/lib/InferenceWidget/widgets/SentenceSimilarityWidget/SentenceSimilarityWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/SentenceSimilarityWidget/SentenceSimilarityWidget.svelte
@@ -85,7 +85,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -126,10 +126,17 @@
 		throw new TypeError("Invalid output: output must be of type Array");
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		sourceSentence = sample.source_sentence;
+		comparisonSentences = sample.sentences;
+		nComparisonSentences = comparisonSentences.length;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		sourceSentence = sample.source_sentence;
 		comparisonSentences = sample.sentences;
 		nComparisonSentences = comparisonSentences.length;
+		getOutput();
 	}
 </script>
 
@@ -142,6 +149,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form class="flex flex-col space-y-2">
diff --git a/widgets/src/lib/InferenceWidget/widgets/StructuredDataClassificationWidget/StructuredDataClassificationWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/StructuredDataClassificationWidget/StructuredDataClassificationWidget.svelte
index ba178370..c06d21f9 100644
--- a/widgets/src/lib/InferenceWidget/widgets/StructuredDataClassificationWidget/StructuredDataClassificationWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/StructuredDataClassificationWidget/StructuredDataClassificationWidget.svelte
@@ -119,7 +119,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -184,8 +184,13 @@
 		}, {});
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		table = sample.structuredData;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		table = sample.structuredData;
+		getOutput();
 	}
 </script>
 
@@ -198,6 +203,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
diff --git a/widgets/src/lib/InferenceWidget/widgets/SummarizationWidget/SummarizationWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/SummarizationWidget/SummarizationWidget.svelte
index 2ef2e68b..f0a45a7b 100644
--- a/widgets/src/lib/InferenceWidget/widgets/SummarizationWidget/SummarizationWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/SummarizationWidget/SummarizationWidget.svelte
@@ -67,7 +67,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -106,8 +106,13 @@
 		);
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -120,6 +125,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form class="space-y-2">
diff --git a/widgets/src/lib/InferenceWidget/widgets/TableQuestionAnsweringWidget/TableQuestionAnsweringWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/TableQuestionAnsweringWidget/TableQuestionAnsweringWidget.svelte
index 2eaab3b4..007456b1 100644
--- a/widgets/src/lib/InferenceWidget/widgets/TableQuestionAnsweringWidget/TableQuestionAnsweringWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/TableQuestionAnsweringWidget/TableQuestionAnsweringWidget.svelte
@@ -106,7 +106,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -155,9 +155,15 @@
 		);
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		query = sample.text;
+		table = sample.table;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		query = sample.text;
 		table = sample.table;
+		getOutput();
 	}
 </script>
 
@@ -170,6 +176,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
diff --git a/widgets/src/lib/InferenceWidget/widgets/TextGenerationWidget/TextGenerationWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/TextGenerationWidget/TextGenerationWidget.svelte
index e8370a2e..0894d8e5 100644
--- a/widgets/src/lib/InferenceWidget/widgets/TextGenerationWidget/TextGenerationWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/TextGenerationWidget/TextGenerationWidget.svelte
@@ -76,7 +76,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -121,8 +121,13 @@
 		);
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -135,6 +140,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form class="space-y-2">
diff --git a/widgets/src/lib/InferenceWidget/widgets/TextToImageWidget/TextToImageWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/TextToImageWidget/TextToImageWidget.svelte
index 3c6794bb..f9a82ab0 100644
--- a/widgets/src/lib/InferenceWidget/widgets/TextToImageWidget/TextToImageWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/TextToImageWidget/TextToImageWidget.svelte
@@ -64,7 +64,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -103,8 +103,13 @@
 		);
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -117,6 +122,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
diff --git a/widgets/src/lib/InferenceWidget/widgets/TextToSpeechWidget/TextToSpeechWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/TextToSpeechWidget/TextToSpeechWidget.svelte
index 804e5fb8..b0da43cc 100644
--- a/widgets/src/lib/InferenceWidget/widgets/TextToSpeechWidget/TextToSpeechWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/TextToSpeechWidget/TextToSpeechWidget.svelte
@@ -65,7 +65,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -104,8 +104,13 @@
 		);
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -118,6 +123,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
diff --git a/widgets/src/lib/InferenceWidget/widgets/TokenClassificationWidget/TokenClassificationWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/TokenClassificationWidget/TokenClassificationWidget.svelte
index 85af8608..587604bb 100644
--- a/widgets/src/lib/InferenceWidget/widgets/TokenClassificationWidget/TokenClassificationWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/TokenClassificationWidget/TokenClassificationWidget.svelte
@@ -82,7 +82,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -228,8 +228,13 @@
 		return a.type === b.type && a.start === b.start && a.end === b.end;
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -242,6 +247,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form>
diff --git a/widgets/src/lib/InferenceWidget/widgets/ZeroShowClassificationWidget/ZeroShotClassificationWidget.svelte b/widgets/src/lib/InferenceWidget/widgets/ZeroShowClassificationWidget/ZeroShotClassificationWidget.svelte
index 8fd1ce3f..8d8c8860 100644
--- a/widgets/src/lib/InferenceWidget/widgets/ZeroShowClassificationWidget/ZeroShotClassificationWidget.svelte
+++ b/widgets/src/lib/InferenceWidget/widgets/ZeroShowClassificationWidget/ZeroShotClassificationWidget.svelte
@@ -100,7 +100,7 @@
 
 		const res = await getResponse(
 			apiUrl,
-			model.modelId,
+			model.id,
 			requestBody,
 			apiToken,
 			parseOutput,
@@ -149,10 +149,17 @@
 		);
 	}
 
+	function previewInputSample(sample: Record<string, any>) {
+		candidateLabels = sample.candidate_labels;
+		multiClass = sample.multi_class;
+		text = sample.text;
+	}
+
 	function applyInputSample(sample: Record<string, any>) {
 		candidateLabels = sample.candidate_labels;
 		multiClass = sample.multi_class;
 		text = sample.text;
+		getOutput();
 	}
 </script>
 
@@ -165,6 +172,7 @@
 	{modelLoading}
 	{noTitle}
 	{outputJson}
+	{previewInputSample}
 >
 	<svelte:fragment slot="top">
 		<form class="flex flex-col space-y-2">
diff --git a/widgets/src/lib/interfaces/Libraries.ts b/widgets/src/lib/interfaces/Libraries.ts
index e8c8248f..90137f86 100644
--- a/widgets/src/lib/interfaces/Libraries.ts
+++ b/widgets/src/lib/interfaces/Libraries.ts
@@ -59,19 +59,19 @@ const adapter_transformers = (model: ModelData) =>
 `from transformers import ${model.config?.adapter_transformers?.model_class}
 
 model = ${model.config?.adapter_transformers?.model_class}.from_pretrained("${model.config?.adapter_transformers?.model_name}")
-model.load_adapter("${model.modelId}", source="hf")`;
+model.load_adapter("${model.id}", source="hf")`;
 
 const allennlpUnknown = (model: ModelData) =>
 `import allennlp_models
 from allennlp.predictors.predictor import Predictor
 
-predictor = Predictor.from_path("hf://${model.modelId}")`
+predictor = Predictor.from_path("hf://${model.id}")`
 
 const allennlpQuestionAnswering = (model: ModelData) =>
 `import allennlp_models
 from allennlp.predictors.predictor import Predictor
 
-predictor = Predictor.from_path("hf://${model.modelId}")
+predictor = Predictor.from_path("hf://${model.id}")
 predictor_input = {"passage": "My name is Wolfgang and I live in Berlin", "question": "Where do I live?"}
 predictions = predictor.predict_json(predictor_input)`;
 
@@ -85,12 +85,12 @@ const allennlp = (model: ModelData) => {
 const asteroid = (model: ModelData) =>
 `from asteroid.models import BaseModel
   
-model = BaseModel.from_pretrained("${model.modelId}")`;
+model = BaseModel.from_pretrained("${model.id}")`;
 
 const espnetTTS = (model: ModelData) =>
 `from espnet2.bin.tts_inference import Text2Speech
     
-model = Text2Speech.from_pretrained("${model.modelId}")
+model = Text2Speech.from_pretrained("${model.id}")
 
 speech, *_ = model("text to generate speech from")`;
 
@@ -98,7 +98,7 @@ const espnetASR = (model: ModelData) =>
 `from espnet2.bin.asr_inference import Speech2Text
     
 model = Speech2Text.from_pretrained(
-  "${model.modelId}"
+  "${model.id}"
 )
 
 speech, rate = soundfile.read("speech.wav")
@@ -119,18 +119,18 @@ const espnet = (model: ModelData) => {
 const flair = (model: ModelData) =>
 `from flair.models import SequenceTagger
   
-tagger = SequenceTagger.load("${model.modelId}")`;
+tagger = SequenceTagger.load("${model.id}")`;
 
 const keras = (model: ModelData) =>
 `from huggingface_hub import from_pretrained_keras
 
-model = from_pretrained_keras("${model.modelId}")
+model = from_pretrained_keras("${model.id}")
 `;
 
 const pyannote = (model: ModelData) =>
 `from pyannote.audio.core.inference import Inference
   
-model = Inference("${model.modelId}")
+model = Inference("${model.id}")
 
 # inference on the whole file
 model("file.wav")
@@ -143,21 +143,21 @@ model.crop("file.wav", excerpt)`;
 const tensorflowttsTextToMel = (model: ModelData) =>
 `from tensorflow_tts.inference import AutoProcessor, TFAutoModel
 
-processor = AutoProcessor.from_pretrained("${model.modelId}")
-model = TFAutoModel.from_pretrained("${model.modelId}")
+processor = AutoProcessor.from_pretrained("${model.id}")
+model = TFAutoModel.from_pretrained("${model.id}")
 `;
 
 const tensorflowttsMelToWav = (model: ModelData) =>
 `from tensorflow_tts.inference import TFAutoModel
 
-model = TFAutoModel.from_pretrained("${model.modelId}")
+model = TFAutoModel.from_pretrained("${model.id}")
 audios = model.inference(mels)
 `;
 
 const tensorflowttsUnknown = (model: ModelData) =>
 `from tensorflow_tts.inference import TFAutoModel
 
-model = TFAutoModel.from_pretrained("${model.modelId}")
+model = TFAutoModel.from_pretrained("${model.id}")
 `;
 
 const tensorflowtts = (model: ModelData) => {
@@ -172,14 +172,14 @@ const tensorflowtts = (model: ModelData) => {
 const timm = (model: ModelData) =>
 `import timm
 
-model = timm.create_model("hf_hub:${model.modelId}", pretrained=True)`;
+model = timm.create_model("hf_hub:${model.id}", pretrained=True)`;
 
 const sklearn = (model: ModelData) => 
 `from huggingface_hub import hf_hub_download
 import joblib
 
 model = joblib.load(
-	hf_hub_download("${model.modelId}", "sklearn_model.joblib")
+	hf_hub_download("${model.id}", "sklearn_model.joblib")
 )`;
 
 const fastai = (model: ModelData) => 
@@ -187,24 +187,24 @@ const fastai = (model: ModelData) =>
 from fastai.learner import load_learner
 
 model = load_learner(
-    hf_hub_download("${model.modelId}", "model.pkl")
+    hf_hub_download("${model.id}", "model.pkl")
 )`;
 
 const sentenceTransformers = (model: ModelData) =>
 `from sentence_transformers import SentenceTransformer
 
-model = SentenceTransformer("${model.modelId}")`;
+model = SentenceTransformer("${model.id}")`;
 
 const spacy = (model: ModelData) =>
-`!pip install https://huggingface.co/${model.modelId}/resolve/main/${nameWithoutNamespace(model.modelId)}-any-py3-none-any.whl
+`!pip install https://huggingface.co/${model.id}/resolve/main/${nameWithoutNamespace(model.id)}-any-py3-none-any.whl
 
 # Using spacy.load().
 import spacy
-nlp = spacy.load("${nameWithoutNamespace(model.modelId)}")
+nlp = spacy.load("${nameWithoutNamespace(model.id)}")
 
 # Importing as module.
-import ${nameWithoutNamespace(model.modelId)}
-nlp = ${nameWithoutNamespace(model.modelId)}.load()`;
+import ${nameWithoutNamespace(model.id)}
+nlp = ${nameWithoutNamespace(model.id)}.load()`;
 
 const speechBrainMethod = (speechbrainInterface: string) => {
 	switch(speechbrainInterface) {
@@ -230,13 +230,12 @@ const speechbrain = (model: ModelData) => {
 
 	const speechbrainMethod = speechBrainMethod(speechbrainInterface);
 	if(speechbrainMethod === undefined) {
-		return `# interface in config.json invalid`
+		return `# interface in config.json invalid`
 	}
 
 	return `from speechbrain.pretrained import ${speechbrainInterface}
-
 model = ${speechbrainInterface}.from_hparams(
-  "${model.modelId}"
+  "${model.id}"
 )
 model.${speechbrainMethod}("file.wav")`;
 }
@@ -244,9 +243,9 @@ model.${speechbrainMethod}("file.wav")`;
 const transformers = (model: ModelData) =>
 `from transformers import AutoTokenizer, ${model.autoArchitecture}
   
-tokenizer = AutoTokenizer.from_pretrained("${model.modelId}"${model.private ? `, use_auth_token=True` : ``})
+tokenizer = AutoTokenizer.from_pretrained("${model.id}"${model.private ? `, use_auth_token=True` : ``})
 
-model = ${model.autoArchitecture}.from_pretrained("${model.modelId}"${model.private ? `, use_auth_token=True` : ``})`;
+model = ${model.autoArchitecture}.from_pretrained("${model.id}"${model.private ? `, use_auth_token=True` : ``})`;
 
 //#endregion
 
diff --git a/widgets/src/lib/interfaces/Types.ts b/widgets/src/lib/interfaces/Types.ts
index 9a77988c..28869788 100644
--- a/widgets/src/lib/interfaces/Types.ts
+++ b/widgets/src/lib/interfaces/Types.ts
@@ -61,28 +61,28 @@ export enum FinerGrainedTaskType {
 export type PipelineModality = "audio" | "cv" | "nlp" | "other";
 
 export const PIPELINE_TAG_MODALITIES: Record<keyof typeof PipelineType, PipelineModality> = {
-	"text-classification": "nlp",
-	"token-classification": "nlp",
-	"table-question-answering": "nlp",
-	"question-answering": "nlp",
-	"zero-shot-classification": "nlp",
-	"translation": "nlp",
-	"summarization": "nlp",
-	"conversational": "nlp",
-	"feature-extraction": "nlp",
-	"text-generation": "nlp",
-	"text2text-generation": "nlp",
-	"fill-mask": "nlp",
-	"sentence-similarity": "nlp",
-	"text-to-speech": "audio",
-	"automatic-speech-recognition": "audio",
-	"audio-to-audio": "audio",
-	"audio-classification": "audio",
-	"voice-activity-detection": "audio",
-	"image-classification": "cv",
-	"object-detection": "cv",
-	"image-segmentation": "cv",
-	"text-to-image": "cv",
+	"text-classification":            "nlp",
+	"token-classification":           "nlp",
+	"table-question-answering":       "nlp",
+	"question-answering":             "nlp",
+	"zero-shot-classification":       "nlp",
+	"translation":                    "nlp",
+	"summarization":                  "nlp",
+	"conversational":                 "nlp",
+	"feature-extraction":             "nlp",
+	"text-generation":                "nlp",
+	"text2text-generation":           "nlp",
+	"fill-mask":                      "nlp",
+	"sentence-similarity":            "nlp",
+	"text-to-speech":                 "audio",
+	"automatic-speech-recognition":   "audio",
+	"audio-to-audio":                 "audio",
+	"audio-classification":           "audio",
+	"voice-activity-detection":       "audio",
+	"image-classification":           "cv",
+	"object-detection":               "cv",
+	"image-segmentation":             "cv",
+	"text-to-image":                  "cv",
 	"structured-data-classification": "other",
 };
 
@@ -92,29 +92,29 @@ export const PIPELINE_TAG_MODALITIES: Record<keyof typeof PipelineType, Pipeline
 export const PIPELINE_TAG_ICO_CLASS: {
 	[key in keyof typeof PipelineType]?: string;
 } = {
-	"audio-classification": "tag-ico-green",
-	"audio-to-audio": "tag-ico-blue",
-	"automatic-speech-recognition": "tag-ico-yellow",
-	"conversational": "tag-ico-green",
-	"fill-mask": "tag-ico-red",
-	"feature-extraction": "tag-ico-red",
-	"image-classification": "tag-ico-blue",
-	"image-segmentation": "tag-ico-green",
-	"object-detection": "tag-ico-orange",
-	"question-answering": "tag-ico-blue",
-	"sentence-similarity": "tag-ico-orange",
+	"audio-classification":           "tag-ico-green",
+	"audio-to-audio":                 "tag-ico-blue",
+	"automatic-speech-recognition":   "tag-ico-yellow",
+	"conversational":                 "tag-ico-green",
+	"fill-mask":                      "tag-ico-red",
+	"feature-extraction":             "tag-ico-red",
+	"image-classification":           "tag-ico-blue",
+	"image-segmentation":             "tag-ico-green",
+	"object-detection":               "tag-ico-orange",
+	"question-answering":             "tag-ico-blue",
+	"sentence-similarity":            "tag-ico-orange",
 	"structured-data-classification": "tag-ico-indigo",
-	"summarization": "tag-ico-indigo",
-	"table-question-answering": "tag-ico-green",
-	"token-classification": "tag-ico-blue",
-	"text2text-generation": "tag-ico-indigo",
-	"text-classification": "tag-ico-orange",
-	"text-generation": "tag-ico-indigo",
-	"text-to-image": "tag-ico-orange",
-	"text-to-speech": "tag-ico-yellow",
-	"translation": "tag-ico-green",
-	"voice-activity-detection": "tag-ico-red",
-	"zero-shot-classification": "tag-ico-yellow",
+	"summarization":                  "tag-ico-indigo",
+	"table-question-answering":       "tag-ico-green",
+	"token-classification":           "tag-ico-blue",
+	"text2text-generation":           "tag-ico-indigo",
+	"text-classification":            "tag-ico-orange",
+	"text-generation":                "tag-ico-indigo",
+	"text-to-image":                  "tag-ico-orange",
+	"text-to-speech":                 "tag-ico-yellow",
+	"translation":                    "tag-ico-green",
+	"voice-activity-detection":       "tag-ico-red",
+	"zero-shot-classification":       "tag-ico-yellow",
 };
 
 /*
@@ -157,7 +157,11 @@ export interface ModelData {
 	/**
 	 * id of model (e.g. 'user/repo_name')
 	 */
-	modelId: string;
+	id: string;
+	/**
+	 * Kept for backward compatibility
+	 */
+	modelId?: string;
 	/**
 	 * is this model private?
 	 */
@@ -201,7 +205,7 @@ export interface ModelData {
 	 */
 	cardData?: {
 		inference?: boolean | {
-			parameters?: Record<string, any>,
-		}
-	}
+			parameters?: Record<string, any>;
+		};
+	};
 }
diff --git a/widgets/src/routes/[...model].svelte b/widgets/src/routes/[...model].svelte
index 6cbea065..df9751dc 100644
--- a/widgets/src/routes/[...model].svelte
+++ b/widgets/src/routes/[...model].svelte
@@ -43,8 +43,8 @@
 <div class="container py-24">
 	{#if model}
 		<div>
-			<a class="text-xs block mb-3 text-gray-300" href="/{model.modelId}">
-				<code>{model.modelId}</code>
+			<a class="text-xs block mb-3 text-gray-300" href="/{model.id}">
+				<code>{model.id}</code>
 			</a>
 			<div class="p-5 shadow-sm rounded-xl bg-white max-w-3xl">
 				<InferenceWidget {model} />
diff --git a/widgets/src/routes/index.svelte b/widgets/src/routes/index.svelte
index 69b8fbbd..f8db2a15 100644
--- a/widgets/src/routes/index.svelte
+++ b/widgets/src/routes/index.svelte
@@ -5,30 +5,30 @@
 
 	const models: ModelData[] = [
 		{
-			modelId: "sgugger/resnet50d",
+			id: "sgugger/resnet50d",
 			pipeline_tag: "image-classification",
 		},
 		{
-			modelId: "facebook/detr-resnet-50",
+			id: "facebook/detr-resnet-50",
 			pipeline_tag: "object-detection",
 		},
 		{
-			modelId: "julien-c/distilbert-feature-extraction",
+			id: "julien-c/distilbert-feature-extraction",
 			pipeline_tag: "feature-extraction",
 			widgetData: [{ text: "Hello world" }],
 		},
 		{
-			modelId: "sentence-transformers/distilbert-base-nli-stsb-mean-tokens",
+			id: "sentence-transformers/distilbert-base-nli-stsb-mean-tokens",
 			pipeline_tag: "feature-extraction",
 			widgetData: [{ text: "Hello, world" }],
 		},
 		{
-			modelId: "roberta-large-mnli",
+			id: "roberta-large-mnli",
 			pipeline_tag: "text-classification",
 			widgetData: [{ text: "I like you. 

 I love you." }],
 		},
 		{
-			modelId: "dbmdz/bert-large-cased-finetuned-conll03-english",
+			id: "dbmdz/bert-large-cased-finetuned-conll03-english",
 			pipeline_tag: "token-classification",
 			widgetData: [
 				{ text: "My name is Wolfgang and I live in Berlin" },
@@ -37,7 +37,7 @@
 			],
 		},
 		{
-			modelId: "distilbert-base-uncased-distilled-squad",
+			id: "distilbert-base-uncased-distilled-squad",
 			pipeline_tag: "question-answering",
 			widgetData: [
 				{
@@ -47,12 +47,12 @@
 			],
 		},
 		{
-			modelId: "t5-base",
+			id: "t5-base",
 			pipeline_tag: "translation",
 			widgetData: [{ text: "My name is Wolfgang and I live in Berlin" }],
 		},
 		{
-			modelId: "facebook/bart-large-cnn",
+			id: "facebook/bart-large-cnn",
 			pipeline_tag: "summarization",
 			widgetData: [
 				{
@@ -61,7 +61,7 @@
 			],
 		},
 		{
-			modelId: "gpt2",
+			id: "gpt2",
 			pipeline_tag: "text-generation",
 			widgetData: [
 				{ text: "My name is Julien and I like to" },
@@ -72,7 +72,7 @@
 			],
 		},
 		{
-			modelId: "distilroberta-base",
+			id: "distilroberta-base",
 			pipeline_tag: "fill-mask",
 			mask_token: "<mask>",
 			widgetData: [
@@ -81,7 +81,7 @@
 			],
 		},
 		{
-			modelId: "facebook/bart-large-mnli",
+			id: "facebook/bart-large-mnli",
 			pipeline_tag: "zero-shot-classification",
 			widgetData: [
 				{
@@ -92,7 +92,7 @@
 			],
 		},
 		{
-			modelId: "google/tapas-base-finetuned-wtq",
+			id: "google/tapas-base-finetuned-wtq",
 			pipeline_tag: "table-question-answering",
 			widgetData: [
 				{
@@ -111,7 +111,7 @@
 			],
 		},
 		{
-			modelId: "julien-c/wine-quality",
+			id: "julien-c/wine-quality",
 			pipeline_tag: "structured-data-classification",
 			widgetData: [
 				{
@@ -132,25 +132,25 @@
 			],
 		},
 		{
-			modelId: "google/t5-small-ssm-nq",
+			id: "bigscience/T0pp",
 			pipeline_tag: "text2text-generation",
 		},
 		{
-			modelId: "facebook/blenderbot-400M-distill",
+			id: "facebook/blenderbot-400M-distill",
 			pipeline_tag: "conversational",
 			widgetData: [{ text: "Hey my name is Julien! How are you?" }],
 		},
 		{
-			modelId: "osanseviero/BigGAN-deep-128",
+			id: "osanseviero/BigGAN-deep-128",
 			pipeline_tag: "text-to-image",
 		},
 		{
-			modelId: "julien-c/kan-bayashi_csmsc_tacotron2",
+			id: "julien-c/kan-bayashi_csmsc_tacotron2",
 			pipeline_tag: "text-to-speech",
 			widgetData: [{ text: "请您说得慢些好吗" }],
 		},
 		{
-			modelId: "superb/wav2vec2-base-superb-sid",
+			id: "superb/wav2vec2-base-superb-sid",
 			pipeline_tag: "audio-classification",
 			widgetData: [
 				{
@@ -160,11 +160,11 @@
 			],
 		},
 		{
-			modelId: "julien-c/mini_an4_asr_train_raw_bpe_valid",
+			id: "julien-c/mini_an4_asr_train_raw_bpe_valid",
 			pipeline_tag: "automatic-speech-recognition",
 		},
 		{
-			modelId: "facebook/wav2vec2-base-960h",
+			id: "facebook/wav2vec2-base-960h",
 			pipeline_tag: "automatic-speech-recognition",
 			widgetData: [
 				{
@@ -174,7 +174,7 @@
 			],
 		},
 		{
-			modelId: "osanseviero/full-sentence-distillroberta2",
+			id: "osanseviero/full-sentence-distillroberta2",
 			pipeline_tag: "sentence-similarity",
 			widgetData: [
 				{
@@ -188,21 +188,21 @@
 			],
 		},
 		{
-			modelId: "speechbrain/mtl-mimic-voicebank",
+			id: "speechbrain/mtl-mimic-voicebank",
 			private: false,
 			pipeline_tag: "audio-to-audio",
 			tags: ["speech-enhancement"],
 			widgetData: [],
 		},
 		{
-			modelId: "speechbrain/sepformer-wham",
+			id: "speechbrain/sepformer-wham",
 			private: false,
 			pipeline_tag: "audio-to-audio",
 			tags: ["audio-source-separation"],
 			widgetData: [],
 		},
 		{
-			modelId: "julien-c/DPRNNTasNet-ks16_WHAM_sepclean",
+			id: "julien-c/DPRNNTasNet-ks16_WHAM_sepclean",
 			private: false,
 			pipeline_tag: "audio-to-audio",
 			tags: ["audio-source-separation"],
@@ -219,8 +219,8 @@
 	>
 		{#each models as model}
 			<div>
-				<a class="text-xs block mb-3 text-gray-300" href="/{model.modelId}">
-					<code>{model.modelId}</code>
+				<a class="text-xs block mb-3 text-gray-300" href="/{model.id}">
+					<code>{model.id}</code>
 				</a>
 				<div class="p-5 shadow-sm rounded-xl bg-white max-w-md">
 					<InferenceWidget {model} />


Test patch:
diff --git a/tests/test_hf_api.py b/tests/test_hf_api.py
index 7e7b9374..8043285a 100644
--- a/tests/test_hf_api.py
+++ b/tests/test_hf_api.py
@@ -21,8 +21,14 @@ import time
 import unittest
 from io import BytesIO
 
+import pytest
+
 import requests
-from huggingface_hub.constants import REPO_TYPE_DATASET, REPO_TYPE_SPACE
+from huggingface_hub.constants import (
+    REPO_TYPE_DATASET,
+    REPO_TYPE_SPACE,
+    SPACES_SDK_TYPES,
+)
 from huggingface_hub.file_download import cached_download, hf_hub_download
 from huggingface_hub.hf_api import (
     DatasetInfo,
@@ -153,28 +159,46 @@ class HfApiEndpointsTest(HfApiCommonTestWithLogin):
             name=DATASET_REPO_NAME, token=self._token, repo_type=REPO_TYPE_DATASET
         )
 
-    @unittest.skip("skipped while spaces in beta")
     def test_create_update_and_delete_space_repo(self):
-        self._api.create_repo(
-            name=SPACE_REPO_NAME, token=self._token, repo_type=REPO_TYPE_SPACE
-        )
-        res = self._api.update_repo_visibility(
-            name=SPACE_REPO_NAME,
-            token=self._token,
-            private=True,
-            repo_type=REPO_TYPE_SPACE,
-        )
-        self.assertTrue(res["private"])
-        res = self._api.update_repo_visibility(
-            name=SPACE_REPO_NAME,
-            token=self._token,
-            private=False,
-            repo_type=REPO_TYPE_SPACE,
-        )
-        self.assertFalse(res["private"])
-        self._api.delete_repo(
-            name=SPACE_REPO_NAME, token=self._token, repo_type=REPO_TYPE_SPACE
-        )
+        with pytest.raises(ValueError, match=r"No space_sdk provided.*"):
+            self._api.create_repo(
+                token=self._token,
+                name=SPACE_REPO_NAME,
+                repo_type=REPO_TYPE_SPACE,
+                space_sdk=None,
+            )
+        with pytest.raises(ValueError, match=r"Invalid space_sdk.*"):
+            self._api.create_repo(
+                token=self._token,
+                name=SPACE_REPO_NAME,
+                repo_type=REPO_TYPE_SPACE,
+                space_sdk="asdfasdf",
+            )
+
+        for sdk in SPACES_SDK_TYPES:
+            self._api.create_repo(
+                name=SPACE_REPO_NAME,
+                token=self._token,
+                repo_type=REPO_TYPE_SPACE,
+                space_sdk=sdk,
+            )
+            res = self._api.update_repo_visibility(
+                name=SPACE_REPO_NAME,
+                token=self._token,
+                private=True,
+                repo_type=REPO_TYPE_SPACE,
+            )
+            self.assertTrue(res["private"])
+            res = self._api.update_repo_visibility(
+                name=SPACE_REPO_NAME,
+                token=self._token,
+                private=False,
+                repo_type=REPO_TYPE_SPACE,
+            )
+            self.assertFalse(res["private"])
+            self._api.delete_repo(
+                name=SPACE_REPO_NAME, token=self._token, repo_type=REPO_TYPE_SPACE
+            )
 
 
 class HfApiUploadFileTest(HfApiCommonTestWithLogin):
diff --git a/tests/test_repository.py b/tests/test_repository.py
index c9e0cc34..f954abeb 100644
--- a/tests/test_repository.py
+++ b/tests/test_repository.py
@@ -735,6 +735,168 @@ class RepositoryTest(RepositoryCommonTest):
         repo.delete_tag("v4.6.0", remote="origin")
         self.assertFalse(repo.tag_exists("v4.6.0", remote="origin"))
 
+    def test_lfs_prune(self):
+        repo = Repository(
+            WORKING_REPO_DIR,
+            clone_from=f"{USER}/{REPO_NAME}",
+            use_auth_token=self._token,
+            git_user="ci",
+            git_email="ci@dummy.com",
+            revision="main",
+        )
+
+        with repo.commit("Committing LFS file"):
+            with open("file.bin", "w+") as f:
+                f.write("Random string 1")
+
+        with repo.commit("Committing LFS file"):
+            with open("file.bin", "w+") as f:
+                f.write("Random string 2")
+
+        root_directory = pathlib.Path(repo.local_dir) / ".git" / "lfs"
+        git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+        repo.lfs_prune()
+        post_prune_git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        # Size of the directory holding LFS files was reduced
+        self.assertLess(post_prune_git_lfs_files_size, git_lfs_files_size)
+
+    def test_lfs_prune_git_push(self):
+        repo = Repository(
+            WORKING_REPO_DIR,
+            clone_from=f"{USER}/{REPO_NAME}",
+            use_auth_token=self._token,
+            git_user="ci",
+            git_email="ci@dummy.com",
+            revision="main",
+        )
+
+        with repo.commit("Committing LFS file"):
+            with open("file.bin", "w+") as f:
+                f.write("Random string 1")
+
+        root_directory = pathlib.Path(repo.local_dir) / ".git" / "lfs"
+        git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        with open(os.path.join(repo.local_dir, "file.bin"), "w+") as f:
+            f.write("Random string 2")
+
+        repo.git_add()
+        repo.git_commit("New commit")
+        repo.git_push(auto_lfs_prune=True)
+
+        post_prune_git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        # Size of the directory holding LFS files is the exact same
+        self.assertEqual(post_prune_git_lfs_files_size, git_lfs_files_size)
+
+    def test_lfs_prune_git_push_non_blocking(self):
+        repo = Repository(
+            WORKING_REPO_DIR,
+            clone_from=f"{USER}/{REPO_NAME}",
+            use_auth_token=self._token,
+            git_user="ci",
+            git_email="ci@dummy.com",
+            revision="main",
+        )
+
+        with repo.commit("Committing LFS file"):
+            with open("file.bin", "w+") as f:
+                f.write("Random string 1")
+
+        root_directory = pathlib.Path(repo.local_dir) / ".git" / "lfs"
+        git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        with open(os.path.join(repo.local_dir, "file.bin"), "w+") as f:
+            f.write("Random string 2")
+
+        repo.git_add()
+        repo.git_commit("New commit")
+        repo.git_push(blocking=False, auto_lfs_prune=True)
+
+        while len(repo.commands_in_progress):
+            time.sleep(0.2)
+
+        post_prune_git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        # Size of the directory holding LFS files is the exact same
+        self.assertEqual(post_prune_git_lfs_files_size, git_lfs_files_size)
+
+    def test_lfs_prune_context_manager(self):
+        repo = Repository(
+            WORKING_REPO_DIR,
+            clone_from=f"{USER}/{REPO_NAME}",
+            use_auth_token=self._token,
+            git_user="ci",
+            git_email="ci@dummy.com",
+            revision="main",
+        )
+
+        with repo.commit("Committing LFS file"):
+            with open("file.bin", "w+") as f:
+                f.write("Random string 1")
+
+        root_directory = pathlib.Path(repo.local_dir) / ".git" / "lfs"
+        git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        with repo.commit("Committing LFS file", auto_lfs_prune=True):
+            with open("file.bin", "w+") as f:
+                f.write("Random string 2")
+
+        post_prune_git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        # Size of the directory holding LFS files is the exact same
+        self.assertEqual(post_prune_git_lfs_files_size, git_lfs_files_size)
+
+    def test_lfs_prune_context_manager_non_blocking(self):
+        repo = Repository(
+            WORKING_REPO_DIR,
+            clone_from=f"{USER}/{REPO_NAME}",
+            use_auth_token=self._token,
+            git_user="ci",
+            git_email="ci@dummy.com",
+            revision="main",
+        )
+
+        with repo.commit("Committing LFS file"):
+            with open("file.bin", "w+") as f:
+                f.write("Random string 1")
+
+        root_directory = pathlib.Path(repo.local_dir) / ".git" / "lfs"
+        git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        with repo.commit("Committing LFS file", auto_lfs_prune=True, blocking=False):
+            with open("file.bin", "w+") as f:
+                f.write("Random string 2")
+
+        while len(repo.commands_in_progress):
+            time.sleep(0.2)
+
+        post_prune_git_lfs_files_size = sum(
+            f.stat().st_size for f in root_directory.glob("**/*") if f.is_file()
+        )
+
+        # Size of the directory holding LFS files is the exact same
+        self.assertEqual(post_prune_git_lfs_files_size, git_lfs_files_size)
+
 
 class RepositoryOfflineTest(RepositoryCommonTest):
     @classmethod