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

247 lines
6.8 KiB
Python
Raw 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-10 08:54:13 +00:00
from open_webui.models.users import Users
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
run_cleanup = True
2024-09-24 13:41:23 +00:00
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,
)
run_cleanup = 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 = {}
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 run_cleanup:
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-06-08 00:35:01 +00:00
SESSION_POOL[sid] = user.id
if user.id in USER_POOL:
USER_POOL[user.id].append(sid)
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-09-22 00:12:55 +00:00
await sio.emit("user-count", {"count": len(USER_POOL.items())})
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):
2024-09-12 13:13:21 +00:00
# print("user-join", sid, data)
2024-06-04 16:52:27 +00:00
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-08-03 13:24:26 +00:00
SESSION_POOL[sid] = user.id
if user.id in USER_POOL:
USER_POOL[user.id].append(sid)
else:
USER_POOL[user.id] = [sid]
2024-06-08 00:35:01 +00:00
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-09-22 00:12:55 +00:00
await sio.emit("user-count", {"count": len(USER_POOL.items())})
2024-06-04 16:52:27 +00:00
2024-06-04 08:10:31 +00:00
@sio.on("user-count")
async def user_count(sid):
2024-09-22 00:12:55 +00:00
await sio.emit("user-count", {"count": len(USER_POOL.items())})
2024-06-04 18:13:43 +00:00
2024-12-14 06:51:43 +00:00
@sio.on("chat")
async def chat(sid, data):
print("chat", sid, SESSION_POOL[sid], data)
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:
user_id = SESSION_POOL[sid]
del SESSION_POOL[sid]
2024-06-08 00:35:01 +00:00
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-06-04 08:10:31 +00:00
await sio.emit("user-count", {"count": len(USER_POOL)})
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):
await sio.emit(
"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 __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__