open-webui/backend/open_webui/socket/main.py

339 lines
9.3 KiB
Python
Raw Permalink Normal View History

2024-06-04 18:13:43 +00:00
import asyncio
2024-08-27 22:10:27 +00:00
import socketio
2024-09-22 00:14:59 +00:00
import logging
import sys
2024-09-22 00:12:55 +00:00
import time
2024-12-27 05:51:09 +00:00
from open_webui.models.users import Users, UserNameResponse
2024-12-23 02:40:01 +00:00
from open_webui.models.channels import Channels
2024-12-25 01:03:14 +00:00
from open_webui.models.chats import Chats
2024-09-20 21:43:22 +00:00
from open_webui.env import (
ENABLE_WEBSOCKET_SUPPORT,
WEBSOCKET_MANAGER,
WEBSOCKET_REDIS_URL,
)
2024-12-09 00:01:56 +00:00
from open_webui.utils.auth import decode_token
from open_webui.socket.utils import RedisDict, RedisLock
2024-06-04 06:39:52 +00:00
2024-09-22 00:14:59 +00:00
from open_webui.env import (
GLOBAL_LOG_LEVEL,
SRC_LOG_LEVELS,
)
logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["SOCKET"])
2024-09-20 21:43:22 +00:00
if WEBSOCKET_MANAGER == "redis":
mgr = socketio.AsyncRedisManager(WEBSOCKET_REDIS_URL)
2024-09-21 00:24:30 +00:00
sio = socketio.AsyncServer(
cors_allowed_origins=[],
async_mode="asgi",
transports=(["websocket"] if ENABLE_WEBSOCKET_SUPPORT else ["polling"]),
2024-09-21 00:24:30 +00:00
allow_upgrades=ENABLE_WEBSOCKET_SUPPORT,
always_connect=True,
client_manager=mgr,
)
2024-09-20 21:43:22 +00:00
else:
sio = socketio.AsyncServer(
cors_allowed_origins=[],
async_mode="asgi",
transports=(["websocket"] if ENABLE_WEBSOCKET_SUPPORT else ["polling"]),
2024-09-20 21:43:22 +00:00
allow_upgrades=ENABLE_WEBSOCKET_SUPPORT,
always_connect=True,
)
# Timeout duration in seconds
TIMEOUT_DURATION = 3
2024-09-24 13:41:23 +00:00
# Dictionary to maintain the user pool
if WEBSOCKET_MANAGER == "redis":
log.debug("Using Redis to manage websockets.")
2024-09-24 13:41:23 +00:00
SESSION_POOL = RedisDict("open-webui:session_pool", redis_url=WEBSOCKET_REDIS_URL)
USER_POOL = RedisDict("open-webui:user_pool", redis_url=WEBSOCKET_REDIS_URL)
USAGE_POOL = RedisDict("open-webui:usage_pool", redis_url=WEBSOCKET_REDIS_URL)
clean_up_lock = RedisLock(
redis_url=WEBSOCKET_REDIS_URL,
lock_name="usage_cleanup_lock",
timeout_secs=TIMEOUT_DURATION * 2,
)
aquire_func = clean_up_lock.aquire_lock
renew_func = clean_up_lock.renew_lock
release_func = clean_up_lock.release_lock
2024-09-24 13:41:23 +00:00
else:
SESSION_POOL = {}
USER_POOL = {}
USAGE_POOL = {}
aquire_func = release_func = renew_func = lambda: True
2024-06-04 06:39:52 +00:00
2024-09-22 00:12:55 +00:00
async def periodic_usage_pool_cleanup():
if not aquire_func():
log.debug("Usage pool cleanup lock already exists. Not running it.")
return
log.debug("Running periodic_usage_pool_cleanup")
try:
while True:
if not renew_func():
log.error(f"Unable to renew cleanup lock. Exiting usage pool cleanup.")
raise Exception("Unable to renew usage pool cleanup lock.")
now = int(time.time())
send_usage = False
for model_id, connections in list(USAGE_POOL.items()):
# Creating a list of sids to remove if they have timed out
expired_sids = [
sid
for sid, details in connections.items()
if now - details["updated_at"] > TIMEOUT_DURATION
]
for sid in expired_sids:
del connections[sid]
if not connections:
log.debug(f"Cleaning up model {model_id} from usage pool")
del USAGE_POOL[model_id]
else:
USAGE_POOL[model_id] = connections
send_usage = True
if send_usage:
# Emit updated usage information after cleaning
await sio.emit("usage", {"models": get_models_in_use()})
await asyncio.sleep(TIMEOUT_DURATION)
finally:
release_func()
2024-09-22 00:12:55 +00:00
2024-09-24 13:31:55 +00:00
app = socketio.ASGIApp(
sio,
socketio_path="/ws/socket.io",
)
2024-09-22 00:12:55 +00:00
def get_models_in_use():
# List models that are currently in use
models_in_use = list(USAGE_POOL.keys())
return models_in_use
@sio.on("usage")
async def usage(sid, data):
model_id = data["model"]
# Record the timestamp for the last update
current_time = int(time.time())
# Store the new usage data and task
USAGE_POOL[model_id] = {
**(USAGE_POOL[model_id] if model_id in USAGE_POOL else {}),
sid: {"updated_at": current_time},
}
# Broadcast the usage data to all clients
await sio.emit("usage", {"models": get_models_in_use()})
2024-06-04 06:39:52 +00:00
@sio.event
async def connect(sid, environ, auth):
user = None
2024-06-04 07:45:56 +00:00
if auth and "token" in auth:
data = decode_token(auth["token"])
if data is not None and "id" in data:
user = Users.get_user_by_id(data["id"])
if user:
2024-12-27 05:51:09 +00:00
SESSION_POOL[sid] = user.model_dump()
2024-06-08 00:35:01 +00:00
if user.id in USER_POOL:
2024-12-19 21:46:30 +00:00
USER_POOL[user.id] = USER_POOL[user.id] + [sid]
2024-06-08 00:35:01 +00:00
else:
USER_POOL[user.id] = [sid]
2024-09-12 13:13:21 +00:00
# print(f"user {user.name}({user.id}) connected with session ID {sid}")
2024-12-27 07:29:33 +00:00
await sio.emit("user-list", {"user_ids": list(USER_POOL.keys())})
2024-06-04 18:38:31 +00:00
await sio.emit("usage", {"models": get_models_in_use()})
2024-06-04 08:10:31 +00:00
2024-06-04 16:52:27 +00:00
@sio.on("user-join")
async def user_join(sid, data):
auth = data["auth"] if "auth" in data else None
2024-08-03 13:24:26 +00:00
if not auth or "token" not in auth:
return
2024-06-04 16:52:27 +00:00
2024-08-03 13:24:26 +00:00
data = decode_token(auth["token"])
if data is None or "id" not in data:
return
2024-06-04 16:52:27 +00:00
2024-08-03 13:24:26 +00:00
user = Users.get_user_by_id(data["id"])
if not user:
return
2024-06-04 16:52:27 +00:00
2024-12-27 05:51:09 +00:00
SESSION_POOL[sid] = user.model_dump()
2024-08-03 13:24:26 +00:00
if user.id in USER_POOL:
2024-12-19 21:46:30 +00:00
USER_POOL[user.id] = USER_POOL[user.id] + [sid]
2024-08-03 13:24:26 +00:00
else:
USER_POOL[user.id] = [sid]
2024-06-08 00:35:01 +00:00
2024-12-23 02:40:01 +00:00
# Join all the channels
channels = Channels.get_channels_by_user_id(user.id)
log.debug(f"{channels=}")
for channel in channels:
await sio.enter_room(sid, f"channel:{channel.id}")
2024-09-12 13:13:21 +00:00
# print(f"user {user.name}({user.id}) connected with session ID {sid}")
2024-06-04 16:52:27 +00:00
2024-12-27 07:29:33 +00:00
await sio.emit("user-list", {"user_ids": list(USER_POOL.keys())})
2024-12-25 09:32:47 +00:00
return {"id": user.id, "name": user.name}
2024-06-04 16:52:27 +00:00
2024-12-23 21:00:58 +00:00
@sio.on("join-channels")
async def join_channel(sid, data):
auth = data["auth"] if "auth" in data else None
if not auth or "token" not in auth:
return
data = decode_token(auth["token"])
if data is None or "id" not in data:
return
user = Users.get_user_by_id(data["id"])
if not user:
return
# Join all the channels
channels = Channels.get_channels_by_user_id(user.id)
log.debug(f"{channels=}")
for channel in channels:
await sio.enter_room(sid, f"channel:{channel.id}")
2024-12-27 05:51:09 +00:00
@sio.on("channel-events")
async def channel_events(sid, data):
room = f"channel:{data['channel_id']}"
participants = sio.manager.get_participants(
namespace="/",
room=room,
)
sids = [sid for sid, _ in participants]
if sid not in sids:
return
event_data = data["data"]
event_type = event_data["type"]
if event_type == "typing":
await sio.emit(
"channel-events",
{
"channel_id": data["channel_id"],
"data": event_data,
"user": UserNameResponse(**SESSION_POOL[sid]).model_dump(),
},
room=room,
)
2024-12-27 07:29:33 +00:00
@sio.on("user-list")
async def user_list(sid):
await sio.emit("user-list", {"user_ids": list(USER_POOL.keys())})
2024-06-04 18:13:43 +00:00
2024-06-04 06:39:52 +00:00
@sio.event
2024-06-04 08:10:31 +00:00
async def disconnect(sid):
2024-06-08 04:38:09 +00:00
if sid in SESSION_POOL:
2024-12-27 05:51:09 +00:00
user = SESSION_POOL[sid]
2024-06-08 04:38:09 +00:00
del SESSION_POOL[sid]
2024-06-08 00:35:01 +00:00
2024-12-27 05:51:09 +00:00
user_id = user["id"]
2024-09-22 00:12:55 +00:00
USER_POOL[user_id] = [_sid for _sid in USER_POOL[user_id] if _sid != sid]
2024-06-08 04:38:09 +00:00
if len(USER_POOL[user_id]) == 0:
del USER_POOL[user_id]
2024-12-27 07:29:33 +00:00
await sio.emit("user-list", {"user_ids": list(USER_POOL.keys())})
2024-06-04 06:39:52 +00:00
else:
2024-09-12 13:13:21 +00:00
pass
# print(f"Unknown session ID {sid} disconnected")
2024-07-11 17:40:10 +00:00
2024-07-31 12:35:02 +00:00
def get_event_emitter(request_info):
2024-07-11 17:40:10 +00:00
async def __event_emitter__(event_data):
2024-12-19 09:00:32 +00:00
user_id = request_info["user_id"]
2024-12-19 21:11:44 +00:00
session_ids = list(
set(USER_POOL.get(user_id, []) + [request_info["session_id"]])
)
2024-12-19 09:00:32 +00:00
for session_id in session_ids:
await sio.emit(
"chat-events",
{
"chat_id": request_info["chat_id"],
"message_id": request_info["message_id"],
"data": event_data,
},
to=session_id,
)
2024-07-11 17:40:10 +00:00
2024-12-25 01:03:14 +00:00
if "type" in event_data and event_data["type"] == "status":
Chats.add_message_status_to_chat_by_id_and_message_id(
request_info["chat_id"],
request_info["message_id"],
event_data.get("data", {}),
)
2024-07-11 17:40:10 +00:00
return __event_emitter__
2024-07-31 12:35:02 +00:00
def get_event_call(request_info):
2024-07-11 17:40:10 +00:00
async def __event_call__(event_data):
response = await sio.call(
"chat-events",
{
"chat_id": request_info["chat_id"],
2024-07-11 17:41:13 +00:00
"message_id": request_info["message_id"],
2024-07-11 17:40:10 +00:00
"data": event_data,
},
to=request_info["session_id"],
)
return response
return __event_call__
2024-12-21 06:54:43 +00:00
def get_user_id_from_session_pool(sid):
2024-12-27 05:51:09 +00:00
user = SESSION_POOL.get(sid)
if user:
return user["id"]
return None
2024-12-25 07:57:39 +00:00
def get_user_ids_from_room(room):
active_session_ids = sio.manager.get_participants(
namespace="/",
room=room,
)
active_user_ids = list(
2024-12-27 05:51:09 +00:00
set(
[SESSION_POOL.get(session_id[0])["id"] for session_id in active_session_ids]
)
2024-12-25 07:57:39 +00:00
)
return active_user_ids
2024-12-27 07:29:33 +00:00
def get_active_status_by_user_id(user_id):
if user_id in USER_POOL:
return True
return False