跳到主要内容

富 UI 元素嵌入

Tools 和 Actions 都支持富 UI 元素嵌入,允许它们返回直接显示在对话中的 HTML 内容和交互式 iframe。此功能支持复杂的视觉界面、交互式小部件、图表、仪表板和其他富 Web 内容——无论函数是由模型(Tool)还是用户(Action)触发的。

当函数返回带有适当标头的 HTMLResponse 时,内容将作为交互式 iframe 嵌入到对话界面中,而不是显示为纯文本。

Tool 用法

要嵌入 HTML 内容,你的工具应返回带有 Content-Disposition: inline 标头的 HTMLResponse

from fastapi.responses import HTMLResponse

def render_checklist(self, items: list[str]) -> HTMLResponse:
    """
    Renders an interactive checklist that embeds in the chat.

    :param items: The items to show in the checklist
    """
    items_html = "".join(
        f'<li><label><input type="checkbox"> {item}</label></li>' for item in items
    )
    html_content = f"""
    <!DOCTYPE html>
    <html>
    <head>
        <title>Checklist</title>
        <style>
            body {{ font-family: system-ui, sans-serif; padding: 1rem; }}
            ul {{ list-style: none; padding: 0; }}
            li {{ padding: 0.25rem 0; }}
        </style>
    </head>
    <body>
        <ul>{items_html}</ul>
    </body>
    </html>
    """

    headers = {"Content-Disposition": "inline"}
    return HTMLResponse(content=html_content, headers=headers)

自定义结果上下文

默认情况下,当工具返回 HTMLResponse 时,LLM 会收到一条通用消息:"<tool_name>: Embedded UI result is active and visible to the user."。这不会向模型提供关于实际生成了什么的信息。

要为 LLM 提供关于嵌入的可操作上下文,请返回 (HTMLResponse, context)元组,其中第二个元素是 strdictlist

from fastapi.responses import HTMLResponse

def render_feedback_form(self, prompt: str) -> tuple:
    """
    Renders an interactive feedback form and returns context to the LLM.

    :param prompt: The question to show the user above the form
    """
    html_content = "<html>...</html>"
    headers = {"Content-Disposition": "inline"}

    # The LLM receives this context instead of the generic message
    result_context = {
        "status": "success",
        "form_type": "feedback",
        "fields": ["rating", "comment"],
        "description": f"Rendered a feedback form asking: {prompt!r}"
    }

    return HTMLResponse(content=html_content, headers=headers), result_context

上下文可以是:

  • 字符串 — 原样发送给 LLM(如 "Generated a bar chart with 5 categories"
  • dict — 序列化为 JSON 提供结构化上下文
  • list — 序列化为 JSON 提供多个项目

如果第二个元素缺失或不是这些类型之一,则使用通用回退消息。

何时使用

当你的工具生成动态内容,且 LLM 需要在后续对话中引用所生成内容时,这尤为有用——例如,告诉 LLM 使用了哪些参数、显示了哪些数据,或用户接下来可以执行什么操作。

Action 用法

Action 的工作方式完全相同。富 UI 嵌入会通过事件发射器传递到对话中:

方案 A — HTMLResponse:

from fastapi.responses import HTMLResponse

async def action(self, body, __event_emitter__=None):
    html = "<html><body><h1>Dashboard</h1></body></html>"
    return HTMLResponse(content=html, headers={"Content-Disposition": "inline"})

方案 B — 带标头的元组:

async def action(self, body, __event_emitter__=None):
    html = "<h1>Interactive Chart</h1><script>...</script>"
    return (html, {"Content-Disposition": "inline", "Content-Type": "text/html"})

Pipe 函数用法

Tool 通过 Open WebUI 内置的工具调用层(无论是原生模式还是旧版模式)被调用时,中间件会自动识别 HTMLResponse 结果、提取 HTML 并将其作为嵌入内容发出,同时触发 "embeds" 事件,无需额外处理。

不过,当 Pipe 函数 直接调用外部提供商的 API(例如 Azure OpenAI、Anthropic)时,它会完全绕过中间件。此时 Pipe 需要自己管理工具调用流程——向提供商发送工具定义、接收响应中的 tool_calls、执行工具并把结果回传。在这种场景下,中间件根本看不到 HTMLResponse,因此 Pipe 必须手动发出嵌入内容

实现模式

在 Pipe 的工具执行逻辑中,检测工具是否返回 HTMLResponse,提取 HTML 正文,通过事件发射器发出,并向 LLM 返回一段文本摘要:

from fastapi.responses import HTMLResponse

async def execute_tool(self, tool_call, tools, __event_emitter__):
    tool = tools.get(tool_call.name)
    if not tool:
        return "Tool not found"

    parsed_args = json.loads(tool_call.arguments) if tool_call.arguments else {}
    result = await tool["callable"](**parsed_args)

    # Detect HTMLResponse and emit as embed
    if isinstance(result, HTMLResponse):
        content_disposition = result.headers.get("Content-Disposition", "")
        if "inline" in content_disposition:
            html_content = result.body.decode("utf-8", "replace")

            # Emit the embed so the frontend renders it
            await __event_emitter__({
                "type": "embeds",
                "data": {"embeds": [html_content]},
            })

            # Return a text summary to the LLM (not the raw HTML)
            return json.dumps({
                "status": "success",
                "message": f"{tool_call.name}: UI rendered successfully.",
            })

    # For non-HTML results, return as normal
    return json.dumps(result)

为什么需要这样做

Open WebUI 的中间件会在 process_tool_result() 中处理工具结果,自动完成 HTMLResponse 检测、嵌入提取和事件发出。但只有当 中间件 负责协调工具调用流程时,这个函数才会被调用。若 Pipe 自己处理工具调用(因为它直接向 LLM 提供商发出 HTTP 请求),就必须自行复现这部分逻辑。

关键步骤

  1. 检测 HTMLResponse —— 检查工具返回值是否为带有 Content-Disposition: inlineHTMLResponse
  2. 提取 HTML —— 对响应正文进行解码
  3. 发出 "embeds" 事件 —— 通过 __event_emitter__ 发送 HTML,让前端将其渲染为富 UI 卡片
  4. 向 LLM 返回文本 —— 模型应收到文本摘要(而不是原始 HTML),这样它才能自然地继续对话

元组上下文支持

和标准 Tool 一样,你也可以从工具中返回 (HTMLResponse, context) 元组。在 Pipe 的执行逻辑里,按同样方式解包即可:

if isinstance(result, tuple) and len(result) == 2 and isinstance(result[0], HTMLResponse):
    html_response, result_context = result
    # ... emit html_response.body as embed ...
    # ... return result_context to the LLM instead of the generic message ...
社区参考

Native Tool Calling Pipe 是社区维护的一个 Pipe,完整实现了 OpenAI 原生工具调用流程,并支持流式与多次调用。它可以改造用于 Azure OpenAI 或其他提供商,是实现该模式的实用参考。

iframe 高度与自动调整

富 UI 嵌入会渲染在一个沙箱化的 iframe 中。为了让内容在不出现滚动条的情况下完整显示,iframe 需要知道自身内容有多高。这里有两种机制:

通过 postMessage 报告高度(推荐)

allowSameOrigin 关闭(默认值)时,父页面无法直接读取 iframe 的内容高度。你的 HTML 必须通过向父窗口发送消息来报告自身高度:

<script>
  function reportHeight() {
    const h = document.documentElement.scrollHeight;
    parent.postMessage({ type: 'iframe:height', height: h }, '*');
  }
  window.addEventListener('load', reportHeight);
  // Also re-report when content changes size
  new ResizeObserver(reportHeight).observe(document.body);
</script>

请把这段脚本放到每个 Rich UI 嵌入的 <body> 末尾。否则 iframe 会保持较小的默认高度,内容会被截断并出现滚动条。

同源自动调整大小

allowSameOrigin 开启时(通过用户设置 iframeSandboxAllowSameOrigin),父页面可以直接测量 iframe 的内容高度并自动调整大小——你的 HTML 中不需要任何脚本。不过,这会带来安全上的权衡(见下文)。

沙箱与安全性

嵌入的 iframe 运行在 sandbox 中。以下沙箱标志默认始终启用:

  • allow-scripts —— 允许执行 JavaScript
  • allow-popups —— 允许弹出窗口(例如 window.open
  • allow-downloads —— 允许文件下载

用户可以在 设置 → 界面 中切换另外两个标志:

设置默认值说明
允许 Iframe 同源访问❌ 关闭允许 iframe 访问父页面上下文
允许 Iframe 表单提交❌ 关闭允许在嵌入内容中提交表单

allowSameOrigin

这是最需要关注的标志。出于安全原因,它默认 关闭

关闭时(默认):

  • iframe 与父页面完全隔离
  • 无法 读取父页面的 cookie、localStorage 或 DOM
  • 父页面 无法 读取 iframe 的内容高度(因此你必须使用上面的 postMessage 模式)
  • 这是最安全的选择,也最适合大多数场景

开启时:

  • iframe 可以与父页面上下文交互
  • 无需在 HTML 中写任何脚本即可自动调整大小
  • 如果检测到 Chart.js 和 Alpine.js 依赖,会自动注入
  • ⚠️ 请谨慎使用 —— 只有在你信任嵌入内容时才开启

用户可以在 设置 → 界面 → Iframe 同源访问 中切换此设置。

沙箱设置的实际影响

allowSameOrigin 关闭(默认)时,Rich UI iframe 会受到强沙箱限制。这意味着:

  • 嵌入内部的下载 很难甚至无法完成——尤其是在 iOS 上,沙箱化 iframe 完全无法触发文件下载
  • 嵌入中的 JavaScript 无法与 Open WebUI 本身交互 —— iframe 无法访问父页面的 DOM、cookie、localStorage 或任何 Open WebUI API
  • 跨框架通信 仅限于 postMessage —— 不过,提示提交 在跨域场景下仍可通过用户确认对话框工作

如果你的 Rich UI 嵌入需要触发下载、与 Open WebUI 前端交互,或执行会影响父页面的 JavaScript,就必须开启同源 iframe 访问。请在 设置 → 界面 → Iframe 同源访问 中启用它。

如果你只是需要一些临时交互,并且需要完整页面访问权限,可以考虑改用 execute 事件,它会在主页面上下文中以未沙箱化的方式运行。

社区展示:开启同源后的流式 Rich UI

如果你想看看开启同源后 Rich UI 能走多远,可以看看社区的 Inline Visualizer v2 工具(也可在社区站点中通过 演示讨论 访问)。

它展示了基础文档里没有的几种模式:

  • 实时流式 HTML/SVG。 这个工具返回一个空壳,模型随后会在正常响应中通过纯文本 @@@VIZ-START / @@@VIZ-END 标记之间插入标记内容。iframe 内部的同源观察者会跟踪父聊天的 DOM,提取不断增长的代码块,并在 token 到来时把新节点合并到 iframe 中——因此仪表盘和图表会逐步“画出来”,而不是等流结束后一次性出现。
  • 双向桥接。 sendPrompt(text) 可以把任意可点击节点变成一条后续用户消息。saveState(k, v) / loadState(k, fallback) 会代理父页面中按消息隔离的 localStorage,让滑块和开关在刷新后仍能保留。copyTexttoast(msg, kind)openLink 则补齐了其他常用能力。
  • 可直接交付的设计系统。 主题感知的 CSS 变量、9 档颜色调色板、SVG 工具类、自动明暗主题适配,以及覆盖 46 种语言的 230 条本地化字符串——全部由一个工具直接提供,无需修改核心代码。
  • 渐进式 DOM 合并。 安全截断的 HTML 解析器会在每个 tick 只释放最长有效前缀;合并器只追加新节点,因此现有元素不会重新挂载,动画也不会在流式过程中重复触发。

当你需要判断某个生成式 UI / 流式 UI 功能是应该改核心还是只放在插件层时,这个案例很有参考价值。(剧透:大多数时候是后者。)

渲染位置

  • Tool 嵌入 会在工具调用结果中 内联 显示,位置在工具调用指示器处(也就是 “View Result from...” 那一行)
  • Action 嵌入 和消息级嵌入会显示在消息正文的 上方

高级通信

iframe 与父窗口之间的通信不止于高度上报。还可以使用以下模式:

负载请求

iframe 可以向父页面请求数据负载。加载完成后把动态数据传入嵌入内容时,这很有用:

<script>
  // Listen for the response
  window.addEventListener('message', (e) => {
    if (e.data?.type === 'payload') {
      const data = e.data.payload;
      // Use the payload data to populate your UI
      console.log('Received payload:', data);
    }
  });

  // Trigger the request
  parent.postMessage({ type: 'payload', requestId: 'my-request' }, '*');
</script>

父页面会返回 { type: 'payload', requestId: ..., payload: ... }

负载来自哪里

并没有一个独立的"设置 payload"调用。payload 就是父组件在实例化 iframe 时已经配置好的内容——目前只有一条路径会真正配置它:

  • 在 chat-controls 嵌入面板中通过引用打开的嵌入 —— 当用户点击某个带有 embed URL 的引用徽章时,侧边面板会打开,并把完整的引用/source 对象(也就是你通过 __event_emitter__ 发出的 source / citation 事件中传入的那个 dict)作为 payload 暴露出来。要设置它,就发出一个 source 事件,把你想让 iframe 获取的内容放在 data 里。iframe 再通过上面的 postMessage 发起请求,父页面就会把那个引用对象原样返回。
  • 内联工具调用嵌入(由 Tool 方法返回 HTMLResponse(HTMLResponse, context) 产生)—— 这条路径父页面不会配置 payload,因此 payload 请求会返回 { type: 'payload', requestId: ..., payload: null }。请改用工具参数注入(需要 allowSameOrigin)把数据传入工具调用嵌入。
  • __event_emitter__({"type": "embeds", ...}) 与 Action 嵌入 —— 这两条路径也不会配置 payload;返回值为 null

简而言之:payload-request 走的是"侧边面板引用"专用通道,并不是通用的 iframe 数据通道。请按需要的数据流选择合适的渲染路径。

仅限工具的参数注入

Tool 方法以富 UI 嵌入的形式内联到工具调用展示位置时(也就是你从 Tool 方法本身返回 HTMLResponse(HTMLResponse, context) 元组),模型传入的实参会以 window.args 的形式暴露在 iframe 上——它是一个 JSON 字符串,不是已经解析好的对象。 使用前请自行解析:

<script>
  window.addEventListener('load', () => {
    const raw = window.args;             // JSON string, or undefined
    if (raw) {
      const args = JSON.parse(raw);      // parse to object
      document.getElementById('output').textContent = JSON.stringify(args, null, 2);
    } else {
      console.warn('window.args not set — see Requirements below.');
    }
  });
</script>
需要 allowSameOrigin —— 否则 window.args 会被静默设为 undefined

这些参数是通过 iframe.contentWindow.args = ... 从父页面注入的;除非 iframe 的 sandbox 带有 allow-same-origin,否则浏览器会因同源策略拦截它。该行为受每个用户的 设置 → 界面 → "iframe Sandbox Allow Same Origin" 开关控制,默认关闭。如果 window.args 返回的是 undefined,且你并没有修改过这个设置,那这就是原因:把它打开并重新加载即可。安全方面的权衡请参见上文 allowSameOrigin

window.args 在哪些场景下会被设置,哪些场景下不会
  • Tool 方法返回 HTMLResponse(HTMLResponse, context) 元组 —— 在 "View Result from..." 工具调用指示器位置以内联方式渲染。window.args 会被注入(前提是满足上面的 allowSameOrigin 要求)。
  • __event_emitter__({"type": "embeds", "data": {"embeds": [...]}}) —— 通过 chat-controls 嵌入面板渲染,这个路径根本不会绑定 args。无论 sandbox 怎么设置,window.args 都会是 undefined。这是设计如此:embeds-event 路径没有挂载工具调用,也就没有 args 可注入。
  • Action 嵌入 —— 由用户而不是模型触发,所以也没有模型提供的 args 可注入。

如果需要在以上 ❌ 路径渲染的嵌入里传入动态数据,请改用上方的负载请求模式。

自动注入的库

当启用 allowSameOrigin 时,iframe 组件会自动检测你的 HTML 中是否使用了某些库,并自动注入它们——不需要再手写 CDN <script> 标签:

  • Alpine.js —— 当 HTML 中出现任意 x-datax-initx-showx-bindx-onx-textx-htmlx-modelx-forx-ifx-effectx-transitionx-cloakx-refx-teleportx-id 指令时就会被检测到
  • Chart.js —— 当 HTML 中出现 new Chart(Chart. 时就会被检测到

这意味着你可以直接在 HTML 中编写 Alpine 或 Chart.js 代码;只要同源功能开启,它就会直接工作,无需导入脚本。

Ping/Pong 连通性

iframe 可以通过一个简单的 ping/pong 模式测试与父窗口的连通性:

<script>
  window.addEventListener('message', (e) => {
    if (e.data?.type === 'pong:ack') {
      console.log('Parent is listening!');
    }
  });

  // Send a pong to test connectivity
  parent.postMessage({ type: 'pong' }, '*');
</script>

提示提交

Rich UI 嵌入可以通过三种消息类型与聊天输入框交互:

消息类型行为
input:prompt用文本填充聊天输入框(不提交)
input:prompt:submit填充并提交提示词到聊天中
action:submit提交当前已在聊天输入框中的文本
<script>
  // Fill the chat input without submitting
  parent.postMessage({ type: 'input:prompt', text: 'Analyze this data' }, '*');

  // Fill and submit a prompt
  parent.postMessage({ type: 'input:prompt:submit', text: 'Show me a summary' }, '*');

  // Submit whatever is currently in the chat input
  parent.postMessage({ type: 'action:submit', text: '' }, '*');
</script>

同源与跨域行为差异:

  • allowSameOrigin 开启时,input:prompt:submit立即 提交提示词——无需用户再次操作。
  • allowSameOrigin 关闭(默认)时,来自跨域 iframe 的 input:prompt:submit 会在提交前向用户显示一个 确认对话框。这既能防滥用,又不需要开启同源访问就能实现交互式嵌入。
  • input:promptaction:submit 无论来源如何都行为一致——它们只会填充或提交用户已经能看到的文本。
提示

这意味着你的 Rich UI 嵌入可以包含交互式按钮(例如“解释这张图表”“用不同参数重新生成”),并把提示词提交到聊天中,而不要求用户开启同源访问。用户只会看到一个确认对话框,点击“确认”即可继续。

富 UI 嵌入与 execute 事件

Rich UI 嵌入与 execute 事件 是创建交互体验的两种互补方式。请根据需求选择:

Rich UI 嵌入execute 事件
运行位置沙箱化 iframe主页面上下文(无沙箱)
持久性持久 —— 会保存到聊天历史中短暂 —— 刷新或跳转后消失
页面访问权限默认与父页面隔离完全访问(DOM、cookie、localStorage)
表单需要开启 allowForms 设置始终可用(无沙箱)
适用场景持久的可视化内容、仪表盘、图表临时交互、副作用、下载、DOM 操作

需要在对话中长期保留的可视化内容,请使用 Rich UI 嵌入。像自定义对话框、触发下载、读取页面状态这类临时交互,则请使用 execute

使用场景

Rich UI 嵌入非常适合以下场景:

  • 交互式仪表盘 —— 实时数据可视化与控制项
  • 图表与曲线 —— 结合 Plotly、D3.js 或 Chart.js 等库进行交互式绘图
  • 表单界面 —— 带校验和动态行为的复杂输入表单
  • 媒体播放器 —— 视频、音频或交互式媒体内容
  • 下载触发器 —— 在 iOS PWA 中尤其有用,因为原生下载链接会被阻止
  • 自定义组件 —— 面向特定工具功能的专用 UI 组件
  • 外部集成 —— 嵌入来自外部服务或 API 的内容
  • 人工触发的可视化 —— 用户点击按钮后显示结果的动作,例如生成报告或触发下载

完整示例 Action

包含 Rich UI 嵌入的完整可运行示例 Action

这个 Action 会返回一张带样式的统计卡片,其中包含推荐的高度上报脚本:

"""
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>above</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"})

外部工具示例

对于通过 HTTP 端点提供的外部工具:

@app.post("/tools/dashboard")
async def create_dashboard():
    html = """
    <div style="padding: 20px;">
        <h2>System Dashboard</h2>
        <canvas id="myChart" width="400" height="200"></canvas>
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
        <script>
            const ctx = document.getElementById('myChart').getContext('2d');
            new Chart(ctx, {
                type: 'line',
                data: { /* your chart data */ }
            });
        </script>
    </div>
    """

    return HTMLResponse(
        content=html,
        headers={"Content-Disposition": "inline"}
    )

嵌入内容会自动继承响应式设计,并与聊天界面无缝集成,让用户在使用你的工具时获得近乎原生的体验。

CORS 与直连工具

直连外部工具是直接在浏览器中运行的工具。此时,工具由用户浏览器中的 JavaScript 调用。 由于我们依赖 Content-Disposition 标头,在远程工具服务器上启用 CORS 时,Open WebUI 可能因为 Access-Control-Expose-Headers 的限制而读不到该标头,从而无法从 fetch 结果中获取某些头信息。 为避免这种情况,你必须将 Access-Control-Expose-Headers 设为 Content-Disposition。下面是一个使用 Node.js 的工具示例:

const app = express();
const cors = require('cors');

app.use(cors())

app.get('/tools/dashboard', (req,res) => {
    let html = `
        <div style="padding: 20px;">
            <h2>System Dashboard</h2>
            <canvas id="myChart" width="400" height="200"></canvas>
            <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
            <script>
                const ctx = document.getElementById('myChart').getContext('2d');
                new Chart(ctx, {
                    type: 'line',
                    data: { /* your chart data */ }
                });
            </script>
        </div>
    `
    res.set({
        'Content-Disposition': 'inline'
        ,'Access-Control-Expose-Headers':'Content-Disposition'
    })
    res.send(html)
})

关于该标头的更多信息:https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Expose-Headers

本内容仅供参考,不构成任何保证、担保或合同承诺。Open WebUI 按“现状”提供。请参阅您的许可协议 以了解适用条款。