mirror of
https://github.com/open-webui/open-terminal.git
synced 2026-11-04 08:10:53 +00:00
refac
This commit is contained in:
1 parent
3ea2648506
commit
c7530495a5
2 files changed
+67
-1
No files matched your search
@@ -167,6 +167,25 @@ Set `OPEN_TERMINAL_FILE_BROWSER_ROOT` to:
|
||||
|
||||
This is a UI hint for clients. It does not restrict terminal commands or file APIs.
|
||||
|
||||
### External Workspace Storage
|
||||
|
||||
Open Terminal uses the filesystem it is mounted on. To store workspace files
|
||||
externally, mount that storage into the container:
|
||||
|
||||
- Single-user: `/home/user`
|
||||
- Multi-user: `/home`
|
||||
|
||||
This can be a Docker volume, Kubernetes persistent volume, NFS/Azure Files
|
||||
mount, or FUSE mount such as blobfuse, s3fs, or rclone.
|
||||
|
||||
### Office Previews
|
||||
|
||||
`GET /files/view?path=...` always returns the original file by default. Clients
|
||||
that want a rendered preview can add `preview=true`; when LibreOffice is
|
||||
available the server can return DOCX and PPTX previews as PDF. When rendered
|
||||
preview support is not available, the same endpoint falls back to returning the
|
||||
original file bytes.
|
||||
|
||||
## Using with Open WebUI
|
||||
|
||||
Open Terminal integrates with [Open WebUI](https://github.com/open-webui/open-webui), giving your AI assistants the ability to run commands, manage files, and interact with a terminal right from the AI interface. Make sure to add it under **Open Terminal** in the integrations settings, not as a tool server. Adding it as an Open Terminal connection gives you a built-in file navigation sidebar where you can browse directories, upload, download, and edit files. There are two ways to connect:
|
||||
|
||||
+48
-1
@@ -3,6 +3,8 @@ import hmac
|
||||
from importlib.metadata import version as _pkg_version
|
||||
import fnmatch
|
||||
import json
|
||||
import logging
|
||||
import tempfile
|
||||
|
||||
import aiofiles
|
||||
import aiofiles.os
|
||||
@@ -32,6 +34,7 @@ from open_terminal.utils.fs import UserFS
|
||||
MATCH_PAGE_SIZE = 100
|
||||
MAX_CONTENT_MATCHES_PER_FILE = 3
|
||||
MAX_CONTENT_SEARCH_FILE_SIZE = 1 * 1024 * 1024
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
if MULTI_USER:
|
||||
from open_terminal.utils.user_isolation import check_environment, resolve_user
|
||||
@@ -169,6 +172,37 @@ def get_file_browser_root(fs: UserFS) -> dict[str, str] | None:
|
||||
return {"path": root_path, "label": label}
|
||||
|
||||
|
||||
def _preview_pdf(target: str) -> bytes | None:
|
||||
libreoffice = shutil.which("soffice") or shutil.which("libreoffice")
|
||||
if not libreoffice:
|
||||
return None
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
command = [
|
||||
libreoffice,
|
||||
"--headless",
|
||||
"--nologo",
|
||||
"--nofirststartwizard",
|
||||
"--nodefault",
|
||||
"--nolockcheck",
|
||||
"--convert-to",
|
||||
"pdf",
|
||||
"--outdir",
|
||||
tmpdir,
|
||||
target,
|
||||
]
|
||||
subprocess.run(command, check=True, capture_output=True, timeout=60)
|
||||
pdf_path = os.path.join(tmpdir, f"{os.path.splitext(os.path.basename(target))[0]}.pdf")
|
||||
if not os.path.isfile(pdf_path):
|
||||
matches = [name for name in os.listdir(tmpdir) if name.lower().endswith(".pdf")]
|
||||
if not matches:
|
||||
return None
|
||||
pdf_path = os.path.join(tmpdir, sorted(matches)[0])
|
||||
|
||||
with open(pdf_path, "rb") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="Open Terminal",
|
||||
description="A remote terminal API.",
|
||||
@@ -627,6 +661,7 @@ async def display_file(
|
||||
)
|
||||
async def view_file(
|
||||
path: str = Query(..., description="Path to the file to view."),
|
||||
preview: bool = Query(False, description="Return a best-effort rendered preview when available."),
|
||||
fs: UserFS = Depends(get_filesystem),
|
||||
):
|
||||
"""Return raw file bytes with the appropriate Content-Type.
|
||||
@@ -638,6 +673,18 @@ async def view_file(
|
||||
if not await fs.isfile(target):
|
||||
raise HTTPException(status_code=404, detail="File not found")
|
||||
|
||||
if preview:
|
||||
ext = os.path.splitext(target)[1].lower()
|
||||
if ext in (".docx", ".pptx"):
|
||||
try:
|
||||
pdf = await asyncio.to_thread(_preview_pdf, target)
|
||||
except Exception:
|
||||
log.exception("Rendered preview failed for %s", target)
|
||||
pdf = None
|
||||
|
||||
if pdf:
|
||||
return Response(content=pdf, media_type="application/pdf")
|
||||
|
||||
import mimetypes
|
||||
|
||||
mime, _ = mimetypes.guess_type(target)
|
||||
@@ -653,7 +700,7 @@ async def view_file(
|
||||
)
|
||||
async def serve_file(path: str, fs: UserFS = Depends(get_filesystem)):
|
||||
"""Path-based alias for view_file — enables relative URL resolution in iframes."""
|
||||
return await view_file(path=f"/{path}", fs=fs)
|
||||
return await view_file(path=f"/{path}", preview=False, fs=fs)
|
||||
|
||||
|
||||
@app.post(
|
||||
|
||||
Reference in new issue
Block a user