From d034700af9db8fc3a6b6754987796eb2576eadfc Mon Sep 17 00:00:00 2001 From: William Gill Date: Sun, 26 Apr 2026 06:03:21 -0500 Subject: [PATCH] Image tools: work around OWUI 0.9.x files-event regression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Open WebUI 0.9.0 introduced chat-history virtualization that unmounts off-screen assistant messages and reconstructs them from persisted shape; `files` attached mid-stream by a tool don't survive the round-trip — the image flashes in during streaming and disappears the moment the message commits. Both image tools now upload via Open WebUI's file store as before but surface the result as a markdown image injected into the assistant message via a `message` event, which is part of the persisted shape and renders reliably across remounts. Trade-off: loses the file-attachment chrome (thumbnail + download button). Each tool has a TODO marking the swap site with the original `files` payload inlined for one-line revert once upstream fixes the regression. smart_image_gen.py 0.7.8 -> 0.7.9 smart_image_pipe.py 0.1.0 -> 0.1.1 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../openwebui-tools/smart_image_gen.py | 46 ++++++++++++++----- .../openwebui-tools/smart_image_pipe.py | 20 ++++++-- 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/deployments/ai-stack/openwebui-tools/smart_image_gen.py b/deployments/ai-stack/openwebui-tools/smart_image_gen.py index e95abc8..4a65716 100644 --- a/deployments/ai-stack/openwebui-tools/smart_image_gen.py +++ b/deployments/ai-stack/openwebui-tools/smart_image_gen.py @@ -1,7 +1,7 @@ """ title: Smart Image Generator & Editor (ComfyUI) author: ai-stack -version: 0.7.8 +version: 0.7.9 description: Generate or edit images via ComfyUI with automatic SDXL checkpoint routing. Two methods — generate_image (txt2img) and edit_image (img2img on the user's most recently attached image). The @@ -9,8 +9,8 @@ description: Generate or edit images via ComfyUI with automatic SDXL score-tag, NoobAI/Illustrious furry, etc. — and each style ships with the creator-recommended sampler, scheduler, CFG, steps, CLIP skip, prompt-prefix dialect, and negatives. The image is uploaded - to Open WebUI's file store and surfaced via a `files` event (the - canonical pattern used by Open WebUI's own image-gen path); the + to Open WebUI's file store and surfaced as a markdown image + appended to the assistant message via a `message` event; the function return is a short confirmation so the LLM doesn't try to describe or re-emit the image. required_open_webui_version: 0.5.0 @@ -586,14 +586,22 @@ async def _push_image_to_chat( event_emitter: Optional[Callable[[dict], Awaitable[None]]], ) -> bool: """ - Surface a generated image in the chat using Open WebUI's canonical - pattern: upload the bytes via the internal file store, then emit a - `files` event referencing the served URL. This is the same path Open - WebUI's own image-generation code uses (utils/middleware.py ~1325). + Surface a generated image in the chat: upload the bytes via the + internal file store, then inject a markdown image referencing the + served URL into the assistant message via a `message` event. - Returns True if the image was uploaded and emitted via the files - event. Returns False if anything is missing — caller should fall - back to a data-URI markdown message in that case. + We deliberately don't use the `files` event (Open WebUI's own + image-gen path). Open WebUI 0.9.x added chat-history virtualization + that unmounts off-screen messages and reconstructs them from + persisted shape — and `files` attached to an assistant message + mid-stream by a tool don't survive that round-trip. The image + flashes in during streaming and disappears the moment the message + commits. Markdown in `message.content` is part of the persisted + shape, so it renders reliably on every remount. + + Returns True if the image was uploaded and emitted. Returns False + if anything is missing — caller falls back to a data-URI markdown + message (same `message` event path, just inline bytes). """ if not (_OPENWEBUI_RUNTIME and request and user_dict and event_emitter): return False @@ -630,9 +638,23 @@ async def _push_image_to_chat( "get_file_content_by_id", id=file_item.id ) + # TODO(open-webui virtualization fix): once chat-history + # virtualization survives `files` events from tool calls again, + # swap back to the `files` payload below. The `files` event + # gives proper file-attachment chrome (thumbnail card + download + # button) that the markdown-image path lacks. Verify the fix by + # emitting `files`, then refreshing the page AND scrolling the + # message off-screen and back — both must keep the image + # visible. Track upstream: github.com/open-webui/open-webui + # release notes mentioning tool calls, `files` events, or + # virtualization. To restore, replace the block below with: + # await event_emitter({ + # "type": "files", + # "data": {"files": [{"type": "image", "url": url}]}, + # }) await event_emitter({ - "type": "files", - "data": {"files": [{"type": "image", "url": url}]}, + "type": "message", + "data": {"content": f"\n![{filename_prefix}]({url})\n"}, }) return True except Exception: diff --git a/deployments/ai-stack/openwebui-tools/smart_image_pipe.py b/deployments/ai-stack/openwebui-tools/smart_image_pipe.py index 6b155d8..35f3608 100644 --- a/deployments/ai-stack/openwebui-tools/smart_image_pipe.py +++ b/deployments/ai-stack/openwebui-tools/smart_image_pipe.py @@ -1,7 +1,7 @@ """ title: Smart Image Studio (Pipe) author: ai-stack -version: 0.1.0 +version: 0.1.1 description: Deterministic image-gen / edit / inpaint pipe — no LLM in the loop for the routing decision. Registers as a model in the chat-model dropdown ('Image Studio (Pipe)'). Reads the user's message + attached @@ -420,9 +420,23 @@ async def _push_image_to_chat(raw, prefix, request, user_dict, metadata, event_e ) file_item = await result if inspect.iscoroutine(result) else result url = request.app.url_path_for("get_file_content_by_id", id=file_item.id) + # TODO(open-webui virtualization fix): once chat-history + # virtualization survives `files` events from tool calls again, + # swap back to the `files` payload below. The `files` event + # gives proper file-attachment chrome (thumbnail card + download + # button) that the markdown-image path lacks. Verify the fix by + # emitting `files`, then refreshing the page AND scrolling the + # message off-screen and back — both must keep the image + # visible. Track upstream: github.com/open-webui/open-webui + # release notes mentioning tool calls, `files` events, or + # virtualization. To restore, replace the block below with: + # await event_emitter({ + # "type": "files", + # "data": {"files": [{"type": "image", "url": url}]}, + # }) await event_emitter({ - "type": "files", - "data": {"files": [{"type": "image", "url": url}]}, + "type": "message", + "data": {"content": f"\n![{prefix}]({url})\n"}, }) return True except Exception: