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} +