From ab20745ee552adea8ada22165b317857e934987e Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Wed, 11 Feb 2026 23:27:00 +0100 Subject: [PATCH] Enable Rich UI embed support for action functions (#21294) Action functions can now return HTMLResponse objects or (html, headers) tuples with Content-Disposition: inline to render rich UI iframes in chat, matching the existing tool behavior. https://claude.ai/code/session_01KCZKQXj1uqgPjqjMd4U2NF Co-authored-by: Claude --- backend/open_webui/utils/chat.py | 24 ++++++++++++++++++++++++ backend/open_webui/utils/middleware.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/utils/chat.py b/backend/open_webui/utils/chat.py index 94d6f4426..c5da749e1 100644 --- a/backend/open_webui/utils/chat.py +++ b/backend/open_webui/utils/chat.py @@ -449,6 +449,30 @@ async def chat_action(request: Request, action_id: str, form_data: dict, user: A else: data = action(**params) + # Process action result for Rich UI embeds (HTMLResponse, tuple with headers) + # Deferred import to avoid circular dependency (middleware imports chat) + from open_webui.utils.middleware import process_tool_result + + processed_result, _, action_embeds = process_tool_result( + request, + action_id, + data, + "action", + ) + + if action_embeds: + await __event_emitter__( + { + "type": "embeds", + "data": { + "embeds": action_embeds, + }, + } + ) + # Replace data with the processed status dict so we don't + # try to serialize the raw HTMLResponse / tuple back to the client + data = processed_result + except Exception as e: raise Exception(f"Error: {e}") diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index b5b62ecf8..a61f56366 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -859,7 +859,7 @@ def process_tool_result( else: tool_result = tool_result.body.decode("utf-8", "replace") - elif (tool_type == "external" and isinstance(tool_result, tuple)) or ( + elif (tool_type in ("external", "action") and isinstance(tool_result, tuple)) or ( direct_tool and isinstance(tool_result, list) and len(tool_result) == 2 ): tool_result, tool_response_headers = tool_result