This commit is contained in:
Timothy Jaeryang Baek
2026-05-09 03:15:53 +09:00
parent f152ad36b3
commit 552bbcecfa
3 changed files with 85 additions and 63 deletions
+21 -11
View File
@@ -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__
+9 -3
View File
@@ -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 = ''
+55 -49
View File
@@ -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));
}
}
};