Repository: gradio-app/gradio

Issue:
The `interactive` parameter in `gr.FileExplorer` has no effect. It should either be correctly documented or the feature should be added
- [X] I have searched to see if a similar issue already exists.


### Is your feature request related to a problem? 

 interactive -  if True, will allow users to upload a file; if False, can only be used to display files. If not provided, this is inferred based on whether the component is used as an input or output.

This functionality apparently does nothing. I used like this:

```python
gr.FileExplorer(root='./voices', height=400, interactive=True, label="voices available", file_count="multiple", glob="**/*.*", ignore_glob="*.db,")
```

Either it should be documented correctly, or the ability to upload files should be added:

This functionality could be like google colab file explorer, enabling users to handle their files without need direct access to server.
And also could allow see empty folders on root folder I defined

![image](https://github.com/gradio-app/gradio/assets/11916455/747cf85c-5000-4b46-b5fe-92046f654641)




Discussion:
gradio-pr-bot: <!-- GRADIO_GITHUB_ACTION_COMMENT_ID_PREVIEWS -->

## 🪼 branch checks and previews

| • | Name | Status | URL |
|:---:|:---|:---|:---|
| :unicorn: | **Changes** | <img align=center src="https://github.com/pngwn/MDsveX/assets/12937446/7f331dc1-7774-4484-94ce-2f2bec254510" width="18px" /> detecting... |  |
gradio-pr-bot: <!-- tag=changesets_gradio -->

###  🦄 change detected

#### This Pull Request includes changes to the following packages. 

| Package | Version |
|--------|--------|
|`@gradio/fileexplorer` | `patch` |
|`gradio` | `patch` |

- [ ] Maintainers can  select this checkbox to manually select packages to update.


#### With the following changelog entry.

> Fix File Explorer interactivity

_Maintainers or the PR author can modify the PR title to modify this entry._
<details><summary>

#### Something isn't right?</summary>

- Maintainers can change the version label to modify the version bump. 
- If the bot has failed to detect any changes, or if this pull request needs to update multiple packages to different versions or requires a more comprehensive changelog entry, maintainers can [update the changelog file directly](https://github.com/gradio-app/gradio/edit/fix_file_explorer_interactivity/.changeset/solid-coats-sort.md).

</details>
abidlabs: Thanks @aliabid94! One thing to think about -- usually, the non-interactive version of a component is used to display the `value` of a component. Here, the `value` are the selected files, however, these are not displayed at all when `interactive=False`. Would it make sense to just show the selected files, but not let users modify them? 

Patch:
diff --git a/.changeset/mean-bushes-hide.md b/.changeset/mean-bushes-hide.md
new file mode 100644
index 000000000..c9e6e2d24
--- /dev/null
+++ b/.changeset/mean-bushes-hide.md
@@ -0,0 +1,5 @@
+---
+"gradio": patch
+---
+
+fix:Allow config to include non-pickle-able values
diff --git a/.changeset/solid-coats-sort.md b/.changeset/solid-coats-sort.md
new file mode 100644
index 000000000..fb8158aac
--- /dev/null
+++ b/.changeset/solid-coats-sort.md
@@ -0,0 +1,5 @@
+---
+"gradio": patch
+---
+
+fix:Fix File Explorer interactivity
diff --git a/.changeset/tough-baboons-greet.md b/.changeset/tough-baboons-greet.md
new file mode 100644
index 000000000..25fbb8f05
--- /dev/null
+++ b/.changeset/tough-baboons-greet.md
@@ -0,0 +1,6 @@
+---
+"@gradio/model3d": minor
+"gradio": minor
+---
+
+feat:Model3D Gaussian Splatting
diff --git a/demo/model3D/run.ipynb b/demo/model3D/run.ipynb
index e44bd515a..93d8e7e11 100644
--- a/demo/model3D/run.ipynb
+++ b/demo/model3D/run.ipynb
@@ -1,1 +1,1 @@
-{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: model3D"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/Bunny.obj https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Bunny.obj\n", "!wget -q -O files/Duck.glb https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Duck.glb\n", "!wget -q -O files/Fox.gltf https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Fox.gltf\n", "!wget -q -O files/face.obj https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/face.obj\n", "!wget -q -O files/sofia.stl https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/sofia.stl\n", "!wget -q -O files/source.txt https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/source.txt"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def load_mesh(mesh_file_name):\n", "    return mesh_file_name\n", "\n", "\n", "demo = gr.Interface(\n", "    fn=load_mesh,\n", "    inputs=gr.Model3D(),\n", "    outputs=gr.Model3D(\n", "            clear_color=[0.0, 0.0, 0.0, 0.0],  label=\"3D Model\"),\n", "    examples=[\n", "        [os.path.join(os.path.abspath(''), \"files/Bunny.obj\")],\n", "        [os.path.join(os.path.abspath(''), \"files/Duck.glb\")],\n", "        [os.path.join(os.path.abspath(''), \"files/Fox.gltf\")],\n", "        [os.path.join(os.path.abspath(''), \"files/face.obj\")],\n", "        [os.path.join(os.path.abspath(''), \"files/sofia.stl\")],\n", "    ],\n", "    cache_examples=True\n", ")\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
+{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: model3D"]}, {"cell_type": "code", "execution_count": null, "id": "272996653310673477252411125948039410165", "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": "288918539441861185822528903084949547379", "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "os.mkdir('files')\n", "!wget -q -O files/Bunny.obj https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Bunny.obj\n", "!wget -q -O files/Duck.glb https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Duck.glb\n", "!wget -q -O files/Fox.gltf https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/Fox.gltf\n", "!wget -q -O files/face.obj https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/face.obj\n", "!wget -q -O files/sofia.stl https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/sofia.stl\n", "!wget -q -O files/source.txt https://github.com/gradio-app/gradio/raw/main/demo/model3D/files/source.txt"]}, {"cell_type": "code", "execution_count": null, "id": "44380577570523278879349135829904343037", "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "\n", "\n", "def load_mesh(mesh_file_name):\n", "    return mesh_file_name\n", "\n", "\n", "demo = gr.Interface(\n", "    fn=load_mesh,\n", "    inputs=gr.Model3D(),\n", "    outputs=gr.Model3D(\n", "            clear_color=[0.0, 0.0, 0.0, 0.0],  label=\"3D Model\"),\n", "    examples=[\n", "        [os.path.join(os.path.abspath(''), \"files/Bunny.obj\")],\n", "        [os.path.join(os.path.abspath(''), \"files/Duck.glb\")],\n", "        [os.path.join(os.path.abspath(''), \"files/Fox.gltf\")],\n", "        [os.path.join(os.path.abspath(''), \"files/face.obj\")],\n", "        [os.path.join(os.path.abspath(''), \"files/sofia.stl\")],\n", "        [\"https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/bonsai/bonsai-7k-mini.splat\"],\n", "        [\"https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/luigi/luigi.ply\"],\n", "    ],\n", "    cache_examples=True\n", ")\n", "\n", "if __name__ == \"__main__\":\n", "    demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
\ No newline at end of file
diff --git a/demo/model3D/run.py b/demo/model3D/run.py
index 232d7aac5..acd3f1465 100644
--- a/demo/model3D/run.py
+++ b/demo/model3D/run.py
@@ -17,6 +17,8 @@ demo = gr.Interface(
         [os.path.join(os.path.dirname(__file__), "files/Fox.gltf")],
         [os.path.join(os.path.dirname(__file__), "files/face.obj")],
         [os.path.join(os.path.dirname(__file__), "files/sofia.stl")],
+        ["https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/bonsai/bonsai-7k-mini.splat"],
+        ["https://huggingface.co/datasets/dylanebert/3dgs/resolve/main/luigi/luigi.ply"],
     ],
     cache_examples=True
 )
diff --git a/gradio/components/file_explorer.py b/gradio/components/file_explorer.py
index d2076a6b2..4af663619 100644
--- a/gradio/components/file_explorer.py
+++ b/gradio/components/file_explorer.py
@@ -66,7 +66,7 @@ class FileExplorer(Component):
             scale: relative size compared to adjacent Components. For example if Components A and B are in a Row, and A has scale=2, and B has scale=1, A will be twice as wide as B. Should be an integer. scale applies in Rows, and to top-level Components in Blocks where fill_height=True.
             min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
             height: The maximum height of the file component, specified in pixels if a number is passed, or in CSS units if a string is passed. If more files are uploaded than can fit in the height, a scrollbar will appear.
-            interactive: if True, will allow users to upload a file; if False, can only be used to display files. If not provided, this is inferred based on whether the component is used as an input or output.
+            interactive: if True, will allow users to select file(s); if False, will only display files. If not provided, this is inferred based on whether the component is used as an input or output.
             visible: If False, component will be hidden.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
             elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
diff --git a/gradio/components/model3d.py b/gradio/components/model3d.py
index a11d8ae6b..034d43096 100644
--- a/gradio/components/model3d.py
+++ b/gradio/components/model3d.py
@@ -15,7 +15,7 @@ from gradio.events import Events
 @document()
 class Model3D(Component):
     """
-    Creates a component allows users to upload or view 3D Model files (.obj, .glb, .stl, or .gltf).
+    Creates a component allows users to upload or view 3D Model files (.obj, .glb, .stl, .gltf, .splat, or .ply).
 
     Demos: model3D
     Guides: how-to-use-3D-model-component
@@ -54,7 +54,7 @@ class Model3D(Component):
     ):
         """
         Parameters:
-            value: path to (.obj, .glb, .stl, or .gltf) file to show in model3D viewer. If callable, the function will be called whenever the app loads to set the initial value of the component.
+            value: path to (.obj, .glb, .stl, .gltf, .splat, or .ply) file to show in model3D viewer. If callable, the function will be called whenever the app loads to set the initial value of the component.
             clear_color: background color of scene, should be a tuple of 4 floats between 0 and 1 representing RGBA values.
             camera_position: initial camera position of scene, provided as a tuple of `(alpha, beta, radius)`. Each value is optional. If provided, `alpha` and `beta` should be in degrees reflecting the angular position along the longitudinal and latitudinal axes, respectively. Radius corresponds to the distance from the center of the object to the camera.
             zoom_speed: the speed of zooming in and out of the scene when the cursor wheel is rotated or when screen is pinched on a mobile device. Should be a positive float, increase this value to make zooming faster, decrease to make it slower. Affects the wheelPrecision property of the camera.
diff --git a/gradio/processing_utils.py b/gradio/processing_utils.py
index fb1794f2e..2d86d1d82 100644
--- a/gradio/processing_utils.py
+++ b/gradio/processing_utils.py
@@ -289,9 +289,11 @@ def move_files_to_cache(
     return client_utils.traverse(data, _move_to_cache, client_utils.is_file_obj)
 
 
-def add_root_url(data, root_url) -> dict:
+def add_root_url(data: dict, root_url: str, previous_root_url: str | None) -> dict:
     def _add_root_url(file_dict: dict):
         if not client_utils.is_http_url_like(file_dict["url"]):
+            if previous_root_url and file_dict["url"].startswith(previous_root_url):
+                file_dict["url"] = file_dict["url"][len(previous_root_url) :]
             file_dict["url"] = f'{root_url}{file_dict["url"]}'
         return file_dict
 
diff --git a/gradio/route_utils.py b/gradio/route_utils.py
index bceda289f..097bd26c4 100644
--- a/gradio/route_utils.py
+++ b/gradio/route_utils.py
@@ -16,7 +16,7 @@ from multipart.multipart import parse_options_header
 from starlette.datastructures import FormData, Headers, UploadFile
 from starlette.formparsers import MultiPartException, MultipartPart
 
-from gradio import utils
+from gradio import processing_utils, utils
 from gradio.data_classes import PredictBody
 from gradio.exceptions import Error
 from gradio.helpers import EventData
@@ -561,3 +561,16 @@ class GradioMultiPartParser:
 def move_uploaded_files_to_cache(files: list[str], destinations: list[str]) -> None:
     for file, dest in zip(files, destinations):
         shutil.move(file, dest)
+
+
+def update_root_in_config(config: dict, root: str) -> dict:
+    """
+    Updates the root "key" in the config dictionary to the new root url. If the
+    root url has changed, all of the urls in the config that correspond to component
+    file urls are updated to use the new root url.
+    """
+    previous_root = config.get("root", None)
+    if previous_root is None or previous_root != root:
+        config["root"] = root
+        config = processing_utils.add_root_url(config, root, previous_root)
+    return config
diff --git a/gradio/routes.py b/gradio/routes.py
index 240aeb451..ef8d2eabe 100644
--- a/gradio/routes.py
+++ b/gradio/routes.py
@@ -5,7 +5,6 @@ from __future__ import annotations
 
 import asyncio
 import contextlib
-import copy
 import sys
 
 if sys.version_info >= (3, 9):
@@ -311,19 +310,18 @@ class App(FastAPI):
         def main(request: fastapi.Request, user: str = Depends(get_current_user)):
             mimetypes.add_type("application/javascript", ".js")
             blocks = app.get_blocks()
-            root_path = route_utils.get_root_url(
+            root = route_utils.get_root_url(
                 request=request, route_path="/", root_path=app.root_path
             )
             if app.auth is None or user is not None:
-                config = copy.deepcopy(app.get_blocks().config)
-                config["root"] = root_path
-                config = add_root_url(config, root_path)
+                config = app.get_blocks().config
+                config = route_utils.update_root_in_config(config, root)
             else:
                 config = {
                     "auth_required": True,
                     "auth_message": blocks.auth_message,
                     "space_id": app.get_blocks().space_id,
-                    "root": root_path,
+                    "root": root,
                 }
 
             try:
@@ -354,13 +352,12 @@ class App(FastAPI):
         @app.get("/config/", dependencies=[Depends(login_check)])
         @app.get("/config", dependencies=[Depends(login_check)])
         def get_config(request: fastapi.Request):
-            config = copy.deepcopy(app.get_blocks().config)
-            root_path = route_utils.get_root_url(
+            config = app.get_blocks().config
+            root = route_utils.get_root_url(
                 request=request, route_path="/config", root_path=app.root_path
             )
-            config["root"] = root_path
-            config = add_root_url(config, root_path)
-            return config
+            config = route_utils.update_root_in_config(config, root)
+            return ORJSONResponse(content=config)
 
         @app.get("/static/{path:path}")
         def static_resource(path: str):
@@ -577,7 +574,7 @@ class App(FastAPI):
             root_path = route_utils.get_root_url(
                 request=request, route_path=f"/api/{api_name}", root_path=app.root_path
             )
-            output = add_root_url(output, root_path)
+            output = add_root_url(output, root_path, None)
             return output
 
         @app.get("/queue/data", dependencies=[Depends(login_check)])
@@ -634,7 +631,7 @@ class App(FastAPI):
                                 "success": False,
                             }
                         if message:
-                            add_root_url(message, root_path)
+                            add_root_url(message, root_path, None)
                             yield f"data: {json.dumps(message)}\n\n"
                             if message["msg"] == ServerMessage.process_completed:
                                 blocks._queue.pending_event_ids_session[
diff --git a/js/model3D/package.json b/js/model3D/package.json
index a04816354..c1239ccce 100644
--- a/js/model3D/package.json
+++ b/js/model3D/package.json
@@ -17,7 +17,8 @@
 		"@types/babylon": "^6.16.6",
 		"babylonjs": "^4.2.1",
 		"babylonjs-loaders": "^4.2.1",
-		"dequal": "^2.0.2"
+		"dequal": "^2.0.2",
+		"gsplat": "^1.0.5"
 	},
 	"main_changeset": true,
 	"main": "./Index.svelte",
diff --git a/js/model3D/shared/Canvas3DGS.svelte b/js/model3D/shared/Canvas3DGS.svelte
new file mode 100644
index 000000000..b34c1e98f
--- /dev/null
+++ b/js/model3D/shared/Canvas3DGS.svelte
@@ -0,0 +1,130 @@
+<script lang="ts">
+	import { onMount } from "svelte";
+	import * as SPLAT from "gsplat";
+	import type { FileData } from "@gradio/client";
+	import { resolve_wasm_src } from "@gradio/wasm/svelte";
+
+	export let value: FileData;
+	export let zoom_speed: number;
+	export let pan_speed: number;
+
+	$: url = value.url;
+
+	/* URL resolution for the Wasm mode. */
+	export let resolved_url: typeof url = undefined; // Exposed to be bound to the download link in the parent component.
+	// The prop can be updated before the Promise from `resolve_wasm_src` is resolved.
+	// In such a case, the resolved url for the old `url` has to be discarded,
+	// This variable `latest_url` is used to pick up only the value resolved for the latest `url`.
+	let latest_url: typeof url;
+	$: {
+		// In normal (non-Wasm) Gradio, the original `url` should be used immediately
+		// without waiting for `resolve_wasm_src()` to resolve.
+		// If it waits, a blank element is displayed until the async task finishes
+		// and it leads to undesirable flickering.
+		// So set `resolved_url` immediately above, and update it with the resolved values below later.
+		resolved_url = url;
+
+		if (url) {
+			latest_url = url;
+			const resolving_url = url;
+			resolve_wasm_src(url).then((resolved) => {
+				if (latest_url === resolving_url) {
+					resolved_url = resolved ?? undefined;
+				} else {
+					resolved && URL.revokeObjectURL(resolved);
+				}
+			});
+		}
+	}
+
+	let canvas: HTMLCanvasElement;
+	let scene: SPLAT.Scene;
+	let camera: SPLAT.Camera;
+	let renderer: SPLAT.WebGLRenderer | null = null;
+	let controls: SPLAT.OrbitControls;
+	let mounted = false;
+	let frameId: number | null = null;
+
+	function reset_scene(): void {
+		if (frameId !== null) {
+			cancelAnimationFrame(frameId);
+			frameId = null;
+		}
+
+		if (renderer !== null) {
+			renderer.dispose();
+			renderer = null;
+		}
+
+		scene = new SPLAT.Scene();
+		camera = new SPLAT.Camera();
+		renderer = new SPLAT.WebGLRenderer(canvas);
+		controls = new SPLAT.OrbitControls(camera, canvas);
+		controls.zoomSpeed = zoom_speed;
+		controls.panSpeed = pan_speed;
+
+		if (!value) {
+			return;
+		}
+
+		let loading = false;
+		const load = async (): Promise<void> => {
+			if (loading) {
+				console.error("Already loading");
+				return;
+			}
+			if (!resolved_url) {
+				throw new Error("No resolved URL");
+			}
+			loading = true;
+			if (resolved_url.endsWith(".ply")) {
+				await SPLAT.PLYLoader.LoadAsync(resolved_url, scene, undefined);
+			} else if (resolved_url.endsWith(".splat")) {
+				await SPLAT.Loader.LoadAsync(resolved_url, scene, undefined);
+			} else {
+				throw new Error("Unsupported file type");
+			}
+			loading = false;
+		};
+
+		const frame = (): void => {
+			if (!renderer) {
+				return;
+			}
+
+			if (loading) {
+				frameId = requestAnimationFrame(frame);
+				return;
+			}
+
+			controls.update();
+			renderer.render(scene, camera);
+
+			frameId = requestAnimationFrame(frame);
+		};
+
+		load();
+		frameId = requestAnimationFrame(frame);
+	}
+
+	onMount(() => {
+		if (value != null) {
+			reset_scene();
+		}
+		mounted = true;
+
+		return () => {
+			if (renderer) {
+				renderer.dispose();
+			}
+		};
+	});
+
+	$: ({ path } = value || {
+		path: undefined
+	});
+
+	$: canvas && mounted && path && reset_scene();
+</script>
+
+<canvas bind:this={canvas}></canvas>
diff --git a/js/model3D/shared/Model3D.svelte b/js/model3D/shared/Model3D.svelte
index c127c11fb..5fdacd66f 100644
--- a/js/model3D/shared/Model3D.svelte
+++ b/js/model3D/shared/Model3D.svelte
@@ -2,7 +2,6 @@
 	import type { FileData } from "@gradio/client";
 	import { BlockLabel, IconButton } from "@gradio/atoms";
 	import { File, Download, Undo } from "@gradio/icons";
-	import Canvas3D from "./Canvas3D.svelte";
 	import type { I18nFormatter } from "@gradio/utils";
 	import { dequal } from "dequal";
 
@@ -22,9 +21,21 @@
 
 	let current_settings = { camera_position, zoom_speed, pan_speed };
 
-	let canvas3d: Canvas3D;
+	let canvas3dgs: any;
+	let canvas3d: any;
+	let use_3dgs = false;
 	let resolved_url: string | undefined;
 
+	async function loadCanvas3D(): Promise<any> {
+		const module = await import("./Canvas3D.svelte");
+		return module.default;
+	}
+
+	async function loadCanvas3DGS(): Promise<any> {
+		const module = await import("./Canvas3DGS.svelte");
+		return module.default;
+	}
+
 	function handle_undo(): void {
 		canvas3d.reset_camera_position(camera_position, zoom_speed, pan_speed);
 	}
@@ -39,6 +50,21 @@
 			current_settings = { camera_position, zoom_speed, pan_speed };
 		}
 	}
+
+	$: {
+		if (value) {
+			use_3dgs = value?.path.endsWith(".splat") || value?.path.endsWith(".ply");
+			if (use_3dgs) {
+				loadCanvas3DGS().then((module) => {
+					canvas3dgs = module;
+				});
+			} else {
+				loadCanvas3D().then((module) => {
+					canvas3d = module;
+				});
+			}
+		}
+	}
 </script>
 
 <BlockLabel
@@ -59,15 +85,25 @@
 			</a>
 		</div>
 
-		<Canvas3D
-			bind:this={canvas3d}
-			bind:resolved_url
-			{value}
-			{clear_color}
-			{camera_position}
-			{zoom_speed}
-			{pan_speed}
-		/>
+		{#if use_3dgs}
+			<svelte:component
+				this={canvas3dgs}
+				bind:resolved_url
+				{value}
+				{zoom_speed}
+				{pan_speed}
+			/>
+		{:else}
+			<svelte:component
+				this={canvas3d}
+				bind:resolved_url
+				{value}
+				{clear_color}
+				{camera_position}
+				{zoom_speed}
+				{pan_speed}
+			/>
+		{/if}
 	</div>
 {/if}
 
diff --git a/js/model3D/shared/Model3DUpload.svelte b/js/model3D/shared/Model3DUpload.svelte
index fd03c46c8..e2b75726f 100644
--- a/js/model3D/shared/Model3DUpload.svelte
+++ b/js/model3D/shared/Model3DUpload.svelte
@@ -5,7 +5,6 @@
 	import { BlockLabel } from "@gradio/atoms";
 	import { File } from "@gradio/icons";
 	import type { I18nFormatter } from "@gradio/utils";
-	import Canvas3D from "./Canvas3D.svelte";
 
 	export let value: null | FileData;
 	export let clear_color: [number, number, number, number] = [0, 0, 0, 0];
@@ -39,9 +38,22 @@
 		dispatch("change");
 	}
 
-	let canvas3D: Canvas3D;
+	let canvas3d: any;
+	let canvas3dgs: any;
+	let use_3dgs = false;
+
+	async function loadCanvas3D(): Promise<any> {
+		const module = await import("./Canvas3D.svelte");
+		return module.default;
+	}
+
+	async function loadCanvas3DGS(): Promise<any> {
+		const module = await import("./Canvas3DGS.svelte");
+		return module.default;
+	}
+
 	async function handle_undo(): Promise<void> {
-		canvas3D.reset_camera_position(camera_position, zoom_speed, pan_speed);
+		canvas3d.reset_camera_position(camera_position, zoom_speed, pan_speed);
 	}
 
 	const dispatch = createEventDispatcher<{
@@ -54,6 +66,21 @@
 	let dragging = false;
 
 	$: dispatch("drag", dragging);
+
+	$: {
+		if (value) {
+			use_3dgs = value?.path.endsWith(".splat") || value?.path.endsWith(".ply");
+			if (use_3dgs) {
+				loadCanvas3DGS().then((module) => {
+					canvas3dgs = module;
+				});
+			} else {
+				loadCanvas3D().then((module) => {
+					canvas3d = module;
+				});
+			}
+		}
+	}
 </script>
 
 <BlockLabel {show_label} Icon={File} label={label || "3D Model"} />
@@ -62,7 +89,7 @@
 	<Upload
 		on:load={handle_upload}
 		{root}
-		filetype={[".stl", ".obj", ".gltf", ".glb", "model/obj"]}
+		filetype={[".stl", ".obj", ".gltf", ".glb", "model/obj", ".splat", ".ply"]}
 		bind:dragging
 	>
 		<slot />
@@ -76,14 +103,19 @@
 			on:undo={handle_undo}
 			absolute
 		/>
-		<Canvas3D
-			bind:this={canvas3D}
-			{value}
-			{clear_color}
-			{camera_position}
-			{zoom_speed}
-			{pan_speed}
-		></Canvas3D>
+
+		{#if use_3dgs}
+			<svelte:component this={canvas3dgs} {value} {zoom_speed} {pan_speed} />
+		{:else}
+			<svelte:component
+				this={canvas3d}
+				{value}
+				{clear_color}
+				{camera_position}
+				{zoom_speed}
+				{pan_speed}
+			/>
+		{/if}
 	</div>
 {/if}
 
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3ddc4a3a3..cb312149d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1182,6 +1182,9 @@ importers:
       dequal:
         specifier: ^2.0.2
         version: 2.0.3
+      gsplat:
+        specifier: ^1.0.5
+        version: 1.0.5
 
   js/number:
     dependencies:
@@ -10985,6 +10988,10 @@ packages:
     engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
     dev: false
 
+  /gsplat@1.0.5:
+    resolution: {integrity: sha512-SM85+qMA/UwdDk2lFRmkJvLhD5A+qloRzINmPzjvXJ6Iugl3yAEr8TomVQmge0z1i98bHEoSZrJC/UOKVXq3Hg==}
+    dev: false
+
   /gunzip-maybe@1.4.2:
     resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==}
     hasBin: true


Test patch:
diff --git a/test/test_processing_utils.py b/test/test_processing_utils.py
index d5743da42..539b812f1 100644
--- a/test/test_processing_utils.py
+++ b/test/test_processing_utils.py
@@ -332,3 +332,42 @@ class TestVideoProcessing:
             )
             # If the conversion succeeded it'd be .mp4
             assert Path(playable_vid).suffix == ".avi"
+
+
+def test_add_root_url():
+    data = {
+        "file": {
+            "path": "path",
+            "url": "/file=path",
+        },
+        "file2": {
+            "path": "path2",
+            "url": "https://www.gradio.app",
+        },
+    }
+    root_url = "http://localhost:7860"
+    expected = {
+        "file": {
+            "path": "path",
+            "url": f"{root_url}/file=path",
+        },
+        "file2": {
+            "path": "path2",
+            "url": "https://www.gradio.app",
+        },
+    }
+    assert processing_utils.add_root_url(data, root_url, None) == expected
+    new_root_url = "https://1234.gradio.live"
+    new_expected = {
+        "file": {
+            "path": "path",
+            "url": f"{root_url}/file=path",
+        },
+        "file2": {
+            "path": "path2",
+            "url": "https://www.gradio.app",
+        },
+    }
+    assert (
+        processing_utils.add_root_url(expected, root_url, new_root_url) == new_expected
+    )
diff --git a/test/test_routes.py b/test/test_routes.py
index 84a3e1a5c..f54da98c6 100644
--- a/test/test_routes.py
+++ b/test/test_routes.py
@@ -439,6 +439,18 @@ class TestRoutes:
         r = app.build_proxy_request("https://google.com")
         assert "authorization" not in dict(r.headers)
 
+    def test_can_get_config_that_includes_non_pickle_able_objects(self):
+        my_dict = {"a": 1, "b": 2, "c": 3}
+        with Blocks() as demo:
+            gr.JSON(my_dict.keys())
+
+        app, _, _ = demo.launch(prevent_thread_lock=True)
+        client = TestClient(app)
+        response = client.get("/")
+        assert response.is_success
+        response = client.get("/config/")
+        assert response.is_success
+
 
 class TestApp:
     def test_create_app(self):