From 552bbcecfae5ae273ab98e2ce3e540d0771aa964 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 9 May 2026 03:15:53 +0900 Subject: [PATCH] refac --- backend/open_webui/socket/main.py | 32 ++++++--- backend/open_webui/tools/builtin.py | 12 +++- src/routes/+layout.svelte | 104 +++++++++++++++------------- 3 files changed, 85 insertions(+), 63 deletions(-) diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index e224408742..bed04c549b 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -948,17 +948,27 @@ async def get_event_emitter(request_info, update_db=True): async def get_event_call(request_info): async def __event_caller__(event_data): - response = await sio.call( - 'events', - { - 'chat_id': request_info.get('chat_id', None), - 'message_id': request_info.get('message_id', None), - 'data': event_data, - }, - to=request_info['session_id'], - timeout=WEBSOCKET_EVENT_CALLER_TIMEOUT, - ) - return response + session_id = request_info['session_id'] + + # Fast-fail if the client has disconnected. + if session_id not in SESSION_POOL: + log.warning(f'Event caller: session {session_id} no longer connected') + return {'error': 'Client session disconnected.'} + + try: + return await sio.call( + 'events', + { + 'chat_id': request_info.get('chat_id', None), + 'message_id': request_info.get('message_id', None), + 'data': event_data, + }, + to=session_id, + timeout=WEBSOCKET_EVENT_CALLER_TIMEOUT, + ) + except TimeoutError: + log.warning(f'Event caller timed out for session {session_id}') + return {'error': 'Event call timed out. The browser tab may be inactive or closed.'} if 'session_id' in request_info and 'chat_id' in request_info and 'message_id' in request_info: return __event_caller__ diff --git a/backend/open_webui/tools/builtin.py b/backend/open_webui/tools/builtin.py index dad212c90d..736402d0c8 100644 --- a/backend/open_webui/tools/builtin.py +++ b/backend/open_webui/tools/builtin.py @@ -471,9 +471,15 @@ async def execute_code( # Parse the output - pyodide returns dict with stdout, stderr, result if isinstance(output, dict): - stdout = output.get('stdout', '') - stderr = output.get('stderr', '') - result = output.get('result', '') + # Handle error responses from event_caller (e.g. session disconnected, timeout) + if output.get('error') and not output.get('stdout') and not output.get('result'): + stderr = output['error'] + stdout = '' + result = '' + else: + stdout = output.get('stdout', '') + stderr = output.get('stderr', '') + result = output.get('result', '') else: stdout = '' stderr = '' diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b41abda179..5efbd43b1c 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -483,59 +483,18 @@ return; } - if ((event.chat_id !== $chatId && !$temporaryChatEnabled) || isInBackground) { - if (type === 'chat:completion') { - const { done, content, title } = data; - const displayTitle = title || $i18n.t('New Chat'); - - if (done) { - if ( - ($settings?.notificationSound ?? true) && - ($settings?.notificationSoundAlways ?? false) - ) { - playingNotificationSound.set(true); - - const audio = new Audio(`/audio/notification.mp3`); - audio.play().finally(() => { - // Ensure the global state is reset after the sound finishes - playingNotificationSound.set(false); - }); - } - - if ($isLastActiveTab) { - if ($settings?.notificationEnabled ?? false) { - new Notification(`${displayTitle} • Open WebUI`, { - body: content, - icon: `${WEBUI_BASE_URL}/static/favicon.png` - }); - } - } - - toast.custom(NotificationToast, { - componentProps: { - onClick: () => { - goto(`/c/${event.chat_id}`); - }, - content: content, - title: displayTitle - }, - duration: 15000, - unstyled: true - }); - } - } else if (type === 'chat:title') { - currentChatPage.set(1); - await chats.set(await getChatList(localStorage.token, $currentChatPage)); - } else if (type === 'chat:tags') { - tags.set(await getAllTags(localStorage.token)); - } - } else if (data?.session_id === $socket.id) { + // Session-targeted RPC calls (code execution, tool calls, direct completion) + // must ALWAYS be processed regardless of active chat or tab visibility, + // because the backend's sio.call blocks waiting for our callback response. + if (data?.session_id === $socket.id) { if (type === 'execute:python') { console.log('execute:python', data); executePythonAsWorker(data.id, data.code, cb, data.files || []); + return; } else if (type === 'execute:tool') { console.log('execute:tool', data); executeTool(data, cb, event.chat_id); + return; } else if (type === 'request:chat:completion') { console.log(data, $socket.id); const { session_id, channel, form_data, model } = data; @@ -621,8 +580,55 @@ done: true }); } - } else { - console.log('chatEventHandler', event); + return; + } + } + + if ((event.chat_id !== $chatId && !$temporaryChatEnabled) || isInBackground) { + if (type === 'chat:completion') { + const { done, content, title } = data; + const displayTitle = title || $i18n.t('New Chat'); + + if (done) { + if ( + ($settings?.notificationSound ?? true) && + ($settings?.notificationSoundAlways ?? false) + ) { + playingNotificationSound.set(true); + + const audio = new Audio(`/audio/notification.mp3`); + audio.play().finally(() => { + // Ensure the global state is reset after the sound finishes + playingNotificationSound.set(false); + }); + } + + if ($isLastActiveTab) { + if ($settings?.notificationEnabled ?? false) { + new Notification(`${displayTitle} • Open WebUI`, { + body: content, + icon: `${WEBUI_BASE_URL}/static/favicon.png` + }); + } + } + + toast.custom(NotificationToast, { + componentProps: { + onClick: () => { + goto(`/c/${event.chat_id}`); + }, + content: content, + title: displayTitle + }, + duration: 15000, + unstyled: true + }); + } + } else if (type === 'chat:title') { + currentChatPage.set(1); + await chats.set(await getChatList(localStorage.token, $currentChatPage)); + } else if (type === 'chat:tags') { + tags.set(await getAllTags(localStorage.token)); } } };