From 1ac87c55fff6c4f68d72f447b4d0b17fcaffd797 Mon Sep 17 00:00:00 2001
From: Timothy Jaeryang Baek <tim@openwebui.com>
Date: Fri, 28 Mar 2025 11:47:14 -0700
Subject: [PATCH] chore: format

---
 backend/open_webui/config.py       |   8 +-
 backend/open_webui/socket/main.py  |  39 +-
 backend/open_webui/socket/utils.py |   9 +-
 backend/open_webui/utils/redis.py  |  46 ++-
 package-lock.json                  | 571 ++++++++++++++++++-----------
 package.json                       |   3 +-
 6 files changed, 432 insertions(+), 244 deletions(-)

diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
index d3085cf54..d8b7b98ed 100644
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -32,6 +32,7 @@ from open_webui.env import (
 from open_webui.internal.db import Base, get_db
 from open_webui.utils.redis import get_redis_connection
 
+
 class EndpointFilter(logging.Filter):
     def filter(self, record: logging.LogRecord) -> bool:
         return record.getMessage().find("/health") == -1
@@ -254,11 +255,14 @@ class AppConfig:
     _state: dict[str, PersistentConfig]
     _redis: Optional[redis.Redis] = None
 
-    def __init__(self, redis_url: Optional[str] = None, redis_sentinels: Optional[list] = []):
+    def __init__(
+        self, redis_url: Optional[str] = None, redis_sentinels: Optional[list] = []
+    ):
         super().__setattr__("_state", {})
         if redis_url:
             super().__setattr__(
-                "_redis", get_redis_connection(redis_url, redis_sentinels, decode_responses=True)
+                "_redis",
+                get_redis_connection(redis_url, redis_sentinels, decode_responses=True),
             )
 
     def __setattr__(self, key, value):
diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py
index f92b7b5e3..83dd74fff 100644
--- a/backend/open_webui/socket/main.py
+++ b/backend/open_webui/socket/main.py
@@ -8,7 +8,11 @@ from redis import asyncio as aioredis
 from open_webui.models.users import Users, UserNameResponse
 from open_webui.models.channels import Channels
 from open_webui.models.chats import Chats
-from open_webui.utils.redis import parse_redis_sentinel_url, get_sentinels_from_env, AsyncRedisSentinelManager
+from open_webui.utils.redis import (
+    parse_redis_sentinel_url,
+    get_sentinels_from_env,
+    AsyncRedisSentinelManager,
+)
 
 from open_webui.env import (
     ENABLE_WEBSOCKET_SUPPORT,
@@ -35,8 +39,15 @@ log.setLevel(SRC_LOG_LEVELS["SOCKET"])
 if WEBSOCKET_MANAGER == "redis":
     if WEBSOCKET_SENTINEL_HOSTS:
         redis_config = parse_redis_sentinel_url(WEBSOCKET_REDIS_URL)
-        mgr = AsyncRedisSentinelManager(WEBSOCKET_SENTINEL_HOSTS.split(','), sentinel_port=int(WEBSOCKET_SENTINEL_PORT), redis_port=redis_config["port"],
-                                        service=redis_config["service"], db=redis_config["db"], username=redis_config["username"], password=redis_config["password"])
+        mgr = AsyncRedisSentinelManager(
+            WEBSOCKET_SENTINEL_HOSTS.split(","),
+            sentinel_port=int(WEBSOCKET_SENTINEL_PORT),
+            redis_port=redis_config["port"],
+            service=redis_config["service"],
+            db=redis_config["db"],
+            username=redis_config["username"],
+            password=redis_config["password"],
+        )
     else:
         mgr = socketio.AsyncRedisManager(WEBSOCKET_REDIS_URL)
     sio = socketio.AsyncServer(
@@ -64,10 +75,24 @@ TIMEOUT_DURATION = 3
 
 if WEBSOCKET_MANAGER == "redis":
     log.debug("Using Redis to manage websockets.")
-    redis_sentinels=get_sentinels_from_env(WEBSOCKET_SENTINEL_HOSTS, WEBSOCKET_SENTINEL_PORT)
-    SESSION_POOL = RedisDict("open-webui:session_pool", redis_url=WEBSOCKET_REDIS_URL, redis_sentinels=redis_sentinels)
-    USER_POOL = RedisDict("open-webui:user_pool", redis_url=WEBSOCKET_REDIS_URL, redis_sentinels=redis_sentinels)
-    USAGE_POOL = RedisDict("open-webui:usage_pool", redis_url=WEBSOCKET_REDIS_URL, redis_sentinels=redis_sentinels)
+    redis_sentinels = get_sentinels_from_env(
+        WEBSOCKET_SENTINEL_HOSTS, WEBSOCKET_SENTINEL_PORT
+    )
+    SESSION_POOL = RedisDict(
+        "open-webui:session_pool",
+        redis_url=WEBSOCKET_REDIS_URL,
+        redis_sentinels=redis_sentinels,
+    )
+    USER_POOL = RedisDict(
+        "open-webui:user_pool",
+        redis_url=WEBSOCKET_REDIS_URL,
+        redis_sentinels=redis_sentinels,
+    )
+    USAGE_POOL = RedisDict(
+        "open-webui:usage_pool",
+        redis_url=WEBSOCKET_REDIS_URL,
+        redis_sentinels=redis_sentinels,
+    )
 
     clean_up_lock = RedisLock(
         redis_url=WEBSOCKET_REDIS_URL,
diff --git a/backend/open_webui/socket/utils.py b/backend/open_webui/socket/utils.py
index a63815c02..85a8bb790 100644
--- a/backend/open_webui/socket/utils.py
+++ b/backend/open_webui/socket/utils.py
@@ -2,13 +2,16 @@ import json
 import uuid
 from open_webui.utils.redis import get_redis_connection
 
+
 class RedisLock:
     def __init__(self, redis_url, lock_name, timeout_secs, redis_sentinels=[]):
         self.lock_name = lock_name
         self.lock_id = str(uuid.uuid4())
         self.timeout_secs = timeout_secs
         self.lock_obtained = False
-        self.redis = get_redis_connection(redis_url, redis_sentinels, decode_responses=True)
+        self.redis = get_redis_connection(
+            redis_url, redis_sentinels, decode_responses=True
+        )
 
     def aquire_lock(self):
         # nx=True will only set this key if it _hasn't_ already been set
@@ -32,7 +35,9 @@ class RedisLock:
 class RedisDict:
     def __init__(self, name, redis_url, redis_sentinels=[]):
         self.name = name
-        self.redis = get_redis_connection(redis_url, redis_sentinels, decode_responses=True)
+        self.redis = get_redis_connection(
+            redis_url, redis_sentinels, decode_responses=True
+        )
 
     def __setitem__(self, key, value):
         serialized_value = json.dumps(value)
diff --git a/backend/open_webui/utils/redis.py b/backend/open_webui/utils/redis.py
index fa90a26db..baccb16ad 100644
--- a/backend/open_webui/utils/redis.py
+++ b/backend/open_webui/utils/redis.py
@@ -3,6 +3,7 @@ import redis
 from redis import asyncio as aioredis
 from urllib.parse import urlparse
 
+
 def parse_redis_sentinel_url(redis_url):
     parsed_url = urlparse(redis_url)
     if parsed_url.scheme != "redis":
@@ -11,39 +12,54 @@ def parse_redis_sentinel_url(redis_url):
     return {
         "username": parsed_url.username or None,
         "password": parsed_url.password or None,
-        "service": parsed_url.hostname or 'mymaster',
+        "service": parsed_url.hostname or "mymaster",
         "port": parsed_url.port or 6379,
         "db": int(parsed_url.path.lstrip("/") or 0),
     }
 
+
 def get_redis_connection(redis_url, redis_sentinels, decode_responses=True):
     if redis_sentinels:
         redis_config = parse_redis_sentinel_url(redis_url)
         sentinel = redis.sentinel.Sentinel(
             redis_sentinels,
-            port=redis_config['port'],
-            db=redis_config['db'],
-            username=redis_config['username'],
-            password=redis_config['password'],
-            decode_responses=decode_responses
+            port=redis_config["port"],
+            db=redis_config["db"],
+            username=redis_config["username"],
+            password=redis_config["password"],
+            decode_responses=decode_responses,
         )
 
         # Get a master connection from Sentinel
-        return sentinel.master_for(redis_config['service'])
+        return sentinel.master_for(redis_config["service"])
     else:
         # Standard Redis connection
         return redis.Redis.from_url(redis_url, decode_responses=decode_responses)
 
+
 def get_sentinels_from_env(sentinel_hosts_env, sentinel_port_env):
     if sentinel_hosts_env:
-        sentinel_hosts=sentinel_hosts_env.split(',')
-        sentinel_port=int(sentinel_port_env)
+        sentinel_hosts = sentinel_hosts_env.split(",")
+        sentinel_port = int(sentinel_port_env)
         return [(host, sentinel_port) for host in sentinel_hosts]
     return []
 
+
 class AsyncRedisSentinelManager(socketio.AsyncRedisManager):
-    def __init__(self, sentinel_hosts, sentinel_port=26379, redis_port=6379, service="mymaster", db=0,
-                 username=None, password=None, channel='socketio', write_only=False, logger=None, redis_options=None):
+    def __init__(
+        self,
+        sentinel_hosts,
+        sentinel_port=26379,
+        redis_port=6379,
+        service="mymaster",
+        db=0,
+        username=None,
+        password=None,
+        channel="socketio",
+        write_only=False,
+        logger=None,
+        redis_options=None,
+    ):
         """
         Initialize the Redis Sentinel Manager.
         This implementation mostly replicates the __init__ of AsyncRedisManager and
@@ -65,7 +81,7 @@ class AsyncRedisSentinelManager(socketio.AsyncRedisManager):
                               ``aioredis.from_url()``.
         """
         self._sentinels = [(host, sentinel_port) for host in sentinel_hosts]
-        self._redis_port=redis_port
+        self._redis_port = redis_port
         self._service = service
         self._db = db
         self._username = username
@@ -75,7 +91,9 @@ class AsyncRedisSentinelManager(socketio.AsyncRedisManager):
 
         # connect and call grandparent constructor
         self._redis_connect()
-        super(socketio.AsyncRedisManager, self).__init__(channel=channel, write_only=write_only, logger=logger)
+        super(socketio.AsyncRedisManager, self).__init__(
+            channel=channel, write_only=write_only, logger=logger
+        )
 
     def _redis_connect(self):
         """Establish connections to Redis through Sentinel."""
@@ -84,7 +102,7 @@ class AsyncRedisSentinelManager(socketio.AsyncRedisManager):
             port=self._redis_port,
             db=self._db,
             password=self._password,
-            **self.redis_options
+            **self.redis_options,
         )
 
         self.redis = sentinel.master_for(self._service)
diff --git a/package-lock.json b/package-lock.json
index 8c4ff6a48..6eb5064d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
 	"name": "open-webui",
-	"version": "0.5.20",
+	"version": "0.6.0",
 	"lockfileVersion": 3,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "open-webui",
-			"version": "0.5.20",
+			"version": "0.6.0",
 			"dependencies": {
 				"@azure/msal-browser": "^4.5.0",
 				"@codemirror/lang-javascript": "^6.2.2",
@@ -37,6 +37,7 @@
 				"file-saver": "^2.0.5",
 				"fuse.js": "^7.0.0",
 				"highlight.js": "^11.9.0",
+				"html-entities": "^2.5.3",
 				"html2canvas-pro": "^1.5.8",
 				"i18next": "^23.10.0",
 				"i18next-browser-languagedetector": "^7.2.0",
@@ -159,9 +160,9 @@
 			}
 		},
 		"node_modules/@babel/runtime": {
-			"version": "7.26.9",
-			"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz",
-			"integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==",
+			"version": "7.27.0",
+			"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
+			"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
 			"license": "MIT",
 			"dependencies": {
 				"regenerator-runtime": "^0.14.0"
@@ -626,371 +627,428 @@
 			}
 		},
 		"node_modules/@esbuild/aix-ppc64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
-			"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
+			"integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
 			"cpu": [
 				"ppc64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"aix"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/android-arm": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
-			"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
+			"integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
 			"cpu": [
 				"arm"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"android"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/android-arm64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
-			"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
+			"integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
 			"cpu": [
 				"arm64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"android"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/android-x64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
-			"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
+			"integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
 			"cpu": [
 				"x64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"android"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/darwin-arm64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
-			"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
+			"integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
 			"cpu": [
 				"arm64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"darwin"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/darwin-x64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
-			"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
+			"integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
 			"cpu": [
 				"x64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"darwin"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/freebsd-arm64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
-			"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
+			"integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
 			"cpu": [
 				"arm64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"freebsd"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/freebsd-x64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
-			"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
+			"integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
 			"cpu": [
 				"x64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"freebsd"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-arm": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
-			"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
+			"integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
 			"cpu": [
 				"arm"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-arm64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
-			"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
+			"integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
 			"cpu": [
 				"arm64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-ia32": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
-			"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
+			"integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
 			"cpu": [
 				"ia32"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-loong64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
-			"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
+			"integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
 			"cpu": [
 				"loong64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-mips64el": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
-			"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
+			"integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
 			"cpu": [
 				"mips64el"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-ppc64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
-			"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
+			"integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
 			"cpu": [
 				"ppc64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-riscv64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
-			"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
+			"integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
 			"cpu": [
 				"riscv64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-s390x": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
-			"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
+			"integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
 			"cpu": [
 				"s390x"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/linux-x64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
-			"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
+			"integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
 			"cpu": [
 				"x64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"linux"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
-		"node_modules/@esbuild/netbsd-x64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
-			"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
+		"node_modules/@esbuild/netbsd-arm64": {
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
+			"integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
 			"cpu": [
-				"x64"
+				"arm64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"netbsd"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
-		"node_modules/@esbuild/openbsd-x64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
-			"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
+		"node_modules/@esbuild/netbsd-x64": {
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
+			"integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
 			"cpu": [
 				"x64"
 			],
 			"dev": true,
+			"license": "MIT",
+			"optional": true,
+			"os": [
+				"netbsd"
+			],
+			"engines": {
+				"node": ">=18"
+			}
+		},
+		"node_modules/@esbuild/openbsd-arm64": {
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
+			"integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
+			"cpu": [
+				"arm64"
+			],
+			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"openbsd"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
-		"node_modules/@esbuild/sunos-x64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
-			"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
+		"node_modules/@esbuild/openbsd-x64": {
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
+			"integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
 			"cpu": [
 				"x64"
 			],
 			"dev": true,
+			"license": "MIT",
+			"optional": true,
+			"os": [
+				"openbsd"
+			],
+			"engines": {
+				"node": ">=18"
+			}
+		},
+		"node_modules/@esbuild/sunos-x64": {
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
+			"integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
+			"cpu": [
+				"x64"
+			],
+			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"sunos"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/win32-arm64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
-			"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
+			"integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
 			"cpu": [
 				"arm64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"win32"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/win32-ia32": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
-			"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
+			"integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
 			"cpu": [
 				"ia32"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"win32"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@esbuild/win32-x64": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
-			"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
+			"integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
 			"cpu": [
 				"x64"
 			],
 			"dev": true,
+			"license": "MIT",
 			"optional": true,
 			"os": [
 				"win32"
 			],
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			}
 		},
 		"node_modules/@eslint-community/eslint-utils": {
@@ -2292,9 +2350,9 @@
 			}
 		},
 		"node_modules/@sveltejs/adapter-static": {
-			"version": "3.0.6",
-			"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz",
-			"integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==",
+			"version": "3.0.8",
+			"resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz",
+			"integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==",
 			"dev": true,
 			"license": "MIT",
 			"peerDependencies": {
@@ -2302,24 +2360,22 @@
 			}
 		},
 		"node_modules/@sveltejs/kit": {
-			"version": "2.12.1",
-			"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.12.1.tgz",
-			"integrity": "sha512-M3rPijGImeOkI0DBJSwjqz+YFX2DyOf6NzWgHVk3mqpT06dlYCpcv5xh1q4rYEqB58yQlk4QA1Y35PUqnUiFKw==",
-			"hasInstallScript": true,
+			"version": "2.20.2",
+			"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.20.2.tgz",
+			"integrity": "sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==",
 			"license": "MIT",
 			"dependencies": {
 				"@types/cookie": "^0.6.0",
 				"cookie": "^0.6.0",
 				"devalue": "^5.1.0",
-				"esm-env": "^1.2.1",
+				"esm-env": "^1.2.2",
 				"import-meta-resolve": "^4.1.0",
 				"kleur": "^4.1.5",
 				"magic-string": "^0.30.5",
 				"mrmime": "^2.0.0",
 				"sade": "^1.8.1",
 				"set-cookie-parser": "^2.6.0",
-				"sirv": "^3.0.0",
-				"tiny-glob": "^0.2.9"
+				"sirv": "^3.0.0"
 			},
 			"bin": {
 				"svelte-kit": "svelte-kit.js"
@@ -3993,7 +4049,8 @@
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
 			"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
-			"dev": true
+			"dev": true,
+			"license": "ISC"
 		},
 		"node_modules/brace-expansion": {
 			"version": "2.0.1",
@@ -4204,9 +4261,9 @@
 			}
 		},
 		"node_modules/canvg": {
-			"version": "3.0.10",
-			"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
-			"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
+			"version": "3.0.11",
+			"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+			"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
 			"license": "MIT",
 			"optional": true,
 			"dependencies": {
@@ -4303,21 +4360,26 @@
 			}
 		},
 		"node_modules/cheerio": {
-			"version": "1.0.0-rc.12",
-			"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
-			"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
+			"version": "1.0.0",
+			"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
+			"integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
 			"dev": true,
+			"license": "MIT",
 			"dependencies": {
 				"cheerio-select": "^2.1.0",
 				"dom-serializer": "^2.0.0",
 				"domhandler": "^5.0.3",
-				"domutils": "^3.0.1",
-				"htmlparser2": "^8.0.1",
-				"parse5": "^7.0.0",
-				"parse5-htmlparser2-tree-adapter": "^7.0.0"
+				"domutils": "^3.1.0",
+				"encoding-sniffer": "^0.2.0",
+				"htmlparser2": "^9.1.0",
+				"parse5": "^7.1.2",
+				"parse5-htmlparser2-tree-adapter": "^7.0.0",
+				"parse5-parser-stream": "^7.1.2",
+				"undici": "^6.19.5",
+				"whatwg-mimetype": "^4.0.0"
 			},
 			"engines": {
-				"node": ">= 6"
+				"node": ">=18.17"
 			},
 			"funding": {
 				"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
@@ -4328,6 +4390,7 @@
 			"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
 			"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
 			"dev": true,
+			"license": "BSD-2-Clause",
 			"dependencies": {
 				"boolbase": "^1.0.0",
 				"css-select": "^5.1.0",
@@ -4340,6 +4403,16 @@
 				"url": "https://github.com/sponsors/fb55"
 			}
 		},
+		"node_modules/cheerio/node_modules/undici": {
+			"version": "6.21.2",
+			"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz",
+			"integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=18.17"
+			}
+		},
 		"node_modules/chokidar": {
 			"version": "3.6.0",
 			"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -4699,9 +4772,9 @@
 			}
 		},
 		"node_modules/core-js": {
-			"version": "3.40.0",
-			"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
-			"integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
+			"version": "3.41.0",
+			"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.41.0.tgz",
+			"integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==",
 			"hasInstallScript": true,
 			"license": "MIT",
 			"optional": true,
@@ -4768,6 +4841,7 @@
 			"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
 			"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
 			"dev": true,
+			"license": "BSD-2-Clause",
 			"dependencies": {
 				"boolbase": "^1.0.0",
 				"css-what": "^6.1.0",
@@ -4796,6 +4870,7 @@
 			"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
 			"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
 			"dev": true,
+			"license": "BSD-2-Clause",
 			"engines": {
 				"node": ">= 6"
 			},
@@ -5573,6 +5648,7 @@
 			"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
 			"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
 			"dev": true,
+			"license": "MIT",
 			"dependencies": {
 				"domelementtype": "^2.3.0",
 				"domhandler": "^5.0.2",
@@ -5592,13 +5668,15 @@
 					"type": "github",
 					"url": "https://github.com/sponsors/fb55"
 				}
-			]
+			],
+			"license": "BSD-2-Clause"
 		},
 		"node_modules/domhandler": {
 			"version": "5.0.3",
 			"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
 			"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
 			"dev": true,
+			"license": "BSD-2-Clause",
 			"dependencies": {
 				"domelementtype": "^2.3.0"
 			},
@@ -5615,10 +5693,11 @@
 			"integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ=="
 		},
 		"node_modules/domutils": {
-			"version": "3.1.0",
-			"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
-			"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+			"version": "3.2.2",
+			"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+			"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
 			"dev": true,
+			"license": "BSD-2-Clause",
 			"dependencies": {
 				"dom-serializer": "^2.0.0",
 				"domelementtype": "^2.3.0",
@@ -5653,6 +5732,20 @@
 			"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
 			"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
 		},
+		"node_modules/encoding-sniffer": {
+			"version": "0.2.0",
+			"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
+			"integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"iconv-lite": "^0.6.3",
+				"whatwg-encoding": "^3.1.1"
+			},
+			"funding": {
+				"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
+			}
+		},
 		"node_modules/end-of-stream": {
 			"version": "1.4.4",
 			"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
@@ -5760,41 +5853,44 @@
 			"dev": true
 		},
 		"node_modules/esbuild": {
-			"version": "0.20.2",
-			"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
-			"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
+			"version": "0.25.1",
+			"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
+			"integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
 			"dev": true,
 			"hasInstallScript": true,
+			"license": "MIT",
 			"bin": {
 				"esbuild": "bin/esbuild"
 			},
 			"engines": {
-				"node": ">=12"
+				"node": ">=18"
 			},
 			"optionalDependencies": {
-				"@esbuild/aix-ppc64": "0.20.2",
-				"@esbuild/android-arm": "0.20.2",
-				"@esbuild/android-arm64": "0.20.2",
-				"@esbuild/android-x64": "0.20.2",
-				"@esbuild/darwin-arm64": "0.20.2",
-				"@esbuild/darwin-x64": "0.20.2",
-				"@esbuild/freebsd-arm64": "0.20.2",
-				"@esbuild/freebsd-x64": "0.20.2",
-				"@esbuild/linux-arm": "0.20.2",
-				"@esbuild/linux-arm64": "0.20.2",
-				"@esbuild/linux-ia32": "0.20.2",
-				"@esbuild/linux-loong64": "0.20.2",
-				"@esbuild/linux-mips64el": "0.20.2",
-				"@esbuild/linux-ppc64": "0.20.2",
-				"@esbuild/linux-riscv64": "0.20.2",
-				"@esbuild/linux-s390x": "0.20.2",
-				"@esbuild/linux-x64": "0.20.2",
-				"@esbuild/netbsd-x64": "0.20.2",
-				"@esbuild/openbsd-x64": "0.20.2",
-				"@esbuild/sunos-x64": "0.20.2",
-				"@esbuild/win32-arm64": "0.20.2",
-				"@esbuild/win32-ia32": "0.20.2",
-				"@esbuild/win32-x64": "0.20.2"
+				"@esbuild/aix-ppc64": "0.25.1",
+				"@esbuild/android-arm": "0.25.1",
+				"@esbuild/android-arm64": "0.25.1",
+				"@esbuild/android-x64": "0.25.1",
+				"@esbuild/darwin-arm64": "0.25.1",
+				"@esbuild/darwin-x64": "0.25.1",
+				"@esbuild/freebsd-arm64": "0.25.1",
+				"@esbuild/freebsd-x64": "0.25.1",
+				"@esbuild/linux-arm": "0.25.1",
+				"@esbuild/linux-arm64": "0.25.1",
+				"@esbuild/linux-ia32": "0.25.1",
+				"@esbuild/linux-loong64": "0.25.1",
+				"@esbuild/linux-mips64el": "0.25.1",
+				"@esbuild/linux-ppc64": "0.25.1",
+				"@esbuild/linux-riscv64": "0.25.1",
+				"@esbuild/linux-s390x": "0.25.1",
+				"@esbuild/linux-x64": "0.25.1",
+				"@esbuild/netbsd-arm64": "0.25.1",
+				"@esbuild/netbsd-x64": "0.25.1",
+				"@esbuild/openbsd-arm64": "0.25.1",
+				"@esbuild/openbsd-x64": "0.25.1",
+				"@esbuild/sunos-x64": "0.25.1",
+				"@esbuild/win32-arm64": "0.25.1",
+				"@esbuild/win32-ia32": "0.25.1",
+				"@esbuild/win32-x64": "0.25.1"
 			}
 		},
 		"node_modules/escape-string-regexp": {
@@ -6000,9 +6096,9 @@
 			}
 		},
 		"node_modules/esm-env": {
-			"version": "1.2.1",
-			"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
-			"integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==",
+			"version": "1.2.2",
+			"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
+			"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
 			"license": "MIT"
 		},
 		"node_modules/espree": {
@@ -6650,11 +6746,6 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
-		"node_modules/globalyzer": {
-			"version": "0.1.0",
-			"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
-			"integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="
-		},
 		"node_modules/globby": {
 			"version": "11.1.0",
 			"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
@@ -6675,11 +6766,6 @@
 				"url": "https://github.com/sponsors/sindresorhus"
 			}
 		},
-		"node_modules/globrex": {
-			"version": "0.1.2",
-			"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
-			"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
-		},
 		"node_modules/gopd": {
 			"version": "1.0.1",
 			"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -6822,6 +6908,22 @@
 				"node": ">=12.0.0"
 			}
 		},
+		"node_modules/html-entities": {
+			"version": "2.5.3",
+			"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.3.tgz",
+			"integrity": "sha512-D3AfvN7SjhTgBSA8L1BN4FpPzuEd06uy4lHwSoRWr0lndi9BKaNzPLKGOWZ2ocSGguozr08TTb2jhCLHaemruw==",
+			"funding": [
+				{
+					"type": "github",
+					"url": "https://github.com/sponsors/mdevils"
+				},
+				{
+					"type": "patreon",
+					"url": "https://patreon.com/mdevils"
+				}
+			],
+			"license": "MIT"
+		},
 		"node_modules/html-escaper": {
 			"version": "3.0.3",
 			"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
@@ -6855,9 +6957,9 @@
 			}
 		},
 		"node_modules/htmlparser2": {
-			"version": "8.0.2",
-			"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
-			"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+			"version": "9.1.0",
+			"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
+			"integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
 			"dev": true,
 			"funding": [
 				"https://github.com/fb55/htmlparser2?sponsor=1",
@@ -6866,11 +6968,12 @@
 					"url": "https://github.com/sponsors/fb55"
 				}
 			],
+			"license": "MIT",
 			"dependencies": {
 				"domelementtype": "^2.3.0",
 				"domhandler": "^5.0.3",
-				"domutils": "^3.0.1",
-				"entities": "^4.4.0"
+				"domutils": "^3.1.0",
+				"entities": "^4.5.0"
 			}
 		},
 		"node_modules/http-signature": {
@@ -6927,34 +7030,35 @@
 			}
 		},
 		"node_modules/i18next-parser": {
-			"version": "9.0.1",
-			"resolved": "https://registry.npmjs.org/i18next-parser/-/i18next-parser-9.0.1.tgz",
-			"integrity": "sha512-/Pr93/yEBdwsMKRsk4Zn63K368ALhzh8BRVrM6JNGOHy86ZKpiNJI6m8l1S/4T4Ofy1J4dlwkD7N98M70GP4aA==",
+			"version": "9.3.0",
+			"resolved": "https://registry.npmjs.org/i18next-parser/-/i18next-parser-9.3.0.tgz",
+			"integrity": "sha512-VaQqk/6nLzTFx1MDiCZFtzZXKKyBV6Dv0cJMFM/hOt4/BWHWRgYafzYfVQRUzotwUwjqeNCprWnutzD/YAGczg==",
 			"dev": true,
+			"license": "MIT",
 			"dependencies": {
-				"@babel/runtime": "^7.23.2",
+				"@babel/runtime": "^7.25.0",
 				"broccoli-plugin": "^4.0.7",
-				"cheerio": "^1.0.0-rc.2",
-				"colors": "1.4.0",
-				"commander": "~12.1.0",
+				"cheerio": "^1.0.0",
+				"colors": "^1.4.0",
+				"commander": "^12.1.0",
 				"eol": "^0.9.1",
-				"esbuild": "^0.20.1",
-				"fs-extra": "^11.1.0",
+				"esbuild": "^0.25.0",
+				"fs-extra": "^11.2.0",
 				"gulp-sort": "^2.0.0",
-				"i18next": "^23.5.1",
-				"js-yaml": "4.1.0",
-				"lilconfig": "^3.0.0",
-				"rsvp": "^4.8.2",
+				"i18next": "^23.5.1 || ^24.2.0",
+				"js-yaml": "^4.1.0",
+				"lilconfig": "^3.1.3",
+				"rsvp": "^4.8.5",
 				"sort-keys": "^5.0.0",
 				"typescript": "^5.0.4",
-				"vinyl": "~3.0.0",
+				"vinyl": "^3.0.0",
 				"vinyl-fs": "^4.0.0"
 			},
 			"bin": {
 				"i18next": "bin/cli.js"
 			},
 			"engines": {
-				"node": ">=18.0.0 || >=20.0.0 || >=22.0.0",
+				"node": "^18.0.0 || ^20.0.0 || ^22.0.0",
 				"npm": ">=6",
 				"yarn": ">=1"
 			}
@@ -7367,18 +7471,18 @@
 			}
 		},
 		"node_modules/jspdf": {
-			"version": "3.0.0",
-			"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.0.tgz",
-			"integrity": "sha512-QvuQZvOI8CjfjVgtajdL0ihrDYif1cN5gXiF9lb9Pd9JOpmocvnNyFO9sdiJ/8RA5Bu8zyGOUjJLj5kiku16ug==",
+			"version": "3.0.1",
+			"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.1.tgz",
+			"integrity": "sha512-qaGIxqxetdoNnFQQXxTKUD9/Z7AloLaw94fFsOiJMxbfYdBbrBuhWmbzI8TVjrw7s3jBY1PFHofBKMV/wZPapg==",
 			"license": "MIT",
 			"dependencies": {
-				"@babel/runtime": "^7.26.0",
+				"@babel/runtime": "^7.26.7",
 				"atob": "^2.1.2",
 				"btoa": "^1.2.1",
 				"fflate": "^0.8.1"
 			},
 			"optionalDependencies": {
-				"canvg": "^3.0.6",
+				"canvg": "^3.0.11",
 				"core-js": "^3.6.0",
 				"dompurify": "^3.2.4",
 				"html2canvas": "^1.0.0-rc.5"
@@ -7750,10 +7854,11 @@
 			}
 		},
 		"node_modules/lilconfig": {
-			"version": "3.1.1",
-			"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz",
-			"integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==",
+			"version": "3.1.3",
+			"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+			"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
 			"dev": true,
+			"license": "MIT",
 			"engines": {
 				"node": ">=14"
 			},
@@ -8885,6 +8990,7 @@
 			"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
 			"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
 			"dev": true,
+			"license": "BSD-2-Clause",
 			"dependencies": {
 				"boolbase": "^1.0.0"
 			},
@@ -9091,24 +9197,39 @@
 			}
 		},
 		"node_modules/parse5": {
-			"version": "7.1.2",
-			"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
-			"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+			"version": "7.2.1",
+			"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
+			"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
 			"dev": true,
+			"license": "MIT",
 			"dependencies": {
-				"entities": "^4.4.0"
+				"entities": "^4.5.0"
 			},
 			"funding": {
 				"url": "https://github.com/inikulin/parse5?sponsor=1"
 			}
 		},
 		"node_modules/parse5-htmlparser2-tree-adapter": {
-			"version": "7.0.0",
-			"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
-			"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
+			"version": "7.1.0",
+			"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
+			"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
 			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"domhandler": "^5.0.3",
+				"parse5": "^7.0.0"
+			},
+			"funding": {
+				"url": "https://github.com/inikulin/parse5?sponsor=1"
+			}
+		},
+		"node_modules/parse5-parser-stream": {
+			"version": "7.1.2",
+			"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
+			"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
+			"dev": true,
+			"license": "MIT",
 			"dependencies": {
-				"domhandler": "^5.0.2",
 				"parse5": "^7.0.0"
 			},
 			"funding": {
@@ -11519,15 +11640,6 @@
 				"xtend": "~4.0.1"
 			}
 		},
-		"node_modules/tiny-glob": {
-			"version": "0.2.9",
-			"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
-			"integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
-			"dependencies": {
-				"globalyzer": "0.1.0",
-				"globrex": "^0.1.2"
-			}
-		},
 		"node_modules/tinybench": {
 			"version": "2.8.0",
 			"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz",
@@ -11973,9 +12085,9 @@
 			}
 		},
 		"node_modules/vite": {
-			"version": "5.4.14",
-			"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
-			"integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
+			"version": "5.4.15",
+			"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.15.tgz",
+			"integrity": "sha512-6ANcZRivqL/4WtwPGTKNaosuNJr5tWiftOC7liM7G9+rMb8+oeJeyzymDu4rTN93seySBmbjSfsS3Vzr19KNtA==",
 			"license": "MIT",
 			"dependencies": {
 				"esbuild": "^0.21.3",
@@ -12702,6 +12814,29 @@
 			"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz",
 			"integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA=="
 		},
+		"node_modules/whatwg-encoding": {
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+			"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+			"dev": true,
+			"license": "MIT",
+			"dependencies": {
+				"iconv-lite": "0.6.3"
+			},
+			"engines": {
+				"node": ">=18"
+			}
+		},
+		"node_modules/whatwg-mimetype": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+			"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+			"dev": true,
+			"license": "MIT",
+			"engines": {
+				"node": ">=18"
+			}
+		},
 		"node_modules/wheel": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz",
diff --git a/package.json b/package.json
index bef3de16b..465fbba0f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "open-webui",
-	"version": "0.5.20",
+	"version": "0.6.0",
 	"private": true,
 	"scripts": {
 		"dev": "npm run pyodide:fetch && vite dev --host",
@@ -80,6 +80,7 @@
 		"file-saver": "^2.0.5",
 		"fuse.js": "^7.0.0",
 		"highlight.js": "^11.9.0",
+		"html-entities": "^2.5.3",
 		"html2canvas-pro": "^1.5.8",
 		"i18next": "^23.10.0",
 		"i18next-browser-languagedetector": "^7.2.0",