diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 27d65ac26f..5bb0a019e2 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -3150,6 +3150,17 @@ async def execute_tool_call(form_data, metadata, event_caller, tool_call): ): return params, 'Error: tool call was not approved.', tool, tool_type, direct_tool + if ( + event_caller + and name.startswith('browser_') + and (tool_type == 'terminal' or tool.get('server', {}).get('is_terminal') is True) + ): + try: + async with asyncio.timeout(15): + await event_caller({'type': 'terminal:browser', 'data': {}}) + except TimeoutError: + pass + tool_call['started_at'] = time.time() started = time.perf_counter() try: diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 8ff4773fa8..82b18d2831 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -1278,7 +1278,12 @@ }); }; - const terminalEventHandler = (type: string, data: any, message: any) => { + const terminalEventHandler = async (type: string, data: any, message: any, cb?: Function) => { + if (type === 'terminal:browser') { + await terminalTabs?.ensureBrowserTab(); + cb?.(true); + return; + } const attachment = history.messages[message?.parentId]?.files?.find( (file: any) => file.type === 'filesystem' ); @@ -1784,7 +1789,7 @@ eventConfirmationInputType = data?.input?.type ?? data?.type ?? ''; eventConfirmationInputOptions = data?.input?.options ?? data?.options ?? []; } else if (type.startsWith('terminal:')) { - terminalEventHandler(type, data, message); + terminalEventHandler(type, data, message, cb); } else { console.log('Unknown message type', data); } diff --git a/src/lib/components/chat/ChatTabs.svelte b/src/lib/components/chat/ChatTabs.svelte index 83492f0d8b..a4191896fb 100644 --- a/src/lib/components/chat/ChatTabs.svelte +++ b/src/lib/components/chat/ChatTabs.svelte @@ -557,6 +557,17 @@ connection: connection ?? undefined }); }; + const readyBrowserTabs = new Set(); + export async function ensureBrowserTab() { + const findBrowserTab = () => + tabs.tabs.find((tab) => tab.kind === 'browser' && tab.terminalId === $selectedTerminalId); + if (!findBrowserTab()) openBrowser(); + for (let waited = 0; waited < 10_000; waited += 100) { + const browserTab = findBrowserTab(); + if (!browserTab || readyBrowserTabs.has(browserTab.id)) return; + await new Promise((resolve) => setTimeout(resolve, 100)); + } + } const closeTab = async (id: string) => { if (id === mainChatTabId) return; if (closing.has(id)) return; @@ -575,6 +586,7 @@ if (tab.kind === 'artifacts') showArtifacts.set(false); if (tab.kind === 'preview') showEmbeds.set(false); tabs = removeTab(tabs, id); + readyBrowserTabs.delete(id); delete dirtyFiles[id]; delete fileContexts[id]; if (!tabs.tabs.length) showTabs.set(false); @@ -1184,6 +1196,7 @@ active={!!$showTabs && visibleTabIds.has(tab.id)} initialUrl={tab.path ?? ''} onOpenBrowser={(url) => openBrowser(undefined, tab.terminalId, tab.browserBaseUrl, url)} + onReady={() => readyBrowserTabs.add(tab.id)} overlay={dragged} onNavigate={(path, label) => { tabs = { diff --git a/src/lib/components/chat/ChatTabs/Browser.svelte b/src/lib/components/chat/ChatTabs/Browser.svelte index afa17fa306..acf31f39e2 100644 --- a/src/lib/components/chat/ChatTabs/Browser.svelte +++ b/src/lib/components/chat/ChatTabs/Browser.svelte @@ -27,6 +27,7 @@ export let overlay = false; export let onNavigate: (url: string, label: string) => void = () => {}; export let onOpenBrowser: (url: string) => void = () => {}; + export let onReady: () => void = () => {}; export let onClose: (() => void) | null = null; let session: BrowserSession | null = null; let sessionChatId = chatId; @@ -270,6 +271,7 @@ return; } await tick(); + onReady(); } catch (reason) { error = 'Browser sessions unavailable. Update Open Terminal. ' + String(reason); return;