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
This commit is contained in:
Ismail Pelaseyed authored and GitHub committed 2026-04-05 13:19:59 +02:00
1 parent a9a78a3a5b
commit 2b7a0396bc
2 files changed
+33 -1

No files matched your search

+1 -1
View File
@@ -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",
+32
View File
@@ -3941,6 +3941,9 @@ function MessageView({
}
if ((entry.toolResult?.media?.length ?? 0) > 0) {
if (name === "generate_image" || name === "generate_video") {
return <MediaAutoOpenView t={t} label={toolLabel(entry.toolCall!)} toolResult={entry.toolResult!} />;
}
return <MediaToolResultView t={t} label={toolLabel(entry.toolCall!)} toolResult={entry.toolResult!} />;
}
@@ -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<Set<string>>(new Set());
useEffect(() => {
for (const asset of media) {
if (!openedRef.current.has(asset.path)) {
openedRef.current.add(asset.path);
openMediaFile(asset.path);
}
}
}, [media]);
return (
<box gap={0}>
<InlineTool t={t} pending={false}>
{label}
</InlineTool>
</box>
);
}
function MediaToolResultView({ t, label, toolResult }: { t: Theme; label: string; toolResult: ToolResult }) {
const media = toolResult.media ?? [];