Преглед на файлове

Merge branch 'dev' into patch-1

Elkana Bardugo преди 2 месеца
родител
ревизия
93590d224b
променени са 100 файла, в които са добавени 1936 реда и са изтрити 1290 реда
  1. 40 5
      backend/open_webui/config.py
  2. 1 0
      backend/open_webui/env.py
  3. 27 15
      backend/open_webui/main.py
  4. 10 14
      backend/open_webui/retrieval/web/duckduckgo.py
  5. 10 3
      backend/open_webui/retrieval/web/tavily.py
  6. 91 2
      backend/open_webui/retrieval/web/utils.py
  7. 22 6
      backend/open_webui/routers/auths.py
  8. 3 1
      backend/open_webui/routers/channels.py
  9. 1 1
      backend/open_webui/routers/ollama.py
  10. 59 51
      backend/open_webui/routers/pipelines.py
  11. 17 4
      backend/open_webui/routers/retrieval.py
  12. 0 0
      backend/open_webui/static/loader.js
  13. 0 2
      backend/open_webui/static/swagger-ui/swagger-ui.css
  14. 54 1
      backend/open_webui/utils/auth.py
  15. 1 7
      backend/open_webui/utils/chat.py
  16. 35 28
      backend/open_webui/utils/middleware.py
  17. 21 7
      backend/open_webui/utils/oauth.py
  18. 3 3
      backend/open_webui/utils/webhook.py
  19. 527 312
      package-lock.json
  20. 2 2
      package.json
  21. 1 2
      postcss.config.js
  22. 14 2
      src/app.css
  23. 1 0
      src/app.html
  24. 1 1
      src/lib/apis/chats/index.ts
  25. 6 6
      src/lib/components/AddConnectionModal.svelte
  26. 1 1
      src/lib/components/ChangelogModal.svelte
  27. 2 2
      src/lib/components/NotificationToast.svelte
  28. 2 2
      src/lib/components/OnBoarding.svelte
  29. 3 3
      src/lib/components/admin/Evaluations/Feedbacks.svelte
  30. 3 3
      src/lib/components/admin/Evaluations/Leaderboard.svelte
  31. 6 6
      src/lib/components/admin/Functions.svelte
  32. 5 5
      src/lib/components/admin/Functions/FunctionEditor.svelte
  33. 3 3
      src/lib/components/admin/Functions/FunctionMenu.svelte
  34. 24 24
      src/lib/components/admin/Settings/Audio.svelte
  35. 4 4
      src/lib/components/admin/Settings/CodeInterpreter.svelte
  36. 4 4
      src/lib/components/admin/Settings/Connections.svelte
  37. 1 1
      src/lib/components/admin/Settings/Connections/ManageOllamaModal.svelte
  38. 1 1
      src/lib/components/admin/Settings/Connections/OllamaConnection.svelte
  39. 2 2
      src/lib/components/admin/Settings/Connections/OpenAIConnection.svelte
  40. 1 1
      src/lib/components/admin/Settings/Database.svelte
  41. 23 23
      src/lib/components/admin/Settings/Documents.svelte
  42. 5 5
      src/lib/components/admin/Settings/Evaluations/ArenaModelModal.svelte
  43. 1 1
      src/lib/components/admin/Settings/Evaluations/Model.svelte
  44. 471 316
      src/lib/components/admin/Settings/General.svelte
  45. 17 17
      src/lib/components/admin/Settings/Images.svelte
  46. 217 209
      src/lib/components/admin/Settings/Interface.svelte
  47. 1 1
      src/lib/components/admin/Settings/Models.svelte
  48. 2 2
      src/lib/components/admin/Settings/Models/ConfigureModelsModal.svelte
  49. 1 1
      src/lib/components/admin/Settings/Models/Manage/ManageMultipleOllama.svelte
  50. 7 7
      src/lib/components/admin/Settings/Models/Manage/ManageOllama.svelte
  51. 8 8
      src/lib/components/admin/Settings/Pipelines.svelte
  52. 13 13
      src/lib/components/admin/Settings/WebSearch.svelte
  53. 3 3
      src/lib/components/admin/Users/Groups.svelte
  54. 2 2
      src/lib/components/admin/Users/Groups/AddGroupModal.svelte
  55. 3 3
      src/lib/components/admin/Users/Groups/Display.svelte
  56. 6 6
      src/lib/components/admin/Users/Groups/Permissions.svelte
  57. 1 1
      src/lib/components/admin/Users/Groups/Users.svelte
  58. 3 3
      src/lib/components/admin/Users/UserList.svelte
  59. 6 6
      src/lib/components/admin/Users/UserList/AddUserModal.svelte
  60. 5 5
      src/lib/components/admin/Users/UserList/EditUserModal.svelte
  61. 1 1
      src/lib/components/admin/Users/UserList/UserChatsModal.svelte
  62. 1 1
      src/lib/components/channel/Channel.svelte
  63. 2 2
      src/lib/components/channel/MessageInput.svelte
  64. 1 1
      src/lib/components/channel/MessageInput/InputMenu.svelte
  65. 4 4
      src/lib/components/channel/Messages/Message.svelte
  66. 1 1
      src/lib/components/channel/Messages/Message/ProfilePreview.svelte
  67. 2 2
      src/lib/components/channel/Messages/Message/ReactionPicker.svelte
  68. 1 1
      src/lib/components/channel/Navbar.svelte
  69. 2 2
      src/lib/components/chat/Chat.svelte
  70. 2 2
      src/lib/components/chat/ChatControls.svelte
  71. 5 5
      src/lib/components/chat/ContentRenderer/FloatingButtons.svelte
  72. 1 1
      src/lib/components/chat/Controls/Controls.svelte
  73. 2 2
      src/lib/components/chat/Controls/Valves.svelte
  74. 10 10
      src/lib/components/chat/MessageInput.svelte
  75. 1 1
      src/lib/components/chat/MessageInput/CallOverlay/VideoInputMenu.svelte
  76. 1 1
      src/lib/components/chat/MessageInput/Commands.svelte
  77. 6 6
      src/lib/components/chat/MessageInput/Commands/Knowledge.svelte
  78. 1 1
      src/lib/components/chat/MessageInput/Commands/Models.svelte
  79. 1 1
      src/lib/components/chat/MessageInput/Commands/Prompts.svelte
  80. 2 2
      src/lib/components/chat/MessageInput/FilesOverlay.svelte
  81. 3 3
      src/lib/components/chat/MessageInput/InputMenu.svelte
  82. 1 1
      src/lib/components/chat/MessageInput/VoiceRecording.svelte
  83. 6 6
      src/lib/components/chat/Messages/Citations.svelte
  84. 5 3
      src/lib/components/chat/Messages/CitationsModal.svelte
  85. 2 2
      src/lib/components/chat/Messages/CodeBlock.svelte
  86. 1 1
      src/lib/components/chat/Messages/CodeExecutionModal.svelte
  87. 3 9
      src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte
  88. 4 4
      src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte
  89. 16 1
      src/lib/components/chat/Messages/Markdown/Source.svelte
  90. 3 3
      src/lib/components/chat/Messages/MultiResponseMessages.svelte
  91. 5 5
      src/lib/components/chat/Messages/RateComment.svelte
  92. 4 4
      src/lib/components/chat/Messages/ResponseMessage.svelte
  93. 7 7
      src/lib/components/chat/Messages/Skeleton.svelte
  94. 3 3
      src/lib/components/chat/Messages/UserMessage.svelte
  95. 12 12
      src/lib/components/chat/ModelSelector/Selector.svelte
  96. 1 1
      src/lib/components/chat/Navbar.svelte
  97. 1 1
      src/lib/components/chat/Placeholder.svelte
  98. 2 2
      src/lib/components/chat/Settings/About.svelte
  99. 3 3
      src/lib/components/chat/Settings/Account.svelte
  100. 3 3
      src/lib/components/chat/Settings/Account/UpdatePassword.svelte

+ 40 - 5
backend/open_webui/config.py

@@ -2,6 +2,8 @@ import json
 import logging
 import logging
 import os
 import os
 import shutil
 import shutil
+import base64
+
 from datetime import datetime
 from datetime import datetime
 from pathlib import Path
 from pathlib import Path
 from typing import Generic, Optional, TypeVar
 from typing import Generic, Optional, TypeVar
@@ -586,6 +588,20 @@ load_oauth_providers()
 
 
 STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static")).resolve()
 STATIC_DIR = Path(os.getenv("STATIC_DIR", OPEN_WEBUI_DIR / "static")).resolve()
 
 
+
+def override_static(path: str, content: str):
+    # Ensure path is safe
+    if "/" in path or ".." in path:
+        log.error(f"Invalid path: {path}")
+        return
+
+    file_path = os.path.join(STATIC_DIR, path)
+    os.makedirs(os.path.dirname(file_path), exist_ok=True)
+
+    with open(file_path, "wb") as f:
+        f.write(base64.b64decode(content))  # Convert Base64 back to raw binary
+
+
 frontend_favicon = FRONTEND_BUILD_DIR / "static" / "favicon.png"
 frontend_favicon = FRONTEND_BUILD_DIR / "static" / "favicon.png"
 
 
 if frontend_favicon.exists():
 if frontend_favicon.exists():
@@ -593,8 +609,6 @@ if frontend_favicon.exists():
         shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png")
         shutil.copyfile(frontend_favicon, STATIC_DIR / "favicon.png")
     except Exception as e:
     except Exception as e:
         logging.error(f"An error occurred: {e}")
         logging.error(f"An error occurred: {e}")
-else:
-    logging.warning(f"Frontend favicon not found at {frontend_favicon}")
 
 
 frontend_splash = FRONTEND_BUILD_DIR / "static" / "splash.png"
 frontend_splash = FRONTEND_BUILD_DIR / "static" / "splash.png"
 
 
@@ -603,12 +617,18 @@ if frontend_splash.exists():
         shutil.copyfile(frontend_splash, STATIC_DIR / "splash.png")
         shutil.copyfile(frontend_splash, STATIC_DIR / "splash.png")
     except Exception as e:
     except Exception as e:
         logging.error(f"An error occurred: {e}")
         logging.error(f"An error occurred: {e}")
-else:
-    logging.warning(f"Frontend splash not found at {frontend_splash}")
+
+frontend_loader = FRONTEND_BUILD_DIR / "static" / "loader.js"
+
+if frontend_loader.exists():
+    try:
+        shutil.copyfile(frontend_loader, STATIC_DIR / "loader.js")
+    except Exception as e:
+        logging.error(f"An error occurred: {e}")
 
 
 
 
 ####################################
 ####################################
-# CUSTOM_NAME
+# CUSTOM_NAME (Legacy)
 ####################################
 ####################################
 
 
 CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "")
 CUSTOM_NAME = os.environ.get("CUSTOM_NAME", "")
@@ -650,6 +670,16 @@ if CUSTOM_NAME:
         pass
         pass
 
 
 
 
+####################################
+# LICENSE_KEY
+####################################
+
+LICENSE_KEY = PersistentConfig(
+    "LICENSE_KEY",
+    "license.key",
+    os.environ.get("LICENSE_KEY", ""),
+)
+
 ####################################
 ####################################
 # STORAGE PROVIDER
 # STORAGE PROVIDER
 ####################################
 ####################################
@@ -1853,6 +1883,11 @@ RAG_WEB_SEARCH_CONCURRENT_REQUESTS = PersistentConfig(
     int(os.getenv("RAG_WEB_SEARCH_CONCURRENT_REQUESTS", "10")),
     int(os.getenv("RAG_WEB_SEARCH_CONCURRENT_REQUESTS", "10")),
 )
 )
 
 
+RAG_WEB_SEARCH_TRUST_ENV = PersistentConfig(
+    "RAG_WEB_SEARCH_TRUST_ENV",
+    "rag.web.search.trust_env",
+    os.getenv("RAG_WEB_SEARCH_TRUST_ENV", False),
+)
 
 
 ####################################
 ####################################
 # Images
 # Images

+ 1 - 0
backend/open_webui/env.py

@@ -113,6 +113,7 @@ if WEBUI_NAME != "Open WebUI":
 
 
 WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
 WEBUI_FAVICON_URL = "https://openwebui.com/favicon.png"
 
 
+TRUSTED_SIGNATURE_KEY = os.environ.get("TRUSTED_SIGNATURE_KEY", "")
 
 
 ####################################
 ####################################
 # ENV (dev,test,prod)
 # ENV (dev,test,prod)

+ 27 - 15
backend/open_webui/main.py

@@ -88,6 +88,7 @@ from open_webui.models.models import Models
 from open_webui.models.users import UserModel, Users
 from open_webui.models.users import UserModel, Users
 
 
 from open_webui.config import (
 from open_webui.config import (
+    LICENSE_KEY,
     # Ollama
     # Ollama
     ENABLE_OLLAMA_API,
     ENABLE_OLLAMA_API,
     OLLAMA_BASE_URLS,
     OLLAMA_BASE_URLS,
@@ -175,6 +176,7 @@ from open_webui.config import (
     RAG_WEB_SEARCH_ENGINE,
     RAG_WEB_SEARCH_ENGINE,
     RAG_WEB_SEARCH_RESULT_COUNT,
     RAG_WEB_SEARCH_RESULT_COUNT,
     RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
     RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
+    RAG_WEB_SEARCH_TRUST_ENV,
     RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
     RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
     JINA_API_KEY,
     JINA_API_KEY,
     SEARCHAPI_API_KEY,
     SEARCHAPI_API_KEY,
@@ -313,15 +315,17 @@ from open_webui.utils.middleware import process_chat_payload, process_chat_respo
 from open_webui.utils.access_control import has_access
 from open_webui.utils.access_control import has_access
 
 
 from open_webui.utils.auth import (
 from open_webui.utils.auth import (
+    get_license_data,
     decode_token,
     decode_token,
     get_admin_user,
     get_admin_user,
     get_verified_user,
     get_verified_user,
 )
 )
-from open_webui.utils.oauth import oauth_manager
+from open_webui.utils.oauth import OAuthManager
 from open_webui.utils.security_headers import SecurityHeadersMiddleware
 from open_webui.utils.security_headers import SecurityHeadersMiddleware
 
 
 from open_webui.tasks import stop_task, list_tasks  # Import from tasks.py
 from open_webui.tasks import stop_task, list_tasks  # Import from tasks.py
 
 
+
 if SAFE_MODE:
 if SAFE_MODE:
     print("SAFE MODE ENABLED")
     print("SAFE MODE ENABLED")
     Functions.deactivate_all_functions()
     Functions.deactivate_all_functions()
@@ -348,12 +352,12 @@ class SPAStaticFiles(StaticFiles):
 
 
 print(
 print(
     rf"""
     rf"""
-  ___                    __        __   _     _   _ ___
- / _ \ _ __   ___ _ __   \ \      / /__| |__ | | | |_ _|
-| | | | '_ \ / _ \ '_ \   \ \ /\ / / _ \ '_ \| | | || |
-| |_| | |_) |  __/ | | |   \ V  V /  __/ |_) | |_| || |
- \___/| .__/ \___|_| |_|    \_/\_/ \___|_.__/ \___/|___|
-      |_|
+ ██████╗ ██████╗ ███████╗███╗   ██╗    ██╗    ██╗███████╗██████╗ ██╗   ██╗██╗
+██╔═══██╗██╔══██╗██╔════╝████╗  ██║    ██║    ██║██╔════╝██╔══██╗██║   ██║██║
+██║   ██║██████╔╝█████╗  ██╔██╗ ██║    ██║ █╗ ██║█████╗  ██████╔╝██║   ██║██║
+██║   ██║██╔═══╝ ██╔══╝  ██║╚██╗██║    ██║███╗██║██╔══╝  ██╔══██╗██║   ██║██║
+╚██████╔╝██║     ███████╗██║ ╚████║    ╚███╔███╔╝███████╗██████╔╝╚██████╔╝██║
+ ╚═════╝ ╚═╝     ╚══════╝╚═╝  ╚═══╝     ╚══╝╚══╝ ╚══════╝╚═════╝  ╚═════╝ ╚═╝
 
 
 
 
 v{VERSION} - building the best open-source AI user interface.
 v{VERSION} - building the best open-source AI user interface.
@@ -368,6 +372,9 @@ async def lifespan(app: FastAPI):
     if RESET_CONFIG_ON_START:
     if RESET_CONFIG_ON_START:
         reset_config()
         reset_config()
 
 
+    if app.state.config.LICENSE_KEY:
+        get_license_data(app, app.state.config.LICENSE_KEY)
+
     asyncio.create_task(periodic_usage_pool_cleanup())
     asyncio.create_task(periodic_usage_pool_cleanup())
     yield
     yield
 
 
@@ -379,8 +386,12 @@ app = FastAPI(
     lifespan=lifespan,
     lifespan=lifespan,
 )
 )
 
 
+oauth_manager = OAuthManager(app)
+
 app.state.config = AppConfig()
 app.state.config = AppConfig()
 
 
+app.state.WEBUI_NAME = WEBUI_NAME
+app.state.config.LICENSE_KEY = LICENSE_KEY
 
 
 ########################################
 ########################################
 #
 #
@@ -482,10 +493,10 @@ app.state.config.LDAP_CIPHERS = LDAP_CIPHERS
 app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
 app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
 
 
+app.state.USER_COUNT = None
 app.state.TOOLS = {}
 app.state.TOOLS = {}
 app.state.FUNCTIONS = {}
 app.state.FUNCTIONS = {}
 
 
-
 ########################################
 ########################################
 #
 #
 # RETRIEVAL
 # RETRIEVAL
@@ -558,6 +569,7 @@ app.state.config.EXA_API_KEY = EXA_API_KEY
 
 
 app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
 app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
 app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
 app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
+app.state.config.RAG_WEB_SEARCH_TRUST_ENV = RAG_WEB_SEARCH_TRUST_ENV
 
 
 app.state.EMBEDDING_FUNCTION = None
 app.state.EMBEDDING_FUNCTION = None
 app.state.ef = None
 app.state.ef = None
@@ -1069,7 +1081,7 @@ async def get_app_config(request: Request):
     return {
     return {
         **({"onboarding": True} if onboarding else {}),
         **({"onboarding": True} if onboarding else {}),
         "status": True,
         "status": True,
-        "name": WEBUI_NAME,
+        "name": app.state.WEBUI_NAME,
         "version": VERSION,
         "version": VERSION,
         "default_locale": str(DEFAULT_LOCALE),
         "default_locale": str(DEFAULT_LOCALE),
         "oauth": {
         "oauth": {
@@ -1204,7 +1216,7 @@ if len(OAUTH_PROVIDERS) > 0:
 
 
 @app.get("/oauth/{provider}/login")
 @app.get("/oauth/{provider}/login")
 async def oauth_login(provider: str, request: Request):
 async def oauth_login(provider: str, request: Request):
-    return await oauth_manager.handle_login(provider, request)
+    return await oauth_manager.handle_login(request, provider)
 
 
 
 
 # OAuth login logic is as follows:
 # OAuth login logic is as follows:
@@ -1215,14 +1227,14 @@ async def oauth_login(provider: str, request: Request):
 #    - Email addresses are considered unique, so we fail registration if the email address is already taken
 #    - Email addresses are considered unique, so we fail registration if the email address is already taken
 @app.get("/oauth/{provider}/callback")
 @app.get("/oauth/{provider}/callback")
 async def oauth_callback(provider: str, request: Request, response: Response):
 async def oauth_callback(provider: str, request: Request, response: Response):
-    return await oauth_manager.handle_callback(provider, request, response)
+    return await oauth_manager.handle_callback(request, provider, response)
 
 
 
 
 @app.get("/manifest.json")
 @app.get("/manifest.json")
 async def get_manifest_json():
 async def get_manifest_json():
     return {
     return {
-        "name": WEBUI_NAME,
-        "short_name": WEBUI_NAME,
+        "name": app.state.WEBUI_NAME,
+        "short_name": app.state.WEBUI_NAME,
         "description": "Open WebUI is an open, extensible, user-friendly interface for AI that adapts to your workflow.",
         "description": "Open WebUI is an open, extensible, user-friendly interface for AI that adapts to your workflow.",
         "start_url": "/",
         "start_url": "/",
         "display": "standalone",
         "display": "standalone",
@@ -1249,8 +1261,8 @@ async def get_manifest_json():
 async def get_opensearch_xml():
 async def get_opensearch_xml():
     xml_content = rf"""
     xml_content = rf"""
     <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
     <OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">
-    <ShortName>{WEBUI_NAME}</ShortName>
-    <Description>Search {WEBUI_NAME}</Description>
+    <ShortName>{app.state.WEBUI_NAME}</ShortName>
+    <Description>Search {app.state.WEBUI_NAME}</Description>
     <InputEncoding>UTF-8</InputEncoding>
     <InputEncoding>UTF-8</InputEncoding>
     <Image width="16" height="16" type="image/x-icon">{app.state.config.WEBUI_URL}/static/favicon.png</Image>
     <Image width="16" height="16" type="image/x-icon">{app.state.config.WEBUI_URL}/static/favicon.png</Image>
     <Url type="text/html" method="get" template="{app.state.config.WEBUI_URL}/?q={"{searchTerms}"}"/>
     <Url type="text/html" method="get" template="{app.state.config.WEBUI_URL}/?q={"{searchTerms}"}"/>

+ 10 - 14
backend/open_webui/retrieval/web/duckduckgo.py

@@ -32,19 +32,15 @@ def search_duckduckgo(
             # Convert the search results into a list
             # Convert the search results into a list
             search_results = [r for r in ddgs_gen]
             search_results = [r for r in ddgs_gen]
 
 
-    # Create an empty list to store the SearchResult objects
-    results = []
-    # Iterate over each search result
-    for result in search_results:
-        # Create a SearchResult object and append it to the results list
-        results.append(
-            SearchResult(
-                link=result["href"],
-                title=result.get("title"),
-                snippet=result.get("body"),
-            )
-        )
     if filter_list:
     if filter_list:
-        results = get_filtered_results(results, filter_list)
+        search_results = get_filtered_results(search_results, filter_list)
+
     # Return the list of search results
     # Return the list of search results
-    return results
+    return [
+        SearchResult(
+            link=result["href"],
+            title=result.get("title"),
+            snippet=result.get("body"),
+        )
+        for result in search_results
+    ]

+ 10 - 3
backend/open_webui/retrieval/web/tavily.py

@@ -1,4 +1,5 @@
 import logging
 import logging
+from typing import Optional
 
 
 import requests
 import requests
 from open_webui.retrieval.web.main import SearchResult
 from open_webui.retrieval.web.main import SearchResult
@@ -8,7 +9,13 @@ log = logging.getLogger(__name__)
 log.setLevel(SRC_LOG_LEVELS["RAG"])
 log.setLevel(SRC_LOG_LEVELS["RAG"])
 
 
 
 
-def search_tavily(api_key: str, query: str, count: int) -> list[SearchResult]:
+def search_tavily(
+    api_key: str,
+    query: str,
+    count: int,
+    filter_list: Optional[list[str]] = None,
+    # **kwargs,
+) -> list[SearchResult]:
     """Search using Tavily's Search API and return the results as a list of SearchResult objects.
     """Search using Tavily's Search API and return the results as a list of SearchResult objects.
 
 
     Args:
     Args:
@@ -20,8 +27,8 @@ def search_tavily(api_key: str, query: str, count: int) -> list[SearchResult]:
     """
     """
     url = "https://api.tavily.com/search"
     url = "https://api.tavily.com/search"
     data = {"query": query, "api_key": api_key}
     data = {"query": query, "api_key": api_key}
-
-    response = requests.post(url, json=data)
+    include_domain = filter_list
+    response = requests.post(url, include_domain, json=data)
     response.raise_for_status()
     response.raise_for_status()
 
 
     json_response = response.json()
     json_response = response.json()

+ 91 - 2
backend/open_webui/retrieval/web/utils.py

@@ -1,7 +1,10 @@
 import socket
 import socket
+import aiohttp
+import asyncio
 import urllib.parse
 import urllib.parse
 import validators
 import validators
-from typing import Union, Sequence, Iterator
+from typing import Any, AsyncIterator, Dict, Iterator, List, Sequence, Union
+
 
 
 from langchain_community.document_loaders import (
 from langchain_community.document_loaders import (
     WebBaseLoader,
     WebBaseLoader,
@@ -68,6 +71,70 @@ def resolve_hostname(hostname):
 class SafeWebBaseLoader(WebBaseLoader):
 class SafeWebBaseLoader(WebBaseLoader):
     """WebBaseLoader with enhanced error handling for URLs."""
     """WebBaseLoader with enhanced error handling for URLs."""
 
 
+    def __init__(self, trust_env: bool = False, *args, **kwargs):
+        """Initialize SafeWebBaseLoader
+        Args:
+            trust_env (bool, optional): set to True if using proxy to make web requests, for example
+                using http(s)_proxy environment variables. Defaults to False.
+        """
+        super().__init__(*args, **kwargs)
+        self.trust_env = trust_env
+
+    async def _fetch(
+        self, url: str, retries: int = 3, cooldown: int = 2, backoff: float = 1.5
+    ) -> str:
+        async with aiohttp.ClientSession(trust_env=self.trust_env) as session:
+            for i in range(retries):
+                try:
+                    kwargs: Dict = dict(
+                        headers=self.session.headers,
+                        cookies=self.session.cookies.get_dict(),
+                    )
+                    if not self.session.verify:
+                        kwargs["ssl"] = False
+
+                    async with session.get(
+                        url, **(self.requests_kwargs | kwargs)
+                    ) as response:
+                        if self.raise_for_status:
+                            response.raise_for_status()
+                        return await response.text()
+                except aiohttp.ClientConnectionError as e:
+                    if i == retries - 1:
+                        raise
+                    else:
+                        log.warning(
+                            f"Error fetching {url} with attempt "
+                            f"{i + 1}/{retries}: {e}. Retrying..."
+                        )
+                        await asyncio.sleep(cooldown * backoff**i)
+        raise ValueError("retry count exceeded")
+
+    def _unpack_fetch_results(
+        self, results: Any, urls: List[str], parser: Union[str, None] = None
+    ) -> List[Any]:
+        """Unpack fetch results into BeautifulSoup objects."""
+        from bs4 import BeautifulSoup
+
+        final_results = []
+        for i, result in enumerate(results):
+            url = urls[i]
+            if parser is None:
+                if url.endswith(".xml"):
+                    parser = "xml"
+                else:
+                    parser = self.default_parser
+                self._check_parser(parser)
+            final_results.append(BeautifulSoup(result, parser, **self.bs_kwargs))
+        return final_results
+
+    async def ascrape_all(
+        self, urls: List[str], parser: Union[str, None] = None
+    ) -> List[Any]:
+        """Async fetch all urls, then return soups for all results."""
+        results = await self.fetch_all(urls)
+        return self._unpack_fetch_results(results, urls, parser=parser)
+
     def lazy_load(self) -> Iterator[Document]:
     def lazy_load(self) -> Iterator[Document]:
         """Lazy load text from the url(s) in web_path with error handling."""
         """Lazy load text from the url(s) in web_path with error handling."""
         for path in self.web_paths:
         for path in self.web_paths:
@@ -91,18 +158,40 @@ class SafeWebBaseLoader(WebBaseLoader):
                 # Log the error and continue with the next URL
                 # Log the error and continue with the next URL
                 log.error(f"Error loading {path}: {e}")
                 log.error(f"Error loading {path}: {e}")
 
 
+    async def alazy_load(self) -> AsyncIterator[Document]:
+        """Async lazy load text from the url(s) in web_path."""
+        results = await self.ascrape_all(self.web_paths)
+        for path, soup in zip(self.web_paths, results):
+            text = soup.get_text(**self.bs_get_text_kwargs)
+            metadata = {"source": path}
+            if title := soup.find("title"):
+                metadata["title"] = title.get_text()
+            if description := soup.find("meta", attrs={"name": "description"}):
+                metadata["description"] = description.get(
+                    "content", "No description found."
+                )
+            if html := soup.find("html"):
+                metadata["language"] = html.get("lang", "No language found.")
+            yield Document(page_content=text, metadata=metadata)
+
+    async def aload(self) -> list[Document]:
+        """Load data into Document objects."""
+        return [document async for document in self.alazy_load()]
+
 
 
 def get_web_loader(
 def get_web_loader(
     urls: Union[str, Sequence[str]],
     urls: Union[str, Sequence[str]],
     verify_ssl: bool = True,
     verify_ssl: bool = True,
     requests_per_second: int = 2,
     requests_per_second: int = 2,
+    trust_env: bool = False,
 ):
 ):
     # Check if the URLs are valid
     # Check if the URLs are valid
     safe_urls = safe_validate_urls([urls] if isinstance(urls, str) else urls)
     safe_urls = safe_validate_urls([urls] if isinstance(urls, str) else urls)
 
 
     return SafeWebBaseLoader(
     return SafeWebBaseLoader(
-        safe_urls,
+        web_path=safe_urls,
         verify_ssl=verify_ssl,
         verify_ssl=verify_ssl,
         requests_per_second=requests_per_second,
         requests_per_second=requests_per_second,
         continue_on_failure=True,
         continue_on_failure=True,
+        trust_env=trust_env,
     )
     )

+ 22 - 6
backend/open_webui/routers/auths.py

@@ -251,9 +251,19 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
             user = Users.get_user_by_email(mail)
             user = Users.get_user_by_email(mail)
             if not user:
             if not user:
                 try:
                 try:
+                    user_count = Users.get_num_users()
+                    if (
+                        request.app.state.USER_COUNT
+                        and user_count >= request.app.state.USER_COUNT
+                    ):
+                        raise HTTPException(
+                            status.HTTP_403_FORBIDDEN,
+                            detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+                        )
+
                     role = (
                     role = (
                         "admin"
                         "admin"
-                        if Users.get_num_users() == 0
+                        if user_count == 0
                         else request.app.state.config.DEFAULT_USER_ROLE
                         else request.app.state.config.DEFAULT_USER_ROLE
                     )
                     )
 
 
@@ -413,6 +423,8 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
 
 
 @router.post("/signup", response_model=SessionUserResponse)
 @router.post("/signup", response_model=SessionUserResponse)
 async def signup(request: Request, response: Response, form_data: SignupForm):
 async def signup(request: Request, response: Response, form_data: SignupForm):
+    user_count = Users.get_num_users()
+
     if WEBUI_AUTH:
     if WEBUI_AUTH:
         if (
         if (
             not request.app.state.config.ENABLE_SIGNUP
             not request.app.state.config.ENABLE_SIGNUP
@@ -422,11 +434,16 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
                 status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
                 status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
             )
             )
     else:
     else:
-        if Users.get_num_users() != 0:
+        if user_count != 0:
             raise HTTPException(
             raise HTTPException(
                 status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
                 status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
             )
             )
 
 
+    if request.app.state.USER_COUNT and user_count >= request.app.state.USER_COUNT:
+        raise HTTPException(
+            status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED
+        )
+
     if not validate_email_format(form_data.email.lower()):
     if not validate_email_format(form_data.email.lower()):
         raise HTTPException(
         raise HTTPException(
             status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
             status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
@@ -437,12 +454,10 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
 
 
     try:
     try:
         role = (
         role = (
-            "admin"
-            if Users.get_num_users() == 0
-            else request.app.state.config.DEFAULT_USER_ROLE
+            "admin" if user_count == 0 else request.app.state.config.DEFAULT_USER_ROLE
         )
         )
 
 
-        if Users.get_num_users() == 0:
+        if user_count == 0:
             # Disable signup after the first user is created
             # Disable signup after the first user is created
             request.app.state.config.ENABLE_SIGNUP = False
             request.app.state.config.ENABLE_SIGNUP = False
 
 
@@ -484,6 +499,7 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
 
 
             if request.app.state.config.WEBHOOK_URL:
             if request.app.state.config.WEBHOOK_URL:
                 post_webhook(
                 post_webhook(
+                    request.app.state.WEBUI_NAME,
                     request.app.state.config.WEBHOOK_URL,
                     request.app.state.config.WEBHOOK_URL,
                     WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
                     WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
                     {
                     {

+ 3 - 1
backend/open_webui/routers/channels.py

@@ -192,7 +192,7 @@ async def get_channel_messages(
 ############################
 ############################
 
 
 
 
-async def send_notification(webui_url, channel, message, active_user_ids):
+async def send_notification(name, webui_url, channel, message, active_user_ids):
     users = get_users_with_access("read", channel.access_control)
     users = get_users_with_access("read", channel.access_control)
 
 
     for user in users:
     for user in users:
@@ -206,6 +206,7 @@ async def send_notification(webui_url, channel, message, active_user_ids):
 
 
                 if webhook_url:
                 if webhook_url:
                     post_webhook(
                     post_webhook(
+                        name,
                         webhook_url,
                         webhook_url,
                         f"#{channel.name} - {webui_url}/channels/{channel.id}\n\n{message.content}",
                         f"#{channel.name} - {webui_url}/channels/{channel.id}\n\n{message.content}",
                         {
                         {
@@ -302,6 +303,7 @@ async def post_new_message(
 
 
             background_tasks.add_task(
             background_tasks.add_task(
                 send_notification,
                 send_notification,
+                request.app.state.WEBUI_NAME,
                 request.app.state.config.WEBUI_URL,
                 request.app.state.config.WEBUI_URL,
                 channel,
                 channel,
                 message,
                 message,

+ 1 - 1
backend/open_webui/routers/ollama.py

@@ -944,7 +944,7 @@ class ChatMessage(BaseModel):
 class GenerateChatCompletionForm(BaseModel):
 class GenerateChatCompletionForm(BaseModel):
     model: str
     model: str
     messages: list[ChatMessage]
     messages: list[ChatMessage]
-    format: Optional[dict] = None
+    format: Optional[Union[dict, str]] = None
     options: Optional[dict] = None
     options: Optional[dict] = None
     template: Optional[str] = None
     template: Optional[str] = None
     stream: Optional[bool] = True
     stream: Optional[bool] = True

+ 59 - 51
backend/open_webui/routers/pipelines.py

@@ -9,6 +9,7 @@ from fastapi import (
     status,
     status,
     APIRouter,
     APIRouter,
 )
 )
+import aiohttp
 import os
 import os
 import logging
 import logging
 import shutil
 import shutil
@@ -56,96 +57,103 @@ def get_sorted_filters(model_id, models):
     return sorted_filters
     return sorted_filters
 
 
 
 
-def process_pipeline_inlet_filter(request, payload, user, models):
+async def process_pipeline_inlet_filter(request, payload, user, models):
     user = {"id": user.id, "email": user.email, "name": user.name, "role": user.role}
     user = {"id": user.id, "email": user.email, "name": user.name, "role": user.role}
     model_id = payload["model"]
     model_id = payload["model"]
-
     sorted_filters = get_sorted_filters(model_id, models)
     sorted_filters = get_sorted_filters(model_id, models)
     model = models[model_id]
     model = models[model_id]
 
 
     if "pipeline" in model:
     if "pipeline" in model:
         sorted_filters.append(model)
         sorted_filters.append(model)
 
 
-    for filter in sorted_filters:
-        r = None
-        try:
-            urlIdx = filter["urlIdx"]
+    async with aiohttp.ClientSession() as session:
+        for filter in sorted_filters:
+            urlIdx = filter.get("urlIdx")
+            if urlIdx is None:
+                continue
 
 
             url = request.app.state.config.OPENAI_API_BASE_URLS[urlIdx]
             url = request.app.state.config.OPENAI_API_BASE_URLS[urlIdx]
             key = request.app.state.config.OPENAI_API_KEYS[urlIdx]
             key = request.app.state.config.OPENAI_API_KEYS[urlIdx]
 
 
-            if key == "":
+            if not key:
                 continue
                 continue
 
 
             headers = {"Authorization": f"Bearer {key}"}
             headers = {"Authorization": f"Bearer {key}"}
-            r = requests.post(
-                f"{url}/{filter['id']}/filter/inlet",
-                headers=headers,
-                json={
-                    "user": user,
-                    "body": payload,
-                },
-            )
-
-            r.raise_for_status()
-            payload = r.json()
-        except Exception as e:
-            # Handle connection error here
-            print(f"Connection error: {e}")
+            request_data = {
+                "user": user,
+                "body": payload,
+            }
 
 
-            if r is not None:
-                res = r.json()
+            try:
+                async with session.post(
+                    f"{url}/{filter['id']}/filter/inlet",
+                    headers=headers,
+                    json=request_data,
+                ) as response:
+                    response.raise_for_status()
+                    payload = await response.json()
+            except aiohttp.ClientResponseError as e:
+                res = (
+                    await response.json()
+                    if response.content_type == "application/json"
+                    else {}
+                )
                 if "detail" in res:
                 if "detail" in res:
-                    raise Exception(r.status_code, res["detail"])
+                    raise Exception(response.status, res["detail"])
+            except Exception as e:
+                print(f"Connection error: {e}")
 
 
     return payload
     return payload
 
 
 
 
-def process_pipeline_outlet_filter(request, payload, user, models):
+async def process_pipeline_outlet_filter(request, payload, user, models):
     user = {"id": user.id, "email": user.email, "name": user.name, "role": user.role}
     user = {"id": user.id, "email": user.email, "name": user.name, "role": user.role}
     model_id = payload["model"]
     model_id = payload["model"]
-
     sorted_filters = get_sorted_filters(model_id, models)
     sorted_filters = get_sorted_filters(model_id, models)
     model = models[model_id]
     model = models[model_id]
 
 
     if "pipeline" in model:
     if "pipeline" in model:
         sorted_filters = [model] + sorted_filters
         sorted_filters = [model] + sorted_filters
 
 
-    for filter in sorted_filters:
-        r = None
-        try:
-            urlIdx = filter["urlIdx"]
+    async with aiohttp.ClientSession() as session:
+        for filter in sorted_filters:
+            urlIdx = filter.get("urlIdx")
+            if urlIdx is None:
+                continue
 
 
             url = request.app.state.config.OPENAI_API_BASE_URLS[urlIdx]
             url = request.app.state.config.OPENAI_API_BASE_URLS[urlIdx]
             key = request.app.state.config.OPENAI_API_KEYS[urlIdx]
             key = request.app.state.config.OPENAI_API_KEYS[urlIdx]
 
 
-            if key != "":
-                r = requests.post(
-                    f"{url}/{filter['id']}/filter/outlet",
-                    headers={"Authorization": f"Bearer {key}"},
-                    json={
-                        "user": user,
-                        "body": payload,
-                    },
-                )
+            if not key:
+                continue
 
 
-                r.raise_for_status()
-                data = r.json()
-                payload = data
-        except Exception as e:
-            # Handle connection error here
-            print(f"Connection error: {e}")
+            headers = {"Authorization": f"Bearer {key}"}
+            request_data = {
+                "user": user,
+                "body": payload,
+            }
 
 
-            if r is not None:
+            try:
+                async with session.post(
+                    f"{url}/{filter['id']}/filter/outlet",
+                    headers=headers,
+                    json=request_data,
+                ) as response:
+                    response.raise_for_status()
+                    payload = await response.json()
+            except aiohttp.ClientResponseError as e:
                 try:
                 try:
-                    res = r.json()
+                    res = (
+                        await response.json()
+                        if "application/json" in response.content_type
+                        else {}
+                    )
                     if "detail" in res:
                     if "detail" in res:
-                        return Exception(r.status_code, res)
+                        raise Exception(response.status, res)
                 except Exception:
                 except Exception:
                     pass
                     pass
-
-            else:
-                pass
+            except Exception as e:
+                print(f"Connection error: {e}")
 
 
     return payload
     return payload
 
 

+ 17 - 4
backend/open_webui/routers/retrieval.py

@@ -21,6 +21,7 @@ from fastapi import (
     APIRouter,
     APIRouter,
 )
 )
 from fastapi.middleware.cors import CORSMiddleware
 from fastapi.middleware.cors import CORSMiddleware
+from fastapi.concurrency import run_in_threadpool
 from pydantic import BaseModel
 from pydantic import BaseModel
 import tiktoken
 import tiktoken
 
 
@@ -450,6 +451,7 @@ class WebSearchConfig(BaseModel):
     exa_api_key: Optional[str] = None
     exa_api_key: Optional[str] = None
     result_count: Optional[int] = None
     result_count: Optional[int] = None
     concurrent_requests: Optional[int] = None
     concurrent_requests: Optional[int] = None
+    trust_env: Optional[bool] = None
     domain_filter_list: Optional[List[str]] = []
     domain_filter_list: Optional[List[str]] = []
 
 
 
 
@@ -569,6 +571,9 @@ async def update_rag_config(
         request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = (
         request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = (
             form_data.web.search.concurrent_requests
             form_data.web.search.concurrent_requests
         )
         )
+        request.app.state.config.RAG_WEB_SEARCH_TRUST_ENV = (
+            form_data.web.search.trust_env
+        )
         request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = (
         request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST = (
             form_data.web.search.domain_filter_list
             form_data.web.search.domain_filter_list
         )
         )
@@ -621,6 +626,7 @@ async def update_rag_config(
                 "exa_api_key": request.app.state.config.EXA_API_KEY,
                 "exa_api_key": request.app.state.config.EXA_API_KEY,
                 "result_count": request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
                 "result_count": request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
                 "concurrent_requests": request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
                 "concurrent_requests": request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
+                "trust_env": request.app.state.config.RAG_WEB_SEARCH_TRUST_ENV,
                 "domain_filter_list": request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
                 "domain_filter_list": request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
             },
             },
         },
         },
@@ -1308,7 +1314,7 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
 
 
 
 
 @router.post("/process/web/search")
 @router.post("/process/web/search")
-def process_web_search(
+async def process_web_search(
     request: Request, form_data: SearchForm, user=Depends(get_verified_user)
     request: Request, form_data: SearchForm, user=Depends(get_verified_user)
 ):
 ):
     try:
     try:
@@ -1340,16 +1346,23 @@ def process_web_search(
             urls,
             urls,
             verify_ssl=request.app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
             verify_ssl=request.app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION,
             requests_per_second=request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
             requests_per_second=request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
+            trust_env=request.app.state.config.RAG_WEB_SEARCH_TRUST_ENV,
         )
         )
-        docs = loader.load()
-        save_docs_to_vector_db(
-            request, docs, collection_name, overwrite=True, user=user
+        docs = await loader.aload()
+        await run_in_threadpool(
+            save_docs_to_vector_db,
+            request,
+            docs,
+            collection_name,
+            overwrite=True,
+            user=user,
         )
         )
 
 
         return {
         return {
             "status": True,
             "status": True,
             "collection_name": collection_name,
             "collection_name": collection_name,
             "filenames": urls,
             "filenames": urls,
+            "loaded_count": len(docs),
         }
         }
     except Exception as e:
     except Exception as e:
         log.exception(e)
         log.exception(e)

+ 0 - 0
backend/open_webui/static/loader.js


+ 0 - 2
backend/open_webui/static/swagger-ui/swagger-ui.css

@@ -9308,5 +9308,3 @@
 	.json-schema-2020-12__title:first-of-type {
 	.json-schema-2020-12__title:first-of-type {
 	font-size: 16px;
 	font-size: 16px;
 }
 }
-
-/*# sourceMappingURL=swagger-ui.css.map*/

+ 54 - 1
backend/open_webui/utils/auth.py

@@ -1,6 +1,11 @@
 import logging
 import logging
 import uuid
 import uuid
 import jwt
 import jwt
+import base64
+import hmac
+import hashlib
+import requests
+
 
 
 from datetime import UTC, datetime, timedelta
 from datetime import UTC, datetime, timedelta
 from typing import Optional, Union, List, Dict
 from typing import Optional, Union, List, Dict
@@ -8,7 +13,8 @@ from typing import Optional, Union, List, Dict
 from open_webui.models.users import Users
 from open_webui.models.users import Users
 
 
 from open_webui.constants import ERROR_MESSAGES
 from open_webui.constants import ERROR_MESSAGES
-from open_webui.env import WEBUI_SECRET_KEY
+from open_webui.config import override_static
+from open_webui.env import WEBUI_SECRET_KEY, TRUSTED_SIGNATURE_KEY
 
 
 from fastapi import Depends, HTTPException, Request, Response, status
 from fastapi import Depends, HTTPException, Request, Response, status
 from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
 from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
@@ -24,6 +30,53 @@ ALGORITHM = "HS256"
 # Auth Utils
 # Auth Utils
 ##############
 ##############
 
 
+
+def verify_signature(payload: str, signature: str) -> bool:
+    """
+    Verifies the HMAC signature of the received payload.
+    """
+    try:
+        expected_signature = base64.b64encode(
+            hmac.new(TRUSTED_SIGNATURE_KEY, payload.encode(), hashlib.sha256).digest()
+        ).decode()
+
+        # Compare securely to prevent timing attacks
+        return hmac.compare_digest(expected_signature, signature)
+
+    except Exception:
+        return False
+
+
+def get_license_data(app, key):
+    if key:
+        try:
+            res = requests.post(
+                "https://api.openwebui.com/api/v1/license",
+                json={"key": key, "version": "1"},
+                timeout=5,
+            )
+
+            if getattr(res, "ok", False):
+                payload = getattr(res, "json", lambda: {})()
+                for k, v in payload.items():
+                    if k == "resources":
+                        for p, c in v.items():
+                            globals().get("override_static", lambda a, b: None)(p, c)
+                    elif k == "user_count":
+                        setattr(app.state, "USER_COUNT", v)
+                    elif k == "webui_name":
+                        setattr(app.state, "WEBUI_NAME", v)
+
+                return True
+            else:
+                print(
+                    f"License: retrieval issue: {getattr(res, 'text', 'unknown error')}"
+                )
+        except Exception as ex:
+            print(f"License: Uncaught Exception: {ex}")
+    return False
+
+
 bearer_security = HTTPBearer(auto_error=False)
 bearer_security = HTTPBearer(auto_error=False)
 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
 
 

+ 1 - 7
backend/open_webui/utils/chat.py

@@ -186,12 +186,6 @@ async def generate_chat_completion(
     if model_id not in models:
     if model_id not in models:
         raise Exception("Model not found")
         raise Exception("Model not found")
 
 
-    # Process the form_data through the pipeline
-    try:
-        form_data = process_pipeline_inlet_filter(request, form_data, user, models)
-    except Exception as e:
-        raise e
-
     model = models[model_id]
     model = models[model_id]
 
 
     if getattr(request.state, "direct", False):
     if getattr(request.state, "direct", False):
@@ -308,7 +302,7 @@ async def chat_completed(request: Request, form_data: dict, user: Any):
     model = models[model_id]
     model = models[model_id]
 
 
     try:
     try:
-        data = process_pipeline_outlet_filter(request, data, user, models)
+        data = await process_pipeline_outlet_filter(request, data, user, models)
     except Exception as e:
     except Exception as e:
         return Exception(f"Error: {e}")
         return Exception(f"Error: {e}")
 
 

+ 35 - 28
backend/open_webui/utils/middleware.py

@@ -39,7 +39,10 @@ from open_webui.routers.tasks import (
 )
 )
 from open_webui.routers.retrieval import process_web_search, SearchForm
 from open_webui.routers.retrieval import process_web_search, SearchForm
 from open_webui.routers.images import image_generations, GenerateImageForm
 from open_webui.routers.images import image_generations, GenerateImageForm
-
+from open_webui.routers.pipelines import (
+    process_pipeline_inlet_filter,
+    process_pipeline_outlet_filter,
+)
 
 
 from open_webui.utils.webhook import post_webhook
 from open_webui.utils.webhook import post_webhook
 
 
@@ -334,21 +337,15 @@ async def chat_web_search_handler(
 
 
     try:
     try:
 
 
-        # Offload process_web_search to a separate thread
-        loop = asyncio.get_running_loop()
-        with ThreadPoolExecutor() as executor:
-            results = await loop.run_in_executor(
-                executor,
-                lambda: process_web_search(
-                    request,
-                    SearchForm(
-                        **{
-                            "query": searchQuery,
-                        }
-                    ),
-                    user,
-                ),
-            )
+        results = await process_web_search(
+            request,
+            SearchForm(
+                **{
+                    "query": searchQuery,
+                }
+            ),
+            user,
+        )
 
 
         if results:
         if results:
             await event_emitter(
             await event_emitter(
@@ -682,6 +679,25 @@ async def process_chat_payload(request, form_data, metadata, user, model):
 
 
     variables = form_data.pop("variables", None)
     variables = form_data.pop("variables", None)
 
 
+    # Process the form_data through the pipeline
+    try:
+        form_data = await process_pipeline_inlet_filter(
+            request, form_data, user, models
+        )
+    except Exception as e:
+        raise e
+
+    try:
+        form_data, flags = await process_filter_functions(
+            request=request,
+            filter_ids=get_sorted_filter_ids(model),
+            filter_type="inlet",
+            form_data=form_data,
+            extra_params=extra_params,
+        )
+    except Exception as e:
+        raise Exception(f"Error: {e}")
+
     features = form_data.pop("features", None)
     features = form_data.pop("features", None)
     if features:
     if features:
         if "web_search" in features and features["web_search"]:
         if "web_search" in features and features["web_search"]:
@@ -704,17 +720,6 @@ async def process_chat_payload(request, form_data, metadata, user, model):
                 form_data["messages"],
                 form_data["messages"],
             )
             )
 
 
-    try:
-        form_data, flags = await process_filter_functions(
-            request=request,
-            filter_ids=get_sorted_filter_ids(model),
-            filter_type="inlet",
-            form_data=form_data,
-            extra_params=extra_params,
-        )
-    except Exception as e:
-        raise Exception(f"Error: {e}")
-
     tool_ids = form_data.pop("tool_ids", None)
     tool_ids = form_data.pop("tool_ids", None)
     files = form_data.pop("files", None)
     files = form_data.pop("files", None)
     # Remove files duplicates
     # Remove files duplicates
@@ -778,7 +783,7 @@ async def process_chat_payload(request, form_data, metadata, user, model):
 
 
             if "document" in source:
             if "document" in source:
                 for doc_idx, doc_context in enumerate(source["document"]):
                 for doc_idx, doc_context in enumerate(source["document"]):
-                    context_string += f"<source><source_id>{doc_idx}</source_id><source_context>{doc_context}</source_context></source>\n"
+                    context_string += f"<source><source_id>{source_idx}</source_id><source_context>{doc_context}</source_context></source>\n"
 
 
         context_string = context_string.strip()
         context_string = context_string.strip()
         prompt = get_last_user_message(form_data["messages"])
         prompt = get_last_user_message(form_data["messages"])
@@ -1003,6 +1008,7 @@ async def process_chat_response(
                         webhook_url = Users.get_user_webhook_url_by_id(user.id)
                         webhook_url = Users.get_user_webhook_url_by_id(user.id)
                         if webhook_url:
                         if webhook_url:
                             post_webhook(
                             post_webhook(
+                                request.app.state.WEBUI_NAME,
                                 webhook_url,
                                 webhook_url,
                                 f"{title} - {request.app.state.config.WEBUI_URL}/c/{metadata['chat_id']}\n\n{content}",
                                 f"{title} - {request.app.state.config.WEBUI_URL}/c/{metadata['chat_id']}\n\n{content}",
                                 {
                                 {
@@ -1868,6 +1874,7 @@ async def process_chat_response(
                     webhook_url = Users.get_user_webhook_url_by_id(user.id)
                     webhook_url = Users.get_user_webhook_url_by_id(user.id)
                     if webhook_url:
                     if webhook_url:
                         post_webhook(
                         post_webhook(
+                            request.app.state.WEBUI_NAME,
                             webhook_url,
                             webhook_url,
                             f"{title} - {request.app.state.config.WEBUI_URL}/c/{metadata['chat_id']}\n\n{content}",
                             f"{title} - {request.app.state.config.WEBUI_URL}/c/{metadata['chat_id']}\n\n{content}",
                             {
                             {

+ 21 - 7
backend/open_webui/utils/oauth.py

@@ -36,7 +36,11 @@ from open_webui.config import (
     AppConfig,
     AppConfig,
 )
 )
 from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
 from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
-from open_webui.env import WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE
+from open_webui.env import (
+    WEBUI_NAME,
+    WEBUI_AUTH_COOKIE_SAME_SITE,
+    WEBUI_AUTH_COOKIE_SECURE,
+)
 from open_webui.utils.misc import parse_duration
 from open_webui.utils.misc import parse_duration
 from open_webui.utils.auth import get_password_hash, create_token
 from open_webui.utils.auth import get_password_hash, create_token
 from open_webui.utils.webhook import post_webhook
 from open_webui.utils.webhook import post_webhook
@@ -66,8 +70,9 @@ auth_manager_config.JWT_EXPIRES_IN = JWT_EXPIRES_IN
 
 
 
 
 class OAuthManager:
 class OAuthManager:
-    def __init__(self):
+    def __init__(self, app):
         self.oauth = OAuth()
         self.oauth = OAuth()
+        self.app = app
         for _, provider_config in OAUTH_PROVIDERS.items():
         for _, provider_config in OAUTH_PROVIDERS.items():
             provider_config["register"](self.oauth)
             provider_config["register"](self.oauth)
 
 
@@ -200,7 +205,7 @@ class OAuthManager:
                     id=group_model.id, form_data=update_form, overwrite=False
                     id=group_model.id, form_data=update_form, overwrite=False
                 )
                 )
 
 
-    async def handle_login(self, provider, request):
+    async def handle_login(self, request, provider):
         if provider not in OAUTH_PROVIDERS:
         if provider not in OAUTH_PROVIDERS:
             raise HTTPException(404)
             raise HTTPException(404)
         # If the provider has a custom redirect URL, use that, otherwise automatically generate one
         # If the provider has a custom redirect URL, use that, otherwise automatically generate one
@@ -212,7 +217,7 @@ class OAuthManager:
             raise HTTPException(404)
             raise HTTPException(404)
         return await client.authorize_redirect(request, redirect_uri)
         return await client.authorize_redirect(request, redirect_uri)
 
 
-    async def handle_callback(self, provider, request, response):
+    async def handle_callback(self, request, provider, response):
         if provider not in OAUTH_PROVIDERS:
         if provider not in OAUTH_PROVIDERS:
             raise HTTPException(404)
             raise HTTPException(404)
         client = self.get_client(provider)
         client = self.get_client(provider)
@@ -266,6 +271,17 @@ class OAuthManager:
                 Users.update_user_role_by_id(user.id, determined_role)
                 Users.update_user_role_by_id(user.id, determined_role)
 
 
         if not user:
         if not user:
+            user_count = Users.get_num_users()
+
+            if (
+                request.app.state.USER_COUNT
+                and user_count >= request.app.state.USER_COUNT
+            ):
+                raise HTTPException(
+                    403,
+                    detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
+                )
+
             # If the user does not exist, check if signups are enabled
             # If the user does not exist, check if signups are enabled
             if auth_manager_config.ENABLE_OAUTH_SIGNUP:
             if auth_manager_config.ENABLE_OAUTH_SIGNUP:
                 # Check if an existing user with the same email already exists
                 # Check if an existing user with the same email already exists
@@ -334,6 +350,7 @@ class OAuthManager:
 
 
                 if auth_manager_config.WEBHOOK_URL:
                 if auth_manager_config.WEBHOOK_URL:
                     post_webhook(
                     post_webhook(
+                        WEBUI_NAME,
                         auth_manager_config.WEBHOOK_URL,
                         auth_manager_config.WEBHOOK_URL,
                         WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
                         WEBHOOK_MESSAGES.USER_SIGNUP(user.name),
                         {
                         {
@@ -380,6 +397,3 @@ class OAuthManager:
         # Redirect back to the frontend with the JWT token
         # Redirect back to the frontend with the JWT token
         redirect_url = f"{request.base_url}auth#token={jwt_token}"
         redirect_url = f"{request.base_url}auth#token={jwt_token}"
         return RedirectResponse(url=redirect_url, headers=response.headers)
         return RedirectResponse(url=redirect_url, headers=response.headers)
-
-
-oauth_manager = OAuthManager()

+ 3 - 3
backend/open_webui/utils/webhook.py

@@ -2,14 +2,14 @@ import json
 import logging
 import logging
 
 
 import requests
 import requests
-from open_webui.config import WEBUI_FAVICON_URL, WEBUI_NAME
+from open_webui.config import WEBUI_FAVICON_URL
 from open_webui.env import SRC_LOG_LEVELS, VERSION
 from open_webui.env import SRC_LOG_LEVELS, VERSION
 
 
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 log.setLevel(SRC_LOG_LEVELS["WEBHOOK"])
 log.setLevel(SRC_LOG_LEVELS["WEBHOOK"])
 
 
 
 
-def post_webhook(url: str, message: str, event_data: dict) -> bool:
+def post_webhook(name: str, url: str, message: str, event_data: dict) -> bool:
     try:
     try:
         log.debug(f"post_webhook: {url}, {message}, {event_data}")
         log.debug(f"post_webhook: {url}, {message}, {event_data}")
         payload = {}
         payload = {}
@@ -39,7 +39,7 @@ def post_webhook(url: str, message: str, event_data: dict) -> bool:
                 "sections": [
                 "sections": [
                     {
                     {
                         "activityTitle": message,
                         "activityTitle": message,
-                        "activitySubtitle": f"{WEBUI_NAME} ({VERSION}) - {action}",
+                        "activitySubtitle": f"{name} ({VERSION}) - {action}",
                         "activityImage": WEBUI_FAVICON_URL,
                         "activityImage": WEBUI_FAVICON_URL,
                         "facts": facts,
                         "facts": facts,
                         "markdown": True,
                         "markdown": True,

Файловите разлики са ограничени, защото са твърде много
+ 527 - 312
package-lock.json


+ 2 - 2
package.json

@@ -26,10 +26,10 @@
 		"@sveltejs/kit": "^2.5.20",
 		"@sveltejs/kit": "^2.5.20",
 		"@sveltejs/vite-plugin-svelte": "^3.1.1",
 		"@sveltejs/vite-plugin-svelte": "^3.1.1",
 		"@tailwindcss/container-queries": "^0.1.1",
 		"@tailwindcss/container-queries": "^0.1.1",
+		"@tailwindcss/postcss": "^4.0.0",
 		"@tailwindcss/typography": "^0.5.13",
 		"@tailwindcss/typography": "^0.5.13",
 		"@typescript-eslint/eslint-plugin": "^6.17.0",
 		"@typescript-eslint/eslint-plugin": "^6.17.0",
 		"@typescript-eslint/parser": "^6.17.0",
 		"@typescript-eslint/parser": "^6.17.0",
-		"autoprefixer": "^10.4.16",
 		"cypress": "^13.15.0",
 		"cypress": "^13.15.0",
 		"eslint": "^8.56.0",
 		"eslint": "^8.56.0",
 		"eslint-config-prettier": "^9.1.0",
 		"eslint-config-prettier": "^9.1.0",
@@ -43,7 +43,7 @@
 		"svelte": "^4.2.18",
 		"svelte": "^4.2.18",
 		"svelte-check": "^3.8.5",
 		"svelte-check": "^3.8.5",
 		"svelte-confetti": "^1.3.2",
 		"svelte-confetti": "^1.3.2",
-		"tailwindcss": "^3.3.3",
+		"tailwindcss": "^4.0.0",
 		"tslib": "^2.4.1",
 		"tslib": "^2.4.1",
 		"typescript": "^5.5.4",
 		"typescript": "^5.5.4",
 		"vite": "^5.4.14",
 		"vite": "^5.4.14",

+ 1 - 2
postcss.config.js

@@ -1,6 +1,5 @@
 export default {
 export default {
 	plugins: {
 	plugins: {
-		tailwindcss: {},
-		autoprefixer: {}
+		'@tailwindcss/postcss': {},
 	}
 	}
 };
 };

+ 14 - 2
src/app.css

@@ -1,3 +1,5 @@
+@reference "./tailwind.css";
+
 @font-face {
 @font-face {
 	font-family: 'Inter';
 	font-family: 'Inter';
 	src: url('/assets/fonts/Inter-Variable.ttf');
 	src: url('/assets/fonts/Inter-Variable.ttf');
@@ -217,8 +219,18 @@ input[type='number'] {
 	width: 100%;
 	width: 100%;
 }
 }
 
 
-.cm-scroller {
-	@apply scrollbar-hidden;
+.cm-scroller:active::-webkit-scrollbar-thumb,
+.cm-scroller:focus::-webkit-scrollbar-thumb,
+.cm-scroller:hover::-webkit-scrollbar-thumb {
+	visibility: visible;
+}
+
+.cm-scroller::-webkit-scrollbar-thumb {
+	visibility: hidden;
+}
+
+.cm-scroller::-webkit-scrollbar-corner {
+	display: none;
 }
 }
 
 
 .cm-editor.cm-focused {
 .cm-editor.cm-focused {

+ 1 - 0
src/app.html

@@ -21,6 +21,7 @@
 			title="Open WebUI"
 			title="Open WebUI"
 			href="/opensearch.xml"
 			href="/opensearch.xml"
 		/>
 		/>
+		<script src="/static/loader.js" defer></script>
 
 
 		<script>
 		<script>
 			function resizeIframe(obj) {
 			function resizeIframe(obj) {

+ 1 - 1
src/lib/apis/chats/index.ts

@@ -459,7 +459,7 @@ export const getChatById = async (token: string, id: string) => {
 			return json;
 			return json;
 		})
 		})
 		.catch((err) => {
 		.catch((err) => {
-			error = err;
+			error = err.detail;
 
 
 			console.log(err);
 			console.log(err);
 			return null;
 			return null;

+ 6 - 6
src/lib/components/AddConnectionModal.svelte

@@ -169,7 +169,7 @@
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
-										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 										type="text"
 										type="text"
 										bind:value={url}
 										bind:value={url}
 										placeholder={$i18n.t('API Base URL')}
 										placeholder={$i18n.t('API Base URL')}
@@ -202,7 +202,7 @@
 								</button>
 								</button>
 							</Tooltip>
 							</Tooltip>
 
 
-							<div class="flex flex-col flex-shrink-0 self-end">
+							<div class="flex flex-col shrink-0 self-end">
 								<Tooltip content={enable ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
 								<Tooltip content={enable ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
 									<Switch bind:state={enable} />
 									<Switch bind:state={enable} />
 								</Tooltip>
 								</Tooltip>
@@ -215,7 +215,7 @@
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<SensitiveInput
 									<SensitiveInput
-										className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 										bind:value={key}
 										bind:value={key}
 										placeholder={$i18n.t('API Key')}
 										placeholder={$i18n.t('API Key')}
 										required={!ollama}
 										required={!ollama}
@@ -233,7 +233,7 @@
 										)}
 										)}
 									>
 									>
 										<input
 										<input
-											class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+											class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 											type="text"
 											type="text"
 											bind:value={prefixId}
 											bind:value={prefixId}
 											placeholder={$i18n.t('Prefix ID')}
 											placeholder={$i18n.t('Prefix ID')}
@@ -258,7 +258,7 @@
 											<div class=" text-sm flex-1 py-1 rounded-lg">
 											<div class=" text-sm flex-1 py-1 rounded-lg">
 												{modelId}
 												{modelId}
 											</div>
 											</div>
-											<div class="flex-shrink-0">
+											<div class="shrink-0">
 												<button
 												<button
 													type="button"
 													type="button"
 													on:click={() => {
 													on:click={() => {
@@ -292,7 +292,7 @@
 							<input
 							<input
 								class="w-full py-1 text-sm rounded-lg bg-transparent {modelId
 								class="w-full py-1 text-sm rounded-lg bg-transparent {modelId
 									? ''
 									? ''
-									: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+									: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 								bind:value={modelId}
 								bind:value={modelId}
 								placeholder={$i18n.t('Add a model ID')}
 								placeholder={$i18n.t('Add a model ID')}
 							/>
 							/>

+ 1 - 1
src/lib/components/ChangelogModal.svelte

@@ -68,7 +68,7 @@
 								v{version} - {changelog[version].date}
 								v{version} - {changelog[version].date}
 							</div>
 							</div>
 
 
-							<hr class=" dark:border-gray-800 my-2" />
+							<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 
 							{#each Object.keys(changelog[version]).filter((section) => section !== 'date') as section}
 							{#each Object.keys(changelog[version]).filter((section) => section !== 'date') as section}
 								<div class="">
 								<div class="">

+ 2 - 2
src/lib/components/NotificationToast.svelte

@@ -31,13 +31,13 @@
 </script>
 </script>
 
 
 <button
 <button
-	class="flex gap-2.5 text-left min-w-[var(--width)] w-full dark:bg-gray-850 dark:text-white bg-white text-black border border-gray-50 dark:border-gray-800 rounded-xl px-3.5 py-3.5"
+	class="flex gap-2.5 text-left min-w-[var(--width)] w-full dark:bg-gray-850 dark:text-white bg-white text-black border border-gray-100 dark:border-gray-850 rounded-xl px-3.5 py-3.5"
 	on:click={() => {
 	on:click={() => {
 		onClick();
 		onClick();
 		dispatch('closeToast');
 		dispatch('closeToast');
 	}}
 	}}
 >
 >
-	<div class="flex-shrink-0 self-top -translate-y-0.5">
+	<div class="shrink-0 self-top -translate-y-0.5">
 		<img src={'/static/favicon.png'} alt="favicon" class="size-7 rounded-full" />
 		<img src={'/static/favicon.png'} alt="favicon" class="size-7 rounded-full" />
 	</div>
 	</div>
 
 

+ 2 - 2
src/lib/components/OnBoarding.svelte

@@ -30,10 +30,10 @@
 		<SlideShow duration={5000} />
 		<SlideShow duration={5000} />
 
 
 		<div
 		<div
-			class="w-full h-full absolute top-0 left-0 bg-gradient-to-t from-20% from-black to-transparent"
+			class="w-full h-full absolute top-0 left-0 bg-linear-to-t from-20% from-black to-transparent"
 		></div>
 		></div>
 
 
-		<div class="w-full h-full absolute top-0 left-0 backdrop-blur-sm bg-black/50"></div>
+		<div class="w-full h-full absolute top-0 left-0 backdrop-blur-xs bg-black/50"></div>
 
 
 		<div class="relative bg-transparent w-full min-h-screen flex z-10">
 		<div class="relative bg-transparent w-full min-h-screen flex z-10">
 			<div class="flex flex-col justify-end w-full items-center pb-10 text-center">
 			<div class="flex flex-col justify-end w-full items-center pb-10 text-center">

+ 3 - 3
src/lib/components/admin/Evaluations/Feedbacks.svelte

@@ -131,14 +131,14 @@
 	</div>
 	</div>
 </div>
 </div>
 
 
-<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded pt-0.5">
+<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5">
 	{#if (feedbacks ?? []).length === 0}
 	{#if (feedbacks ?? []).length === 0}
 		<div class="text-center text-xs text-gray-500 dark:text-gray-400 py-1">
 		<div class="text-center text-xs text-gray-500 dark:text-gray-400 py-1">
 			{$i18n.t('No feedbacks found')}
 			{$i18n.t('No feedbacks found')}
 		</div>
 		</div>
 	{:else}
 	{:else}
 		<table
 		<table
-			class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded"
+			class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded-sm"
 		>
 		>
 			<thead
 			<thead
 				class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
 				class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
@@ -169,7 +169,7 @@
 						<td class=" py-0.5 text-right font-semibold">
 						<td class=" py-0.5 text-right font-semibold">
 							<div class="flex justify-center">
 							<div class="flex justify-center">
 								<Tooltip content={feedback?.user?.name}>
 								<Tooltip content={feedback?.user?.name}>
-									<div class="flex-shrink-0">
+									<div class="shrink-0">
 										<img
 										<img
 											src={feedback?.user?.profile_image_url ?? '/user.png'}
 											src={feedback?.user?.profile_image_url ?? '/user.png'}
 											alt={feedback?.user?.name}
 											alt={feedback?.user?.name}

+ 3 - 3
src/lib/components/admin/Evaluations/Leaderboard.svelte

@@ -288,7 +288,7 @@
 					<MagnifyingGlass className="size-3" />
 					<MagnifyingGlass className="size-3" />
 				</div>
 				</div>
 				<input
 				<input
-					class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+					class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
 					bind:value={query}
 					bind:value={query}
 					placeholder={$i18n.t('Search')}
 					placeholder={$i18n.t('Search')}
 					on:focus={() => {
 					on:focus={() => {
@@ -300,7 +300,7 @@
 	</div>
 	</div>
 </div>
 </div>
 
 
-<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded pt-0.5">
+<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5">
 	{#if loadingLeaderboard}
 	{#if loadingLeaderboard}
 		<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
 		<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
 			<div class="m-auto">
 			<div class="m-auto">
@@ -349,7 +349,7 @@
 						</td>
 						</td>
 						<td class="px-3 py-1.5 flex flex-col justify-center">
 						<td class="px-3 py-1.5 flex flex-col justify-center">
 							<div class="flex items-center gap-2">
 							<div class="flex items-center gap-2">
-								<div class="flex-shrink-0">
+								<div class="shrink-0">
 									<img
 									<img
 										src={model?.info?.meta?.profile_image_url ?? '/favicon.png'}
 										src={model?.info?.meta?.profile_image_url ?? '/favicon.png'}
 										alt={model.name}
 										alt={model.name}

+ 6 - 6
src/lib/components/admin/Functions.svelte

@@ -180,12 +180,12 @@
 
 
 		window.addEventListener('keydown', onKeyDown);
 		window.addEventListener('keydown', onKeyDown);
 		window.addEventListener('keyup', onKeyUp);
 		window.addEventListener('keyup', onKeyUp);
-		window.addEventListener('blur', onBlur);
+		window.addEventListener('blur-sm', onBlur);
 
 
 		return () => {
 		return () => {
 			window.removeEventListener('keydown', onKeyDown);
 			window.removeEventListener('keydown', onKeyDown);
 			window.removeEventListener('keyup', onKeyUp);
 			window.removeEventListener('keyup', onKeyUp);
-			window.removeEventListener('blur', onBlur);
+			window.removeEventListener('blur-sm', onBlur);
 		};
 		};
 	});
 	});
 </script>
 </script>
@@ -211,7 +211,7 @@
 				<Search className="size-3.5" />
 				<Search className="size-3.5" />
 			</div>
 			</div>
 			<input
 			<input
-				class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+				class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
 				bind:value={query}
 				bind:value={query}
 				placeholder={$i18n.t('Search Functions')}
 				placeholder={$i18n.t('Search Functions')}
 			/>
 			/>
@@ -241,14 +241,14 @@
 					<div class=" flex-1 self-center pl-1">
 					<div class=" flex-1 self-center pl-1">
 						<div class=" font-semibold flex items-center gap-1.5">
 						<div class=" font-semibold flex items-center gap-1.5">
 							<div
 							<div
-								class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+								class=" text-xs font-bold px-1 rounded-sm uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
 							>
 							>
 								{func.type}
 								{func.type}
 							</div>
 							</div>
 
 
 							{#if func?.meta?.manifest?.version}
 							{#if func?.meta?.manifest?.version}
 								<div
 								<div
-									class="text-xs font-bold px-1 rounded line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+									class="text-xs font-bold px-1 rounded-sm line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
 								>
 								>
 									v{func?.meta?.manifest?.version ?? ''}
 									v{func?.meta?.manifest?.version ?? ''}
 								</div>
 								</div>
@@ -260,7 +260,7 @@
 						</div>
 						</div>
 
 
 						<div class="flex gap-1.5 px-1">
 						<div class="flex gap-1.5 px-1">
-							<div class=" text-gray-500 text-xs font-medium flex-shrink-0">{func.id}</div>
+							<div class=" text-gray-500 text-xs font-medium shrink-0">{func.id}</div>
 
 
 							<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
 							<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
 								{func.meta.description}
 								{func.meta.description}

+ 5 - 5
src/lib/components/admin/Functions/FunctionEditor.svelte

@@ -300,7 +300,7 @@ class Pipe:
 			<div class="flex flex-col flex-1 overflow-auto h-0 rounded-lg">
 			<div class="flex flex-col flex-1 overflow-auto h-0 rounded-lg">
 				<div class="w-full mb-2 flex flex-col gap-0.5">
 				<div class="w-full mb-2 flex flex-col gap-0.5">
 					<div class="flex w-full items-center">
 					<div class="flex w-full items-center">
-						<div class=" flex-shrink-0 mr-2">
+						<div class=" shrink-0 mr-2">
 							<Tooltip content={$i18n.t('Back')}>
 							<Tooltip content={$i18n.t('Back')}>
 								<button
 								<button
 									class="w-full text-left text-sm py-1.5 px-1 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
 									class="w-full text-left text-sm py-1.5 px-1 rounded-lg dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-gray-850"
@@ -317,7 +317,7 @@ class Pipe:
 						<div class="flex-1">
 						<div class="flex-1">
 							<Tooltip content={$i18n.t('e.g. My Filter')} placement="top-start">
 							<Tooltip content={$i18n.t('e.g. My Filter')} placement="top-start">
 								<input
 								<input
-									class="w-full text-2xl font-medium bg-transparent outline-none font-primary"
+									class="w-full text-2xl font-medium bg-transparent outline-hidden font-primary"
 									type="text"
 									type="text"
 									placeholder={$i18n.t('Function Name')}
 									placeholder={$i18n.t('Function Name')}
 									bind:value={name}
 									bind:value={name}
@@ -333,13 +333,13 @@ class Pipe:
 
 
 					<div class=" flex gap-2 px-1 items-center">
 					<div class=" flex gap-2 px-1 items-center">
 						{#if edit}
 						{#if edit}
-							<div class="text-sm text-gray-500 flex-shrink-0">
+							<div class="text-sm text-gray-500 shrink-0">
 								{id}
 								{id}
 							</div>
 							</div>
 						{:else}
 						{:else}
 							<Tooltip className="w-full" content={$i18n.t('e.g. my_filter')} placement="top-start">
 							<Tooltip className="w-full" content={$i18n.t('e.g. my_filter')} placement="top-start">
 								<input
 								<input
-									class="w-full text-sm disabled:text-gray-500 bg-transparent outline-none"
+									class="w-full text-sm disabled:text-gray-500 bg-transparent outline-hidden"
 									type="text"
 									type="text"
 									placeholder={$i18n.t('Function ID')}
 									placeholder={$i18n.t('Function ID')}
 									bind:value={id}
 									bind:value={id}
@@ -355,7 +355,7 @@ class Pipe:
 							placement="top-start"
 							placement="top-start"
 						>
 						>
 							<input
 							<input
-								class="w-full text-sm bg-transparent outline-none"
+								class="w-full text-sm bg-transparent outline-hidden"
 								type="text"
 								type="text"
 								placeholder={$i18n.t('Function Description')}
 								placeholder={$i18n.t('Function Description')}
 								bind:value={meta.description}
 								bind:value={meta.description}

+ 3 - 3
src/lib/components/admin/Functions/FunctionMenu.svelte

@@ -42,7 +42,7 @@
 
 
 	<div slot="content">
 	<div slot="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="w-full max-w-[180px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			class="w-full max-w-[180px] rounded-xl px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
 			sideOffset={-2}
 			sideOffset={-2}
 			side="bottom"
 			side="bottom"
 			align="start"
 			align="start"
@@ -63,7 +63,7 @@
 					</div>
 					</div>
 				</div>
 				</div>
 
 
-				<hr class="border-gray-100 dark:border-gray-800 my-1" />
+				<hr class="border-gray-100 dark:border-gray-850 my-1" />
 			{/if}
 			{/if}
 
 
 			<DropdownMenu.Item
 			<DropdownMenu.Item
@@ -122,7 +122,7 @@
 				<div class="flex items-center">{$i18n.t('Export')}</div>
 				<div class="flex items-center">{$i18n.t('Export')}</div>
 			</DropdownMenu.Item>
 			</DropdownMenu.Item>
 
 
-			<hr class="border-gray-100 dark:border-gray-800 my-1" />
+			<hr class="border-gray-100 dark:border-gray-850 my-1" />
 
 
 			<DropdownMenu.Item
 			<DropdownMenu.Item
 				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 				class="flex  gap-2  items-center px-3 py-2 text-sm  font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"

+ 24 - 24
src/lib/components/admin/Settings/Audio.svelte

@@ -172,7 +172,7 @@
 					<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
 					<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
 					<div class="flex items-center relative">
 					<div class="flex items-center relative">
 						<select
 						<select
-							class="dark:bg-gray-900 cursor-pointer w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							class="dark:bg-gray-900 cursor-pointer w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
 							bind:value={STT_ENGINE}
 							bind:value={STT_ENGINE}
 							placeholder="Select an engine"
 							placeholder="Select an engine"
 						>
 						>
@@ -188,7 +188,7 @@
 					<div>
 					<div>
 						<div class="mt-1 flex gap-2 mb-1">
 						<div class="mt-1 flex gap-2 mb-1">
 							<input
 							<input
-								class="flex-1 w-full bg-transparent outline-none"
+								class="flex-1 w-full bg-transparent outline-hidden"
 								placeholder={$i18n.t('API Base URL')}
 								placeholder={$i18n.t('API Base URL')}
 								bind:value={STT_OPENAI_API_BASE_URL}
 								bind:value={STT_OPENAI_API_BASE_URL}
 								required
 								required
@@ -198,7 +198,7 @@
 						</div>
 						</div>
 					</div>
 					</div>
 
 
-					<hr class=" dark:border-gray-850 my-2" />
+					<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 
 					<div>
 					<div>
 						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
 						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
@@ -206,7 +206,7 @@
 							<div class="flex-1">
 							<div class="flex-1">
 								<input
 								<input
 									list="model-list"
 									list="model-list"
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									bind:value={STT_MODEL}
 									bind:value={STT_MODEL}
 									placeholder="Select a model"
 									placeholder="Select a model"
 								/>
 								/>
@@ -224,14 +224,14 @@
 						</div>
 						</div>
 					</div>
 					</div>
 
 
-					<hr class=" dark:border-gray-850 my-2" />
+					<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 
 					<div>
 					<div>
 						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
 						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
 						<div class="flex w-full">
 						<div class="flex w-full">
 							<div class="flex-1">
 							<div class="flex-1">
 								<input
 								<input
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									bind:value={STT_MODEL}
 									bind:value={STT_MODEL}
 									placeholder="Select a model (optional)"
 									placeholder="Select a model (optional)"
 								/>
 								/>
@@ -255,7 +255,7 @@
 						<div class="flex w-full">
 						<div class="flex w-full">
 							<div class="flex-1 mr-2">
 							<div class="flex-1 mr-2">
 								<input
 								<input
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									placeholder={$i18n.t('Set whisper model')}
 									placeholder={$i18n.t('Set whisper model')}
 									bind:value={STT_WHISPER_MODEL}
 									bind:value={STT_WHISPER_MODEL}
 								/>
 								/>
@@ -333,7 +333,7 @@
 				{/if}
 				{/if}
 			</div>
 			</div>
 
 
-			<hr class=" dark:border-gray-800" />
+			<hr class="border-gray-100 dark:border-gray-850" />
 
 
 			<div>
 			<div>
 				<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
 				<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
@@ -342,7 +342,7 @@
 					<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
 					<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
 					<div class="flex items-center relative">
 					<div class="flex items-center relative">
 						<select
 						<select
-							class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
 							bind:value={TTS_ENGINE}
 							bind:value={TTS_ENGINE}
 							placeholder="Select a mode"
 							placeholder="Select a mode"
 							on:change={async (e) => {
 							on:change={async (e) => {
@@ -372,7 +372,7 @@
 					<div>
 					<div>
 						<div class="mt-1 flex gap-2 mb-1">
 						<div class="mt-1 flex gap-2 mb-1">
 							<input
 							<input
-								class="flex-1 w-full bg-transparent outline-none"
+								class="flex-1 w-full bg-transparent outline-hidden"
 								placeholder={$i18n.t('API Base URL')}
 								placeholder={$i18n.t('API Base URL')}
 								bind:value={TTS_OPENAI_API_BASE_URL}
 								bind:value={TTS_OPENAI_API_BASE_URL}
 								required
 								required
@@ -385,7 +385,7 @@
 					<div>
 					<div>
 						<div class="mt-1 flex gap-2 mb-1">
 						<div class="mt-1 flex gap-2 mb-1">
 							<input
 							<input
-								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('API Key')}
 								placeholder={$i18n.t('API Key')}
 								bind:value={TTS_API_KEY}
 								bind:value={TTS_API_KEY}
 								required
 								required
@@ -396,13 +396,13 @@
 					<div>
 					<div>
 						<div class="mt-1 flex gap-2 mb-1">
 						<div class="mt-1 flex gap-2 mb-1">
 							<input
 							<input
-								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('API Key')}
 								placeholder={$i18n.t('API Key')}
 								bind:value={TTS_API_KEY}
 								bind:value={TTS_API_KEY}
 								required
 								required
 							/>
 							/>
 							<input
 							<input
-								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="flex-1 w-full rounded-lg py-2 pl-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('Azure Region')}
 								placeholder={$i18n.t('Azure Region')}
 								bind:value={TTS_AZURE_SPEECH_REGION}
 								bind:value={TTS_AZURE_SPEECH_REGION}
 								required
 								required
@@ -411,7 +411,7 @@
 					</div>
 					</div>
 				{/if}
 				{/if}
 
 
-				<hr class=" dark:border-gray-850 my-2" />
+				<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 
 				{#if TTS_ENGINE === ''}
 				{#if TTS_ENGINE === ''}
 					<div>
 					<div>
@@ -419,7 +419,7 @@
 						<div class="flex w-full">
 						<div class="flex w-full">
 							<div class="flex-1">
 							<div class="flex-1">
 								<select
 								<select
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									bind:value={TTS_VOICE}
 									bind:value={TTS_VOICE}
 								>
 								>
 									<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
 									<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
@@ -442,7 +442,7 @@
 							<div class="flex-1">
 							<div class="flex-1">
 								<input
 								<input
 									list="model-list"
 									list="model-list"
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									bind:value={TTS_MODEL}
 									bind:value={TTS_MODEL}
 									placeholder="CMU ARCTIC speaker embedding name"
 									placeholder="CMU ARCTIC speaker embedding name"
 								/>
 								/>
@@ -484,7 +484,7 @@
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
 										list="voice-list"
 										list="voice-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										bind:value={TTS_VOICE}
 										bind:value={TTS_VOICE}
 										placeholder="Select a voice"
 										placeholder="Select a voice"
 									/>
 									/>
@@ -503,7 +503,7 @@
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
 										list="tts-model-list"
 										list="tts-model-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										bind:value={TTS_MODEL}
 										bind:value={TTS_MODEL}
 										placeholder="Select a model"
 										placeholder="Select a model"
 									/>
 									/>
@@ -525,7 +525,7 @@
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
 										list="voice-list"
 										list="voice-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										bind:value={TTS_VOICE}
 										bind:value={TTS_VOICE}
 										placeholder="Select a voice"
 										placeholder="Select a voice"
 									/>
 									/>
@@ -544,7 +544,7 @@
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
 										list="tts-model-list"
 										list="tts-model-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										bind:value={TTS_MODEL}
 										bind:value={TTS_MODEL}
 										placeholder="Select a model"
 										placeholder="Select a model"
 									/>
 									/>
@@ -566,7 +566,7 @@
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
 										list="voice-list"
 										list="voice-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										bind:value={TTS_VOICE}
 										bind:value={TTS_VOICE}
 										placeholder="Select a voice"
 										placeholder="Select a voice"
 									/>
 									/>
@@ -593,7 +593,7 @@
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
 										list="tts-model-list"
 										list="tts-model-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										bind:value={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
 										bind:value={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
 										placeholder="Select a output format"
 										placeholder="Select a output format"
 									/>
 									/>
@@ -603,13 +603,13 @@
 					</div>
 					</div>
 				{/if}
 				{/if}
 
 
-				<hr class="dark:border-gray-850 my-2" />
+				<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 
 				<div class="pt-0.5 flex w-full justify-between">
 				<div class="pt-0.5 flex w-full justify-between">
 					<div class="self-center text-xs font-medium">{$i18n.t('Response splitting')}</div>
 					<div class="self-center text-xs font-medium">{$i18n.t('Response splitting')}</div>
 					<div class="flex items-center relative">
 					<div class="flex items-center relative">
 						<select
 						<select
-							class="dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							class="dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
 							aria-label="Select how to split message text for TTS requests"
 							aria-label="Select how to split message text for TTS requests"
 							bind:value={TTS_SPLIT_ON}
 							bind:value={TTS_SPLIT_ON}
 						>
 						>

+ 4 - 4
src/lib/components/admin/Settings/CodeInterpreter.svelte

@@ -58,7 +58,7 @@
 					<div class=" self-center text-xs font-medium">{$i18n.t('Code Interpreter Engine')}</div>
 					<div class=" self-center text-xs font-medium">{$i18n.t('Code Interpreter Engine')}</div>
 					<div class="flex items-center relative">
 					<div class="flex items-center relative">
 						<select
 						<select
-							class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
 							bind:value={config.CODE_INTERPRETER_ENGINE}
 							bind:value={config.CODE_INTERPRETER_ENGINE}
 							placeholder={$i18n.t('Select a engine')}
 							placeholder={$i18n.t('Select a engine')}
 							required
 							required
@@ -80,7 +80,7 @@
 						<div class="flex w-full">
 						<div class="flex w-full">
 							<div class="flex-1">
 							<div class="flex-1">
 								<input
 								<input
-									class="w-full text-sm py-0.5 placeholder:text-gray-300 dark:placeholder:text-gray-700 bg-transparent outline-none"
+									class="w-full text-sm py-0.5 placeholder:text-gray-300 dark:placeholder:text-gray-700 bg-transparent outline-hidden"
 									type="text"
 									type="text"
 									placeholder={$i18n.t('Enter Jupyter URL')}
 									placeholder={$i18n.t('Enter Jupyter URL')}
 									bind:value={config.CODE_INTERPRETER_JUPYTER_URL}
 									bind:value={config.CODE_INTERPRETER_JUPYTER_URL}
@@ -97,7 +97,7 @@
 
 
 						<div>
 						<div>
 							<select
 							<select
-								class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-left"
+								class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
 								bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
 								bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
 								placeholder={$i18n.t('Select an auth method')}
 								placeholder={$i18n.t('Select an auth method')}
 							>
 							>
@@ -132,7 +132,7 @@
 				{/if}
 				{/if}
 			</div>
 			</div>
 
 
-			<hr class=" dark:border-gray-850 my-2" />
+			<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 
 			<div>
 			<div>
 				<div class="py-0.5 w-full">
 				<div class="py-0.5 w-full">

+ 4 - 4
src/lib/components/admin/Settings/Connections.svelte

@@ -234,7 +234,7 @@
 					</div>
 					</div>
 
 
 					{#if ENABLE_OPENAI_API}
 					{#if ENABLE_OPENAI_API}
-						<hr class=" border-gray-50 dark:border-gray-850" />
+						<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 						<div class="">
 						<div class="">
 							<div class="flex justify-between items-center">
 							<div class="flex justify-between items-center">
@@ -283,7 +283,7 @@
 				</div>
 				</div>
 			</div>
 			</div>
 
 
-			<hr class=" border-gray-50 dark:border-gray-850" />
+			<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 			<div class="pr-1.5 my-2">
 			<div class="pr-1.5 my-2">
 				<div class="flex justify-between items-center text-sm mb-2">
 				<div class="flex justify-between items-center text-sm mb-2">
@@ -300,7 +300,7 @@
 				</div>
 				</div>
 
 
 				{#if ENABLE_OLLAMA_API}
 				{#if ENABLE_OLLAMA_API}
-					<hr class=" border-gray-50 dark:border-gray-850 my-2" />
+					<hr class=" border-gray-100 dark:border-gray-850 my-2" />
 
 
 					<div class="">
 					<div class="">
 						<div class="flex justify-between items-center">
 						<div class="flex justify-between items-center">
@@ -357,7 +357,7 @@
 				{/if}
 				{/if}
 			</div>
 			</div>
 
 
-			<hr class=" border-gray-50 dark:border-gray-850" />
+			<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 			<div class="pr-1.5 my-2">
 			<div class="pr-1.5 my-2">
 				<div class="flex justify-between items-center text-sm">
 				<div class="flex justify-between items-center text-sm">

+ 1 - 1
src/lib/components/admin/Settings/Connections/ManageOllamaModal.svelte

@@ -16,7 +16,7 @@
 			<div
 			<div
 				class="flex w-full justify-between items-center text-lg font-medium self-center font-primary"
 				class="flex w-full justify-between items-center text-lg font-medium self-center font-primary"
 			>
 			>
-				<div class=" flex-shrink-0">
+				<div class=" shrink-0">
 					{$i18n.t('Manage Ollama')}
 					{$i18n.t('Manage Ollama')}
 				</div>
 				</div>
 			</div>
 			</div>

+ 1 - 1
src/lib/components/admin/Settings/Connections/OllamaConnection.svelte

@@ -56,7 +56,7 @@
 		{/if}
 		{/if}
 
 
 		<input
 		<input
-			class="w-full text-sm bg-transparent outline-none"
+			class="w-full text-sm bg-transparent outline-hidden"
 			placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
 			placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
 			bind:value={url}
 			bind:value={url}
 		/>
 		/>

+ 2 - 2
src/lib/components/admin/Settings/Connections/OpenAIConnection.svelte

@@ -54,7 +54,7 @@
 		<div class="flex w-full">
 		<div class="flex w-full">
 			<div class="flex-1 relative">
 			<div class="flex-1 relative">
 				<input
 				<input
-					class=" outline-none w-full bg-transparent {pipeline ? 'pr-8' : ''}"
+					class=" outline-hidden w-full bg-transparent {pipeline ? 'pr-8' : ''}"
 					placeholder={$i18n.t('API Base URL')}
 					placeholder={$i18n.t('API Base URL')}
 					bind:value={url}
 					bind:value={url}
 					autocomplete="off"
 					autocomplete="off"
@@ -85,7 +85,7 @@
 			</div>
 			</div>
 
 
 			<SensitiveInput
 			<SensitiveInput
-				inputClassName=" outline-none bg-transparent w-full"
+				inputClassName=" outline-hidden bg-transparent w-full"
 				placeholder={$i18n.t('API Key')}
 				placeholder={$i18n.t('API Key')}
 				bind:value={key}
 				bind:value={key}
 			/>
 			/>

+ 1 - 1
src/lib/components/admin/Settings/Database.svelte

@@ -119,7 +119,7 @@
 				</div>
 				</div>
 			</button>
 			</button>
 
 
-			<hr class=" dark:border-gray-850 my-1" />
+			<hr class="border-gray-100 dark:border-gray-850 my-1" />
 
 
 			{#if $config?.features.enable_admin_export ?? true}
 			{#if $config?.features.enable_admin_export ?? true}
 				<div class="  flex w-full justify-between">
 				<div class="  flex w-full justify-between">

+ 23 - 23
src/lib/components/admin/Settings/Documents.svelte

@@ -296,7 +296,7 @@
 				<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Model Engine')}</div>
 				<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Model Engine')}</div>
 				<div class="flex items-center relative">
 				<div class="flex items-center relative">
 					<select
 					<select
-						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+						class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
 						bind:value={embeddingEngine}
 						bind:value={embeddingEngine}
 						placeholder="Select an embedding model engine"
 						placeholder="Select an embedding model engine"
 						on:change={(e) => {
 						on:change={(e) => {
@@ -319,7 +319,7 @@
 			{#if embeddingEngine === 'openai'}
 			{#if embeddingEngine === 'openai'}
 				<div class="my-0.5 flex gap-2 pr-2">
 				<div class="my-0.5 flex gap-2 pr-2">
 					<input
 					<input
-						class="flex-1 w-full rounded-lg text-sm bg-transparent outline-none"
+						class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
 						placeholder={$i18n.t('API Base URL')}
 						placeholder={$i18n.t('API Base URL')}
 						bind:value={OpenAIUrl}
 						bind:value={OpenAIUrl}
 						required
 						required
@@ -330,7 +330,7 @@
 			{:else if embeddingEngine === 'ollama'}
 			{:else if embeddingEngine === 'ollama'}
 				<div class="my-0.5 flex gap-2 pr-2">
 				<div class="my-0.5 flex gap-2 pr-2">
 					<input
 					<input
-						class="flex-1 w-full rounded-lg text-sm bg-transparent outline-none"
+						class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
 						placeholder={$i18n.t('API Base URL')}
 						placeholder={$i18n.t('API Base URL')}
 						bind:value={OllamaUrl}
 						bind:value={OllamaUrl}
 						required
 						required
@@ -375,7 +375,7 @@
 				<div class=" self-center text-xs font-medium">{$i18n.t('Hybrid Search')}</div>
 				<div class=" self-center text-xs font-medium">{$i18n.t('Hybrid Search')}</div>
 
 
 				<button
 				<button
-					class="p-1 px-3 text-xs flex rounded transition"
+					class="p-1 px-3 text-xs flex rounded-sm transition"
 					on:click={() => {
 					on:click={() => {
 						toggleHybridSearch();
 						toggleHybridSearch();
 					}}
 					}}
@@ -390,7 +390,7 @@
 			</div>
 			</div>
 		</div>
 		</div>
 
 
-		<hr class="dark:border-gray-850" />
+		<hr class="border-gray-100 dark:border-gray-850" />
 
 
 		<div class="space-y-2" />
 		<div class="space-y-2" />
 		<div>
 		<div>
@@ -400,7 +400,7 @@
 				<div class="flex w-full">
 				<div class="flex w-full">
 					<div class="flex-1 mr-2">
 					<div class="flex-1 mr-2">
 						<input
 						<input
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							bind:value={embeddingModel}
 							bind:value={embeddingModel}
 							placeholder={$i18n.t('Set embedding model')}
 							placeholder={$i18n.t('Set embedding model')}
 							required
 							required
@@ -411,7 +411,7 @@
 				<div class="flex w-full">
 				<div class="flex w-full">
 					<div class="flex-1 mr-2">
 					<div class="flex-1 mr-2">
 						<input
 						<input
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							placeholder={$i18n.t('Set embedding model (e.g. {{model}})', {
 							placeholder={$i18n.t('Set embedding model (e.g. {{model}})', {
 								model: embeddingModel.slice(-40)
 								model: embeddingModel.slice(-40)
 							})}
 							})}
@@ -490,7 +490,7 @@
 					<div class="flex w-full">
 					<div class="flex w-full">
 						<div class="flex-1 mr-2">
 						<div class="flex-1 mr-2">
 							<input
 							<input
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
 								placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
 									model: 'BAAI/bge-reranker-v2-m3'
 									model: 'BAAI/bge-reranker-v2-m3'
 								})}
 								})}
@@ -555,7 +555,7 @@
 			{/if}
 			{/if}
 		</div>
 		</div>
 
 
-		<hr class=" dark:border-gray-850" />
+		<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 		<div class="">
 		<div class="">
 			<div class="text-sm font-medium mb-1">{$i18n.t('Content Extraction')}</div>
 			<div class="text-sm font-medium mb-1">{$i18n.t('Content Extraction')}</div>
@@ -564,7 +564,7 @@
 				<div class="self-center text-xs font-medium">{$i18n.t('Engine')}</div>
 				<div class="self-center text-xs font-medium">{$i18n.t('Engine')}</div>
 				<div class="flex items-center relative">
 				<div class="flex items-center relative">
 					<select
 					<select
-						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 text-xs bg-transparent outline-none text-right"
+						class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
 						bind:value={contentExtractionEngine}
 						bind:value={contentExtractionEngine}
 						on:change={(e) => {
 						on:change={(e) => {
 							showTikaServerUrl = e.target.value === 'tika';
 							showTikaServerUrl = e.target.value === 'tika';
@@ -580,7 +580,7 @@
 				<div class="flex w-full mt-1">
 				<div class="flex w-full mt-1">
 					<div class="flex-1 mr-2">
 					<div class="flex-1 mr-2">
 						<input
 						<input
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							placeholder={$i18n.t('Enter Tika Server URL')}
 							placeholder={$i18n.t('Enter Tika Server URL')}
 							bind:value={tikaServerUrl}
 							bind:value={tikaServerUrl}
 						/>
 						/>
@@ -589,7 +589,7 @@
 			{/if}
 			{/if}
 		</div>
 		</div>
 
 
-		<hr class=" dark:border-gray-850" />
+		<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 		<div class="text-sm font-medium mb-1">{$i18n.t('Google Drive')}</div>
 		<div class="text-sm font-medium mb-1">{$i18n.t('Google Drive')}</div>
 
 
@@ -602,7 +602,7 @@
 			</div>
 			</div>
 		</div>
 		</div>
 
 
-		<hr class=" dark:border-gray-850" />
+		<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 		<div class=" ">
 		<div class=" ">
 			<div class=" text-sm font-medium mb-1">{$i18n.t('Query Params')}</div>
 			<div class=" text-sm font-medium mb-1">{$i18n.t('Query Params')}</div>
@@ -613,7 +613,7 @@
 
 
 					<div class="w-full">
 					<div class="w-full">
 						<input
 						<input
-							class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							type="number"
 							type="number"
 							placeholder={$i18n.t('Enter Top K')}
 							placeholder={$i18n.t('Enter Top K')}
 							bind:value={querySettings.k}
 							bind:value={querySettings.k}
@@ -631,7 +631,7 @@
 
 
 						<div class="w-full">
 						<div class="w-full">
 							<input
 							<input
-								class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								type="number"
 								type="number"
 								step="0.01"
 								step="0.01"
 								placeholder={$i18n.t('Enter Score')}
 								placeholder={$i18n.t('Enter Score')}
@@ -667,7 +667,7 @@
 			</div>
 			</div>
 		</div>
 		</div>
 
 
-		<hr class=" dark:border-gray-850" />
+		<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 		<div class=" ">
 		<div class=" ">
 			<div class="mb-1 text-sm font-medium">{$i18n.t('Chunk Params')}</div>
 			<div class="mb-1 text-sm font-medium">{$i18n.t('Chunk Params')}</div>
@@ -676,7 +676,7 @@
 				<div class="self-center text-xs font-medium">{$i18n.t('Text Splitter')}</div>
 				<div class="self-center text-xs font-medium">{$i18n.t('Text Splitter')}</div>
 				<div class="flex items-center relative">
 				<div class="flex items-center relative">
 					<select
 					<select
-						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 text-xs bg-transparent outline-none text-right"
+						class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
 						bind:value={textSplitter}
 						bind:value={textSplitter}
 					>
 					>
 						<option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option>
 						<option value="">{$i18n.t('Default')} ({$i18n.t('Character')})</option>
@@ -692,7 +692,7 @@
 					</div>
 					</div>
 					<div class="self-center">
 					<div class="self-center">
 						<input
 						<input
-							class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							type="number"
 							type="number"
 							placeholder={$i18n.t('Enter Chunk Size')}
 							placeholder={$i18n.t('Enter Chunk Size')}
 							bind:value={chunkSize}
 							bind:value={chunkSize}
@@ -709,7 +709,7 @@
 
 
 					<div class="self-center">
 					<div class="self-center">
 						<input
 						<input
-							class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							type="number"
 							type="number"
 							placeholder={$i18n.t('Enter Chunk Overlap')}
 							placeholder={$i18n.t('Enter Chunk Overlap')}
 							bind:value={chunkOverlap}
 							bind:value={chunkOverlap}
@@ -731,7 +731,7 @@
 			</div>
 			</div>
 		</div>
 		</div>
 
 
-		<hr class=" dark:border-gray-850" />
+		<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 		<div class="">
 		<div class="">
 			<div class="text-sm font-medium mb-1">{$i18n.t('Files')}</div>
 			<div class="text-sm font-medium mb-1">{$i18n.t('Files')}</div>
@@ -750,7 +750,7 @@
 							placement="top-start"
 							placement="top-start"
 						>
 						>
 							<input
 							<input
-								class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								type="number"
 								type="number"
 								placeholder={$i18n.t('Leave empty for unlimited')}
 								placeholder={$i18n.t('Leave empty for unlimited')}
 								bind:value={fileMaxSize}
 								bind:value={fileMaxSize}
@@ -773,7 +773,7 @@
 							placement="top-start"
 							placement="top-start"
 						>
 						>
 							<input
 							<input
-								class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class=" w-full rounded-lg py-1.5 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								type="number"
 								type="number"
 								placeholder={$i18n.t('Leave empty for unlimited')}
 								placeholder={$i18n.t('Leave empty for unlimited')}
 								bind:value={fileMaxCount}
 								bind:value={fileMaxCount}
@@ -786,7 +786,7 @@
 			</div>
 			</div>
 		</div>
 		</div>
 
 
-		<hr class=" dark:border-gray-850" />
+		<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 		<div>
 		<div>
 			<button
 			<button

+ 5 - 5
src/lib/components/admin/Settings/Evaluations/ArenaModelModal.svelte

@@ -245,7 +245,7 @@
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
-										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 										type="text"
 										type="text"
 										bind:value={name}
 										bind:value={name}
 										placeholder={$i18n.t('Model Name')}
 										placeholder={$i18n.t('Model Name')}
@@ -260,7 +260,7 @@
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
-										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 										type="text"
 										type="text"
 										bind:value={id}
 										bind:value={id}
 										placeholder={$i18n.t('Model ID')}
 										placeholder={$i18n.t('Model ID')}
@@ -277,7 +277,7 @@
 
 
 							<div class="flex-1">
 							<div class="flex-1">
 								<input
 								<input
-									class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+									class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 									type="text"
 									type="text"
 									bind:value={description}
 									bind:value={description}
 									placeholder={$i18n.t('Enter description')}
 									placeholder={$i18n.t('Enter description')}
@@ -324,7 +324,7 @@
 											<div class=" text-sm flex-1 py-1 rounded-lg">
 											<div class=" text-sm flex-1 py-1 rounded-lg">
 												{$models.find((model) => model.id === modelId)?.name}
 												{$models.find((model) => model.id === modelId)?.name}
 											</div>
 											</div>
-											<div class="flex-shrink-0">
+											<div class="shrink-0">
 												<button
 												<button
 													type="button"
 													type="button"
 													on:click={() => {
 													on:click={() => {
@@ -350,7 +350,7 @@
 							<select
 							<select
 								class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
 								class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
 									? ''
 									? ''
-									: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+									: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 								bind:value={selectedModelId}
 								bind:value={selectedModelId}
 							>
 							>
 								<option value="">{$i18n.t('Select a model')}</option>
 								<option value="">{$i18n.t('Select a model')}</option>

+ 1 - 1
src/lib/components/admin/Settings/Evaluations/Model.svelte

@@ -34,7 +34,7 @@
 
 
 				<div class="w-full flex flex-col">
 				<div class="w-full flex flex-col">
 					<div class="flex items-center gap-1">
 					<div class="flex items-center gap-1">
-						<div class="flex-shrink-0 line-clamp-1">
+						<div class="shrink-0 line-clamp-1">
 							{model.name}
 							{model.name}
 						</div>
 						</div>
 					</div>
 					</div>

+ 471 - 316
src/lib/components/admin/Settings/General.svelte

@@ -1,5 +1,5 @@
 <script lang="ts">
 <script lang="ts">
-	import { getBackendConfig, getWebhookUrl, updateWebhookUrl } from '$lib/apis';
+	import { getBackendConfig, getVersionUpdates, getWebhookUrl, updateWebhookUrl } from '$lib/apis';
 	import {
 	import {
 		getAdminConfig,
 		getAdminConfig,
 		getLdapConfig,
 		getLdapConfig,
@@ -11,7 +11,9 @@
 	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
 	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
 	import Switch from '$lib/components/common/Switch.svelte';
 	import Switch from '$lib/components/common/Switch.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
-	import { config } from '$lib/stores';
+	import { WEBUI_BUILD_HASH, WEBUI_VERSION } from '$lib/constants';
+	import { config, showChangelog } from '$lib/stores';
+	import { compareVersion } from '$lib/utils';
 	import { onMount, getContext } from 'svelte';
 	import { onMount, getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 	import { toast } from 'svelte-sonner';
 
 
@@ -19,6 +21,12 @@
 
 
 	export let saveHandler: Function;
 	export let saveHandler: Function;
 
 
+	let updateAvailable = null;
+	let version = {
+		current: '',
+		latest: ''
+	};
+
 	let adminConfig = null;
 	let adminConfig = null;
 	let webhookUrl = '';
 	let webhookUrl = '';
 
 
@@ -39,6 +47,21 @@
 		ciphers: ''
 		ciphers: ''
 	};
 	};
 
 
+	const checkForVersionUpdates = async () => {
+		updateAvailable = null;
+		version = await getVersionUpdates(localStorage.token).catch((error) => {
+			return {
+				current: WEBUI_VERSION,
+				latest: WEBUI_VERSION
+			};
+		});
+
+		console.log(version);
+
+		updateAvailable = compareVersion(version.latest, version.current);
+		console.log(updateAvailable);
+	};
+
 	const updateLdapServerHandler = async () => {
 	const updateLdapServerHandler = async () => {
 		if (!ENABLE_LDAP) return;
 		if (!ENABLE_LDAP) return;
 		const res = await updateLdapServer(localStorage.token, LDAP_SERVER).catch((error) => {
 		const res = await updateLdapServer(localStorage.token, LDAP_SERVER).catch((error) => {
@@ -63,6 +86,8 @@
 	};
 	};
 
 
 	onMount(async () => {
 	onMount(async () => {
+		checkForVersionUpdates();
+
 		await Promise.all([
 		await Promise.all([
 			(async () => {
 			(async () => {
 				adminConfig = await getAdminConfig(localStorage.token);
 				adminConfig = await getAdminConfig(localStorage.token);
@@ -87,381 +112,511 @@
 		updateHandler();
 		updateHandler();
 	}}
 	}}
 >
 >
-	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
+	<div class="mt-0.5 space-y-3 overflow-y-scroll scrollbar-hidden h-full">
 		{#if adminConfig !== null}
 		{#if adminConfig !== null}
-			<div>
-				<div class=" mb-3 text-sm font-medium">{$i18n.t('General Settings')}</div>
+			<div class="">
+				<div class="mb-3.5">
+					<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
 
 
-				<div class="  flex w-full justify-between pr-2">
-					<div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div>
+					<hr class=" border-gray-100 dark:border-gray-850 my-2" />
 
 
-					<Switch bind:state={adminConfig.ENABLE_SIGNUP} />
-				</div>
+					<div class="mb-2.5">
+						<div class=" mb-1 text-xs font-medium flex space-x-2 items-center">
+							<div>
+								{$i18n.t('Version')}
+							</div>
+						</div>
+						<div class="flex w-full justify-between items-center">
+							<div class="flex flex-col text-xs text-gray-700 dark:text-gray-200">
+								<div class="flex gap-1">
+									<Tooltip content={WEBUI_BUILD_HASH}>
+										v{WEBUI_VERSION}
+									</Tooltip>
+
+									<a
+										href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
+										target="_blank"
+									>
+										{updateAvailable === null
+											? $i18n.t('Checking for updates...')
+											: updateAvailable
+												? `(v${version.latest} ${$i18n.t('available!')})`
+												: $i18n.t('(latest)')}
+									</a>
+								</div>
 
 
-				<div class="  my-3 flex w-full justify-between">
-					<div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div>
-					<div class="flex items-center relative">
-						<select
-							class="dark:bg-gray-900 w-fit pr-8 rounded px-2 text-xs bg-transparent outline-none text-right"
-							bind:value={adminConfig.DEFAULT_USER_ROLE}
-							placeholder="Select a role"
-						>
-							<option value="pending">{$i18n.t('pending')}</option>
-							<option value="user">{$i18n.t('user')}</option>
-							<option value="admin">{$i18n.t('admin')}</option>
-						</select>
-					</div>
-				</div>
+								<button
+									class=" underline flex items-center space-x-1 text-xs text-gray-500 dark:text-gray-500"
+									type="button"
+									on:click={() => {
+										showChangelog.set(true);
+									}}
+								>
+									<div>{$i18n.t("See what's new")}</div>
+								</button>
+							</div>
 
 
-				<div class=" flex w-full justify-between pr-2 my-3">
-					<div class=" self-center text-xs font-medium">{$i18n.t('Enable API Key')}</div>
+							<button
+								class=" text-xs px-3 py-1.5 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-medium"
+								type="button"
+								on:click={() => {
+									checkForVersionUpdates();
+								}}
+							>
+								{$i18n.t('Check for updates')}
+							</button>
+						</div>
+					</div>
 
 
-					<Switch bind:state={adminConfig.ENABLE_API_KEY} />
-				</div>
+					<div class="mb-2.5">
+						<div class="flex w-full justify-between items-center">
+							<div class="text-xs pr-2">
+								<div class="">
+									{$i18n.t('Help')}
+								</div>
+								<div class=" text-xs text-gray-500">
+									{$i18n.t('Discover how to use Open WebUI and seek support from the community.')}
+								</div>
+							</div>
 
 
-				{#if adminConfig?.ENABLE_API_KEY}
-					<div class=" flex w-full justify-between pr-2 my-3">
-						<div class=" self-center text-xs font-medium">
-							{$i18n.t('API Key Endpoint Restrictions')}
+							<a
+								class="flex-shrink-0 text-xs font-medium underline"
+								href="https://docs.openwebui.com/"
+								target="_blank"
+							>
+								{$i18n.t('Documentation')}
+							</a>
 						</div>
 						</div>
 
 
-						<Switch bind:state={adminConfig.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS} />
-					</div>
+						<div class="mt-1">
+							<div class="flex space-x-1">
+								<a href="https://discord.gg/5rJgQTnV4s" target="_blank">
+									<img
+										alt="Discord"
+										src="https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white"
+									/>
+								</a>
 
 
-					{#if adminConfig?.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS}
-						<div class=" flex w-full flex-col pr-2">
-							<div class=" text-xs font-medium">
-								{$i18n.t('Allowed Endpoints')}
-							</div>
+								<a href="https://twitter.com/OpenWebUI" target="_blank">
+									<img
+										alt="X (formerly Twitter) Follow"
+										src="https://img.shields.io/twitter/follow/OpenWebUI"
+									/>
+								</a>
 
 
-							<input
-								class="w-full mt-1 rounded-lg text-sm dark:text-gray-300 bg-transparent outline-none"
-								type="text"
-								placeholder={`e.g.) /api/v1/messages, /api/v1/channels`}
-								bind:value={adminConfig.API_KEY_ALLOWED_ENDPOINTS}
-							/>
+								<a href="https://github.com/open-webui/open-webui" target="_blank">
+									<img
+										alt="Github Repo"
+										src="https://img.shields.io/github/stars/open-webui/open-webui?style=social&label=Star us on Github"
+									/>
+								</a>
+							</div>
+						</div>
+					</div>
 
 
-							<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
-								<!-- https://docs.openwebui.com/getting-started/advanced-topics/api-endpoints -->
+					<div class="mb-2.5">
+						<div class="flex w-full justify-between items-center">
+							<div class="text-xs pr-2">
+								<div class="">
+									{$i18n.t('License')}
+								</div>
 								<a
 								<a
-									href="https://docs.openwebui.com/getting-started/api-endpoints"
+									class=" text-xs text-gray-500 hover:underline"
+									href="https://docs.openwebui.com/enterprise"
 									target="_blank"
 									target="_blank"
-									class=" text-gray-300 font-medium underline"
 								>
 								>
-									{$i18n.t('To learn more about available endpoints, visit our documentation.')}
+									{$i18n.t(
+										'Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.'
+									)}
 								</a>
 								</a>
 							</div>
 							</div>
-						</div>
-					{/if}
-				{/if}
-
-				<hr class=" border-gray-50 dark:border-gray-850 my-2" />
-
-				<div class="my-3 flex w-full items-center justify-between pr-2">
-					<div class=" self-center text-xs font-medium">
-						{$i18n.t('Show Admin Details in Account Pending Overlay')}
-					</div>
-
-					<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} />
-				</div>
-
-				<div class="my-3 flex w-full items-center justify-between pr-2">
-					<div class=" self-center text-xs font-medium">{$i18n.t('Enable Community Sharing')}</div>
-
-					<Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} />
-				</div>
-
-				<div class="my-3 flex w-full items-center justify-between pr-2">
-					<div class=" self-center text-xs font-medium">{$i18n.t('Enable Message Rating')}</div>
-
-					<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
-				</div>
-
-				<hr class=" border-gray-50 dark:border-gray-850 my-2" />
-
-				<div class=" w-full justify-between">
-					<div class="flex w-full justify-between">
-						<div class=" self-center text-xs font-medium">{$i18n.t('WebUI URL')}</div>
-					</div>
 
 
-					<div class="flex mt-2 space-x-2">
-						<input
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
-							type="text"
-							placeholder={`e.g.) "http://localhost:3000"`}
-							bind:value={adminConfig.WEBUI_URL}
-						/>
-					</div>
-
-					<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
-						{$i18n.t(
-							'Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.'
-						)}
+							<button
+								class="flex-shrink-0 text-xs px-3 py-1.5 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-medium"
+							>
+								{$i18n.t('Activate')}
+							</button>
+						</div>
 					</div>
 					</div>
 				</div>
 				</div>
 
 
-				<hr class=" border-gray-50 dark:border-gray-850 my-2" />
-
-				<div class=" w-full justify-between">
-					<div class="flex w-full justify-between">
-						<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
-					</div>
+				<div class="mb-3">
+					<div class=" mb-2.5 text-base font-medium">{$i18n.t('Authentication')}</div>
 
 
-					<div class="flex mt-2 space-x-2">
-						<input
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
-							type="text"
-							placeholder={`e.g.) "30m","1h", "10d". `}
-							bind:value={adminConfig.JWT_EXPIRES_IN}
-						/>
-					</div>
+					<hr class=" border-gray-100 dark:border-gray-850 my-2" />
 
 
-					<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
-						{$i18n.t('Valid time units:')}
-						<span class=" text-gray-300 font-medium"
-							>{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span
-						>
+					<div class="  mb-2.5 flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">{$i18n.t('Default User Role')}</div>
+						<div class="flex items-center relative">
+							<select
+								class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
+								bind:value={adminConfig.DEFAULT_USER_ROLE}
+								placeholder="Select a role"
+							>
+								<option value="pending">{$i18n.t('pending')}</option>
+								<option value="user">{$i18n.t('user')}</option>
+								<option value="admin">{$i18n.t('admin')}</option>
+							</select>
+						</div>
 					</div>
 					</div>
-				</div>
-
-				<hr class=" border-gray-50 dark:border-gray-850 my-2" />
 
 
-				<div class=" w-full justify-between">
-					<div class="flex w-full justify-between">
-						<div class=" self-center text-xs font-medium">{$i18n.t('Webhook URL')}</div>
-					</div>
+					<div class=" mb-2.5 flex w-full justify-between pr-2">
+						<div class=" self-center text-xs font-medium">{$i18n.t('Enable New Sign Ups')}</div>
 
 
-					<div class="flex mt-2 space-x-2">
-						<input
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
-							type="text"
-							placeholder={`https://example.com/webhook`}
-							bind:value={webhookUrl}
-						/>
+						<Switch bind:state={adminConfig.ENABLE_SIGNUP} />
 					</div>
 					</div>
-				</div>
 
 
-				<hr class=" border-gray-50 dark:border-gray-850 my-2" />
+					<div class="mb-2.5 flex w-full items-center justify-between pr-2">
+						<div class=" self-center text-xs font-medium">
+							{$i18n.t('Show Admin Details in Account Pending Overlay')}
+						</div>
 
 
-				<div class="pt-1 flex w-full justify-between pr-2">
-					<div class=" self-center text-sm font-medium">
-						{$i18n.t('Channels')} ({$i18n.t('Beta')})
+						<Switch bind:state={adminConfig.SHOW_ADMIN_DETAILS} />
 					</div>
 					</div>
 
 
-					<Switch bind:state={adminConfig.ENABLE_CHANNELS} />
-				</div>
-			</div>
-		{/if}
-
-		<hr class=" border-gray-50 dark:border-gray-850" />
-
-		<div class=" space-y-3">
-			<div class="mt-2 space-y-2 pr-1.5">
-				<div class="flex justify-between items-center text-sm">
-					<div class="  font-medium">{$i18n.t('LDAP')}</div>
+					<div class="mb-2.5 flex w-full justify-between pr-2">
+						<div class=" self-center text-xs font-medium">{$i18n.t('Enable API Key')}</div>
 
 
-					<div class="mt-1">
-						<Switch
-							bind:state={ENABLE_LDAP}
-							on:change={async () => {
-								updateLdapConfig(localStorage.token, ENABLE_LDAP);
-							}}
-						/>
+						<Switch bind:state={adminConfig.ENABLE_API_KEY} />
 					</div>
 					</div>
-				</div>
 
 
-				{#if ENABLE_LDAP}
-					<div class="flex flex-col gap-1">
-						<div class="flex w-full gap-2">
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Label')}
-								</div>
-								<input
-									class="w-full bg-transparent outline-none py-0.5"
-									required
-									placeholder={$i18n.t('Enter server label')}
-									bind:value={LDAP_SERVER.label}
-								/>
+					{#if adminConfig?.ENABLE_API_KEY}
+						<div class="mb-2.5 flex w-full justify-between pr-2">
+							<div class=" self-center text-xs font-medium">
+								{$i18n.t('API Key Endpoint Restrictions')}
 							</div>
 							</div>
-							<div class="w-full"></div>
+
+							<Switch bind:state={adminConfig.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS} />
 						</div>
 						</div>
-						<div class="flex w-full gap-2">
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Host')}
+
+						{#if adminConfig?.ENABLE_API_KEY_ENDPOINT_RESTRICTIONS}
+							<div class=" flex w-full flex-col pr-2">
+								<div class=" text-xs font-medium">
+									{$i18n.t('Allowed Endpoints')}
 								</div>
 								</div>
+
 								<input
 								<input
-									class="w-full bg-transparent outline-none py-0.5"
-									required
-									placeholder={$i18n.t('Enter server host')}
-									bind:value={LDAP_SERVER.host}
+									class="w-full mt-1 rounded-lg text-sm dark:text-gray-300 bg-transparent outline-hidden"
+									type="text"
+									placeholder={`e.g.) /api/v1/messages, /api/v1/channels`}
+									bind:value={adminConfig.API_KEY_ALLOWED_ENDPOINTS}
 								/>
 								/>
-							</div>
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Port')}
-								</div>
-								<Tooltip
-									placement="top-start"
-									content={$i18n.t('Default to 389 or 636 if TLS is enabled')}
-									className="w-full"
-								>
-									<input
-										class="w-full bg-transparent outline-none py-0.5"
-										type="number"
-										placeholder={$i18n.t('Enter server port')}
-										bind:value={LDAP_SERVER.port}
-									/>
-								</Tooltip>
-							</div>
-						</div>
-						<div class="flex w-full gap-2">
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Application DN')}
-								</div>
-								<Tooltip
-									content={$i18n.t('The Application Account DN you bind with for search')}
-									placement="top-start"
-								>
-									<input
-										class="w-full bg-transparent outline-none py-0.5"
-										required
-										placeholder={$i18n.t('Enter Application DN')}
-										bind:value={LDAP_SERVER.app_dn}
-									/>
-								</Tooltip>
-							</div>
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Application DN Password')}
-								</div>
-								<SensitiveInput
-									placeholder={$i18n.t('Enter Application DN Password')}
-									bind:value={LDAP_SERVER.app_dn_password}
-								/>
-							</div>
-						</div>
-						<div class="flex w-full gap-2">
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Attribute for Mail')}
-								</div>
-								<Tooltip
-									content={$i18n.t(
-										'The LDAP attribute that maps to the mail that users use to sign in.'
-									)}
-									placement="top-start"
-								>
-									<input
-										class="w-full bg-transparent outline-none py-0.5"
-										required
-										placeholder={$i18n.t('Example: mail')}
-										bind:value={LDAP_SERVER.attribute_for_mail}
-									/>
-								</Tooltip>
-							</div>
-						</div>
-						<div class="flex w-full gap-2">
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Attribute for Username')}
-								</div>
-								<Tooltip
-									content={$i18n.t(
-										'The LDAP attribute that maps to the username that users use to sign in.'
-									)}
-									placement="top-start"
-								>
-									<input
-										class="w-full bg-transparent outline-none py-0.5"
-										required
-										placeholder={$i18n.t('Example: sAMAccountName or uid or userPrincipalName')}
-										bind:value={LDAP_SERVER.attribute_for_username}
-									/>
-								</Tooltip>
-							</div>
-						</div>
-						<div class="flex w-full gap-2">
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Search Base')}
+
+								<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+									<!-- https://docs.openwebui.com/getting-started/advanced-topics/api-endpoints -->
+									<a
+										href="https://docs.openwebui.com/getting-started/api-endpoints"
+										target="_blank"
+										class=" text-gray-300 font-medium underline"
+									>
+										{$i18n.t('To learn more about available endpoints, visit our documentation.')}
+									</a>
 								</div>
 								</div>
-								<Tooltip content={$i18n.t('The base to search for users')} placement="top-start">
-									<input
-										class="w-full bg-transparent outline-none py-0.5"
-										required
-										placeholder={$i18n.t('Example: ou=users,dc=foo,dc=example')}
-										bind:value={LDAP_SERVER.search_base}
-									/>
-								</Tooltip>
 							</div>
 							</div>
+						{/if}
+					{/if}
+
+					<div class=" mb-2.5 w-full justify-between">
+						<div class="flex w-full justify-between">
+							<div class=" self-center text-xs font-medium">{$i18n.t('JWT Expiration')}</div>
 						</div>
 						</div>
-						<div class="flex w-full gap-2">
-							<div class="w-full">
-								<div class=" self-center text-xs font-medium min-w-fit mb-1">
-									{$i18n.t('Search Filters')}
-								</div>
-								<input
-									class="w-full bg-transparent outline-none py-0.5"
-									placeholder={$i18n.t('Example: (&(objectClass=inetOrgPerson)(uid=%s))')}
-									bind:value={LDAP_SERVER.search_filters}
-								/>
-							</div>
+
+						<div class="flex mt-2 space-x-2">
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+								type="text"
+								placeholder={`e.g.) "30m","1h", "10d". `}
+								bind:value={adminConfig.JWT_EXPIRES_IN}
+							/>
 						</div>
 						</div>
-						<div class="text-xs text-gray-400 dark:text-gray-500">
-							<a
-								class=" text-gray-300 font-medium underline"
-								href="https://ldap.com/ldap-filters/"
-								target="_blank"
+
+						<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+							{$i18n.t('Valid time units:')}
+							<span class=" text-gray-300 font-medium"
+								>{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span
 							>
 							>
-								{$i18n.t('Click here for filter guides.')}
-							</a>
 						</div>
 						</div>
-						<div>
+					</div>
+
+					<div class=" space-y-3">
+						<div class="mt-2 space-y-2 pr-1.5">
 							<div class="flex justify-between items-center text-sm">
 							<div class="flex justify-between items-center text-sm">
-								<div class="  font-medium">{$i18n.t('TLS')}</div>
+								<div class="  font-medium">{$i18n.t('LDAP')}</div>
 
 
 								<div class="mt-1">
 								<div class="mt-1">
-									<Switch bind:state={LDAP_SERVER.use_tls} />
+									<Switch
+										bind:state={ENABLE_LDAP}
+										on:change={async () => {
+											updateLdapConfig(localStorage.token, ENABLE_LDAP);
+										}}
+									/>
 								</div>
 								</div>
 							</div>
 							</div>
-							{#if LDAP_SERVER.use_tls}
-								<div class="flex w-full gap-2">
-									<div class="w-full">
-										<div class=" self-center text-xs font-medium min-w-fit mb-1 mt-1">
-											{$i18n.t('Certificate Path')}
+
+							{#if ENABLE_LDAP}
+								<div class="flex flex-col gap-1">
+									<div class="flex w-full gap-2">
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Label')}
+											</div>
+											<input
+												class="w-full bg-transparent outline-hidden py-0.5"
+												required
+												placeholder={$i18n.t('Enter server label')}
+												bind:value={LDAP_SERVER.label}
+											/>
 										</div>
 										</div>
-										<input
-											class="w-full bg-transparent outline-none py-0.5"
-											required
-											placeholder={$i18n.t('Enter certificate path')}
-											bind:value={LDAP_SERVER.certificate_path}
-										/>
+										<div class="w-full"></div>
 									</div>
 									</div>
-								</div>
-								<div class="flex w-full gap-2">
-									<div class="w-full">
-										<div class=" self-center text-xs font-medium min-w-fit mb-1">
-											{$i18n.t('Ciphers')}
+									<div class="flex w-full gap-2">
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Host')}
+											</div>
+											<input
+												class="w-full bg-transparent outline-hidden py-0.5"
+												required
+												placeholder={$i18n.t('Enter server host')}
+												bind:value={LDAP_SERVER.host}
+											/>
 										</div>
 										</div>
-										<Tooltip content={$i18n.t('Default to ALL')} placement="top-start">
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Port')}
+											</div>
+											<Tooltip
+												placement="top-start"
+												content={$i18n.t('Default to 389 or 636 if TLS is enabled')}
+												className="w-full"
+											>
+												<input
+													class="w-full bg-transparent outline-hidden py-0.5"
+													type="number"
+													placeholder={$i18n.t('Enter server port')}
+													bind:value={LDAP_SERVER.port}
+												/>
+											</Tooltip>
+										</div>
+									</div>
+									<div class="flex w-full gap-2">
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Application DN')}
+											</div>
+											<Tooltip
+												content={$i18n.t('The Application Account DN you bind with for search')}
+												placement="top-start"
+											>
+												<input
+													class="w-full bg-transparent outline-hidden py-0.5"
+													required
+													placeholder={$i18n.t('Enter Application DN')}
+													bind:value={LDAP_SERVER.app_dn}
+												/>
+											</Tooltip>
+										</div>
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Application DN Password')}
+											</div>
+											<SensitiveInput
+												placeholder={$i18n.t('Enter Application DN Password')}
+												bind:value={LDAP_SERVER.app_dn_password}
+											/>
+										</div>
+									</div>
+									<div class="flex w-full gap-2">
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Attribute for Mail')}
+											</div>
+											<Tooltip
+												content={$i18n.t(
+													'The LDAP attribute that maps to the mail that users use to sign in.'
+												)}
+												placement="top-start"
+											>
+												<input
+													class="w-full bg-transparent outline-hidden py-0.5"
+													required
+													placeholder={$i18n.t('Example: mail')}
+													bind:value={LDAP_SERVER.attribute_for_mail}
+												/>
+											</Tooltip>
+										</div>
+									</div>
+									<div class="flex w-full gap-2">
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Attribute for Username')}
+											</div>
+											<Tooltip
+												content={$i18n.t(
+													'The LDAP attribute that maps to the username that users use to sign in.'
+												)}
+												placement="top-start"
+											>
+												<input
+													class="w-full bg-transparent outline-hidden py-0.5"
+													required
+													placeholder={$i18n.t(
+														'Example: sAMAccountName or uid or userPrincipalName'
+													)}
+													bind:value={LDAP_SERVER.attribute_for_username}
+												/>
+											</Tooltip>
+										</div>
+									</div>
+									<div class="flex w-full gap-2">
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Search Base')}
+											</div>
+											<Tooltip
+												content={$i18n.t('The base to search for users')}
+												placement="top-start"
+											>
+												<input
+													class="w-full bg-transparent outline-hidden py-0.5"
+													required
+													placeholder={$i18n.t('Example: ou=users,dc=foo,dc=example')}
+													bind:value={LDAP_SERVER.search_base}
+												/>
+											</Tooltip>
+										</div>
+									</div>
+									<div class="flex w-full gap-2">
+										<div class="w-full">
+											<div class=" self-center text-xs font-medium min-w-fit mb-1">
+												{$i18n.t('Search Filters')}
+											</div>
 											<input
 											<input
-												class="w-full bg-transparent outline-none py-0.5"
-												placeholder={$i18n.t('Example: ALL')}
-												bind:value={LDAP_SERVER.ciphers}
+												class="w-full bg-transparent outline-hidden py-0.5"
+												placeholder={$i18n.t('Example: (&(objectClass=inetOrgPerson)(uid=%s))')}
+												bind:value={LDAP_SERVER.search_filters}
 											/>
 											/>
-										</Tooltip>
+										</div>
+									</div>
+									<div class="text-xs text-gray-400 dark:text-gray-500">
+										<a
+											class=" text-gray-300 font-medium underline"
+											href="https://ldap.com/ldap-filters/"
+											target="_blank"
+										>
+											{$i18n.t('Click here for filter guides.')}
+										</a>
+									</div>
+									<div>
+										<div class="flex justify-between items-center text-sm">
+											<div class="  font-medium">{$i18n.t('TLS')}</div>
+
+											<div class="mt-1">
+												<Switch bind:state={LDAP_SERVER.use_tls} />
+											</div>
+										</div>
+										{#if LDAP_SERVER.use_tls}
+											<div class="flex w-full gap-2">
+												<div class="w-full">
+													<div class=" self-center text-xs font-medium min-w-fit mb-1 mt-1">
+														{$i18n.t('Certificate Path')}
+													</div>
+													<input
+														class="w-full bg-transparent outline-hidden py-0.5"
+														required
+														placeholder={$i18n.t('Enter certificate path')}
+														bind:value={LDAP_SERVER.certificate_path}
+													/>
+												</div>
+											</div>
+											<div class="flex w-full gap-2">
+												<div class="w-full">
+													<div class=" self-center text-xs font-medium min-w-fit mb-1">
+														{$i18n.t('Ciphers')}
+													</div>
+													<Tooltip content={$i18n.t('Default to ALL')} placement="top-start">
+														<input
+															class="w-full bg-transparent outline-hidden py-0.5"
+															placeholder={$i18n.t('Example: ALL')}
+															bind:value={LDAP_SERVER.ciphers}
+														/>
+													</Tooltip>
+												</div>
+												<div class="w-full"></div>
+											</div>
+										{/if}
 									</div>
 									</div>
-									<div class="w-full"></div>
 								</div>
 								</div>
 							{/if}
 							{/if}
 						</div>
 						</div>
 					</div>
 					</div>
-				{/if}
+				</div>
+
+				<div class="mb-3">
+					<div class=" mb-2.5 text-base font-medium">{$i18n.t('Features')}</div>
+
+					<hr class=" border-gray-100 dark:border-gray-850 my-2" />
+
+					<div class="mb-2.5 flex w-full items-center justify-between pr-2">
+						<div class=" self-center text-xs font-medium">
+							{$i18n.t('Enable Community Sharing')}
+						</div>
+
+						<Switch bind:state={adminConfig.ENABLE_COMMUNITY_SHARING} />
+					</div>
+
+					<div class="mb-2.5 flex w-full items-center justify-between pr-2">
+						<div class=" self-center text-xs font-medium">{$i18n.t('Enable Message Rating')}</div>
+
+						<Switch bind:state={adminConfig.ENABLE_MESSAGE_RATING} />
+					</div>
+
+					<div class="mb-2.5 flex w-full items-center justify-between pr-2">
+						<div class=" self-center text-xs font-medium">
+							{$i18n.t('Channels')} ({$i18n.t('Beta')})
+						</div>
+
+						<Switch bind:state={adminConfig.ENABLE_CHANNELS} />
+					</div>
+
+					<div class="mb-2.5 w-full justify-between">
+						<div class="flex w-full justify-between">
+							<div class=" self-center text-xs font-medium">{$i18n.t('WebUI URL')}</div>
+						</div>
+
+						<div class="flex mt-2 space-x-2">
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+								type="text"
+								placeholder={`e.g.) "http://localhost:3000"`}
+								bind:value={adminConfig.WEBUI_URL}
+							/>
+						</div>
+
+						<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
+							{$i18n.t(
+								'Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.'
+							)}
+						</div>
+					</div>
+
+					<div class=" w-full justify-between">
+						<div class="flex w-full justify-between">
+							<div class=" self-center text-xs font-medium">{$i18n.t('Webhook URL')}</div>
+						</div>
+
+						<div class="flex mt-2 space-x-2">
+							<input
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+								type="text"
+								placeholder={`https://example.com/webhook`}
+								bind:value={webhookUrl}
+							/>
+						</div>
+					</div>
+				</div>
 			</div>
 			</div>
-		</div>
+		{/if}
 	</div>
 	</div>
 
 
 	<div class="flex justify-end pt-3 text-sm font-medium">
 	<div class="flex justify-end pt-3 text-sm font-medium">

+ 17 - 17
src/lib/components/admin/Settings/Images.svelte

@@ -284,7 +284,7 @@
 					<div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
 					<div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
 					<div class="flex items-center relative">
 					<div class="flex items-center relative">
 						<select
 						<select
-							class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
 							bind:value={config.engine}
 							bind:value={config.engine}
 							placeholder={$i18n.t('Select Engine')}
 							placeholder={$i18n.t('Select Engine')}
 							on:change={async () => {
 							on:change={async () => {
@@ -298,7 +298,7 @@
 					</div>
 					</div>
 				</div>
 				</div>
 			</div>
 			</div>
-			<hr class=" dark:border-gray-850" />
+			<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 			<div class="flex flex-col gap-2">
 			<div class="flex flex-col gap-2">
 				{#if (config?.engine ?? 'automatic1111') === 'automatic1111'}
 				{#if (config?.engine ?? 'automatic1111') === 'automatic1111'}
@@ -307,7 +307,7 @@
 						<div class="flex w-full">
 						<div class="flex w-full">
 							<div class="flex-1 mr-2">
 							<div class="flex-1 mr-2">
 								<input
 								<input
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
 									placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
 									bind:value={config.automatic1111.AUTOMATIC1111_BASE_URL}
 									bind:value={config.automatic1111.AUTOMATIC1111_BASE_URL}
 								/>
 								/>
@@ -386,7 +386,7 @@
 								<Tooltip content={$i18n.t('Enter Sampler (e.g. Euler a)')} placement="top-start">
 								<Tooltip content={$i18n.t('Enter Sampler (e.g. Euler a)')} placement="top-start">
 									<input
 									<input
 										list="sampler-list"
 										list="sampler-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										placeholder={$i18n.t('Enter Sampler (e.g. Euler a)')}
 										placeholder={$i18n.t('Enter Sampler (e.g. Euler a)')}
 										bind:value={config.automatic1111.AUTOMATIC1111_SAMPLER}
 										bind:value={config.automatic1111.AUTOMATIC1111_SAMPLER}
 									/>
 									/>
@@ -408,7 +408,7 @@
 								<Tooltip content={$i18n.t('Enter Scheduler (e.g. Karras)')} placement="top-start">
 								<Tooltip content={$i18n.t('Enter Scheduler (e.g. Karras)')} placement="top-start">
 									<input
 									<input
 										list="scheduler-list"
 										list="scheduler-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										placeholder={$i18n.t('Enter Scheduler (e.g. Karras)')}
 										placeholder={$i18n.t('Enter Scheduler (e.g. Karras)')}
 										bind:value={config.automatic1111.AUTOMATIC1111_SCHEDULER}
 										bind:value={config.automatic1111.AUTOMATIC1111_SCHEDULER}
 									/>
 									/>
@@ -429,7 +429,7 @@
 							<div class="flex-1 mr-2">
 							<div class="flex-1 mr-2">
 								<Tooltip content={$i18n.t('Enter CFG Scale (e.g. 7.0)')} placement="top-start">
 								<Tooltip content={$i18n.t('Enter CFG Scale (e.g. 7.0)')} placement="top-start">
 									<input
 									<input
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										placeholder={$i18n.t('Enter CFG Scale (e.g. 7.0)')}
 										placeholder={$i18n.t('Enter CFG Scale (e.g. 7.0)')}
 										bind:value={config.automatic1111.AUTOMATIC1111_CFG_SCALE}
 										bind:value={config.automatic1111.AUTOMATIC1111_CFG_SCALE}
 									/>
 									/>
@@ -443,7 +443,7 @@
 						<div class="flex w-full">
 						<div class="flex w-full">
 							<div class="flex-1 mr-2">
 							<div class="flex-1 mr-2">
 								<input
 								<input
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
 									placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
 									bind:value={config.comfyui.COMFYUI_BASE_URL}
 									bind:value={config.comfyui.COMFYUI_BASE_URL}
 								/>
 								/>
@@ -497,7 +497,7 @@
 
 
 						{#if config.comfyui.COMFYUI_WORKFLOW}
 						{#if config.comfyui.COMFYUI_WORKFLOW}
 							<textarea
 							<textarea
-								class="w-full rounded-lg mb-1 py-2 px-4 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none disabled:text-gray-600 resize-none"
+								class="w-full rounded-lg mb-1 py-2 px-4 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden disabled:text-gray-600 resize-none"
 								rows="10"
 								rows="10"
 								bind:value={config.comfyui.COMFYUI_WORKFLOW}
 								bind:value={config.comfyui.COMFYUI_WORKFLOW}
 								required
 								required
@@ -525,7 +525,7 @@
 								/>
 								/>
 
 
 								<button
 								<button
-									class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
+									class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-850 dark:hover:bg-gray-850 text-center rounded-xl"
 									type="button"
 									type="button"
 									on:click={() => {
 									on:click={() => {
 										document.getElementById('upload-comfyui-workflow-input')?.click();
 										document.getElementById('upload-comfyui-workflow-input')?.click();
@@ -548,7 +548,7 @@
 							<div class="text-xs flex flex-col gap-1.5">
 							<div class="text-xs flex flex-col gap-1.5">
 								{#each requiredWorkflowNodes as node}
 								{#each requiredWorkflowNodes as node}
 									<div class="flex w-full items-center border dark:border-gray-850 rounded-lg">
 									<div class="flex w-full items-center border dark:border-gray-850 rounded-lg">
-										<div class="flex-shrink-0">
+										<div class="shrink-0">
 											<div
 											<div
 												class=" capitalize line-clamp-1 font-medium px-3 py-1 w-20 text-center rounded-l-lg bg-green-500/10 text-green-700 dark:text-green-200"
 												class=" capitalize line-clamp-1 font-medium px-3 py-1 w-20 text-center rounded-l-lg bg-green-500/10 text-green-700 dark:text-green-200"
 											>
 											>
@@ -558,7 +558,7 @@
 										<div class="">
 										<div class="">
 											<Tooltip content="Input Key (e.g. text, unet_name, steps)">
 											<Tooltip content="Input Key (e.g. text, unet_name, steps)">
 												<input
 												<input
-													class="py-1 px-3 w-24 text-xs text-center bg-transparent outline-none border-r dark:border-gray-850"
+													class="py-1 px-3 w-24 text-xs text-center bg-transparent outline-hidden border-r dark:border-gray-850"
 													placeholder="Key"
 													placeholder="Key"
 													bind:value={node.key}
 													bind:value={node.key}
 													required
 													required
@@ -572,7 +572,7 @@
 												placement="top-start"
 												placement="top-start"
 											>
 											>
 												<input
 												<input
-													class="w-full py-1 px-4 rounded-r-lg text-xs bg-transparent outline-none"
+													class="w-full py-1 px-4 rounded-r-lg text-xs bg-transparent outline-hidden"
 													placeholder="Node Ids"
 													placeholder="Node Ids"
 													bind:value={node.node_ids}
 													bind:value={node.node_ids}
 												/>
 												/>
@@ -593,7 +593,7 @@
 
 
 						<div class="flex gap-2 mb-1">
 						<div class="flex gap-2 mb-1">
 							<input
 							<input
-								class="flex-1 w-full text-sm bg-transparent outline-none"
+								class="flex-1 w-full text-sm bg-transparent outline-hidden"
 								placeholder={$i18n.t('API Base URL')}
 								placeholder={$i18n.t('API Base URL')}
 								bind:value={config.openai.OPENAI_API_BASE_URL}
 								bind:value={config.openai.OPENAI_API_BASE_URL}
 								required
 								required
@@ -609,7 +609,7 @@
 			</div>
 			</div>
 
 
 			{#if config?.enabled}
 			{#if config?.enabled}
-				<hr class=" dark:border-gray-850" />
+				<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 				<div>
 				<div>
 					<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
 					<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
@@ -620,7 +620,7 @@
 									<Tooltip content={$i18n.t('Enter Model ID')} placement="top-start">
 									<Tooltip content={$i18n.t('Enter Model ID')} placement="top-start">
 										<input
 										<input
 											list="model-list"
 											list="model-list"
-											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 											bind:value={imageGenerationConfig.MODEL}
 											bind:value={imageGenerationConfig.MODEL}
 											placeholder="Select a model"
 											placeholder="Select a model"
 											required
 											required
@@ -644,7 +644,7 @@
 						<div class="flex-1 mr-2">
 						<div class="flex-1 mr-2">
 							<Tooltip content={$i18n.t('Enter Image Size (e.g. 512x512)')} placement="top-start">
 							<Tooltip content={$i18n.t('Enter Image Size (e.g. 512x512)')} placement="top-start">
 								<input
 								<input
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
 									placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
 									bind:value={imageGenerationConfig.IMAGE_SIZE}
 									bind:value={imageGenerationConfig.IMAGE_SIZE}
 									required
 									required
@@ -660,7 +660,7 @@
 						<div class="flex-1 mr-2">
 						<div class="flex-1 mr-2">
 							<Tooltip content={$i18n.t('Enter Number of Steps (e.g. 50)')} placement="top-start">
 							<Tooltip content={$i18n.t('Enter Number of Steps (e.g. 50)')} placement="top-start">
 								<input
 								<input
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 									placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
 									placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
 									bind:value={imageGenerationConfig.IMAGE_STEPS}
 									bind:value={imageGenerationConfig.IMAGE_STEPS}
 									required
 									required

+ 217 - 209
src/lib/components/admin/Settings/Interface.svelte

@@ -69,9 +69,13 @@
 		}}
 		}}
 	>
 	>
 		<div class="  overflow-y-scroll scrollbar-hidden h-full pr-1.5">
 		<div class="  overflow-y-scroll scrollbar-hidden h-full pr-1.5">
-			<div>
-				<div class=" mb-2.5 text-sm font-medium flex items-center">
-					<div class=" mr-1">{$i18n.t('Set Task Model')}</div>
+			<div class="mb-3.5">
+				<div class=" mb-2.5 text-base font-medium">{$i18n.t('Tasks')}</div>
+
+				<hr class=" border-gray-100 dark:border-gray-850 my-2" />
+
+				<div class=" mb-1 font-medium flex items-center">
+					<div class=" text-xs mr-1">{$i18n.t('Set Task Model')}</div>
 					<Tooltip
 					<Tooltip
 						content={$i18n.t(
 						content={$i18n.t(
 							'A task model is used when performing tasks such as generating titles for chats and web search queries'
 							'A task model is used when performing tasks such as generating titles for chats and web search queries'
@@ -93,11 +97,12 @@
 						</svg>
 						</svg>
 					</Tooltip>
 					</Tooltip>
 				</div>
 				</div>
-				<div class="flex w-full gap-2">
+
+				<div class=" mb-2.5 flex w-full gap-2">
 					<div class="flex-1">
 					<div class="flex-1">
 						<div class=" text-xs mb-1">{$i18n.t('Local Models')}</div>
 						<div class=" text-xs mb-1">{$i18n.t('Local Models')}</div>
 						<select
 						<select
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							bind:value={taskConfig.TASK_MODEL}
 							bind:value={taskConfig.TASK_MODEL}
 							placeholder={$i18n.t('Select a model')}
 							placeholder={$i18n.t('Select a model')}
 						>
 						>
@@ -113,7 +118,7 @@
 					<div class="flex-1">
 					<div class="flex-1">
 						<div class=" text-xs mb-1">{$i18n.t('External Models')}</div>
 						<div class=" text-xs mb-1">{$i18n.t('External Models')}</div>
 						<select
 						<select
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							bind:value={taskConfig.TASK_MODEL_EXTERNAL}
 							bind:value={taskConfig.TASK_MODEL_EXTERNAL}
 							placeholder={$i18n.t('Select a model')}
 							placeholder={$i18n.t('Select a model')}
 						>
 						>
@@ -127,9 +132,7 @@
 					</div>
 					</div>
 				</div>
 				</div>
 
 
-				<hr class=" border-gray-50 dark:border-gray-850 my-3" />
-
-				<div class="my-3 flex w-full items-center justify-between">
+				<div class="mb-2.5 flex w-full items-center justify-between">
 					<div class=" self-center text-xs font-medium">
 					<div class=" self-center text-xs font-medium">
 						{$i18n.t('Title Generation')}
 						{$i18n.t('Title Generation')}
 					</div>
 					</div>
@@ -138,8 +141,8 @@
 				</div>
 				</div>
 
 
 				{#if taskConfig.ENABLE_TITLE_GENERATION}
 				{#if taskConfig.ENABLE_TITLE_GENERATION}
-					<div class="mt-3">
-						<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Title Generation Prompt')}</div>
+					<div class="mb-2.5">
+						<div class=" mb-1 text-xs font-medium">{$i18n.t('Title Generation Prompt')}</div>
 
 
 						<Tooltip
 						<Tooltip
 							content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
 							content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
@@ -155,56 +158,7 @@
 					</div>
 					</div>
 				{/if}
 				{/if}
 
 
-				<div class="mt-3">
-					<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Image Prompt Generation Prompt')}</div>
-
-					<Tooltip
-						content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
-						placement="top-start"
-					>
-						<Textarea
-							bind:value={taskConfig.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE}
-							placeholder={$i18n.t(
-								'Leave empty to use the default prompt, or enter a custom prompt'
-							)}
-						/>
-					</Tooltip>
-				</div>
-
-				<hr class=" border-gray-50 dark:border-gray-850 my-3" />
-
-				<div class="my-3 flex w-full items-center justify-between">
-					<div class=" self-center text-xs font-medium">
-						{$i18n.t('Autocomplete Generation')}
-					</div>
-
-					<Tooltip content={$i18n.t('Enable autocomplete generation for chat messages')}>
-						<Switch bind:state={taskConfig.ENABLE_AUTOCOMPLETE_GENERATION} />
-					</Tooltip>
-				</div>
-
-				{#if taskConfig.ENABLE_AUTOCOMPLETE_GENERATION}
-					<div class="mt-3">
-						<div class=" mb-2.5 text-xs font-medium">
-							{$i18n.t('Autocomplete Generation Input Max Length')}
-						</div>
-
-						<Tooltip
-							content={$i18n.t('Character limit for autocomplete generation input')}
-							placement="top-start"
-						>
-							<input
-								class="w-full outline-none bg-transparent"
-								bind:value={taskConfig.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH}
-								placeholder={$i18n.t('-1 for no limit, or a positive integer for a specific limit')}
-							/>
-						</Tooltip>
-					</div>
-				{/if}
-
-				<hr class=" border-gray-50 dark:border-gray-850 my-3" />
-
-				<div class="my-3 flex w-full items-center justify-between">
+				<div class="mb-2.5 flex w-full items-center justify-between">
 					<div class=" self-center text-xs font-medium">
 					<div class=" self-center text-xs font-medium">
 						{$i18n.t('Tags Generation')}
 						{$i18n.t('Tags Generation')}
 					</div>
 					</div>
@@ -213,8 +167,8 @@
 				</div>
 				</div>
 
 
 				{#if taskConfig.ENABLE_TAGS_GENERATION}
 				{#if taskConfig.ENABLE_TAGS_GENERATION}
-					<div class="mt-3">
-						<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Tags Generation Prompt')}</div>
+					<div class="mb-2.5">
+						<div class=" mb-1 text-xs font-medium">{$i18n.t('Tags Generation Prompt')}</div>
 
 
 						<Tooltip
 						<Tooltip
 							content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
 							content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
@@ -230,9 +184,7 @@
 					</div>
 					</div>
 				{/if}
 				{/if}
 
 
-				<hr class=" border-gray-50 dark:border-gray-850 my-3" />
-
-				<div class="my-3 flex w-full items-center justify-between">
+				<div class="mb-2.5 flex w-full items-center justify-between">
 					<div class=" self-center text-xs font-medium">
 					<div class=" self-center text-xs font-medium">
 						{$i18n.t('Retrieval Query Generation')}
 						{$i18n.t('Retrieval Query Generation')}
 					</div>
 					</div>
@@ -240,7 +192,7 @@
 					<Switch bind:state={taskConfig.ENABLE_RETRIEVAL_QUERY_GENERATION} />
 					<Switch bind:state={taskConfig.ENABLE_RETRIEVAL_QUERY_GENERATION} />
 				</div>
 				</div>
 
 
-				<div class="my-3 flex w-full items-center justify-between">
+				<div class="mb-2.5 flex w-full items-center justify-between">
 					<div class=" self-center text-xs font-medium">
 					<div class=" self-center text-xs font-medium">
 						{$i18n.t('Web Search Query Generation')}
 						{$i18n.t('Web Search Query Generation')}
 					</div>
 					</div>
@@ -248,8 +200,8 @@
 					<Switch bind:state={taskConfig.ENABLE_SEARCH_QUERY_GENERATION} />
 					<Switch bind:state={taskConfig.ENABLE_SEARCH_QUERY_GENERATION} />
 				</div>
 				</div>
 
 
-				<div class="">
-					<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Query Generation Prompt')}</div>
+				<div class="mb-2.5">
+					<div class=" mb-1 text-xs font-medium">{$i18n.t('Query Generation Prompt')}</div>
 
 
 					<Tooltip
 					<Tooltip
 						content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
 						content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
@@ -263,131 +215,96 @@
 						/>
 						/>
 					</Tooltip>
 					</Tooltip>
 				</div>
 				</div>
-			</div>
 
 
-			<div class="mt-3">
-				<div class=" mb-2.5 text-xs font-medium">{$i18n.t('Tools Function Calling Prompt')}</div>
-
-				<Tooltip
-					content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
-					placement="top-start"
-				>
-					<Textarea
-						bind:value={taskConfig.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE}
-						placeholder={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
-					/>
-				</Tooltip>
-			</div>
+				<div class="mb-2.5 flex w-full items-center justify-between">
+					<div class=" self-center text-xs font-medium">
+						{$i18n.t('Autocomplete Generation')}
+					</div>
 
 
-			<hr class=" border-gray-50 dark:border-gray-850 my-3" />
+					<Tooltip content={$i18n.t('Enable autocomplete generation for chat messages')}>
+						<Switch bind:state={taskConfig.ENABLE_AUTOCOMPLETE_GENERATION} />
+					</Tooltip>
+				</div>
 
 
-			<div class=" space-y-3 {banners.length > 0 ? ' mb-3' : ''}">
-				<div class="flex w-full justify-between">
-					<div class=" self-center text-sm font-semibold">
-						{$i18n.t('Banners')}
-					</div>
+				{#if taskConfig.ENABLE_AUTOCOMPLETE_GENERATION}
+					<div class="mb-2.5">
+						<div class=" mb-1 text-xs font-medium">
+							{$i18n.t('Autocomplete Generation Input Max Length')}
+						</div>
 
 
-					<button
-						class="p-1 px-3 text-xs flex rounded transition"
-						type="button"
-						on:click={() => {
-							if (banners.length === 0 || banners.at(-1).content !== '') {
-								banners = [
-									...banners,
-									{
-										id: uuidv4(),
-										type: '',
-										title: '',
-										content: '',
-										dismissible: true,
-										timestamp: Math.floor(Date.now() / 1000)
-									}
-								];
-							}
-						}}
-					>
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							viewBox="0 0 20 20"
-							fill="currentColor"
-							class="w-4 h-4"
+						<Tooltip
+							content={$i18n.t('Character limit for autocomplete generation input')}
+							placement="top-start"
 						>
 						>
-							<path
-								d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+							<input
+								class="w-full outline-hidden bg-transparent"
+								bind:value={taskConfig.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH}
+								placeholder={$i18n.t('-1 for no limit, or a positive integer for a specific limit')}
 							/>
 							/>
-						</svg>
-					</button>
+						</Tooltip>
+					</div>
+				{/if}
+
+				<div class="mb-2.5">
+					<div class=" mb-1 text-xs font-medium">{$i18n.t('Image Prompt Generation Prompt')}</div>
+
+					<Tooltip
+						content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+						placement="top-start"
+					>
+						<Textarea
+							bind:value={taskConfig.IMAGE_PROMPT_GENERATION_PROMPT_TEMPLATE}
+							placeholder={$i18n.t(
+								'Leave empty to use the default prompt, or enter a custom prompt'
+							)}
+						/>
+					</Tooltip>
 				</div>
 				</div>
-				<div class="flex flex-col space-y-1">
-					{#each banners as banner, bannerIdx}
-						<div class=" flex justify-between">
-							<div class="flex flex-row flex-1 border rounded-xl dark:border-gray-800">
-								<select
-									class="w-fit capitalize rounded-xl py-2 px-4 text-xs bg-transparent outline-none"
-									bind:value={banner.type}
-									required
-								>
-									{#if banner.type == ''}
-										<option value="" selected disabled class="text-gray-900"
-											>{$i18n.t('Type')}</option
-										>
-									{/if}
-									<option value="info" class="text-gray-900">{$i18n.t('Info')}</option>
-									<option value="warning" class="text-gray-900">{$i18n.t('Warning')}</option>
-									<option value="error" class="text-gray-900">{$i18n.t('Error')}</option>
-									<option value="success" class="text-gray-900">{$i18n.t('Success')}</option>
-								</select>
-
-								<input
-									class="pr-5 py-1.5 text-xs w-full bg-transparent outline-none"
-									placeholder={$i18n.t('Content')}
-									bind:value={banner.content}
-								/>
 
 
-								<div class="relative top-1.5 -left-2">
-									<Tooltip content={$i18n.t('Dismissible')} className="flex h-fit items-center">
-										<Switch bind:state={banner.dismissible} />
-									</Tooltip>
-								</div>
-							</div>
+				<div class="mb-2.5">
+					<div class=" mb-1 text-xs font-medium">{$i18n.t('Tools Function Calling Prompt')}</div>
 
 
-							<button
-								class="px-2"
-								type="button"
-								on:click={() => {
-									banners.splice(bannerIdx, 1);
-									banners = banners;
-								}}
-							>
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									viewBox="0 0 20 20"
-									fill="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
-									/>
-								</svg>
-							</button>
-						</div>
-					{/each}
+					<Tooltip
+						content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
+						placement="top-start"
+					>
+						<Textarea
+							bind:value={taskConfig.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE}
+							placeholder={$i18n.t(
+								'Leave empty to use the default prompt, or enter a custom prompt'
+							)}
+						/>
+					</Tooltip>
 				</div>
 				</div>
 			</div>
 			</div>
 
 
-			{#if $user.role === 'admin'}
-				<div class=" space-y-3">
-					<div class="flex w-full justify-between mb-2">
+			<div class="mb-3.5">
+				<div class=" mb-2.5 text-base font-medium">{$i18n.t('UI')}</div>
+
+				<hr class=" border-gray-100 dark:border-gray-850 my-2" />
+
+				<div class="  {banners.length > 0 ? ' mb-3' : ''}">
+					<div class="mb-2.5 flex w-full justify-between">
 						<div class=" self-center text-sm font-semibold">
 						<div class=" self-center text-sm font-semibold">
-							{$i18n.t('Default Prompt Suggestions')}
+							{$i18n.t('Banners')}
 						</div>
 						</div>
 
 
 						<button
 						<button
-							class="p-1 px-3 text-xs flex rounded transition"
+							class="p-1 px-3 text-xs flex rounded-sm transition"
 							type="button"
 							type="button"
 							on:click={() => {
 							on:click={() => {
-								if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
-									promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
+								if (banners.length === 0 || banners.at(-1).content !== '') {
+									banners = [
+										...banners,
+										{
+											id: uuidv4(),
+											type: '',
+											title: '',
+											content: '',
+											dismissible: true,
+											timestamp: Math.floor(Date.now() / 1000)
+										}
+									];
 								}
 								}
 							}}
 							}}
 						>
 						>
@@ -403,40 +320,48 @@
 							</svg>
 							</svg>
 						</button>
 						</button>
 					</div>
 					</div>
-					<div class="grid lg:grid-cols-2 flex-col gap-1.5">
-						{#each promptSuggestions as prompt, promptIdx}
-							<div
-								class=" flex border border-gray-100 dark:border-none dark:bg-gray-850 rounded-xl py-1.5"
-							>
-								<div class="flex flex-col flex-1 pl-1">
-									<div class="flex border-b border-gray-100 dark:border-gray-800 w-full">
-										<input
-											class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800"
-											placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
-											bind:value={prompt.title[0]}
-										/>
 
 
-										<input
-											class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800"
-											placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
-											bind:value={prompt.title[1]}
-										/>
-									</div>
-
-									<textarea
-										class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r border-gray-100 dark:border-gray-800 resize-none"
-										placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
-										rows="3"
-										bind:value={prompt.content}
+					<div class=" flex flex-col space-y-1">
+						{#each banners as banner, bannerIdx}
+							<div class=" flex justify-between">
+								<div
+									class="flex flex-row flex-1 border rounded-xl border-gray-100 dark:border-gray-850"
+								>
+									<select
+										class="w-fit capitalize rounded-xl py-2 px-4 text-xs bg-transparent outline-hidden"
+										bind:value={banner.type}
+										required
+									>
+										{#if banner.type == ''}
+											<option value="" selected disabled class="text-gray-900"
+												>{$i18n.t('Type')}</option
+											>
+										{/if}
+										<option value="info" class="text-gray-900">{$i18n.t('Info')}</option>
+										<option value="warning" class="text-gray-900">{$i18n.t('Warning')}</option>
+										<option value="error" class="text-gray-900">{$i18n.t('Error')}</option>
+										<option value="success" class="text-gray-900">{$i18n.t('Success')}</option>
+									</select>
+
+									<input
+										class="pr-5 py-1.5 text-xs w-full bg-transparent outline-hidden"
+										placeholder={$i18n.t('Content')}
+										bind:value={banner.content}
 									/>
 									/>
+
+									<div class="relative top-1.5 -left-2">
+										<Tooltip content={$i18n.t('Dismissible')} className="flex h-fit items-center">
+											<Switch bind:state={banner.dismissible} />
+										</Tooltip>
+									</div>
 								</div>
 								</div>
 
 
 								<button
 								<button
-									class="px-3"
+									class="px-2"
 									type="button"
 									type="button"
 									on:click={() => {
 									on:click={() => {
-										promptSuggestions.splice(promptIdx, 1);
-										promptSuggestions = promptSuggestions;
+										banners.splice(bannerIdx, 1);
+										banners = banners;
 									}}
 									}}
 								>
 								>
 									<svg
 									<svg
@@ -453,14 +378,97 @@
 							</div>
 							</div>
 						{/each}
 						{/each}
 					</div>
 					</div>
+				</div>
+
+				{#if $user.role === 'admin'}
+					<div class=" space-y-3">
+						<div class="flex w-full justify-between mb-2">
+							<div class=" self-center text-sm font-semibold">
+								{$i18n.t('Default Prompt Suggestions')}
+							</div>
+
+							<button
+								class="p-1 px-3 text-xs flex rounded-sm transition"
+								type="button"
+								on:click={() => {
+									if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
+										promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
+									}
+								}}
+							>
+								<svg
+									xmlns="http://www.w3.org/2000/svg"
+									viewBox="0 0 20 20"
+									fill="currentColor"
+									class="w-4 h-4"
+								>
+									<path
+										d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
+									/>
+								</svg>
+							</button>
+						</div>
+						<div class="grid lg:grid-cols-2 flex-col gap-1.5">
+							{#each promptSuggestions as prompt, promptIdx}
+								<div
+									class=" flex border border-gray-100 dark:border-none dark:bg-gray-850 rounded-xl py-1.5"
+								>
+									<div class="flex flex-col flex-1 pl-1">
+										<div class="flex border-b border-gray-100 dark:border-gray-850 w-full">
+											<input
+												class="px-3 py-1.5 text-xs w-full bg-transparent outline-hidden border-r border-gray-100 dark:border-gray-850"
+												placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
+												bind:value={prompt.title[0]}
+											/>
+
+											<input
+												class="px-3 py-1.5 text-xs w-full bg-transparent outline-hidden border-r border-gray-100 dark:border-gray-850"
+												placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
+												bind:value={prompt.title[1]}
+											/>
+										</div>
+
+										<textarea
+											class="px-3 py-1.5 text-xs w-full bg-transparent outline-hidden border-r border-gray-100 dark:border-gray-850 resize-none"
+											placeholder={$i18n.t(
+												'Prompt (e.g. Tell me a fun fact about the Roman Empire)'
+											)}
+											rows="3"
+											bind:value={prompt.content}
+										/>
+									</div>
 
 
-					{#if promptSuggestions.length > 0}
-						<div class="text-xs text-left w-full mt-2">
-							{$i18n.t('Adjusting these settings will apply changes universally to all users.')}
+									<button
+										class="px-3"
+										type="button"
+										on:click={() => {
+											promptSuggestions.splice(promptIdx, 1);
+											promptSuggestions = promptSuggestions;
+										}}
+									>
+										<svg
+											xmlns="http://www.w3.org/2000/svg"
+											viewBox="0 0 20 20"
+											fill="currentColor"
+											class="w-4 h-4"
+										>
+											<path
+												d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+											/>
+										</svg>
+									</button>
+								</div>
+							{/each}
 						</div>
 						</div>
-					{/if}
-				</div>
-			{/if}
+
+						{#if promptSuggestions.length > 0}
+							<div class="text-xs text-left w-full mt-2">
+								{$i18n.t('Adjusting these settings will apply changes universally to all users.')}
+							</div>
+						{/if}
+					</div>
+				{/if}
+			</div>
 		</div>
 		</div>
 
 
 		<div class="flex justify-end text-sm font-medium">
 		<div class="flex justify-end text-sm font-medium">

+ 1 - 1
src/lib/components/admin/Settings/Models.svelte

@@ -199,7 +199,7 @@
 						<Search className="size-3.5" />
 						<Search className="size-3.5" />
 					</div>
 					</div>
 					<input
 					<input
-						class=" w-full text-sm py-1 rounded-r-xl outline-none bg-transparent"
+						class=" w-full text-sm py-1 rounded-r-xl outline-hidden bg-transparent"
 						bind:value={searchValue}
 						bind:value={searchValue}
 						placeholder={$i18n.t('Search Models')}
 						placeholder={$i18n.t('Search Models')}
 					/>
 					/>

+ 2 - 2
src/lib/components/admin/Settings/Models/ConfigureModelsModal.svelte

@@ -165,7 +165,7 @@
 									<select
 									<select
 										class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
 										class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
 											? ''
 											? ''
-											: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+											: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 										bind:value={selectedModelId}
 										bind:value={selectedModelId}
 									>
 									>
 										<option value="">{$i18n.t('Select a model')}</option>
 										<option value="">{$i18n.t('Select a model')}</option>
@@ -186,7 +186,7 @@
 												<div class=" text-sm flex-1 py-1 rounded-lg">
 												<div class=" text-sm flex-1 py-1 rounded-lg">
 													{$models.find((model) => model.id === modelId)?.name}
 													{$models.find((model) => model.id === modelId)?.name}
 												</div>
 												</div>
-												<div class="flex-shrink-0">
+												<div class="shrink-0">
 													<button
 													<button
 														type="button"
 														type="button"
 														on:click={() => {
 														on:click={() => {

+ 1 - 1
src/lib/components/admin/Settings/Models/Manage/ManageMultipleOllama.svelte

@@ -12,7 +12,7 @@
 {#if ollamaConfig}
 {#if ollamaConfig}
 	<div class="flex-1 mb-2.5 pr-1.5 rounded-lg bg-gray-50 dark:text-gray-300 dark:bg-gray-850">
 	<div class="flex-1 mb-2.5 pr-1.5 rounded-lg bg-gray-50 dark:text-gray-300 dark:bg-gray-850">
 		<select
 		<select
-			class="w-full py-2 px-4 text-sm outline-none bg-transparent"
+			class="w-full py-2 px-4 text-sm outline-hidden bg-transparent"
 			bind:value={selectedUrlIdx}
 			bind:value={selectedUrlIdx}
 			placeholder={$i18n.t('Select an Ollama instance')}
 			placeholder={$i18n.t('Select an Ollama instance')}
 		>
 		>

+ 7 - 7
src/lib/components/admin/Settings/Models/Manage/ManageOllama.svelte

@@ -598,7 +598,7 @@
 					<div class="flex w-full">
 					<div class="flex w-full">
 						<div class="flex-1 mr-2">
 						<div class="flex-1 mr-2">
 							<input
 							<input
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
 								placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
 									modelTag: 'mistral:7b'
 									modelTag: 'mistral:7b'
 								})}
 								})}
@@ -740,7 +740,7 @@
 							class="flex-1 mr-2 pr-1.5 rounded-lg bg-gray-50 dark:text-gray-300 dark:bg-gray-850"
 							class="flex-1 mr-2 pr-1.5 rounded-lg bg-gray-50 dark:text-gray-300 dark:bg-gray-850"
 						>
 						>
 							<select
 							<select
-								class="w-full py-2 px-4 text-sm outline-none bg-transparent"
+								class="w-full py-2 px-4 text-sm outline-hidden bg-transparent"
 								bind:value={deleteModelTag}
 								bind:value={deleteModelTag}
 								placeholder={$i18n.t('Select a model')}
 								placeholder={$i18n.t('Select a model')}
 							>
 							>
@@ -781,7 +781,7 @@
 					<div class="flex w-full">
 					<div class="flex w-full">
 						<div class="flex-1 mr-2 flex flex-col gap-2">
 						<div class="flex-1 mr-2 flex flex-col gap-2">
 							<input
 							<input
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
 								placeholder={$i18n.t('Enter model tag (e.g. {{modelTag}})', {
 									modelTag: 'my-modelfile'
 									modelTag: 'my-modelfile'
 								})}
 								})}
@@ -791,7 +791,7 @@
 
 
 							<textarea
 							<textarea
 								bind:value={createModelObject}
 								bind:value={createModelObject}
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none scrollbar-hidden"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-hidden resize-none scrollbar-hidden"
 								rows="6"
 								rows="6"
 								placeholder={`e.g. {"model": "my-modelfile", "from": "ollama:7b"})`}
 								placeholder={`e.g. {"model": "my-modelfile", "from": "ollama:7b"})`}
 								disabled={createModelLoading}
 								disabled={createModelLoading}
@@ -870,7 +870,7 @@
 							<div class="  text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
 							<div class="  text-sm font-medium">{$i18n.t('Upload a GGUF model')}</div>
 
 
 							<button
 							<button
-								class="p-1 px-3 text-xs flex rounded transition"
+								class="p-1 px-3 text-xs flex rounded-sm transition"
 								on:click={() => {
 								on:click={() => {
 									if (modelUploadMode === 'file') {
 									if (modelUploadMode === 'file') {
 										modelUploadMode = 'url';
 										modelUploadMode = 'url';
@@ -922,7 +922,7 @@
 								{:else}
 								{:else}
 									<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
 									<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
 										<input
 										<input
-											class="w-full rounded-lg text-left py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
+											class="w-full rounded-lg text-left py-2 px-4 bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden {modelFileUrl !==
 											''
 											''
 												? 'mr-2'
 												? 'mr-2'
 												: ''}"
 												: ''}"
@@ -998,7 +998,7 @@
 									</div>
 									</div>
 									<textarea
 									<textarea
 										bind:value={modelFileContent}
 										bind:value={modelFileContent}
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-none resize-none"
+										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-100 dark:bg-gray-850 outline-hidden resize-none"
 										rows="6"
 										rows="6"
 									/>
 									/>
 								</div>
 								</div>

+ 8 - 8
src/lib/components/admin/Settings/Pipelines.svelte

@@ -234,7 +234,7 @@
 					<div class="flex gap-2">
 					<div class="flex gap-2">
 						<div class="flex-1">
 						<div class="flex-1">
 							<select
 							<select
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								bind:value={selectedPipelinesUrlIdx}
 								bind:value={selectedPipelinesUrlIdx}
 								placeholder={$i18n.t('Select a pipeline url')}
 								placeholder={$i18n.t('Select a pipeline url')}
 								on:change={async () => {
 								on:change={async () => {
@@ -271,7 +271,7 @@
 							/>
 							/>
 
 
 							<button
 							<button
-								class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
+								class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-850 dark:hover:bg-gray-850 text-center rounded-xl"
 								type="button"
 								type="button"
 								on:click={() => {
 								on:click={() => {
 									document.getElementById('pipelines-upload-input')?.click();
 									document.getElementById('pipelines-upload-input')?.click();
@@ -348,7 +348,7 @@
 					<div class="flex w-full">
 					<div class="flex w-full">
 						<div class="flex-1 mr-2">
 						<div class="flex-1 mr-2">
 							<input
 							<input
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('Enter Github Raw URL')}
 								placeholder={$i18n.t('Enter Github Raw URL')}
 								bind:value={pipelineDownloadUrl}
 								bind:value={pipelineDownloadUrl}
 							/>
 							/>
@@ -418,7 +418,7 @@
 					</div>
 					</div>
 				</div>
 				</div>
 
 
-				<hr class=" dark:border-gray-800 my-3 w-full" />
+				<hr class="border-gray-100 dark:border-gray-850 my-3 w-full" />
 
 
 				{#if pipelines !== null}
 				{#if pipelines !== null}
 					{#if pipelines.length > 0}
 					{#if pipelines.length > 0}
@@ -432,7 +432,7 @@
 								<div class="flex gap-2">
 								<div class="flex gap-2">
 									<div class="flex-1">
 									<div class="flex-1">
 										<select
 										<select
-											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 											bind:value={selectedPipelineIdx}
 											bind:value={selectedPipelineIdx}
 											placeholder={$i18n.t('Select a pipeline')}
 											placeholder={$i18n.t('Select a pipeline')}
 											on:change={async () => {
 											on:change={async () => {
@@ -482,7 +482,7 @@
 													</div>
 													</div>
 
 
 													<button
 													<button
-														class="p-1 px-3 text-xs flex rounded transition"
+														class="p-1 px-3 text-xs flex rounded-sm transition"
 														type="button"
 														type="button"
 														on:click={() => {
 														on:click={() => {
 															valves[property] = (valves[property] ?? null) === null ? '' : null;
 															valves[property] = (valves[property] ?? null) === null ? '' : null;
@@ -502,7 +502,7 @@
 														<div class=" flex-1">
 														<div class=" flex-1">
 															{#if valves_spec.properties[property]?.enum ?? null}
 															{#if valves_spec.properties[property]?.enum ?? null}
 																<select
 																<select
-																	class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+																	class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 																	bind:value={valves[property]}
 																	bind:value={valves[property]}
 																>
 																>
 																	{#each valves_spec.properties[property].enum as option}
 																	{#each valves_spec.properties[property].enum as option}
@@ -523,7 +523,7 @@
 																</div>
 																</div>
 															{:else}
 															{:else}
 																<input
 																<input
-																	class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+																	class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 																	type="text"
 																	type="text"
 																	placeholder={valves_spec.properties[property].title}
 																	placeholder={valves_spec.properties[property].title}
 																	bind:value={valves[property]}
 																	bind:value={valves[property]}

+ 13 - 13
src/lib/components/admin/Settings/WebSearch.svelte

@@ -103,7 +103,7 @@
 					<div class=" self-center text-xs font-medium">{$i18n.t('Web Search Engine')}</div>
 					<div class=" self-center text-xs font-medium">{$i18n.t('Web Search Engine')}</div>
 					<div class="flex items-center relative">
 					<div class="flex items-center relative">
 						<select
 						<select
-							class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
+							class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
 							bind:value={webConfig.search.engine}
 							bind:value={webConfig.search.engine}
 							placeholder={$i18n.t('Select a engine')}
 							placeholder={$i18n.t('Select a engine')}
 							required
 							required
@@ -127,7 +127,7 @@
 								<div class="flex w-full">
 								<div class="flex w-full">
 									<div class="flex-1">
 									<div class="flex-1">
 										<input
 										<input
-											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 											type="text"
 											type="text"
 											placeholder={$i18n.t('Enter Searxng Query URL')}
 											placeholder={$i18n.t('Enter Searxng Query URL')}
 											bind:value={webConfig.search.searxng_query_url}
 											bind:value={webConfig.search.searxng_query_url}
@@ -155,7 +155,7 @@
 								<div class="flex w-full">
 								<div class="flex w-full">
 									<div class="flex-1">
 									<div class="flex-1">
 										<input
 										<input
-											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 											type="text"
 											type="text"
 											placeholder={$i18n.t('Enter Google PSE Engine Id')}
 											placeholder={$i18n.t('Enter Google PSE Engine Id')}
 											bind:value={webConfig.search.google_pse_engine_id}
 											bind:value={webConfig.search.google_pse_engine_id}
@@ -260,7 +260,7 @@
 								<div class="flex w-full">
 								<div class="flex w-full">
 									<div class="flex-1">
 									<div class="flex-1">
 										<input
 										<input
-											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 											type="text"
 											type="text"
 											placeholder={$i18n.t('Enter SearchApi Engine')}
 											placeholder={$i18n.t('Enter SearchApi Engine')}
 											bind:value={webConfig.search.searchapi_engine}
 											bind:value={webConfig.search.searchapi_engine}
@@ -288,7 +288,7 @@
 								<div class="flex w-full">
 								<div class="flex w-full">
 									<div class="flex-1">
 									<div class="flex-1">
 										<input
 										<input
-											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 											type="text"
 											type="text"
 											placeholder={$i18n.t('Enter SerpApi Engine')}
 											placeholder={$i18n.t('Enter SerpApi Engine')}
 											bind:value={webConfig.search.serpapi_engine}
 											bind:value={webConfig.search.serpapi_engine}
@@ -339,7 +339,7 @@
 								<div class="flex w-full">
 								<div class="flex w-full">
 									<div class="flex-1">
 									<div class="flex-1">
 										<input
 										<input
-											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 											type="text"
 											type="text"
 											placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
 											placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
 											bind:value={webConfig.search.bing_search_v7_endpoint}
 											bind:value={webConfig.search.bing_search_v7_endpoint}
@@ -371,7 +371,7 @@
 							</div>
 							</div>
 
 
 							<input
 							<input
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('Search Result Count')}
 								placeholder={$i18n.t('Search Result Count')}
 								bind:value={webConfig.search.result_count}
 								bind:value={webConfig.search.result_count}
 								required
 								required
@@ -384,7 +384,7 @@
 							</div>
 							</div>
 
 
 							<input
 							<input
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								placeholder={$i18n.t('Concurrent Requests')}
 								placeholder={$i18n.t('Concurrent Requests')}
 								bind:value={webConfig.search.concurrent_requests}
 								bind:value={webConfig.search.concurrent_requests}
 								required
 								required
@@ -398,7 +398,7 @@
 						</div>
 						</div>
 
 
 						<input
 						<input
-							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							placeholder={$i18n.t(
 							placeholder={$i18n.t(
 								'Enter domains separated by commas (e.g., example.com,site.org)'
 								'Enter domains separated by commas (e.g., example.com,site.org)'
 							)}
 							)}
@@ -408,7 +408,7 @@
 				{/if}
 				{/if}
 			</div>
 			</div>
 
 
-			<hr class=" dark:border-gray-850 my-2" />
+			<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 
 			<div>
 			<div>
 				<div class=" mb-1 text-sm font-medium">
 				<div class=" mb-1 text-sm font-medium">
@@ -422,7 +422,7 @@
 						</div>
 						</div>
 
 
 						<button
 						<button
-							class="p-1 px-3 text-xs flex rounded transition"
+							class="p-1 px-3 text-xs flex rounded-sm transition"
 							on:click={() => {
 							on:click={() => {
 								webConfig.web_loader_ssl_verification = !webConfig.web_loader_ssl_verification;
 								webConfig.web_loader_ssl_verification = !webConfig.web_loader_ssl_verification;
 								submitHandler();
 								submitHandler();
@@ -447,7 +447,7 @@
 						<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Language')}</div>
 						<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Language')}</div>
 						<div class=" flex-1 self-center">
 						<div class=" flex-1 self-center">
 							<input
 							<input
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								type="text"
 								type="text"
 								placeholder={$i18n.t('Enter language codes')}
 								placeholder={$i18n.t('Enter language codes')}
 								bind:value={youtubeLanguage}
 								bind:value={youtubeLanguage}
@@ -462,7 +462,7 @@
 						<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Proxy URL')}</div>
 						<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Proxy URL')}</div>
 						<div class=" flex-1 self-center">
 						<div class=" flex-1 self-center">
 							<input
 							<input
-								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
+								class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 								type="text"
 								type="text"
 								placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
 								placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
 								bind:value={youtubeProxyUrl}
 								bind:value={youtubeProxyUrl}

+ 3 - 3
src/lib/components/admin/Users/Groups.svelte

@@ -140,7 +140,7 @@
 						</svg>
 						</svg>
 					</div>
 					</div>
 					<input
 					<input
-						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+						class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
 						bind:value={search}
 						bind:value={search}
 						placeholder={$i18n.t('Search')}
 						placeholder={$i18n.t('Search')}
 					/>
 					/>
@@ -195,7 +195,7 @@
 					<div class="w-full"></div>
 					<div class="w-full"></div>
 				</div>
 				</div>
 
 
-				<hr class="mt-1.5 border-gray-50 dark:border-gray-850" />
+				<hr class="mt-1.5 border-gray-100 dark:border-gray-850" />
 
 
 				{#each filteredGroups as group}
 				{#each filteredGroups as group}
 					<div class="my-2">
 					<div class="my-2">
@@ -205,7 +205,7 @@
 			</div>
 			</div>
 		{/if}
 		{/if}
 
 
-		<hr class="mb-2 border-gray-50 dark:border-gray-850" />
+		<hr class="mb-2 border-gray-100 dark:border-gray-850" />
 
 
 		<GroupModal
 		<GroupModal
 			bind:show={showDefaultPermissionsModal}
 			bind:show={showDefaultPermissionsModal}

+ 2 - 2
src/lib/components/admin/Users/Groups/AddGroupModal.svelte

@@ -78,7 +78,7 @@
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
-										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 										type="text"
 										type="text"
 										bind:value={name}
 										bind:value={name}
 										placeholder={$i18n.t('Group Name')}
 										placeholder={$i18n.t('Group Name')}
@@ -94,7 +94,7 @@
 
 
 							<div class="flex-1">
 							<div class="flex-1">
 								<Textarea
 								<Textarea
-									className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none resize-none"
+									className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden resize-none"
 									rows={2}
 									rows={2}
 									bind:value={description}
 									bind:value={description}
 									placeholder={$i18n.t('Group Description')}
 									placeholder={$i18n.t('Group Description')}

+ 3 - 3
src/lib/components/admin/Users/Groups/Display.svelte

@@ -16,7 +16,7 @@
 
 
 		<div class="flex-1">
 		<div class="flex-1">
 			<input
 			<input
-				class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+				class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 				type="text"
 				type="text"
 				bind:value={name}
 				bind:value={name}
 				placeholder={$i18n.t('Group Name')}
 				placeholder={$i18n.t('Group Name')}
@@ -36,7 +36,7 @@
 				<div class="text-gray-500">#</div>
 				<div class="text-gray-500">#</div>
 
 
 				<input
 				<input
-					class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+					class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 					type="text"
 					type="text"
 					bind:value={color}
 					bind:value={color}
 					placeholder={$i18n.t('Hex Color')}
 					placeholder={$i18n.t('Hex Color')}
@@ -52,7 +52,7 @@
 
 
 	<div class="flex-1">
 	<div class="flex-1">
 		<Textarea
 		<Textarea
-			className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none resize-none"
+			className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden resize-none"
 			rows={4}
 			rows={4}
 			bind:value={description}
 			bind:value={description}
 			placeholder={$i18n.t('Group Description')}
 			placeholder={$i18n.t('Group Description')}

+ 6 - 6
src/lib/components/admin/Users/Groups/Permissions.svelte

@@ -76,7 +76,7 @@
 										<div class=" text-sm flex-1 rounded-lg">
 										<div class=" text-sm flex-1 rounded-lg">
 											{modelId}
 											{modelId}
 										</div>
 										</div>
-										<div class="flex-shrink-0">
+										<div class="shrink-0">
 											<button
 											<button
 												type="button"
 												type="button"
 												on:click={() => {
 												on:click={() => {
@@ -102,7 +102,7 @@
 					<select
 					<select
 						class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
 						class="w-full py-1 text-sm rounded-lg bg-transparent {selectedModelId
 							? ''
 							? ''
-							: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-none"
+							: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
 						bind:value={selectedModelId}
 						bind:value={selectedModelId}
 					>
 					>
 						<option value="">{$i18n.t('Select a model')}</option>
 						<option value="">{$i18n.t('Select a model')}</option>
@@ -137,7 +137,7 @@
 
 
 			<div class="flex-1 mr-2">
 			<div class="flex-1 mr-2">
 				<select
 				<select
-					class="w-full bg-transparent outline-none py-0.5 text-sm"
+					class="w-full bg-transparent outline-hidden py-0.5 text-sm"
 					bind:value={permissions.model.default_id}
 					bind:value={permissions.model.default_id}
 					placeholder="Select a model"
 					placeholder="Select a model"
 				>
 				>
@@ -150,7 +150,7 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<hr class=" border-gray-50 dark:border-gray-850 my-2" /> -->
+	<hr class=" border-gray-100 dark:border-gray-850 my-2" /> -->
 
 
 	<div>
 	<div>
 		<div class=" mb-2 text-sm font-medium">{$i18n.t('Workspace Permissions')}</div>
 		<div class=" mb-2 text-sm font-medium">{$i18n.t('Workspace Permissions')}</div>
@@ -192,7 +192,7 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<hr class=" border-gray-50 dark:border-gray-850 my-2" />
+	<hr class=" border-gray-100 dark:border-gray-850 my-2" />
 
 
 	<div>
 	<div>
 		<div class=" mb-2 text-sm font-medium">{$i18n.t('Chat Permissions')}</div>
 		<div class=" mb-2 text-sm font-medium">{$i18n.t('Chat Permissions')}</div>
@@ -238,7 +238,7 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<hr class=" border-gray-50 dark:border-gray-850 my-2" />
+	<hr class=" border-gray-100 dark:border-gray-850 my-2" />
 
 
 	<div>
 	<div>
 		<div class=" mb-2 text-sm font-medium">{$i18n.t('Features Permissions')}</div>
 		<div class=" mb-2 text-sm font-medium">{$i18n.t('Features Permissions')}</div>

+ 1 - 1
src/lib/components/admin/Users/Groups/Users.svelte

@@ -64,7 +64,7 @@
 				</svg>
 				</svg>
 			</div>
 			</div>
 			<input
 			<input
-				class=" w-full text-sm pr-4 rounded-r-xl outline-none bg-transparent"
+				class=" w-full text-sm pr-4 rounded-r-xl outline-hidden bg-transparent"
 				bind:value={query}
 				bind:value={query}
 				placeholder={$i18n.t('Search')}
 				placeholder={$i18n.t('Search')}
 			/>
 			/>

+ 3 - 3
src/lib/components/admin/Users/UserList.svelte

@@ -149,7 +149,7 @@
 					</svg>
 					</svg>
 				</div>
 				</div>
 				<input
 				<input
-					class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+					class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-hidden bg-transparent"
 					bind:value={search}
 					bind:value={search}
 					placeholder={$i18n.t('Search')}
 					placeholder={$i18n.t('Search')}
 				/>
 				/>
@@ -171,9 +171,9 @@
 	</div>
 	</div>
 </div>
 </div>
 
 
-<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded pt-0.5">
+<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5">
 	<table
 	<table
-		class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded"
+		class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded-sm"
 	>
 	>
 		<thead
 		<thead
 			class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
 			class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"

+ 6 - 6
src/lib/components/admin/Users/UserList/AddUserModal.svelte

@@ -181,7 +181,7 @@
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<select
 									<select
-										class="w-full capitalize rounded-lg text-sm bg-transparent dark:disabled:text-gray-500 outline-none"
+										class="w-full capitalize rounded-lg text-sm bg-transparent dark:disabled:text-gray-500 outline-hidden"
 										bind:value={_user.role}
 										bind:value={_user.role}
 										placeholder={$i18n.t('Enter Your Role')}
 										placeholder={$i18n.t('Enter Your Role')}
 										required
 										required
@@ -198,7 +198,7 @@
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
-										class="w-full text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+										class="w-full text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
 										type="text"
 										type="text"
 										bind:value={_user.name}
 										bind:value={_user.name}
 										placeholder={$i18n.t('Enter Your Full Name')}
 										placeholder={$i18n.t('Enter Your Full Name')}
@@ -208,14 +208,14 @@
 								</div>
 								</div>
 							</div>
 							</div>
 
 
-							<hr class=" border-gray-50 dark:border-gray-850 my-2.5 w-full" />
+							<hr class=" border-gray-100 dark:border-gray-850 my-2.5 w-full" />
 
 
 							<div class="flex flex-col w-full">
 							<div class="flex flex-col w-full">
 								<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
 								<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Email')}</div>
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
-										class="w-full text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+										class="w-full text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
 										type="email"
 										type="email"
 										bind:value={_user.email}
 										bind:value={_user.email}
 										placeholder={$i18n.t('Enter Your Email')}
 										placeholder={$i18n.t('Enter Your Email')}
@@ -229,7 +229,7 @@
 
 
 								<div class="flex-1">
 								<div class="flex-1">
 									<input
 									<input
-										class="w-full text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+										class="w-full text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
 										type="password"
 										type="password"
 										bind:value={_user.password}
 										bind:value={_user.password}
 										placeholder={$i18n.t('Enter Your Password')}
 										placeholder={$i18n.t('Enter Your Password')}
@@ -249,7 +249,7 @@
 									/>
 									/>
 
 
 									<button
 									<button
-										class="w-full text-sm font-medium py-3 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-800 dark:hover:bg-gray-850 text-center rounded-xl"
+										class="w-full text-sm font-medium py-3 bg-transparent hover:bg-gray-100 border border-dashed dark:border-gray-850 dark:hover:bg-gray-850 text-center rounded-xl"
 										type="button"
 										type="button"
 										on:click={() => {
 										on:click={() => {
 											document.getElementById('upload-user-csv-input')?.click();
 											document.getElementById('upload-user-csv-input')?.click();

+ 5 - 5
src/lib/components/admin/Users/UserList/EditUserModal.svelte

@@ -65,7 +65,7 @@
 				</svg>
 				</svg>
 			</button>
 			</button>
 		</div>
 		</div>
-		<hr class=" dark:border-gray-800" />
+		<hr class="border-gray-100 dark:border-gray-850" />
 
 
 		<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
 		<div class="flex flex-col md:flex-row w-full p-5 md:space-x-4 dark:text-gray-200">
 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
 			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
@@ -94,7 +94,7 @@
 						</div>
 						</div>
 					</div>
 					</div>
 
 
-					<hr class=" dark:border-gray-800 my-3 w-full" />
+					<hr class="border-gray-100 dark:border-gray-850 my-3 w-full" />
 
 
 					<div class=" flex flex-col space-y-1.5">
 					<div class=" flex flex-col space-y-1.5">
 						<div class="flex flex-col w-full">
 						<div class="flex flex-col w-full">
@@ -102,7 +102,7 @@
 
 
 							<div class="flex-1">
 							<div class="flex-1">
 								<input
 								<input
-									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 disabled:text-gray-500 dark:disabled:text-gray-500 outline-none"
+									class="w-full rounded-sm py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
 									type="email"
 									type="email"
 									bind:value={_user.email}
 									bind:value={_user.email}
 									autocomplete="off"
 									autocomplete="off"
@@ -117,7 +117,7 @@
 
 
 							<div class="flex-1">
 							<div class="flex-1">
 								<input
 								<input
-									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+									class="w-full rounded-sm py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-hidden"
 									type="text"
 									type="text"
 									bind:value={_user.name}
 									bind:value={_user.name}
 									autocomplete="off"
 									autocomplete="off"
@@ -131,7 +131,7 @@
 
 
 							<div class="flex-1">
 							<div class="flex-1">
 								<input
 								<input
-									class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
+									class="w-full rounded-sm py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-hidden"
 									type="password"
 									type="password"
 									bind:value={_user.password}
 									bind:value={_user.password}
 									autocomplete="new-password"
 									autocomplete="new-password"

+ 1 - 1
src/lib/components/admin/Users/UserList/UserChatsModal.svelte

@@ -82,7 +82,7 @@
 						<div class="relative overflow-x-auto">
 						<div class="relative overflow-x-auto">
 							<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
 							<table class="w-full text-sm text-left text-gray-600 dark:text-gray-400 table-auto">
 								<thead
 								<thead
-									class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-800"
+									class="text-xs text-gray-700 uppercase bg-transparent dark:text-gray-200 border-b-2 dark:border-gray-850"
 								>
 								>
 									<tr>
 									<tr>
 										<th
 										<th

+ 1 - 1
src/lib/components/channel/Channel.svelte

@@ -281,7 +281,7 @@
 			<PaneResizer
 			<PaneResizer
 				class="relative flex w-[3px] items-center justify-center bg-background group bg-gray-50 dark:bg-gray-850"
 				class="relative flex w-[3px] items-center justify-center bg-background group bg-gray-50 dark:bg-gray-850"
 			>
 			>
-				<div class="z-10 flex h-7 w-5 items-center justify-center rounded-sm">
+				<div class="z-10 flex h-7 w-5 items-center justify-center rounded-xs">
 					<EllipsisVertical className="size-4 invisible group-hover:visible" />
 					<EllipsisVertical className="size-4 invisible group-hover:visible" />
 				</div>
 				</div>
 			</PaneResizer>
 			</PaneResizer>

+ 2 - 2
src/lib/components/channel/MessageInput.svelte

@@ -455,7 +455,7 @@
 
 
 						<div class="px-2.5">
 						<div class="px-2.5">
 							<div
 							<div
-								class="scrollbar-hidden font-primary text-left bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
+								class="scrollbar-hidden font-primary text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
 							>
 							>
 								<RichTextInput
 								<RichTextInput
 									bind:value={content}
 									bind:value={content}
@@ -513,7 +513,7 @@
 									}}
 									}}
 								>
 								>
 									<button
 									<button
-										class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5 outline-none focus:outline-none"
+										class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5 outline-hidden focus:outline-hidden"
 										type="button"
 										type="button"
 										aria-label="More"
 										aria-label="More"
 									>
 									>

+ 1 - 1
src/lib/components/channel/MessageInput/InputMenu.svelte

@@ -44,7 +44,7 @@
 
 
 	<div slot="content">
 	<div slot="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="w-full max-w-[200px] rounded-xl px-1 py-1  border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			class="w-full max-w-[200px] rounded-xl px-1 py-1  border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
 			sideOffset={15}
 			sideOffset={15}
 			alignOffset={-8}
 			alignOffset={-8}
 			side="top"
 			side="top"

+ 4 - 4
src/lib/components/channel/Messages/Message.svelte

@@ -72,7 +72,7 @@
 				class=" absolute {showButtons ? '' : 'invisible group-hover:visible'} right-1 -top-2 z-10"
 				class=" absolute {showButtons ? '' : 'invisible group-hover:visible'} right-1 -top-2 z-10"
 			>
 			>
 				<div
 				<div
-					class="flex gap-1 rounded-lg bg-white dark:bg-gray-850 shadow-md p-0.5 border border-gray-100 dark:border-gray-800"
+					class="flex gap-1 rounded-lg bg-white dark:bg-gray-850 shadow-md p-0.5 border border-gray-100 dark:border-gray-850"
 				>
 				>
 					<ReactionPicker
 					<ReactionPicker
 						onClose={() => (showButtons = false)}
 						onClose={() => (showButtons = false)}
@@ -138,7 +138,7 @@
 			dir={$settings.chatDirection}
 			dir={$settings.chatDirection}
 		>
 		>
 			<div
 			<div
-				class={`flex-shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'} w-9`}
+				class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'} w-9`}
 			>
 			>
 				{#if showUserProfile}
 				{#if showUserProfile}
 					<ProfilePreview user={message.user}>
 					<ProfilePreview user={message.user}>
@@ -153,7 +153,7 @@
 
 
 					{#if message.created_at}
 					{#if message.created_at}
 						<div
 						<div
-							class="mt-1.5 flex flex-shrink-0 items-center text-xs self-center invisible group-hover:visible text-gray-500 font-medium first-letter:capitalize"
+							class="mt-1.5 flex shrink-0 items-center text-xs self-center invisible group-hover:visible text-gray-500 font-medium first-letter:capitalize"
 						>
 						>
 							<Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}>
 							<Tooltip content={dayjs(message.created_at / 1000000).format('LLLL')}>
 								{dayjs(message.created_at / 1000000).format('HH:mm')}
 								{dayjs(message.created_at / 1000000).format('HH:mm')}
@@ -206,7 +206,7 @@
 				{#if edit}
 				{#if edit}
 					<div class="py-2">
 					<div class="py-2">
 						<Textarea
 						<Textarea
-							className=" bg-transparent outline-none w-full resize-none"
+							className=" bg-transparent outline-hidden w-full resize-none"
 							bind:value={editedContent}
 							bind:value={editedContent}
 							onKeydown={(e) => {
 							onKeydown={(e) => {
 								if (e.key === 'Escape') {
 								if (e.key === 'Escape') {

+ 1 - 1
src/lib/components/channel/Messages/Message/ProfilePreview.svelte

@@ -29,7 +29,7 @@
 
 
 	<slot name="content">
 	<slot name="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="max-w-full w-[240px] rounded-lg z-[9999] bg-white dark:bg-black dark:text-white shadow-lg"
+			class="max-w-full w-[240px] rounded-lg z-9999 bg-white dark:bg-black dark:text-white shadow-lg"
 			sideOffset={8}
 			sideOffset={8}
 			{side}
 			{side}
 			{align}
 			{align}

+ 2 - 2
src/lib/components/channel/Messages/Message/ReactionPicker.svelte

@@ -107,7 +107,7 @@
 		<slot />
 		<slot />
 	</DropdownMenu.Trigger>
 	</DropdownMenu.Trigger>
 	<DropdownMenu.Content
 	<DropdownMenu.Content
-		class="max-w-full w-80 bg-gray-50 dark:bg-gray-850 rounded-lg z-[9999] shadow-lg dark:text-white"
+		class="max-w-full w-80 bg-gray-50 dark:bg-gray-850 rounded-lg z-9999 shadow-lg dark:text-white"
 		sideOffset={8}
 		sideOffset={8}
 		{side}
 		{side}
 		{align}
 		{align}
@@ -116,7 +116,7 @@
 		<div class="mb-1 px-3 pt-2 pb-2">
 		<div class="mb-1 px-3 pt-2 pb-2">
 			<input
 			<input
 				type="text"
 				type="text"
-				class="w-full text-sm bg-transparent outline-none"
+				class="w-full text-sm bg-transparent outline-hidden"
 				placeholder="Search all emojis"
 				placeholder="Search all emojis"
 				bind:value={search}
 				bind:value={search}
 			/>
 			/>

+ 1 - 1
src/lib/components/channel/Navbar.svelte

@@ -18,7 +18,7 @@
 
 
 <nav class="sticky top-0 z-30 w-full px-1.5 py-1.5 -mb-8 flex items-center drag-region">
 <nav class="sticky top-0 z-30 w-full px-1.5 py-1.5 -mb-8 flex items-center drag-region">
 	<div
 	<div
-		class=" bg-gradient-to-b via-50% from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pointer-events-none absolute inset-0 -bottom-7 z-[-1] blur"
+		class=" bg-linear-to-b via-50% from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pointer-events-none absolute inset-0 -bottom-7 z-[-1]"
 	></div>
 	></div>
 
 
 	<div class=" flex max-w-full w-full mx-auto px-1 pt-0.5 bg-transparent">
 	<div class=" flex max-w-full w-full mx-auto px-1 pt-0.5 bg-transparent">

+ 2 - 2
src/lib/components/chat/Chat.svelte

@@ -1241,7 +1241,7 @@
 			// Response not done
 			// Response not done
 			return;
 			return;
 		}
 		}
-		if (messages.length != 0 && messages.at(-1).error) {
+		if (messages.length != 0 && messages.at(-1).error && !messages.at(-1).content) {
 			// Error in response
 			// Error in response
 			toast.error($i18n.t(`Oops! There was an error in the previous response.`));
 			toast.error($i18n.t(`Oops! There was an error in the previous response.`));
 			return;
 			return;
@@ -1896,7 +1896,7 @@
 			/>
 			/>
 
 
 			<div
 			<div
-				class="absolute top-0 left-0 w-full h-full bg-gradient-to-t from-white to-white/85 dark:from-gray-900 dark:to-[#171717]/90 z-0"
+				class="absolute top-0 left-0 w-full h-full bg-linear-to-t from-white to-white/85 dark:from-gray-900 dark:to-gray-900/90 z-0"
 			/>
 			/>
 		{/if}
 		{/if}
 
 

+ 2 - 2
src/lib/components/chat/ChatControls.svelte

@@ -195,7 +195,7 @@
 
 
 		{#if $showControls}
 		{#if $showControls}
 			<PaneResizer class="relative flex w-2 items-center justify-center bg-background group">
 			<PaneResizer class="relative flex w-2 items-center justify-center bg-background group">
-				<div class="z-10 flex h-7 w-5 items-center justify-center rounded-sm">
+				<div class="z-10 flex h-7 w-5 items-center justify-center rounded-xs">
 					<EllipsisVertical className="size-4 invisible group-hover:visible" />
 					<EllipsisVertical className="size-4 invisible group-hover:visible" />
 				</div>
 				</div>
 			</PaneResizer>
 			</PaneResizer>
@@ -230,7 +230,7 @@
 					<div
 					<div
 						class="w-full {($showOverview || $showArtifacts) && !$showCallOverlay
 						class="w-full {($showOverview || $showArtifacts) && !$showCallOverlay
 							? ' '
 							? ' '
-							: 'px-4 py-4 bg-white dark:shadow-lg dark:bg-gray-850  border border-gray-50 dark:border-gray-850'}  rounded-xl z-40 pointer-events-auto overflow-y-auto scrollbar-hidden"
+							: 'px-4 py-4 bg-white dark:shadow-lg dark:bg-gray-850  border border-gray-100 dark:border-gray-850'}  rounded-xl z-40 pointer-events-auto overflow-y-auto scrollbar-hidden"
 					>
 					>
 						{#if $showCallOverlay}
 						{#if $showCallOverlay}
 							<div class="w-full h-full flex justify-center">
 							<div class="w-full h-full flex justify-center">

+ 5 - 5
src/lib/components/chat/ContentRenderer/FloatingButtons.svelte

@@ -221,7 +221,7 @@
 
 
 <div
 <div
 	id={`floating-buttons-${id}`}
 	id={`floating-buttons-${id}`}
-	class="absolute rounded-lg mt-1 text-xs z-[9999]"
+	class="absolute rounded-lg mt-1 text-xs z-9999"
 	style="display: none"
 	style="display: none"
 >
 >
 	{#if responseContent === null}
 	{#if responseContent === null}
@@ -230,7 +230,7 @@
 				class="flex flex-row gap-0.5 shrink-0 p-1 bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-lg shadow-xl"
 				class="flex flex-row gap-0.5 shrink-0 p-1 bg-white dark:bg-gray-850 dark:text-gray-100 text-medium rounded-lg shadow-xl"
 			>
 			>
 				<button
 				<button
-					class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-1 min-w-fit"
+					class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-sm flex items-center gap-1 min-w-fit"
 					on:click={async () => {
 					on:click={async () => {
 						selectedText = window.getSelection().toString();
 						selectedText = window.getSelection().toString();
 						floatingInput = true;
 						floatingInput = true;
@@ -249,7 +249,7 @@
 					<div class="shrink-0">Ask</div>
 					<div class="shrink-0">Ask</div>
 				</button>
 				</button>
 				<button
 				<button
-					class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded flex items-center gap-1 min-w-fit"
+					class="px-1 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-sm flex items-center gap-1 min-w-fit"
 					on:click={() => {
 					on:click={() => {
 						selectedText = window.getSelection().toString();
 						selectedText = window.getSelection().toString();
 						explainHandler();
 						explainHandler();
@@ -262,12 +262,12 @@
 			</div>
 			</div>
 		{:else}
 		{:else}
 			<div
 			<div
-				class="py-1 flex dark:text-gray-100 bg-gray-50 dark:bg-gray-800 border dark:border-gray-800 w-72 rounded-full shadow-xl"
+				class="py-1 flex dark:text-gray-100 bg-gray-50 dark:bg-gray-800 border dark:border-gray-850 w-72 rounded-full shadow-xl"
 			>
 			>
 				<input
 				<input
 					type="text"
 					type="text"
 					id="floating-message-input"
 					id="floating-message-input"
-					class="ml-5 bg-transparent outline-none w-full flex-1 text-sm"
+					class="ml-5 bg-transparent outline-hidden w-full flex-1 text-sm"
 					placeholder={$i18n.t('Ask a question')}
 					placeholder={$i18n.t('Ask a question')}
 					bind:value={floatingInputValue}
 					bind:value={floatingInputValue}
 					on:keydown={(e) => {
 					on:keydown={(e) => {

+ 1 - 1
src/lib/components/chat/Controls/Controls.svelte

@@ -74,7 +74,7 @@
 				<div class="" slot="content">
 				<div class="" slot="content">
 					<textarea
 					<textarea
 						bind:value={params.system}
 						bind:value={params.system}
-						class="w-full text-xs py-1.5 bg-transparent outline-none resize-none"
+						class="w-full text-xs py-1.5 bg-transparent outline-hidden resize-none"
 						rows="4"
 						rows="4"
 						placeholder={$i18n.t('Enter system prompt')}
 						placeholder={$i18n.t('Enter system prompt')}
 					/>
 					/>

+ 2 - 2
src/lib/components/chat/Controls/Valves.svelte

@@ -148,7 +148,7 @@
 				<div class="flex gap-2">
 				<div class="flex gap-2">
 					<div class="flex-1">
 					<div class="flex-1">
 						<select
 						<select
-							class="  w-full rounded text-xs py-2 px-1 bg-transparent outline-none"
+							class="  w-full rounded-sm text-xs py-2 px-1 bg-transparent outline-hidden"
 							bind:value={tab}
 							bind:value={tab}
 							placeholder="Select"
 							placeholder="Select"
 						>
 						>
@@ -161,7 +161,7 @@
 
 
 					<div class="flex-1">
 					<div class="flex-1">
 						<select
 						<select
-							class="w-full rounded py-2 px-1 text-xs bg-transparent outline-none"
+							class="w-full rounded-sm py-2 px-1 text-xs bg-transparent outline-hidden"
 							bind:value={selectedId}
 							bind:value={selectedId}
 							on:change={async () => {
 							on:change={async () => {
 								await tick();
 								await tick();

+ 10 - 10
src/lib/components/chat/MessageInput.svelte

@@ -394,7 +394,7 @@
 				<div class="w-full relative">
 				<div class="w-full relative">
 					{#if atSelectedModel !== undefined || selectedToolIds.length > 0 || webSearchEnabled || ($settings?.webSearch ?? false) === 'always' || imageGenerationEnabled || codeInterpreterEnabled}
 					{#if atSelectedModel !== undefined || selectedToolIds.length > 0 || webSearchEnabled || ($settings?.webSearch ?? false) === 'always' || imageGenerationEnabled || codeInterpreterEnabled}
 						<div
 						<div
-							class="px-3 pb-0.5 pt-1.5 text-left w-full flex flex-col absolute bottom-0 left-0 right-0 bg-gradient-to-t from-white dark:from-gray-900 z-10"
+							class="px-3 pb-0.5 pt-1.5 text-left w-full flex flex-col absolute bottom-0 left-0 right-0 bg-linear-to-t from-white dark:from-gray-900 z-10"
 						>
 						>
 							{#if selectedToolIds.length > 0}
 							{#if selectedToolIds.length > 0}
 								<div class="flex items-center justify-between w-full">
 								<div class="flex items-center justify-between w-full">
@@ -413,7 +413,7 @@
 											}) as tool, toolIdx (toolIdx)}
 											}) as tool, toolIdx (toolIdx)}
 												<Tooltip
 												<Tooltip
 													content={tool?.meta?.description ?? ''}
 													content={tool?.meta?.description ?? ''}
-													className=" {toolIdx !== 0 ? 'pl-0.5' : ''} flex-shrink-0"
+													className=" {toolIdx !== 0 ? 'pl-0.5' : ''} shrink-0"
 													placement="top"
 													placement="top"
 												>
 												>
 													{tool.name}
 													{tool.name}
@@ -682,7 +682,7 @@
 								<div class="px-2.5">
 								<div class="px-2.5">
 									{#if $settings?.richTextInput ?? true}
 									{#if $settings?.richTextInput ?? true}
 										<div
 										<div
-											class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 resize-none h-fit max-h-80 overflow-auto"
+											class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 resize-none h-fit max-h-80 overflow-auto"
 										>
 										>
 											<RichTextInput
 											<RichTextInput
 												bind:this={chatInputElement}
 												bind:this={chatInputElement}
@@ -886,7 +886,7 @@
 										<textarea
 										<textarea
 											id="chat-input"
 											id="chat-input"
 											bind:this={chatInputElement}
 											bind:this={chatInputElement}
-											class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-none w-full pt-3 px-1 resize-none"
+											class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 resize-none"
 											placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
 											placeholder={placeholder ? placeholder : $i18n.t('Send a Message')}
 											bind:value={prompt}
 											bind:value={prompt}
 											on:keypress={(e) => {
 											on:keypress={(e) => {
@@ -1114,7 +1114,7 @@
 											}}
 											}}
 										>
 										>
 											<button
 											<button
-												class="bg-transparent hover:bg-gray-100 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5 outline-none focus:outline-none"
+												class="bg-transparent hover:bg-gray-100 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5 outline-hidden focus:outline-hidden"
 												type="button"
 												type="button"
 												aria-label="More"
 												aria-label="More"
 											>
 											>
@@ -1138,10 +1138,10 @@
 														<button
 														<button
 															on:click|preventDefault={() => (webSearchEnabled = !webSearchEnabled)}
 															on:click|preventDefault={() => (webSearchEnabled = !webSearchEnabled)}
 															type="button"
 															type="button"
-															class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {webSearchEnabled ||
+															class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {webSearchEnabled ||
 															($settings?.webSearch ?? false) === 'always'
 															($settings?.webSearch ?? false) === 'always'
 																? 'bg-blue-100 dark:bg-blue-500/20 text-blue-500 dark:text-blue-400'
 																? 'bg-blue-100 dark:bg-blue-500/20 text-blue-500 dark:text-blue-400'
-																: 'bg-transparent text-gray-600 dark:text-gray-400 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'}"
+																: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'}"
 														>
 														>
 															<GlobeAlt className="size-5" strokeWidth="1.75" />
 															<GlobeAlt className="size-5" strokeWidth="1.75" />
 															<span
 															<span
@@ -1158,7 +1158,7 @@
 															on:click|preventDefault={() =>
 															on:click|preventDefault={() =>
 																(imageGenerationEnabled = !imageGenerationEnabled)}
 																(imageGenerationEnabled = !imageGenerationEnabled)}
 															type="button"
 															type="button"
-															class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {imageGenerationEnabled
+															class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {imageGenerationEnabled
 																? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400'
 																? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400'
 																: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
 																: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
 														>
 														>
@@ -1177,7 +1177,7 @@
 															on:click|preventDefault={() =>
 															on:click|preventDefault={() =>
 																(codeInterpreterEnabled = !codeInterpreterEnabled)}
 																(codeInterpreterEnabled = !codeInterpreterEnabled)}
 															type="button"
 															type="button"
-															class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-none max-w-full overflow-hidden {codeInterpreterEnabled
+															class="px-1.5 @sm:px-2.5 py-1.5 flex gap-1.5 items-center text-sm rounded-full font-medium transition-colors duration-300 focus:outline-hidden max-w-full overflow-hidden {codeInterpreterEnabled
 																? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400'
 																? 'bg-gray-100 dark:bg-gray-500/20 text-gray-600 dark:text-gray-400'
 																: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
 																: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
 														>
 														>
@@ -1193,7 +1193,7 @@
 										</div>
 										</div>
 									</div>
 									</div>
 
 
-									<div class="self-end flex space-x-1 mr-1 flex-shrink-0">
+									<div class="self-end flex space-x-1 mr-1 shrink-0">
 										{#if !history?.currentId || history.messages[history.currentId]?.done == true}
 										{#if !history?.currentId || history.messages[history.currentId]?.done == true}
 											<Tooltip content={$i18n.t('Record voice')}>
 											<Tooltip content={$i18n.t('Record voice')}>
 												<button
 												<button

+ 1 - 1
src/lib/components/chat/MessageInput/CallOverlay/VideoInputMenu.svelte

@@ -26,7 +26,7 @@
 
 
 	<div slot="content">
 	<div slot="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="w-full max-w-[180px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-[9999] bg-white dark:bg-gray-900 dark:text-white shadow-sm"
+			class="w-full max-w-[180px] rounded-lg px-1 py-1.5 border border-gray-300/30 dark:border-gray-700/50 z-9999 bg-white dark:bg-gray-900 dark:text-white shadow-xs"
 			sideOffset={6}
 			sideOffset={6}
 			side="top"
 			side="top"
 			align="start"
 			align="start"

+ 1 - 1
src/lib/components/chat/MessageInput/Commands.svelte

@@ -112,7 +112,7 @@
 			id="commands-container"
 			id="commands-container"
 			class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
 			class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
 		>
 		>
-			<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850">
+			<div class="flex w-full rounded-xl border border-gray-100 dark:border-gray-850">
 				<div
 				<div
 					class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
 					class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
 				>
 				>

+ 6 - 6
src/lib/components/chat/MessageInput/Commands/Knowledge.svelte

@@ -161,7 +161,7 @@
 		id="commands-container"
 		id="commands-container"
 		class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
 		class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
 	>
 	>
-		<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850">
+		<div class="flex w-full rounded-xl border border-gray-100 dark:border-gray-850">
 			<div
 			<div
 				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
 				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
 			>
 			>
@@ -185,25 +185,25 @@
 								<div class=" font-medium text-black dark:text-gray-100 flex items-center gap-1">
 								<div class=" font-medium text-black dark:text-gray-100 flex items-center gap-1">
 									{#if item.legacy}
 									{#if item.legacy}
 										<div
 										<div
-											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded-sm uppercase text-xs font-bold px-1 shrink-0"
 										>
 										>
 											Legacy
 											Legacy
 										</div>
 										</div>
 									{:else if item?.meta?.document}
 									{:else if item?.meta?.document}
 										<div
 										<div
-											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded-sm uppercase text-xs font-bold px-1 shrink-0"
 										>
 										>
 											Document
 											Document
 										</div>
 										</div>
 									{:else if item?.type === 'file'}
 									{:else if item?.type === 'file'}
 										<div
 										<div
-											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+											class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded-sm uppercase text-xs font-bold px-1 shrink-0"
 										>
 										>
 											File
 											File
 										</div>
 										</div>
 									{:else}
 									{:else}
 										<div
 										<div
-											class="bg-green-500/20 text-green-700 dark:text-green-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+											class="bg-green-500/20 text-green-700 dark:text-green-200 rounded-sm uppercase text-xs font-bold px-1 shrink-0"
 										>
 										>
 											Collection
 											Collection
 										</div>
 										</div>
@@ -238,7 +238,7 @@
 													class=" font-medium text-black dark:text-gray-100 flex items-center gap-1"
 													class=" font-medium text-black dark:text-gray-100 flex items-center gap-1"
 												>
 												>
 													<div
 													<div
-														class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded uppercase text-xs font-bold px-1 flex-shrink-0"
+														class="bg-gray-500/20 text-gray-700 dark:text-gray-200 rounded-sm uppercase text-xs font-bold px-1 shrink-0"
 													>
 													>
 														File
 														File
 													</div>
 													</div>

+ 1 - 1
src/lib/components/chat/MessageInput/Commands/Models.svelte

@@ -70,7 +70,7 @@
 		id="commands-container"
 		id="commands-container"
 		class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
 		class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
 	>
 	>
-		<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850">
+		<div class="flex w-full rounded-xl border border-gray-100 dark:border-gray-850">
 			<div
 			<div
 				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
 				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
 			>
 			>

+ 1 - 1
src/lib/components/chat/MessageInput/Commands/Prompts.svelte

@@ -139,7 +139,7 @@
 		id="commands-container"
 		id="commands-container"
 		class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
 		class="px-2 mb-2 text-left w-full absolute bottom-0 left-0 right-0 z-10"
 	>
 	>
-		<div class="flex w-full rounded-xl border border-gray-50 dark:border-gray-850">
+		<div class="flex w-full rounded-xl border border-gray-100 dark:border-gray-850">
 			<div
 			<div
 				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
 				class="max-h-60 flex flex-col w-full rounded-xl bg-white dark:bg-gray-900 dark:text-gray-100"
 			>
 			>

+ 2 - 2
src/lib/components/chat/MessageInput/FilesOverlay.svelte

@@ -19,12 +19,12 @@
 		bind:this={overlayElement}
 		bind:this={overlayElement}
 		class="fixed {$showSidebar
 		class="fixed {$showSidebar
 			? 'left-0 md:left-[260px] md:w-[calc(100%-260px)]'
 			? 'left-0 md:left-[260px] md:w-[calc(100%-260px)]'
-			: 'left-0'}  fixed top-0 right-0 bottom-0 w-full h-full flex z-[9999] touch-none pointer-events-none"
+			: 'left-0'}  fixed top-0 right-0 bottom-0 w-full h-full flex z-9999 touch-none pointer-events-none"
 		id="dropzone"
 		id="dropzone"
 		role="region"
 		role="region"
 		aria-label="Drag and Drop Container"
 		aria-label="Drag and Drop Container"
 	>
 	>
-		<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
+		<div class="absolute w-full h-full backdrop-blur-sm bg-gray-800/40 flex justify-center">
 			<div class="m-auto pt-64 flex flex-col justify-center">
 			<div class="m-auto pt-64 flex flex-col justify-center">
 				<div class="max-w-md">
 				<div class="max-w-md">
 					<AddFilesPlaceholder />
 					<AddFilesPlaceholder />

+ 3 - 3
src/lib/components/chat/MessageInput/InputMenu.svelte

@@ -92,7 +92,7 @@
 
 
 	<div slot="content">
 	<div slot="content">
 		<DropdownMenu.Content
 		<DropdownMenu.Content
-			class="w-full max-w-[220px] rounded-xl px-1 py-1  border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			class="w-full max-w-[220px] rounded-xl px-1 py-1  border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-sm"
 			sideOffset={15}
 			sideOffset={15}
 			alignOffset={-8}
 			alignOffset={-8}
 			side="top"
 			side="top"
@@ -114,7 +114,7 @@
 									placement="top-start"
 									placement="top-start"
 									className="flex flex-1 gap-2 items-center"
 									className="flex flex-1 gap-2 items-center"
 								>
 								>
-									<div class="flex-shrink-0">
+									<div class="shrink-0">
 										<WrenchSolid />
 										<WrenchSolid />
 									</div>
 									</div>
 
 
@@ -122,7 +122,7 @@
 								</Tooltip>
 								</Tooltip>
 							</div>
 							</div>
 
 
-							<div class=" flex-shrink-0">
+							<div class=" shrink-0">
 								<Switch
 								<Switch
 									state={tools[toolId].enabled}
 									state={tools[toolId].enabled}
 									on:change={async (e) => {
 									on:change={async (e) => {

+ 1 - 1
src/lib/components/chat/MessageInput/VoiceRecording.svelte

@@ -362,7 +362,7 @@
 			{#each visualizerData.slice().reverse() as rms}
 			{#each visualizerData.slice().reverse() as rms}
 				<div class="flex items-center h-full">
 				<div class="flex items-center h-full">
 					<div
 					<div
-						class="w-[2px] flex-shrink-0
+						class="w-[2px] shrink-0
                     
                     
                     {loading
                     {loading
 							? ' bg-gray-500 dark:bg-gray-400   '
 							? ' bg-gray-500 dark:bg-gray-400   '

+ 6 - 6
src/lib/components/chat/Messages/Citations.svelte

@@ -101,7 +101,7 @@
 				{#each citations as citation, idx}
 				{#each citations as citation, idx}
 					<button
 					<button
 						id={`source-${idx}`}
 						id={`source-${idx}`}
-						class="no-toggle outline-none flex dark:text-gray-300 p-1 bg-white dark:bg-gray-900 rounded-xl max-w-96"
+						class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-white dark:bg-gray-900 rounded-xl max-w-96"
 						on:click={() => {
 						on:click={() => {
 							showCitationModal = true;
 							showCitationModal = true;
 							selectedCitation = citation;
 							selectedCitation = citation;
@@ -133,14 +133,14 @@
 					<div
 					<div
 						class="flex-1 flex items-center gap-1 overflow-auto scrollbar-none w-full max-w-full"
 						class="flex-1 flex items-center gap-1 overflow-auto scrollbar-none w-full max-w-full"
 					>
 					>
-						<span class="whitespace-nowrap hidden sm:inline flex-shrink-0"
+						<span class="whitespace-nowrap hidden sm:inline shrink-0"
 							>{$i18n.t('References from')}</span
 							>{$i18n.t('References from')}</span
 						>
 						>
 						<div class="flex items-center overflow-auto scrollbar-none w-full max-w-full flex-1">
 						<div class="flex items-center overflow-auto scrollbar-none w-full max-w-full flex-1">
 							<div class="flex text-xs font-medium items-center">
 							<div class="flex text-xs font-medium items-center">
 								{#each citations.slice(0, 2) as citation, idx}
 								{#each citations.slice(0, 2) as citation, idx}
 									<button
 									<button
-										class="no-toggle outline-none flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
+										class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
 										on:click={() => {
 										on:click={() => {
 											showCitationModal = true;
 											showCitationModal = true;
 											selectedCitation = citation;
 											selectedCitation = citation;
@@ -161,13 +161,13 @@
 								{/each}
 								{/each}
 							</div>
 							</div>
 						</div>
 						</div>
-						<div class="flex items-center gap-1 whitespace-nowrap flex-shrink-0">
+						<div class="flex items-center gap-1 whitespace-nowrap shrink-0">
 							<span class="hidden sm:inline">{$i18n.t('and')}</span>
 							<span class="hidden sm:inline">{$i18n.t('and')}</span>
 							{citations.length - 2}
 							{citations.length - 2}
 							<span>{$i18n.t('more')}</span>
 							<span>{$i18n.t('more')}</span>
 						</div>
 						</div>
 					</div>
 					</div>
-					<div class="flex-shrink-0">
+					<div class="shrink-0">
 						{#if isCollapsibleOpen}
 						{#if isCollapsibleOpen}
 							<ChevronUp strokeWidth="3.5" className="size-3.5" />
 							<ChevronUp strokeWidth="3.5" className="size-3.5" />
 						{:else}
 						{:else}
@@ -180,7 +180,7 @@
 						{#each citations as citation, idx}
 						{#each citations as citation, idx}
 							<button
 							<button
 								id={`source-${idx}`}
 								id={`source-${idx}`}
-								class="no-toggle outline-none flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
+								class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
 								on:click={() => {
 								on:click={() => {
 									showCitationModal = true;
 									showCitationModal = true;
 									selectedCitation = citation;
 									selectedCitation = citation;

+ 5 - 3
src/lib/components/chat/Messages/CitationsModal.svelte

@@ -90,7 +90,7 @@
 							>
 							>
 								<div class="text-sm dark:text-gray-400 flex items-center gap-2 w-fit">
 								<div class="text-sm dark:text-gray-400 flex items-center gap-2 w-fit">
 									<a
 									<a
-										class="hover:text-gray-500 hover:dark:text-gray-100 underline flex-grow"
+										class="hover:text-gray-500 dark:hover:text-gray-100 underline grow"
 										href={document?.metadata?.file_id
 										href={document?.metadata?.file_id
 											? `${WEBUI_API_BASE_URL}/files/${document?.metadata?.file_id}/content${document?.metadata?.page !== undefined ? `#page=${document.metadata.page + 1}` : ''}`
 											? `${WEBUI_API_BASE_URL}/files/${document?.metadata?.file_id}/content${document?.metadata?.page !== undefined ? `#page=${document.metadata.page + 1}` : ''}`
 											: document.source?.url?.includes('http')
 											: document.source?.url?.includes('http')
@@ -122,7 +122,9 @@
 										<div class="text-sm my-1 dark:text-gray-400 flex items-center gap-2 w-fit">
 										<div class="text-sm my-1 dark:text-gray-400 flex items-center gap-2 w-fit">
 											{#if showPercentage}
 											{#if showPercentage}
 												{@const percentage = calculatePercentage(document.distance)}
 												{@const percentage = calculatePercentage(document.distance)}
-												<span class={`px-1 rounded font-medium ${getRelevanceColor(percentage)}`}>
+												<span
+													class={`px-1 rounded-sm font-medium ${getRelevanceColor(percentage)}`}
+												>
 													{percentage.toFixed(2)}%
 													{percentage.toFixed(2)}%
 												</span>
 												</span>
 												<span class="text-gray-500 dark:text-gray-500">
 												<span class="text-gray-500 dark:text-gray-500">
@@ -166,7 +168,7 @@
 					</div>
 					</div>
 
 
 					{#if documentIdx !== mergedDocuments.length - 1}
 					{#if documentIdx !== mergedDocuments.length - 1}
-						<hr class=" dark:border-gray-850 my-3" />
+						<hr class="border-gray-100 dark:border-gray-850 my-3" />
 					{/if}
 					{/if}
 				{/each}
 				{/each}
 			</div>
 			</div>

+ 2 - 2
src/lib/components/chat/Messages/CodeBlock.svelte

@@ -302,7 +302,7 @@
 		{#if lang === 'mermaid'}
 		{#if lang === 'mermaid'}
 			{#if mermaidHtml}
 			{#if mermaidHtml}
 				<SvgPanZoom
 				<SvgPanZoom
-					className=" border border-gray-50 dark:border-gray-850 rounded-lg max-h-fit overflow-hidden"
+					className=" border border-gray-100 dark:border-gray-850 rounded-lg max-h-fit overflow-hidden"
 					svg={mermaidHtml}
 					svg={mermaidHtml}
 					content={_token.text}
 					content={_token.text}
 				/>
 				/>
@@ -377,7 +377,7 @@
 
 
 			{#if executing || stdout || stderr || result}
 			{#if executing || stdout || stderr || result}
 				<div
 				<div
-					class="bg-gray-50 dark:bg-[#202123] dark:text-white !rounded-b-lg py-4 px-4 flex flex-col gap-2"
+					class="bg-gray-50 dark:bg-[#202123] dark:text-white rounded-b-lg! py-4 px-4 flex flex-col gap-2"
 				>
 				>
 					{#if executing}
 					{#if executing}
 						<div class=" ">
 						<div class=" ">

+ 1 - 1
src/lib/components/chat/Messages/CodeExecutionModal.svelte

@@ -99,7 +99,7 @@
 				{/if}
 				{/if}
 				{#if codeExecution?.result?.files && codeExecution?.result?.files.length > 0}
 				{#if codeExecution?.result?.files && codeExecution?.result?.files.length > 0}
 					<div class="flex flex-col w-full">
 					<div class="flex flex-col w-full">
-						<hr class=" dark:border-gray-850 my-2" />
+						<hr class="border-gray-100 dark:border-gray-850 my-2" />
 						<div class=" text-sm font-medium dark:text-gray-300">
 						<div class=" text-sm font-medium dark:text-gray-300">
 							{$i18n.t('Files')}
 							{$i18n.t('Files')}
 						</div>
 						</div>

+ 3 - 9
src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte

@@ -44,13 +44,9 @@
 	{:else if token.type === 'image'}
 	{:else if token.type === 'image'}
 		<Image src={token.href} alt={token.text} />
 		<Image src={token.href} alt={token.text} />
 	{:else if token.type === 'strong'}
 	{:else if token.type === 'strong'}
-		<strong>
-			<svelte:self id={`${id}-strong`} tokens={token.tokens} {onSourceClick} />
-		</strong>
+		<strong><svelte:self id={`${id}-strong`} tokens={token.tokens} {onSourceClick} /></strong>
 	{:else if token.type === 'em'}
 	{:else if token.type === 'em'}
-		<em>
-			<svelte:self id={`${id}-em`} tokens={token.tokens} {onSourceClick} />
-		</em>
+		<em><svelte:self id={`${id}-em`} tokens={token.tokens} {onSourceClick} /></em>
 	{:else if token.type === 'codespan'}
 	{:else if token.type === 'codespan'}
 		<!-- svelte-ignore a11y-click-events-have-key-events -->
 		<!-- svelte-ignore a11y-click-events-have-key-events -->
 		<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
 		<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
@@ -64,9 +60,7 @@
 	{:else if token.type === 'br'}
 	{:else if token.type === 'br'}
 		<br />
 		<br />
 	{:else if token.type === 'del'}
 	{:else if token.type === 'del'}
-		<del>
-			<svelte:self id={`${id}-del`} tokens={token.tokens} {onSourceClick} />
-		</del>
+		<del><svelte:self id={`${id}-del`} tokens={token.tokens} {onSourceClick} /></del>
 	{:else if token.type === 'inlineKatex'}
 	{:else if token.type === 'inlineKatex'}
 		{#if token.text}
 		{#if token.text}
 			<KatexRenderer content={token.text} displayMode={false} />
 			<KatexRenderer content={token.text} displayMode={false} />

+ 4 - 4
src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte

@@ -74,7 +74,7 @@
 <!-- {JSON.stringify(tokens)} -->
 <!-- {JSON.stringify(tokens)} -->
 {#each tokens as token, tokenIdx (tokenIdx)}
 {#each tokens as token, tokenIdx (tokenIdx)}
 	{#if token.type === 'hr'}
 	{#if token.type === 'hr'}
-		<hr class=" border-gray-50 dark:border-gray-850" />
+		<hr class=" border-gray-100 dark:border-gray-850" />
 	{:else if token.type === 'heading'}
 	{:else if token.type === 'heading'}
 		<svelte:element this={headerComponent(token.depth)}>
 		<svelte:element this={headerComponent(token.depth)}>
 			<MarkdownInlineTokens id={`${id}-${tokenIdx}-h`} tokens={token.tokens} {onSourceClick} />
 			<MarkdownInlineTokens id={`${id}-${tokenIdx}-h`} tokens={token.tokens} {onSourceClick} />
@@ -115,11 +115,11 @@
 							{#each token.header as header, headerIdx}
 							{#each token.header as header, headerIdx}
 								<th
 								<th
 									scope="col"
 									scope="col"
-									class="!px-3 !py-1.5 cursor-pointer border border-gray-50 dark:border-gray-850"
+									class="px-3! py-1.5! cursor-pointer border border-gray-100 dark:border-gray-850"
 									style={token.align[headerIdx] ? '' : `text-align: ${token.align[headerIdx]}`}
 									style={token.align[headerIdx] ? '' : `text-align: ${token.align[headerIdx]}`}
 								>
 								>
 									<div class="flex flex-col gap-1.5 text-start">
 									<div class="flex flex-col gap-1.5 text-start">
-										<div class="flex-shrink-0 break-normal">
+										<div class="shrink-0 break-normal">
 											<MarkdownInlineTokens
 											<MarkdownInlineTokens
 												id={`${id}-${tokenIdx}-header-${headerIdx}`}
 												id={`${id}-${tokenIdx}-header-${headerIdx}`}
 												tokens={header.tokens}
 												tokens={header.tokens}
@@ -136,7 +136,7 @@
 							<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
 							<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
 								{#each row ?? [] as cell, cellIdx}
 								{#each row ?? [] as cell, cellIdx}
 									<td
 									<td
-										class="!px-3 !py-1.5 text-gray-900 dark:text-white w-max border border-gray-50 dark:border-gray-850"
+										class="px-3! py-1.5! text-gray-900 dark:text-white w-max border border-gray-100 dark:border-gray-850"
 										style={token.align[cellIdx] ? '' : `text-align: ${token.align[cellIdx]}`}
 										style={token.align[cellIdx] ? '' : `text-align: ${token.align[cellIdx]}`}
 									>
 									>
 										<div class="flex flex-col break-normal">
 										<div class="flex flex-col break-normal">

+ 16 - 1
src/lib/components/chat/Messages/Markdown/Source.svelte

@@ -17,6 +17,21 @@
 		return attrs;
 		return attrs;
 	}
 	}
 
 
+	// Helper function to return only the domain from a URL
+	function getDomain(url: string): string {
+		const domain = url.replace('http://', '').replace('https://', '').split(/[/?#]/)[0];
+		return domain;
+	}
+
+	// Helper function to check if text is a URL and return the domain
+	function formattedTitle(title: string): string {
+		if (title.startsWith('http')) {
+			return getDomain(title);
+		}
+
+		return title;
+	}
+
 	$: attributes = extractAttributes(token.text);
 	$: attributes = extractAttributes(token.text);
 </script>
 </script>
 
 
@@ -27,6 +42,6 @@
 	}}
 	}}
 >
 >
 	<span class="line-clamp-1">
 	<span class="line-clamp-1">
-		{attributes.title}
+		{formattedTitle(attributes.title)}
 	</span>
 	</span>
 </button>
 </button>

+ 3 - 3
src/lib/components/chat/Messages/MultiResponseMessages.svelte

@@ -194,10 +194,10 @@
 					<div
 					<div
 						class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
 						class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
 							?.modelIdx == modelIdx
 							?.modelIdx == modelIdx
-							? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
+							? `border-gray-100 dark:border-gray-850 border-[1.5px] ${
 									$mobile ? 'min-w-full' : 'min-w-80'
 									$mobile ? 'min-w-full' : 'min-w-80'
 								}`
 								}`
-							: `border-gray-50 dark:border-gray-850 border-dashed ${
+							: `border-gray-100 dark:border-gray-850 border-dashed ${
 									$mobile ? 'min-w-full' : 'min-w-80'
 									$mobile ? 'min-w-full' : 'min-w-80'
 								}`} transition-all p-5 rounded-2xl"
 								}`} transition-all p-5 rounded-2xl"
 						on:click={async () => {
 						on:click={async () => {
@@ -286,7 +286,7 @@
 					</div>
 					</div>
 
 
 					{#if isLastMessage}
 					{#if isLastMessage}
-						<div class=" flex-shrink-0 text-gray-600 dark:text-gray-500 mt-1">
+						<div class=" shrink-0 text-gray-600 dark:text-gray-500 mt-1">
 							<Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
 							<Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
 								<button
 								<button
 									type="button"
 									type="button"

+ 5 - 5
src/lib/components/chat/Messages/RateComment.svelte

@@ -110,7 +110,7 @@
 {/if}
 {/if}
 
 
 <div
 <div
-	class=" my-2.5 rounded-xl px-4 py-3 border border-gray-50 dark:border-gray-850"
+	class=" my-2.5 rounded-xl px-4 py-3 border border-gray-100 dark:border-gray-850"
 	id="message-feedback-{message.id}"
 	id="message-feedback-{message.id}"
 >
 >
 	<div class="flex justify-between items-center">
 	<div class="flex justify-between items-center">
@@ -142,10 +142,10 @@
 				<!-- 1-10 scale -->
 				<!-- 1-10 scale -->
 				{#each Array.from({ length: 10 }).map((_, i) => i + 1) as rating}
 				{#each Array.from({ length: 10 }).map((_, i) => i + 1) as rating}
 					<button
 					<button
-						class="size-7 text-sm border border-gray-50 dark:border-gray-850 hover:bg-gray-50 dark:hover:bg-gray-850 {detailedRating ===
+						class="size-7 text-sm border border-gray-100 dark:border-gray-850 hover:bg-gray-50 dark:hover:bg-gray-850 {detailedRating ===
 						rating
 						rating
 							? 'bg-gray-100 dark:bg-gray-800'
 							? 'bg-gray-100 dark:bg-gray-800'
-							: ''} transition rounded-full disabled:cursor-not-allowed disabled:text-gray-500 disabled:bg-white disabled:dark:bg-gray-900"
+							: ''} transition rounded-full disabled:cursor-not-allowed disabled:text-gray-500 disabled:bg-white dark:disabled:bg-gray-900"
 						on:click={() => {
 						on:click={() => {
 							detailedRating = rating;
 							detailedRating = rating;
 						}}
 						}}
@@ -175,7 +175,7 @@
 			<div class="flex flex-wrap gap-1.5 text-sm mt-1.5">
 			<div class="flex flex-wrap gap-1.5 text-sm mt-1.5">
 				{#each reasons as reason}
 				{#each reasons as reason}
 					<button
 					<button
-						class="px-3 py-0.5 border border-gray-50 dark:border-gray-850 hover:bg-gray-50 dark:hover:bg-gray-850 {selectedReason ===
+						class="px-3 py-0.5 border border-gray-100 dark:border-gray-850 hover:bg-gray-50 dark:hover:bg-gray-850 {selectedReason ===
 						reason
 						reason
 							? 'bg-gray-100 dark:bg-gray-800'
 							? 'bg-gray-100 dark:bg-gray-800'
 							: ''} transition rounded-xl"
 							: ''} transition rounded-xl"
@@ -223,7 +223,7 @@
 	<div class="mt-2">
 	<div class="mt-2">
 		<textarea
 		<textarea
 			bind:value={comment}
 			bind:value={comment}
-			class="w-full text-sm px-1 py-2 bg-transparent outline-none resize-none rounded-xl"
+			class="w-full text-sm px-1 py-2 bg-transparent outline-hidden resize-none rounded-xl"
 			placeholder={$i18n.t('Feel free to add specific details')}
 			placeholder={$i18n.t('Feel free to add specific details')}
 			rows="3"
 			rows="3"
 		/>
 		/>

+ 4 - 4
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -15,7 +15,7 @@
 	import { getChatById } from '$lib/apis/chats';
 	import { getChatById } from '$lib/apis/chats';
 	import { generateTags } from '$lib/apis';
 	import { generateTags } from '$lib/apis';
 
 
-	import { config, models, settings, TTSWorker, user } from '$lib/stores';
+	import { config, models, settings, temporaryChatEnabled, TTSWorker, user } from '$lib/stores';
 	import { synthesizeOpenAISpeech } from '$lib/apis/audio';
 	import { synthesizeOpenAISpeech } from '$lib/apis/audio';
 	import { imageGenerations } from '$lib/apis/images';
 	import { imageGenerations } from '$lib/apis/images';
 	import {
 	import {
@@ -522,7 +522,7 @@
 		id="message-{message.id}"
 		id="message-{message.id}"
 		dir={$settings.chatDirection}
 		dir={$settings.chatDirection}
 	>
 	>
-		<div class={`flex-shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
+		<div class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
 			<ProfileImage
 			<ProfileImage
 				src={model?.info?.meta?.profile_image_url ??
 				src={model?.info?.meta?.profile_image_url ??
 					($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
 					($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
@@ -645,7 +645,7 @@
 								<textarea
 								<textarea
 									id="message-edit-{message.id}"
 									id="message-edit-{message.id}"
 									bind:this={editTextAreaElement}
 									bind:this={editTextAreaElement}
-									class=" bg-transparent outline-none w-full resize-none"
+									class=" bg-transparent outline-hidden w-full resize-none"
 									bind:value={editedContent}
 									bind:value={editedContent}
 									on:input={(e) => {
 									on:input={(e) => {
 										e.target.style.height = '';
 										e.target.style.height = '';
@@ -1089,7 +1089,7 @@
 								{/if}
 								{/if}
 
 
 								{#if !readOnly}
 								{#if !readOnly}
-									{#if $config?.features.enable_message_rating ?? true}
+									{#if !$temporaryChatEnabled && ($config?.features.enable_message_rating ?? true)}
 										<Tooltip content={$i18n.t('Good Response')} placement="bottom">
 										<Tooltip content={$i18n.t('Good Response')} placement="bottom">
 											<button
 											<button
 												class="{isLastMessage
 												class="{isLastMessage

+ 7 - 7
src/lib/components/chat/Messages/Skeleton.svelte

@@ -5,31 +5,31 @@
 <div class="w-full mt-2 mb-2">
 <div class="w-full mt-2 mb-2">
 	<div class="animate-pulse flex w-full">
 	<div class="animate-pulse flex w-full">
 		<div class="{size === 'md' ? 'space-y-2' : 'space-y-1.5'} w-full">
 		<div class="{size === 'md' ? 'space-y-2' : 'space-y-1.5'} w-full">
-			<div class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded mr-14" />
+			<div class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm mr-14" />
 
 
 			<div class="grid grid-cols-3 gap-4">
 			<div class="grid grid-cols-3 gap-4">
 				<div
 				<div
-					class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded col-span-2"
+					class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
 				/>
 				/>
 				<div
 				<div
-					class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded col-span-1"
+					class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
 				/>
 				/>
 			</div>
 			</div>
 			<div class="grid grid-cols-4 gap-4">
 			<div class="grid grid-cols-4 gap-4">
 				<div
 				<div
-					class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded col-span-1"
+					class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
 				/>
 				/>
 				<div
 				<div
-					class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded col-span-2"
+					class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
 				/>
 				/>
 				<div
 				<div
 					class="{size === 'md'
 					class="{size === 'md'
 						? 'h-2'
 						? 'h-2'
-						: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded col-span-1 mr-4"
+						: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1 mr-4"
 				/>
 				/>
 			</div>
 			</div>
 
 
-			<div class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded" />
+			<div class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm" />
 		</div>
 		</div>
 	</div>
 	</div>
 </div>
 </div>

+ 3 - 3
src/lib/components/chat/Messages/UserMessage.svelte

@@ -87,7 +87,7 @@
 
 
 <div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
 <div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
 	{#if !($settings?.chatBubble ?? true)}
 	{#if !($settings?.chatBubble ?? true)}
-		<div class={`flex-shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
+		<div class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
 			<ProfileImage
 			<ProfileImage
 				src={message.user
 				src={message.user
 					? ($models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ??
 					? ($models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ??
@@ -152,7 +152,7 @@
 							<textarea
 							<textarea
 								id="message-edit-{message.id}"
 								id="message-edit-{message.id}"
 								bind:this={messageEditTextAreaElement}
 								bind:this={messageEditTextAreaElement}
-								class=" bg-transparent outline-none w-full resize-none"
+								class=" bg-transparent outline-hidden w-full resize-none"
 								bind:value={editedContent}
 								bind:value={editedContent}
 								on:input={(e) => {
 								on:input={(e) => {
 									e.target.style.height = '';
 									e.target.style.height = '';
@@ -338,7 +338,7 @@
 							{#if !isFirstMessage && !readOnly}
 							{#if !isFirstMessage && !readOnly}
 								<Tooltip content={$i18n.t('Delete')} placement="bottom">
 								<Tooltip content={$i18n.t('Delete')} placement="bottom">
 									<button
 									<button
-										class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
+										class="invisible group-hover:visible p-1 rounded-sm dark:hover:text-white hover:text-black transition"
 										on:click={() => {
 										on:click={() => {
 											deleteMessageHandler();
 											deleteMessageHandler();
 										}}
 										}}

+ 12 - 12
src/lib/components/chat/ModelSelector/Selector.svelte

@@ -248,7 +248,7 @@
 		id="model-selector-{id}-button"
 		id="model-selector-{id}-button"
 	>
 	>
 		<div
 		<div
-			class="flex w-full text-left px-0.5 outline-none bg-transparent truncate {triggerClassName} justify-between font-medium placeholder-gray-400 focus:outline-none"
+			class="flex w-full text-left px-0.5 outline-hidden bg-transparent truncate {triggerClassName} justify-between font-medium placeholder-gray-400 focus:outline-hidden"
 		>
 		>
 			{#if selectedModel}
 			{#if selectedModel}
 				{selectedModel.label}
 				{selectedModel.label}
@@ -262,7 +262,7 @@
 	<DropdownMenu.Content
 	<DropdownMenu.Content
 		class=" z-40 {$mobile
 		class=" z-40 {$mobile
 			? `w-full`
 			? `w-full`
-			: `${className}`} max-w-[calc(100vw-1rem)] justify-start rounded-xl  bg-white dark:bg-gray-850 dark:text-white shadow-lg  outline-none"
+			: `${className}`} max-w-[calc(100vw-1rem)] justify-start rounded-xl  bg-white dark:bg-gray-850 dark:text-white shadow-lg  outline-hidden"
 		transition={flyAndScale}
 		transition={flyAndScale}
 		side={$mobile ? 'bottom' : 'bottom-start'}
 		side={$mobile ? 'bottom' : 'bottom-start'}
 		sideOffset={3}
 		sideOffset={3}
@@ -275,7 +275,7 @@
 					<input
 					<input
 						id="model-search-input"
 						id="model-search-input"
 						bind:value={searchValue}
 						bind:value={searchValue}
-						class="w-full text-sm bg-transparent outline-none"
+						class="w-full text-sm bg-transparent outline-hidden"
 						placeholder={searchPlaceholder}
 						placeholder={searchPlaceholder}
 						autocomplete="off"
 						autocomplete="off"
 						on:keydown={(e) => {
 						on:keydown={(e) => {
@@ -298,14 +298,14 @@
 					/>
 					/>
 				</div>
 				</div>
 
 
-				<hr class="border-gray-50 dark:border-gray-800" />
+				<hr class="border-gray-100 dark:border-gray-850" />
 			{/if}
 			{/if}
 
 
 			<div class="px-3 my-2 max-h-64 overflow-y-auto scrollbar-hidden group">
 			<div class="px-3 my-2 max-h-64 overflow-y-auto scrollbar-hidden group">
 				{#each filteredItems as item, index}
 				{#each filteredItems as item, index}
 					<button
 					<button
 						aria-label="model-item"
 						aria-label="model-item"
-						class="flex w-full text-left font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted {index ===
+						class="flex w-full text-left font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-hidden transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-highlighted:bg-muted {index ===
 						selectedModelIdx
 						selectedModelIdx
 							? 'bg-gray-100 dark:bg-gray-800 group-hover:bg-transparent'
 							? 'bg-gray-100 dark:bg-gray-800 group-hover:bg-transparent'
 							: ''}"
 							: ''}"
@@ -322,7 +322,7 @@
 								<div class="flex gap-0.5 self-start h-full mb-1.5 -translate-x-1">
 								<div class="flex gap-0.5 self-start h-full mb-1.5 -translate-x-1">
 									{#each item.model?.info?.meta.tags as tag}
 									{#each item.model?.info?.meta.tags as tag}
 										<div
 										<div
-											class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+											class=" text-xs font-bold px-1 rounded-sm uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
 										>
 										>
 											{tag.name}
 											{tag.name}
 										</div>
 										</div>
@@ -445,7 +445,7 @@
 										{#each item.model?.info?.meta.tags as tag}
 										{#each item.model?.info?.meta.tags as tag}
 											<Tooltip content={tag.name}>
 											<Tooltip content={tag.name}>
 												<div
 												<div
-													class=" text-xs font-bold px-1 rounded uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
+													class=" text-xs font-bold px-1 rounded-sm uppercase line-clamp-1 bg-gray-500/20 text-gray-700 dark:text-gray-200"
 												>
 												>
 													{tag.name}
 													{tag.name}
 												</div>
 												</div>
@@ -478,7 +478,7 @@
 						placement="top-start"
 						placement="top-start"
 					>
 					>
 						<button
 						<button
-							class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+							class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-hidden transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-highlighted:bg-muted"
 							on:click={() => {
 							on:click={() => {
 								pullModelHandler();
 								pullModelHandler();
 							}}
 							}}
@@ -492,7 +492,7 @@
 
 
 				{#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
 				{#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
 					<div
 					<div
-						class="flex w-full justify-between font-medium select-none rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						class="flex w-full justify-between font-medium select-none rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-hidden transition-all duration-75 rounded-lg cursor-pointer data-highlighted:bg-muted"
 					>
 					>
 						<div class="flex">
 						<div class="flex">
 							<div class="-ml-2 mr-2.5 translate-y-0.5">
 							<div class="-ml-2 mr-2.5 translate-y-0.5">
@@ -527,7 +527,7 @@
 										Downloading "{model}"
 										Downloading "{model}"
 									</div>
 									</div>
 
 
-									<div class="flex-shrink-0">
+									<div class="shrink-0">
 										{'pullProgress' in $MODEL_DOWNLOAD_POOL[model]
 										{'pullProgress' in $MODEL_DOWNLOAD_POOL[model]
 											? `(${$MODEL_DOWNLOAD_POOL[model].pullProgress}%)`
 											? `(${$MODEL_DOWNLOAD_POOL[model].pullProgress}%)`
 											: ''}
 											: ''}
@@ -575,11 +575,11 @@
 			</div>
 			</div>
 
 
 			{#if showTemporaryChatControl}
 			{#if showTemporaryChatControl}
-				<hr class="border-gray-50 dark:border-gray-800" />
+				<hr class="border-gray-100 dark:border-gray-850" />
 
 
 				<div class="flex items-center mx-2 my-2">
 				<div class="flex items-center mx-2 my-2">
 					<button
 					<button
-						class="flex justify-between w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 px-3 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
+						class="flex justify-between w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 px-3 text-sm text-gray-700 dark:text-gray-100 outline-hidden transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-highlighted:bg-muted"
 						on:click={async () => {
 						on:click={async () => {
 							temporaryChatEnabled.set(!$temporaryChatEnabled);
 							temporaryChatEnabled.set(!$temporaryChatEnabled);
 							await goto('/');
 							await goto('/');

+ 1 - 1
src/lib/components/chat/Navbar.svelte

@@ -45,7 +45,7 @@
 
 
 <nav class="sticky top-0 z-30 w-full px-1.5 py-1.5 -mb-8 flex items-center drag-region">
 <nav class="sticky top-0 z-30 w-full px-1.5 py-1.5 -mb-8 flex items-center drag-region">
 	<div
 	<div
-		class=" bg-gradient-to-b via-50% from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pointer-events-none absolute inset-0 -bottom-7 z-[-1] blur"
+		class=" bg-linear-to-b via-50% from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pointer-events-none absolute inset-0 -bottom-7 z-[-1]"
 	></div>
 	></div>
 
 
 	<div class=" flex max-w-full w-full mx-auto px-1 pt-0.5 bg-transparent">
 	<div class=" flex max-w-full w-full mx-auto px-1 pt-0.5 bg-transparent">

+ 1 - 1
src/lib/components/chat/Placeholder.svelte

@@ -106,7 +106,7 @@
 	>
 	>
 		<div class="w-full flex flex-col justify-center items-center">
 		<div class="w-full flex flex-col justify-center items-center">
 			<div class="flex flex-row justify-center gap-3 @sm:gap-3.5 w-fit px-5">
 			<div class="flex flex-row justify-center gap-3 @sm:gap-3.5 w-fit px-5">
-				<div class="flex flex-shrink-0 justify-center">
+				<div class="flex shrink-0 justify-center">
 					<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 100 }}>
 					<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 100 }}>
 						{#each models as model, modelIdx}
 						{#each models as model, modelIdx}
 							<Tooltip
 							<Tooltip

+ 2 - 2
src/lib/components/chat/Settings/About.svelte

@@ -92,7 +92,7 @@
 		</div>
 		</div>
 
 
 		{#if ollamaVersion}
 		{#if ollamaVersion}
-			<hr class=" dark:border-gray-850" />
+			<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 			<div>
 			<div>
 				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Version')}</div>
 				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Version')}</div>
@@ -104,7 +104,7 @@
 			</div>
 			</div>
 		{/if}
 		{/if}
 
 
-		<hr class=" dark:border-gray-850" />
+		<hr class=" border-gray-100 dark:border-gray-850" />
 
 
 		<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 		<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
 			Emoji graphics provided by
 			Emoji graphics provided by

+ 3 - 3
src/lib/components/chat/Settings/Account.svelte

@@ -236,7 +236,7 @@
 
 
 					<div class="flex-1">
 					<div class="flex-1">
 						<input
 						<input
-							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							type="text"
 							type="text"
 							bind:value={name}
 							bind:value={name}
 							required
 							required
@@ -251,7 +251,7 @@
 
 
 					<div class="flex-1">
 					<div class="flex-1">
 						<input
 						<input
-							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
+							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 							type="url"
 							type="url"
 							placeholder={$i18n.t('Enter your webhook URL')}
 							placeholder={$i18n.t('Enter your webhook URL')}
 							bind:value={webhookUrl}
 							bind:value={webhookUrl}
@@ -266,7 +266,7 @@
 			<UpdatePassword />
 			<UpdatePassword />
 		</div>
 		</div>
 
 
-		<hr class=" dark:border-gray-850 my-4" />
+		<hr class="border-gray-100 dark:border-gray-850 my-4" />
 
 
 		<div class="flex justify-between items-center text-sm">
 		<div class="flex justify-between items-center text-sm">
 			<div class="  font-medium">{$i18n.t('API keys')}</div>
 			<div class="  font-medium">{$i18n.t('API keys')}</div>

+ 3 - 3
src/lib/components/chat/Settings/Account/UpdatePassword.svelte

@@ -60,7 +60,7 @@
 
 
 				<div class="flex-1">
 				<div class="flex-1">
 					<input
 					<input
-						class="w-full bg-transparent dark:text-gray-300 outline-none placeholder:opacity-30"
+						class="w-full bg-transparent dark:text-gray-300 outline-hidden placeholder:opacity-30"
 						type="password"
 						type="password"
 						bind:value={currentPassword}
 						bind:value={currentPassword}
 						placeholder={$i18n.t('Enter your current password')}
 						placeholder={$i18n.t('Enter your current password')}
@@ -75,7 +75,7 @@
 
 
 				<div class="flex-1">
 				<div class="flex-1">
 					<input
 					<input
-						class="w-full bg-transparent text-sm dark:text-gray-300 outline-none placeholder:opacity-30"
+						class="w-full bg-transparent text-sm dark:text-gray-300 outline-hidden placeholder:opacity-30"
 						type="password"
 						type="password"
 						bind:value={newPassword}
 						bind:value={newPassword}
 						placeholder={$i18n.t('Enter your new password')}
 						placeholder={$i18n.t('Enter your new password')}
@@ -90,7 +90,7 @@
 
 
 				<div class="flex-1">
 				<div class="flex-1">
 					<input
 					<input
-						class="w-full bg-transparent text-sm dark:text-gray-300 outline-none placeholder:opacity-30"
+						class="w-full bg-transparent text-sm dark:text-gray-300 outline-hidden placeholder:opacity-30"
 						type="password"
 						type="password"
 						bind:value={newPasswordConfirm}
 						bind:value={newPasswordConfirm}
 						placeholder={$i18n.t('Confirm your new password')}
 						placeholder={$i18n.t('Confirm your new password')}

Някои файлове не бяха показани, защото твърде много файлове са промени