From 2b7a0396bc32347bacc5385cf8aa9e5cd9aa9cd4 Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Sun, 5 Apr 2026 13:19:59 +0200 Subject: [PATCH] feat: auto-open generated images and videos in OS viewer (#244) * feat: add inline image and video rendering in terminal UI Use sharp to render images as half-block character grids directly in the terminal when media tool results are displayed, with ffmpeg fallback for video thumbnail extraction. * feat: auto-open generated media in OS viewer instead of inline rendering Replace pixelated half-block terminal image rendering with native OS viewer (`open` on macOS, `xdg-open` on Linux). Generated images and videos now open automatically in full quality. Remove sharp dependency and inline rendering utilities. * bump version * fix: use execFile instead of exec to prevent shell injection * fix: remove auto-open from MediaToolResultView, keep only in MediaAutoOpenView --- package.json | 2 +- src/ui/app.tsx | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 786e675..352c78e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grok-dev", - "version": "1.1.4-rc5", + "version": "1.1.4", "description": "An open-source AI coding agent powered by Grok, built with Bun and OpenTUI.", "type": "module", "main": "dist/index.js", diff --git a/src/ui/app.tsx b/src/ui/app.tsx index e7faf89..5392cb1 100644 --- a/src/ui/app.tsx +++ b/src/ui/app.tsx @@ -3941,6 +3941,9 @@ function MessageView({ } if ((entry.toolResult?.media?.length ?? 0) > 0) { + if (name === "generate_image" || name === "generate_video") { + return ; + } return ; } @@ -4378,6 +4381,35 @@ function ToolTextOutputView({ t, label, content }: { t: Theme; label: string; co ); } +function openMediaFile(filePath: string): void { + try { + const cmd = process.platform === "darwin" ? "open" : "xdg-open"; + require("child_process").execFile(cmd, [filePath]); + } catch {} +} + +function MediaAutoOpenView({ t, label, toolResult }: { t: Theme; label: string; toolResult: ToolResult }) { + const media = toolResult.media ?? []; + const openedRef = useRef>(new Set()); + + useEffect(() => { + for (const asset of media) { + if (!openedRef.current.has(asset.path)) { + openedRef.current.add(asset.path); + openMediaFile(asset.path); + } + } + }, [media]); + + return ( + + + {label} + + + ); +} + function MediaToolResultView({ t, label, toolResult }: { t: Theme; label: string; toolResult: ToolResult }) { const media = toolResult.media ?? [];