diff --git a/backend/open_webui/routers/terminals.py b/backend/open_webui/routers/terminals.py
index 49c39c8bf..59f1f3ab4 100644
--- a/backend/open_webui/routers/terminals.py
+++ b/backend/open_webui/routers/terminals.py
@@ -31,14 +31,20 @@ def _sanitize_proxy_path(path: str) -> str | None:
"""Sanitize a proxy path to prevent directory traversal / SSRF.
Returns the cleaned path, or None if the path is invalid.
+ Trailing slashes are preserved — many upstream frameworks treat
+ ``/path`` and ``/path/`` differently.
"""
decoded = unquote(path)
+ had_trailing_slash = decoded.endswith('/')
normalized = posixpath.normpath(decoded)
# Remove any leading slashes that would reset the base
cleaned = normalized.lstrip('/')
# Reject if normpath resolved to parent traversal or current-dir only
if cleaned.startswith('..') or cleaned == '.':
return None
+ # Restore trailing slash if the original path had one
+ if had_trailing_slash and cleaned and not cleaned.endswith('/'):
+ cleaned += '/'
return cleaned
diff --git a/src/lib/components/chat/FileNav.svelte b/src/lib/components/chat/FileNav.svelte
index 770d2b270..adb8bdf6d 100644
--- a/src/lib/components/chat/FileNav.svelte
+++ b/src/lib/components/chat/FileNav.svelte
@@ -40,6 +40,7 @@
import FilePreview from './FileNav/FilePreview.svelte';
import FileEntryRow from './FileNav/FileEntryRow.svelte';
import PortList from './FileNav/PortList.svelte';
+ import PortPreview from './FileNav/PortPreview.svelte';
import XTerminal from './XTerminal.svelte';
const i18n = getContext('i18n');
@@ -90,6 +91,7 @@
// ── File preview state ───────────────────────────────────────────────
let selectedFile: string | null = null;
+ let previewPort: number | null = null;
let fileContent: string | null = null;
let fileImageUrl: string | null = null;
let fileVideoUrl: string | null = null;
@@ -253,6 +255,7 @@
loading = true;
error = null;
selectedFile = null;
+ previewPort = null;
clearFilePreview();
currentPath = path;
savedPath = path;
@@ -632,6 +635,7 @@
{/if}
+ {#if previewPort === null}