Repository: gradio-app/gradio

Issue:
API docs confusing
### Describe the bug

When using `TabbedInterface`, the `fn_index` field should be included in **Input Payload** to specify which to call.

### Is there an existing issue for this?

- [X] I have searched the existing issues

### Reproduction

```python
from operator import add, sub

gr.TabbedInterface(
    [
        gr.Interface(add, ["number", "number"], "number"),
        gr.Interface(sub, ["number", "number"], "number"),
    ]
).launch()
```

```python
import requests

resp = requests.post("http://127.0.0.1:7860/api/predict", json={"data": [2, 1]})
resp = requests.post("http://127.0.0.1:7860/api/predict", json={"data": [2, 1], "fn_index": 3})
```

### Screenshot

![image](https://user-images.githubusercontent.com/46232992/187624384-e1697abc-6e57-4ace-aa9f-f3fd4f867b19.png)


### Logs

```shell
No response
```


### System Info

```shell
gradio==3.1.8b3
```


### Severity

annoying


Discussion:

Resolving patch:
diff --git a/gradio/blocks.py b/gradio/blocks.py
--- a/gradio/blocks.py
+++ b/gradio/blocks.py
@@ -146,6 +146,15 @@ def set_event_trigger(
         if not isinstance(outputs, list):
             outputs = [outputs]
         Context.root_block.fns.append(BlockFunction(fn, preprocess, postprocess))
+        if api_name is not None:
+            api_name_ = utils.append_unique_suffix(
+                api_name, [dep["api_name"] for dep in Context.root_block.dependencies]
+            )
+            if not (api_name == api_name_):
+                warnings.warn(
+                    "api_name {} already exists, using {}".format(api_name, api_name_)
+                )
+                api_name = api_name_
         dependency = {
             "targets": [self._id] if not no_target else [],
             "trigger": event_name,
@@ -572,7 +581,21 @@ def render(self):
         if Context.root_block is not None:
             Context.root_block.blocks.update(self.blocks)
             Context.root_block.fns.extend(self.fns)
-            Context.root_block.dependencies.extend(self.dependencies)
+            for dependency in self.dependencies:
+                api_name = dependency["api_name"]
+                if api_name is not None:
+                    api_name_ = utils.append_unique_suffix(
+                        api_name,
+                        [dep["api_name"] for dep in Context.root_block.dependencies],
+                    )
+                    if not (api_name == api_name_):
+                        warnings.warn(
+                            "api_name {} already exists, using {}".format(
+                                api_name, api_name_
+                            )
+                        )
+                        dependency["api_name"] = api_name_
+                Context.root_block.dependencies.append(dependency)
             Context.root_block.temp_dirs = Context.root_block.temp_dirs | self.temp_dirs
         if Context.block is not None:
             Context.block.children.extend(self.children)
diff --git a/gradio/routes.py b/gradio/routes.py
--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -68,7 +68,7 @@ class QueuePushBody(BaseModel):
 class PredictBody(BaseModel):
     session_hash: Optional[str]
     data: Any
-    fn_index: int = 0
+    fn_index: Optional[int]
 
 
 ###########
diff --git a/gradio/utils.py b/gradio/utils.py
--- a/gradio/utils.py
+++ b/gradio/utils.py
@@ -656,3 +656,17 @@ def sanitize_list_for_csv(
             sanitized_value = sanitize_value_for_csv(value)
             sanitized_values.append(sanitized_value)
     return sanitized_values
+
+
+def append_unique_suffix(name: str, list_of_names: List[str]):
+    """Appends a numerical suffix to `name` so that it does not appear in `list_of_names`."""
+    list_of_names = set(list_of_names)  # for O(1) lookup
+    if name not in list_of_names:
+        return name
+    else:
+        suffix_counter = 1
+        new_name = name + f"_{suffix_counter}"
+        while new_name in list_of_names:
+            suffix_counter += 1
+            new_name = name + f"_{suffix_counter}"
+        return new_name


Test patch:
diff --git a/test/test_routes.py b/test/test_routes.py
--- a/test/test_routes.py
+++ b/test/test_routes.py
@@ -7,7 +7,7 @@
 from fastapi import FastAPI
 from fastapi.testclient import TestClient
 
-from gradio import Interface, close_all, routes
+from gradio import Blocks, Interface, Textbox, close_all, routes
 
 os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
 
@@ -45,6 +45,69 @@ def test_predict_route(self):
         output = dict(response.json())
         self.assertEqual(output["data"], ["testtest"])
 
+    def test_named_predict_route(self):
+        with Blocks() as demo:
+            i = Textbox()
+            o = Textbox()
+            i.change(lambda x: x + "1", i, o, api_name="p")
+            i.change(lambda x: x + "2", i, o, api_name="q")
+
+        app, _, _ = demo.launch(prevent_thread_lock=True)
+        client = TestClient(app)
+        response = client.post("/api/p/", json={"data": ["test"]})
+        assert response.status_code == 200
+        output = dict(response.json())
+        assert output["data"] == ["test1"]
+
+        response = client.post("/api/q/", json={"data": ["test"]})
+        assert response.status_code == 200
+        output = dict(response.json())
+        assert output["data"] == ["test2"]
+
+    def test_same_named_predict_route(self):
+        with Blocks() as demo:
+            i = Textbox()
+            o = Textbox()
+            i.change(lambda x: x + "0", i, o, api_name="p")
+            i.change(lambda x: x + "1", i, o, api_name="p")
+
+        app, _, _ = demo.launch(prevent_thread_lock=True)
+        client = TestClient(app)
+        response = client.post("/api/p/", json={"data": ["test"]})
+        assert response.status_code == 200
+        output = dict(response.json())
+        assert output["data"] == ["test0"]
+
+        response = client.post("/api/p_1/", json={"data": ["test"]})
+        assert response.status_code == 200
+        output = dict(response.json())
+        assert output["data"] == ["test1"]
+
+    def test_multiple_renamed(self):
+        with Blocks() as demo:
+            i = Textbox()
+            o = Textbox()
+            i.change(lambda x: x + "0", i, o, api_name="p")
+            i.change(lambda x: x + "1", i, o, api_name="p")
+            i.change(lambda x: x + "2", i, o, api_name="p_1")
+
+        app, _, _ = demo.launch(prevent_thread_lock=True)
+        client = TestClient(app)
+        response = client.post("/api/p/", json={"data": ["test"]})
+        assert response.status_code == 200
+        output = dict(response.json())
+        assert output["data"] == ["test0"]
+
+        response = client.post("/api/p_1/", json={"data": ["test"]})
+        assert response.status_code == 200
+        output = dict(response.json())
+        assert output["data"] == ["test1"]
+
+        response = client.post("/api/p_1_1/", json={"data": ["test"]})
+        assert response.status_code == 200
+        output = dict(response.json())
+        assert output["data"] == ["test2"]
+
     def test_predict_route_without_fn_index(self):
         response = self.client.post("/api/predict/", json={"data": ["test"]})
         self.assertEqual(response.status_code, 200)
diff --git a/test/test_utils.py b/test/test_utils.py
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -20,6 +20,7 @@
 )
 from gradio.utils import (
     Request,
+    append_unique_suffix,
     assert_configs_are_equivalent_besides_ids,
     colab_check,
     delete_none,
@@ -492,5 +493,22 @@ def test_list(self):
         assert sanitize_list_for_csv([1, ["ab", "=de"]]) == [1, ["ab", "'=de"]]
 
 
+class TestAppendUniqueSuffix:
+    def test_no_suffix(self):
+        name = "test"
+        list_of_names = ["test_1", "test_2"]
+        assert append_unique_suffix(name, list_of_names) == name
+
+    def test_first_suffix(self):
+        name = "test"
+        list_of_names = ["test", "test_-1"]
+        assert append_unique_suffix(name, list_of_names) == "test_1"
+
+    def test_later_suffix(self):
+        name = "test"
+        list_of_names = ["test", "test_1", "test_2", "test_3"]
+        assert append_unique_suffix(name, list_of_names) == "test_4"
+
+
 if __name__ == "__main__":
     unittest.main()