[GH-ISSUE #23049] issue: Rich UI embeds rendering above message content instead of below #19874

Closed
opened 2026-04-20 02:24:04 -05:00 by GiteaMirror · 6 comments
Owner

Originally created by @ApexArray on GitHub (Mar 26, 2026).
Original GitHub issue: https://github.com/open-webui/open-webui/issues/23049

Check Existing Issues

  • I have searched for any existing and/or related issues.
  • I have searched for any existing and/or related discussions.
  • I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!).
  • I am using the latest version of Open WebUI.

Installation Method

Docker

Open WebUI Version

v0.8.10 (also present in v0.8.11)

Ollama Version (if applicable)

No response

Operating System

Debian 13

Browser (if applicable)

Chromium 146.0.7680.164, Firefox

Confirmation

  • I have read and followed all instructions in README.md.
  • I am using the latest version of both Open WebUI and Ollama.
  • I have included the browser console logs.
  • I have included the Docker container logs.
  • I have provided every relevant configuration, setting, and environment variable used in my setup.
  • I have clearly listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc).
  • I have documented step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation. My steps:
  • Start with the initial platform/version/OS and dependencies used,
  • Specify exact install/launch/configure commands,
  • List URLs visited, user input (incl. example values/emails/passwords if needed),
  • Describe all options and toggles enabled or changed,
  • Include any files or environmental changes,
  • Identify the expected and actual result at each stage,
  • Ensure any reasonably skilled user can follow and hit the same issue.

Expected Behavior

Rich HTML UI embeds (from functions/actions) from should render below the message text content.

Actual Behavior

Rich UI embeds are rendered above the the message content (see screenshot)

Steps to Reproduce

  1. Navigate to the admin panel and create new Function using the sample code from the docs:
"""
title: Rich UI Demo Action
author: open-webui
version: 0.1.0
description: Demonstrates Rich UI embedding from an Action function.
"""

from pydantic import BaseModel, Field


class Action:
    class Valves(BaseModel):
        pass

    def __init__(self):
        self.valves = self.Valves()

    async def action(self, body: dict, __user__=None, __event_emitter__=None) -> None:
        from fastapi.responses import HTMLResponse

        html = """
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                * { margin: 0; padding: 0; box-sizing: border-box; }
                body {
                    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    padding: 24px;
                    color: #fff;
                }
                .card {
                    background: rgba(255,255,255,0.15);
                    backdrop-filter: blur(10px);
                    border-radius: 16px;
                    padding: 24px;
                    border: 1px solid rgba(255,255,255,0.2);
                }
                h1 { font-size: 1.4em; margin-bottom: 8px; }
                p { opacity: 0.9; line-height: 1.5; margin-bottom: 12px; }
                .badge {
                    display: inline-block;
                    background: rgba(255,255,255,0.25);
                    padding: 4px 12px;
                    border-radius: 20px;
                    font-size: 0.85em;
                    font-weight: 600;
                }
                .stats {
                    display: flex;
                    gap: 16px;
                    margin-top: 16px;
                }
                .stat {
                    flex: 1;
                    text-align: center;
                    background: rgba(255,255,255,0.1);
                    border-radius: 12px;
                    padding: 12px;
                }
                .stat-value { font-size: 1.8em; font-weight: 700; }
                .stat-label { font-size: 0.8em; opacity: 0.8; margin-top: 4px; }
            </style>
        </head>
        <body>
            <div class="card">
                <h1>Rich UI Embed Demo</h1>
                <p>This embed renders <strong>below</strong> the message text.</p>
                <span class="badge">Action Embed</span>
                <div class="stats">
                    <div class="stat">
                        <div class="stat-value">42</div>
                        <div class="stat-label">Answers</div>
                    </div>
                    <div class="stat">
                        <div class="stat-value">99%</div>
                        <div class="stat-label">Accuracy</div>
                    </div>
                    <div class="stat">
                        <div class="stat-value">0ms</div>
                        <div class="stat-label">Latency</div>
                    </div>
                </div>
            </div>
            <script>
                // Report height to parent so the iframe auto-sizes
                function reportHeight() {
                    const h = document.documentElement.scrollHeight;
                    parent.postMessage({ type: 'iframe:height', height: h }, '*');
                }
                window.addEventListener('load', reportHeight);
                new ResizeObserver(reportHeight).observe(document.body);
            </script>
        </body>
        </html>
        """

        return HTMLResponse(content=html, headers={"Content-Disposition": "inline"})
  1. Start a new chat and wait for the response
  2. Click the Rich UI Demo action button
  3. Observe the rendered content

Logs & Screenshots

Current behavior:
Image

After git patch:

Image

Additional Information

What I've tried:

  • Different HTML content with and without script/style tags
  • Different models
  • Chats with and without tool calls
  • Setting function mode to default and native

This seems to just come down to the ordering in the ResponseMessage.svelelte file.

Embeds rendered starting on L296

4d058a125b/src/lib/components/chat/Messages/ResponseMessage.svelte (L696-L713)

Message text rendered starting on L783
4d058a125b/src/lib/components/chat/Messages/ResponseMessage.svelte (L783-L793)

I tested the following patch, and the content is rendered in the correct position (see screenshot):

diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte
index 98b0d95a0..58f8a2467 100644
--- a/src/lib/components/chat/Messages/ResponseMessage.svelte
+++ b/src/lib/components/chat/Messages/ResponseMessage.svelte
@@ -693,25 +693,6 @@
 							</div>
 						{/if}
 
-						{#if message?.embeds && message.embeds.length > 0}
-							<div
-								class="my-1 w-full flex overflow-x-auto gap-2 flex-wrap"
-								id={`${message.id}-embeds-container`}
-							>
-								{#each message.embeds as embed, idx}
-									<div class="my-2 w-full" id={`${message.id}-embeds-${idx}`}>
-										<FullHeightIframe
-											src={embed}
-											allowScripts={true}
-											allowForms={true}
-											allowSameOrigin={$settings?.iframeSandboxAllowSameOrigin ?? false}
-											allowPopups={true}
-										/>
-									</div>
-								{/each}
-							</div>
-						{/if}
-
 						{#if edit === true}
 							<div class="w-full bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 my-2">
 								<textarea
@@ -849,6 +830,25 @@
 								<CodeExecutions codeExecutions={message.code_executions} />
 							{/if}
 						</div>
+
+						{#if message?.embeds && message.embeds.length > 0}
+							<div
+								class="my-1 w-full flex overflow-x-auto gap-2 flex-wrap"
+								id={`${message.id}-embeds-container`}
+							>
+								{#each message.embeds as embed, idx}
+									<div class="my-2 w-full" id={`${message.id}-embeds-${idx}`}>
+										<FullHeightIframe
+											src={embed}
+											allowScripts={true}
+											allowForms={true}
+											allowSameOrigin={$settings?.iframeSandboxAllowSameOrigin ?? false}
+											allowPopups={true}
+										/>
+									</div>
+								{/each}
+							</div>
+						{/if}
 					</div>
 				</div>

Not sure if there's any other edge-cases that rely on the existing rendering order. I'm happy to open a PR, or feel free to use the above patch if that's easier.

Originally created by @ApexArray on GitHub (Mar 26, 2026). Original GitHub issue: https://github.com/open-webui/open-webui/issues/23049 ### Check Existing Issues - [x] I have searched for any existing and/or related issues. - [x] I have searched for any existing and/or related discussions. - [x] I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!). - [x] I am using the latest version of Open WebUI. ### Installation Method Docker ### Open WebUI Version v0.8.10 (also present in v0.8.11) ### Ollama Version (if applicable) _No response_ ### Operating System Debian 13 ### Browser (if applicable) Chromium 146.0.7680.164, Firefox ### Confirmation - [x] I have read and followed all instructions in `README.md`. - [x] I am using the latest version of **both** Open WebUI and Ollama. - [x] I have included the browser console logs. - [x] I have included the Docker container logs. - [x] I have **provided every relevant configuration, setting, and environment variable used in my setup.** - [x] I have clearly **listed every relevant configuration, custom setting, environment variable, and command-line option that influences my setup** (such as Docker Compose overrides, .env values, browser settings, authentication configurations, etc). - [x] I have documented **step-by-step reproduction instructions that are precise, sequential, and leave nothing to interpretation**. My steps: - Start with the initial platform/version/OS and dependencies used, - Specify exact install/launch/configure commands, - List URLs visited, user input (incl. example values/emails/passwords if needed), - Describe all options and toggles enabled or changed, - Include any files or environmental changes, - Identify the expected and actual result at each stage, - Ensure any reasonably skilled user can follow and hit the same issue. ### Expected Behavior Rich HTML UI embeds (from functions/actions) from should render [below the message text content](https://docs.openwebui.com/features/extensibility/plugin/development/rich-ui/#rendering-position). ### Actual Behavior Rich UI embeds are rendered above the the message content (see screenshot) ### Steps to Reproduce 1. Navigate to the admin panel and create new Function using the sample code from the docs: ```python """ title: Rich UI Demo Action author: open-webui version: 0.1.0 description: Demonstrates Rich UI embedding from an Action function. """ from pydantic import BaseModel, Field class Action: class Valves(BaseModel): pass def __init__(self): self.valves = self.Valves() async def action(self, body: dict, __user__=None, __event_emitter__=None) -> None: from fastapi.responses import HTMLResponse html = """ <!DOCTYPE html> <html> <head> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 24px; color: #fff; } .card { background: rgba(255,255,255,0.15); backdrop-filter: blur(10px); border-radius: 16px; padding: 24px; border: 1px solid rgba(255,255,255,0.2); } h1 { font-size: 1.4em; margin-bottom: 8px; } p { opacity: 0.9; line-height: 1.5; margin-bottom: 12px; } .badge { display: inline-block; background: rgba(255,255,255,0.25); padding: 4px 12px; border-radius: 20px; font-size: 0.85em; font-weight: 600; } .stats { display: flex; gap: 16px; margin-top: 16px; } .stat { flex: 1; text-align: center; background: rgba(255,255,255,0.1); border-radius: 12px; padding: 12px; } .stat-value { font-size: 1.8em; font-weight: 700; } .stat-label { font-size: 0.8em; opacity: 0.8; margin-top: 4px; } </style> </head> <body> <div class="card"> <h1>Rich UI Embed Demo</h1> <p>This embed renders <strong>below</strong> the message text.</p> <span class="badge">Action Embed</span> <div class="stats"> <div class="stat"> <div class="stat-value">42</div> <div class="stat-label">Answers</div> </div> <div class="stat"> <div class="stat-value">99%</div> <div class="stat-label">Accuracy</div> </div> <div class="stat"> <div class="stat-value">0ms</div> <div class="stat-label">Latency</div> </div> </div> </div> <script> // Report height to parent so the iframe auto-sizes function reportHeight() { const h = document.documentElement.scrollHeight; parent.postMessage({ type: 'iframe:height', height: h }, '*'); } window.addEventListener('load', reportHeight); new ResizeObserver(reportHeight).observe(document.body); </script> </body> </html> """ return HTMLResponse(content=html, headers={"Content-Disposition": "inline"}) ``` 3. Start a new chat and wait for the response 4. Click the **Rich UI Demo** action button 5. Observe the rendered content ### Logs & Screenshots Current behavior: <img width="939" height="879" alt="Image" src="https://github.com/user-attachments/assets/5bec7c63-daf3-4820-bde6-58992809b824" /> After git patch: <img width="943" height="880" alt="Image" src="https://github.com/user-attachments/assets/534360aa-e18d-431a-97dc-b7a9f1ac73aa" /> ### Additional Information What I've tried: - Different HTML content with and without script/style tags - Different models - Chats with and without tool calls - Setting function mode to default and native This seems to just come down to the ordering in the ResponseMessage.svelelte file. Embeds rendered starting on L296 https://github.com/open-webui/open-webui/blob/4d058a125b17eb57212af5eab98d683548d546e3/src/lib/components/chat/Messages/ResponseMessage.svelte#L696-L713 Message text rendered starting on L783 https://github.com/open-webui/open-webui/blob/4d058a125b17eb57212af5eab98d683548d546e3/src/lib/components/chat/Messages/ResponseMessage.svelte#L783-L793 I tested the following patch, and the content is rendered in the correct position (see screenshot): ```diff diff --git a/src/lib/components/chat/Messages/ResponseMessage.svelte b/src/lib/components/chat/Messages/ResponseMessage.svelte index 98b0d95a0..58f8a2467 100644 --- a/src/lib/components/chat/Messages/ResponseMessage.svelte +++ b/src/lib/components/chat/Messages/ResponseMessage.svelte @@ -693,25 +693,6 @@ </div> {/if} - {#if message?.embeds && message.embeds.length > 0} - <div - class="my-1 w-full flex overflow-x-auto gap-2 flex-wrap" - id={`${message.id}-embeds-container`} - > - {#each message.embeds as embed, idx} - <div class="my-2 w-full" id={`${message.id}-embeds-${idx}`}> - <FullHeightIframe - src={embed} - allowScripts={true} - allowForms={true} - allowSameOrigin={$settings?.iframeSandboxAllowSameOrigin ?? false} - allowPopups={true} - /> - </div> - {/each} - </div> - {/if} - {#if edit === true} <div class="w-full bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 my-2"> <textarea @@ -849,6 +830,25 @@ <CodeExecutions codeExecutions={message.code_executions} /> {/if} </div> + + {#if message?.embeds && message.embeds.length > 0} + <div + class="my-1 w-full flex overflow-x-auto gap-2 flex-wrap" + id={`${message.id}-embeds-container`} + > + {#each message.embeds as embed, idx} + <div class="my-2 w-full" id={`${message.id}-embeds-${idx}`}> + <FullHeightIframe + src={embed} + allowScripts={true} + allowForms={true} + allowSameOrigin={$settings?.iframeSandboxAllowSameOrigin ?? false} + allowPopups={true} + /> + </div> + {/each} + </div> + {/if} </div> </div> ``` Not sure if there's any other edge-cases that rely on the existing rendering order. I'm happy to open a PR, or feel free to use the above patch if that's easier.
GiteaMirror added the bug label 2026-04-20 02:24:04 -05:00
Author
Owner

@Classic298 commented on GitHub (Mar 26, 2026):

Sorry the docs is wrong here my mistake i will correct it.

It's correct that they render above the message and not below it (has been discussed multiple times) but unfortunately this is the final decision that was made

<!-- gh-comment-id:4131809709 --> @Classic298 commented on GitHub (Mar 26, 2026): Sorry the docs is wrong here my mistake i will correct it. It's correct that they render above the message and not below it (has been discussed multiple times) but unfortunately this is the final decision that was made
Author
Owner

@ApexArray commented on GitHub (Mar 26, 2026):

Sorry the docs is wrong here my mistake i will correct it.

It's correct that they render above the message and not below it (has been discussed multiple times) but unfortunately this is the final decision that was made

Thanks for the update, I did not find any of those discussions when searching before I opened the issue.

I'm working on a context usage meter plugin (filter) that would be better suited for the bottom of the message.

Image

Is there any appetite to support both options? I'd be happy to work on a PR introducing an extra (optional) emitter argument or similar to control whether it gets rendered at the top or bottom.

Alternatively, it would be nice to have some "middle ground" between embeds (stored in db and not removable, thus polluting chat history) and execute (ephemeral and don't persist after navigating away).

There are a some cases where it makes sense to have a live "footer" displayed at the bottom of the most recent message that persists in the database (or is built dynamically based on db values), but is not displayed for every single message in the chat history.

Let me know if it's worth opening a discussion or submitting an RFC.

<!-- gh-comment-id:4132147032 --> @ApexArray commented on GitHub (Mar 26, 2026): > Sorry the docs is wrong here my mistake i will correct it. > > It's correct that they render above the message and not below it (has been discussed multiple times) but unfortunately this is the final decision that was made Thanks for the update, I did not find any of those discussions when searching before I opened the issue. I'm working on a context usage meter plugin (filter) that would be better suited for the bottom of the message. <img width="923" height="205" alt="Image" src="https://github.com/user-attachments/assets/da9a455a-64aa-4ffd-978f-64743542b8ff" /> Is there any appetite to support both options? I'd be happy to work on a PR introducing an extra (optional) emitter argument or similar to control whether it gets rendered at the top or bottom. Alternatively, it would be nice to have some "middle ground" between embeds (stored in db and not removable, thus polluting chat history) and execute (ephemeral and don't persist after navigating away). There are a some cases where it makes sense to have a live "footer" displayed at the bottom of the most recent message that persists in the database (or is built dynamically based on db values), but is not displayed for every single message in the chat history. Let me know if it's worth opening a discussion or submitting an RFC.
Author
Owner

@Classic298 commented on GitHub (Mar 26, 2026):

All of this was discussed in the discussions i mentioned that were had. This is not wanted (unfortunately)

<!-- gh-comment-id:4132756832 --> @Classic298 commented on GitHub (Mar 26, 2026): All of this was discussed in the discussions i mentioned that were had. This is not wanted (unfortunately)
Author
Owner

@Classic298 commented on GitHub (Mar 26, 2026):

What you can try is have it be emitted via a filter

<!-- gh-comment-id:4132778613 --> @Classic298 commented on GitHub (Mar 26, 2026): What you can try is have it be emitted via a filter
Author
Owner

@ApexArray commented on GitHub (Mar 26, 2026):

What you can try is have it be emitted via a filter

That's how it's working currently. The filter grabs token usage stats returned from the inference provider in the stream method and falls back to manually counting tokens in body["messages"] if no stats are returned.

It then builds the HTML fragment and emits via __event_emitter__({"type": "embeds", "data": {"embeds": [html_fragment]}})

I know I could also use an execute event, but those won't persist after navigating away from the page.

Let me know if there's an alternative workflow that would work better for this case.

<!-- gh-comment-id:4137700644 --> @ApexArray commented on GitHub (Mar 26, 2026): > What you can try is have it be emitted via a filter That's how it's working currently. The filter grabs token usage stats returned from the inference provider in the `stream` method and falls back to manually counting tokens in `body["messages"]` if no stats are returned. It then builds the HTML fragment and emits via `__event_emitter__({"type": "embeds", "data": {"embeds": [html_fragment]}})` I know I could also use an execute event, but those won't persist after navigating away from the page. Let me know if there's an alternative workflow that would work better for this case.
Author
Owner

@Classic298 commented on GitHub (Mar 26, 2026):

@tjbck

<!-- gh-comment-id:4137980818 --> @Classic298 commented on GitHub (Mar 26, 2026): @tjbck
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/open-webui#19874