diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py
index 97f19dcded..0cd593de11 100644
--- a/backend/open_webui/utils/middleware.py
+++ b/backend/open_webui/utils/middleware.py
@@ -1581,7 +1581,8 @@ async def process_chat_response(
break
if tool_result is not None:
- tool_calls_display_content = f'{tool_calls_display_content}\nTool Executed
\n \n'
+ tool_result_embeds = result.get("embeds", "")
+ tool_calls_display_content = f'{tool_calls_display_content}\nTool Executed
\n \n'
else:
tool_calls_display_content = f'{tool_calls_display_content}\nExecuting...
\n \n'
@@ -2402,6 +2403,38 @@ async def process_chat_response(
except Exception as e:
tool_result = str(e)
+ tool_result_embeds = []
+ if tool.get("type") == "external" and isinstance(
+ tool_result, tuple
+ ):
+
+ tool_result, tool_response_headers = tool_result
+
+ if tool_response_headers:
+ content_disposition = tool_response_headers.get(
+ "Content-Disposition", ""
+ )
+
+ if "inline" in content_disposition:
+ content_type = tool_response_headers.get(
+ "Content-Type", ""
+ )
+ location = tool_response_headers.get("Location", "")
+
+ if "text/html" in content_type:
+ # Display as iframe embed
+ tool_result_embeds.append(tool_result)
+ tool_result = {
+ "status": "success",
+ "message": "Displayed as embed",
+ }
+ elif location:
+ tool_result_embeds.append(location)
+ tool_result = {
+ "status": "success",
+ "message": "Displayed as embed",
+ }
+
tool_result_files = []
if isinstance(tool_result, list):
for item in tool_result:
@@ -2426,6 +2459,11 @@ async def process_chat_response(
if tool_result_files
else {}
),
+ **(
+ {"embeds": tool_result_embeds}
+ if tool_result_embeds
+ else {}
+ ),
}
)
diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py
index bc90dd9d74..63df47700f 100644
--- a/backend/open_webui/utils/tools.py
+++ b/backend/open_webui/utils/tools.py
@@ -171,6 +171,8 @@ async def get_tools(
"tool_id": tool_id,
"callable": callable,
"spec": spec,
+ # Misc info
+ "type": "external",
}
# Handle function name collisions
@@ -646,7 +648,7 @@ async def execute_tool_server(
name: str,
params: Dict[str, Any],
server_data: Dict[str, Any],
-) -> Any:
+) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]:
error = None
try:
openapi = server_data.get("openapi", {})
@@ -718,6 +720,7 @@ async def execute_tool_server(
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
+ allow_redirects=False,
) as response:
if response.status >= 400:
text = await response.text()
@@ -728,13 +731,15 @@ async def execute_tool_server(
except Exception:
response_data = await response.text()
- return response_data
+ response_headers = response.headers
+ return (response_data, response_headers)
else:
async with request_method(
final_url,
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
+ allow_redirects=False,
) as response:
if response.status >= 400:
text = await response.text()
@@ -745,12 +750,13 @@ async def execute_tool_server(
except Exception:
response_data = await response.text()
- return response_data
+ response_headers = response.headers
+ return (response_data, response_headers)
except Exception as err:
error = str(err)
log.exception(f"API Request Error: {error}")
- return {"error": error}
+ return ({"error": error}, None)
def get_tool_server_url(url: Optional[str], path: str) -> str:
diff --git a/src/lib/components/common/Collapsible.svelte b/src/lib/components/common/Collapsible.svelte
index b092a49826..83522d7006 100644
--- a/src/lib/components/common/Collapsible.svelte
+++ b/src/lib/components/common/Collapsible.svelte
@@ -38,6 +38,8 @@
import CodeBlock from '../chat/Messages/CodeBlock.svelte';
import Markdown from '../chat/Messages/Markdown.svelte';
import Image from './Image.svelte';
+ import FullHeightIframe from './FullHeightIframe.svelte';
+ import { settings } from '$lib/stores';
export let open = false;
@@ -213,6 +215,21 @@
{@const args = decode(attributes?.arguments)}
{@const result = decode(attributes?.result ?? '')}
{@const files = parseJSONString(decode(attributes?.files ?? ''))}
+ {@const embeds = parseJSONString(decode(attributes?.embeds ?? ''))}
+
+ {#if embeds && Array.isArray(embeds) && embeds.length > 0}
+ {#each embeds as embed, idx}
+
+
+
+ {/each}
+ {/if}
{#if !grow}
{#if open && !hide}
diff --git a/src/lib/components/common/FullHeightIframe.svelte b/src/lib/components/common/FullHeightIframe.svelte
new file mode 100644
index 0000000000..ec9d49c5c3
--- /dev/null
+++ b/src/lib/components/common/FullHeightIframe.svelte
@@ -0,0 +1,144 @@
+
+
+{#if iframeDoc}
+
+{:else if iframeSrc}
+
+{/if}