Repository: gradio-app/gradio

Issue:
Dataframe allow Markdown or HTML content
- [X] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**

Simple example here: https://huggingface.co/spaces/Gradio-Blocks/Leaderboard. Currently it's not possible to create hyperlinks inside a Dataframe table. I can see other use cases beyond `<a>` elements, maybe images or simple video previews?  I'm thinking that would be handy to preview rich content Dataframes outputs. I understand the performance issues with large tables, but I guess it needs lazy loading anyway.


Comments:

Patch:
diff --git a/demo/blocks_outputs/run.py b/demo/blocks_outputs/run.py
index 4af996dbc..084be0da9 100644
--- a/demo/blocks_outputs/run.py
+++ b/demo/blocks_outputs/run.py
@@ -1,5 +1,26 @@
 import gradio as gr
 
+
+def make_markdown():
+    return [
+        [
+            "# hello again",
+            "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
+            '<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
+        ],
+        [
+            "## hello again again",
+            "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
+            '<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
+        ],
+        [
+            "### hello thrice",
+            "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
+            '<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
+        ],
+    ]
+
+
 with gr.Blocks() as demo:
     with gr.Column():
         txt = gr.Textbox(label="Small Textbox", lines=1, show_label=False)
@@ -43,27 +64,31 @@ with gr.Blocks() as demo:
         gr.Dataframe(
             interactive=True, headers=["One", "Two", "Three", "Four"], col_count=4
         )
-        gr.DataFrame(
+        df = gr.DataFrame(
             [
                 [
+                    "# hello",
                     "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
-                    "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
-                    "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
+                    '<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
                 ],
                 [
+                    "## hello",
                     "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
-                    "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
-                    "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
+                    '<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
                 ],
                 [
+                    "### hello",
                     "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
-                    "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
-                    "Hello my name is frank, I am liking the small turtle you have there. It would be a shame if it went missing.",
+                    '<img src="https://images.unsplash.com/photo-1574613362884-f79513a5128c?fit=crop&w=500&q=80"/>',
                 ],
             ],
             headers=["One", "Two", "Three"],
             wrap=True,
+            datatype=["markdown", "markdown", "html"],
+            interactive=True,
         )
+        btn = gr.Button("Run")
+        btn.click(fn=make_markdown, inputs=None, outputs=df)
 
 
 if __name__ == "__main__":
diff --git a/gradio/blocks.py b/gradio/blocks.py
index 73b8f7bef..4910c8546 100644
--- a/gradio/blocks.py
+++ b/gradio/blocks.py
@@ -288,6 +288,7 @@ class Blocks(BlockContext):
     @classmethod
     def from_config(cls, config: dict, fns: List[Callable]) -> Blocks:
         """Factory method that creates a Blocks from a config and list of functions."""
+        config = copy.deepcopy(config)
         components_config = config["components"]
         original_mapping: Dict[int, Block] = {}
 
@@ -325,6 +326,7 @@ class Blocks(BlockContext):
                 targets = dependency.pop("targets")
                 trigger = dependency.pop("trigger")
                 dependency.pop("backend_fn")
+                dependency.pop("documentation", None)
                 dependency["inputs"] = [
                     original_mapping[i] for i in dependency["inputs"]
                 ]
diff --git a/gradio/components.py b/gradio/components.py
index f977fdca9..3a07fd38d 100644
--- a/gradio/components.py
+++ b/gradio/components.py
@@ -1233,9 +1233,7 @@ class Radio(Changeable, IOComponent):
         Returns:
         (str): string of choice
         """
-        return (
-            y if y is not None else self.choices[0] if len(self.choices) > 0 else None
-        )
+        return y
 
     def deserialize(self, x):
         """
@@ -2356,6 +2354,8 @@ class Dataframe(Changeable, IOComponent):
     Demos: filter_records, matrix_transpose, tax_calculator
     """
 
+    markdown_parser = None
+
     def __init__(
         self,
         value: Optional[List[List[Any]]] = None,
@@ -2405,13 +2405,17 @@ class Dataframe(Changeable, IOComponent):
         self.__validate_headers(headers, self.col_count[0])
 
         self.headers = headers
-        self.datatype = datatype
+        self.datatype = (
+            datatype if isinstance(datatype, list) else [datatype] * self.col_count[0]
+        )
         self.type = type
         values = {
             "str": "",
             "number": 0,
             "bool": False,
             "date": "01/01/1970",
+            "markdown": "",
+            "html": "",
         }
         column_dtypes = (
             [datatype] * self.col_count[0] if isinstance(datatype, str) else datatype
@@ -2419,7 +2423,10 @@ class Dataframe(Changeable, IOComponent):
         self.test_input = [
             [values[c] for c in column_dtypes] for _ in range(self.row_count[0])
         ]
+
         self.value = value if value is not None else self.test_input
+        self.value = self.__process_markdown(self.value, datatype)
+
         self.max_rows = max_rows
         self.max_cols = max_cols
         self.overflow_row_behaviour = overflow_row_behaviour
@@ -2520,16 +2527,24 @@ class Dataframe(Changeable, IOComponent):
         if y is None:
             return y
         if isinstance(y, str):
-            y = pd.read_csv(str)
-            return {"headers": list(y.columns), "data": y.values.tolist()}
+            y = pd.read_csv(y)
+            return {
+                "headers": list(y.columns),
+                "data": Dataframe.__process_markdown(y.values.tolist(), self.datatype),
+            }
         if isinstance(y, pd.DataFrame):
-            return {"headers": list(y.columns), "data": y.values.tolist()}
+            return {
+                "headers": list(y.columns),
+                "data": Dataframe.__process_markdown(y.values.tolist(), self.datatype),
+            }
         if isinstance(y, (np.ndarray, list)):
             if isinstance(y, np.ndarray):
                 y = y.tolist()
             if len(y) == 0 or not isinstance(y[0], list):
                 y = [y]
-            return {"data": y}
+            return {
+                "data": Dataframe.__process_markdown(y, self.datatype),
+            }
         raise ValueError("Cannot process value as a Dataframe")
 
     @staticmethod
@@ -2550,10 +2565,24 @@ class Dataframe(Changeable, IOComponent):
                 )
             )
 
+    @classmethod
+    def __process_markdown(cls, data: List[List[Any]], datatype: List[str]):
+        if "markdown" not in datatype:
+            return data
+
+        if cls.markdown_parser is None:
+            cls.markdown_parser = MarkdownIt()
+
+        for i in range(len(data)):
+            for j in range(len(data[i])):
+                if datatype[j] == "markdown":
+                    data[i][j] = Dataframe.markdown_parser.render(data[i][j])
+
+        return data
+
     def style(
         self,
         rounded: Optional[bool | Tuple[bool, bool, bool, bool]] = None,
-        border: Optional[bool | Tuple[bool, bool, bool, bool]] = None,
     ):
         return IOComponent.style(
             self,
@@ -2695,7 +2724,6 @@ class Timeseries(Changeable, IOComponent):
     def style(
         self,
         rounded: Optional[bool | Tuple[bool, bool, bool, bool]] = None,
-        border: Optional[bool | Tuple[bool, bool, bool, bool]] = None,
     ):
         return IOComponent.style(
             self,
diff --git a/ui/packages/app/src/Blocks.svelte b/ui/packages/app/src/Blocks.svelte
index 752d9b5e4..0243e1548 100644
--- a/ui/packages/app/src/Blocks.svelte
+++ b/ui/packages/app/src/Blocks.svelte
@@ -119,12 +119,22 @@
 		const is_input = is_dep(id, "inputs", dependencies);
 		const is_output = is_dep(id, "outputs", dependencies);
 
-		if (!is_input && !is_output && !props.value) acc.add(id); // default dynamic
+		if (!is_input && !is_output && has_no_default_value(props.value))
+			acc.add(id); // default dynamic
 		if (is_input) acc.add(id);
 
 		return acc;
 	}, new Set());
 
+	function has_no_default_value(value: any) {
+		return (
+			(Array.isArray(value) && value.length === 0) ||
+			value === "" ||
+			value === 0 ||
+			!value
+		);
+	}
+
 	let instance_map = components.reduce((acc, next) => {
 		acc[next.id] = next;
 		return acc;
diff --git a/ui/packages/app/src/components/DataFrame/DataFrame.svelte b/ui/packages/app/src/components/DataFrame/DataFrame.svelte
index c09fae421..9f4f87c7f 100644
--- a/ui/packages/app/src/components/DataFrame/DataFrame.svelte
+++ b/ui/packages/app/src/components/DataFrame/DataFrame.svelte
@@ -7,6 +7,7 @@
 
 	type Headers = Array<string>;
 	type Data = Array<Array<string | number>>;
+	type Datatype = "str" | "markdown" | "html" | "number" | "bool" | "date";
 
 	export let headers: Headers = [];
 	export let elem_id: string = "";
@@ -19,6 +20,7 @@
 	export let style: Styles = {};
 	export let label: string | null = null;
 	export let wrap: boolean;
+	export let datatype: Datatype | Array<Datatype>;
 
 	$: {
 		if (value && !Array.isArray(value)) {
@@ -60,5 +62,6 @@
 		editable={mode === "dynamic"}
 		{style}
 		{wrap}
+		{datatype}
 	/>
 </div>
diff --git a/ui/packages/table/src/EditableCell.svelte b/ui/packages/table/src/EditableCell.svelte
index adaa2e938..d111140fd 100644
--- a/ui/packages/table/src/EditableCell.svelte
+++ b/ui/packages/table/src/EditableCell.svelte
@@ -3,6 +3,7 @@
 	export let value: string | number = "";
 	export let el: HTMLInputElement | null;
 	export let header: boolean = false;
+	export let datatype: "str" | "markdown" | "html" | "number" | "bool" | "date";
 </script>
 
 {#if edit}
@@ -24,5 +25,11 @@
 	role="button"
 	class:opacity-0={edit}
 	class:pointer-events-none={edit}
-	class="p-2  outline-none border-0 flex-1">{value}</span
+	class="p-2  outline-none border-0 flex-1"
 >
+	{#if datatype === "markdown" || datatype === "html"}
+		{@html value}
+	{:else}
+		{value}
+	{/if}
+</span>
diff --git a/ui/packages/table/src/Table.svelte b/ui/packages/table/src/Table.svelte
index f4a40278c..cd91052be 100644
--- a/ui/packages/table/src/Table.svelte
+++ b/ui/packages/table/src/Table.svelte
@@ -9,6 +9,9 @@
 	import { Upload } from "@gradio/upload";
 	import EditableCell from "./EditableCell.svelte";
 
+	type Datatype = "str" | "markdown" | "html" | "number" | "bool" | "date";
+
+	export let datatype: Datatype | Array<Datatype>;
 	export let label: string | null = null;
 	export let headers: Array<string> = [];
 	export let values: Array<Array<string | number>> = [[]];
@@ -567,6 +570,9 @@
 											bind:value
 											bind:el={els[id].input}
 											edit={editing === id}
+											datatype={Array.isArray(datatype)
+												? datatype[j]
+												: datatype}
 										/>
 									</div>
 								</td>


Test patch:
diff --git a/test/test_blocks.py b/test/test_blocks.py
index 400e14702..f1feb9c4f 100644
--- a/test/test_blocks.py
+++ b/test/test_blocks.py
@@ -88,6 +88,23 @@ class TestBlocks(unittest.TestCase):
         config.pop("version")  # remove version key
         self.assertTrue(assert_configs_are_equivalent_besides_ids(XRAY_CONFIG, config))
 
+    def test_load_from_config(self):
+        def update(name):
+            return f"Welcome to Gradio, {name}!"
+
+        with gr.Blocks() as demo1:
+            inp = gr.Textbox(placeholder="What is your name?")
+            out = gr.Textbox()
+
+            inp.submit(fn=update, inputs=inp, outputs=out, api_name="greet")
+
+            gr.Image().style(height=54, width=240)
+
+        config1 = demo1.get_config_file()
+        demo2 = gr.Blocks.from_config(config1, [update])
+        config2 = demo2.get_config_file()
+        self.assertTrue(assert_configs_are_equivalent_besides_ids(config1, config2))
+
     @pytest.mark.asyncio
     async def test_async_function(self):
         async def wait():
diff --git a/test/test_components.py b/test/test_components.py
index d0ff01735..276ce37d5 100644
--- a/test/test_components.py
+++ b/test/test_components.py
@@ -537,7 +537,7 @@ class TestRadio(unittest.TestCase):
             radio_input.get_config(),
             {
                 "choices": ["a", "b", "c"],
-                "value": "a",
+                "value": None,
                 "name": "radio",
                 "show_label": True,
                 "label": "Pick Your One Input",
@@ -1022,7 +1022,7 @@ class TestDataframe(unittest.TestCase):
             dataframe_input.get_config(),
             {
                 "headers": ["Name", "Age", "Member"],
-                "datatype": "str",
+                "datatype": ["str", "str", "str"],
                 "row_count": (3, "dynamic"),
                 "col_count": (3, "dynamic"),
                 "value": [
@@ -1079,7 +1079,7 @@ class TestDataframe(unittest.TestCase):
                 "style": {},
                 "elem_id": None,
                 "visible": True,
-                "datatype": "str",
+                "datatype": ["str", "str", "str"],
                 "row_count": (3, "dynamic"),
                 "col_count": (3, "dynamic"),
                 "value": [