Browse Source

Merge branch 'dev' into main

Jarrod Lowe 4 months ago
parent
commit
a8bbaa61bd
96 changed files with 1710 additions and 771 deletions
  1. 8 1
      backend/open_webui/__init__.py
  2. 49 5
      backend/open_webui/config.py
  3. 1 0
      backend/open_webui/constants.py
  4. 36 0
      backend/open_webui/env.py
  5. 50 30
      backend/open_webui/main.py
  6. 1 1
      backend/open_webui/retrieval/loaders/youtube.py
  7. 8 0
      backend/open_webui/retrieval/utils.py
  8. 8 8
      backend/open_webui/retrieval/web/duckduckgo.py
  9. 60 0
      backend/open_webui/retrieval/web/sougou.py
  10. 135 11
      backend/open_webui/routers/audio.py
  11. 12 3
      backend/open_webui/routers/auths.py
  12. 42 0
      backend/open_webui/routers/files.py
  13. 69 0
      backend/open_webui/routers/knowledge.py
  14. 25 0
      backend/open_webui/routers/retrieval.py
  15. 14 16
      backend/open_webui/routers/tools.py
  16. 35 17
      backend/open_webui/utils/middleware.py
  17. 1 1
      backend/open_webui/utils/plugin.py
  18. 71 27
      backend/open_webui/utils/tools.py
  19. 19 16
      backend/requirements.txt
  20. 1 1
      backend/start.sh
  21. 2 1
      backend/start_windows.bat
  22. 60 26
      package-lock.json
  23. 4 2
      package.json
  24. 3 1
      pyproject.toml
  25. 0 4
      src/app.css
  26. 1 1
      src/app.html
  27. 11 3
      src/lib/apis/index.ts
  28. 29 0
      src/lib/apis/knowledge/index.ts
  29. 46 9
      src/lib/components/admin/Settings/Audio.svelte
  30. 34 5
      src/lib/components/admin/Settings/Documents.svelte
  31. 33 3
      src/lib/components/admin/Settings/WebSearch.svelte
  32. 22 21
      src/lib/components/chat/Chat.svelte
  33. 3 3
      src/lib/components/chat/ChatControls.svelte
  34. 13 18
      src/lib/components/chat/MessageInput.svelte
  35. 2 1
      src/lib/components/chat/Messages/ContentRenderer.svelte
  36. 1 1
      src/lib/components/chat/Messages/UserMessage.svelte
  37. 64 59
      src/lib/components/chat/ModelSelector/Selector.svelte
  38. 30 0
      src/lib/components/chat/Settings/Interface.svelte
  39. 1 1
      src/lib/components/chat/ToolServersModal.svelte
  40. 11 1
      src/lib/components/common/CodeEditor.svelte
  41. 1 1
      src/lib/components/common/Collapsible.svelte
  42. 2 0
      src/lib/components/layout/Sidebar/ChatItem.svelte
  43. 4 0
      src/lib/i18n/locales/ar-BH/translation.json
  44. 4 0
      src/lib/i18n/locales/ar/translation.json
  45. 4 0
      src/lib/i18n/locales/bg-BG/translation.json
  46. 4 0
      src/lib/i18n/locales/bn-BD/translation.json
  47. 4 0
      src/lib/i18n/locales/bo-TB/translation.json
  48. 41 37
      src/lib/i18n/locales/ca-ES/translation.json
  49. 4 0
      src/lib/i18n/locales/ceb-PH/translation.json
  50. 4 0
      src/lib/i18n/locales/cs-CZ/translation.json
  51. 4 0
      src/lib/i18n/locales/da-DK/translation.json
  52. 4 0
      src/lib/i18n/locales/de-DE/translation.json
  53. 4 0
      src/lib/i18n/locales/dg-DG/translation.json
  54. 4 0
      src/lib/i18n/locales/el-GR/translation.json
  55. 4 0
      src/lib/i18n/locales/en-GB/translation.json
  56. 8 0
      src/lib/i18n/locales/en-US/translation.json
  57. 4 0
      src/lib/i18n/locales/es-ES/translation.json
  58. 4 0
      src/lib/i18n/locales/et-EE/translation.json
  59. 4 0
      src/lib/i18n/locales/eu-ES/translation.json
  60. 4 0
      src/lib/i18n/locales/fa-IR/translation.json
  61. 4 0
      src/lib/i18n/locales/fi-FI/translation.json
  62. 4 0
      src/lib/i18n/locales/fr-CA/translation.json
  63. 4 0
      src/lib/i18n/locales/fr-FR/translation.json
  64. 4 0
      src/lib/i18n/locales/he-IL/translation.json
  65. 4 0
      src/lib/i18n/locales/hi-IN/translation.json
  66. 4 0
      src/lib/i18n/locales/hr-HR/translation.json
  67. 313 313
      src/lib/i18n/locales/hu-HU/translation.json
  68. 4 0
      src/lib/i18n/locales/id-ID/translation.json
  69. 4 0
      src/lib/i18n/locales/ie-GA/translation.json
  70. 4 0
      src/lib/i18n/locales/it-IT/translation.json
  71. 4 0
      src/lib/i18n/locales/ja-JP/translation.json
  72. 4 0
      src/lib/i18n/locales/ka-GE/translation.json
  73. 4 0
      src/lib/i18n/locales/ko-KR/translation.json
  74. 4 0
      src/lib/i18n/locales/lt-LT/translation.json
  75. 4 0
      src/lib/i18n/locales/ms-MY/translation.json
  76. 4 0
      src/lib/i18n/locales/nb-NO/translation.json
  77. 4 0
      src/lib/i18n/locales/nl-NL/translation.json
  78. 4 0
      src/lib/i18n/locales/pa-IN/translation.json
  79. 4 0
      src/lib/i18n/locales/pl-PL/translation.json
  80. 4 0
      src/lib/i18n/locales/pt-BR/translation.json
  81. 4 0
      src/lib/i18n/locales/pt-PT/translation.json
  82. 4 0
      src/lib/i18n/locales/ro-RO/translation.json
  83. 61 57
      src/lib/i18n/locales/ru-RU/translation.json
  84. 4 0
      src/lib/i18n/locales/sk-SK/translation.json
  85. 4 0
      src/lib/i18n/locales/sr-RS/translation.json
  86. 4 0
      src/lib/i18n/locales/sv-SE/translation.json
  87. 4 0
      src/lib/i18n/locales/th-TH/translation.json
  88. 4 0
      src/lib/i18n/locales/tk-TW/translation.json
  89. 4 0
      src/lib/i18n/locales/tr-TR/translation.json
  90. 4 0
      src/lib/i18n/locales/uk-UA/translation.json
  91. 4 0
      src/lib/i18n/locales/ur-PK/translation.json
  92. 4 0
      src/lib/i18n/locales/vi-VN/translation.json
  93. 20 12
      src/lib/i18n/locales/zh-CN/translation.json
  94. 4 0
      src/lib/i18n/locales/zh-TW/translation.json
  95. 59 53
      src/routes/(app)/+layout.svelte
  96. 2 1
      src/routes/+layout.svelte

+ 8 - 1
backend/open_webui/__init__.py

@@ -73,8 +73,15 @@ def serve(
             os.environ["LD_LIBRARY_PATH"] = ":".join(LD_LIBRARY_PATH)
 
     import open_webui.main  # we need set environment variables before importing main
+    from open_webui.env import UVICORN_WORKERS  # Import the workers setting
 
-    uvicorn.run(open_webui.main.app, host=host, port=port, forwarded_allow_ips="*")
+    uvicorn.run(
+        open_webui.main.app, 
+        host=host, 
+        port=port, 
+        forwarded_allow_ips="*",
+        workers=UVICORN_WORKERS
+    )
 
 
 @app.command()

+ 49 - 5
backend/open_webui/config.py

@@ -201,6 +201,7 @@ def save_config(config):
 
 T = TypeVar("T")
 
+ENABLE_PERSISTENT_CONFIG = os.environ.get("ENABLE_PERSISTENT_CONFIG", "True").lower() == "true"
 
 class PersistentConfig(Generic[T]):
     def __init__(self, env_name: str, config_path: str, env_value: T):
@@ -208,7 +209,7 @@ class PersistentConfig(Generic[T]):
         self.config_path = config_path
         self.env_value = env_value
         self.config_value = get_config_value(config_path)
-        if self.config_value is not None:
+        if self.config_value is not None and ENABLE_PERSISTENT_CONFIG:
             log.info(f"'{env_name}' loaded from the latest database entry")
             self.value = self.config_value
         else:
@@ -456,6 +457,12 @@ OAUTH_SCOPES = PersistentConfig(
     os.environ.get("OAUTH_SCOPES", "openid email profile"),
 )
 
+OAUTH_CODE_CHALLENGE_METHOD = PersistentConfig(
+    "OAUTH_CODE_CHALLENGE_METHOD",
+    "oauth.oidc.code_challenge_method",
+    os.environ.get("OAUTH_CODE_CHALLENGE_METHOD", None),
+)
+
 OAUTH_PROVIDER_NAME = PersistentConfig(
     "OAUTH_PROVIDER_NAME",
     "oauth.oidc.provider_name",
@@ -560,7 +567,7 @@ def load_oauth_providers():
                 name="microsoft",
                 client_id=MICROSOFT_CLIENT_ID.value,
                 client_secret=MICROSOFT_CLIENT_SECRET.value,
-                server_metadata_url=f"https://login.microsoftonline.com/{MICROSOFT_CLIENT_TENANT_ID.value}/v2.0/.well-known/openid-configuration",
+                server_metadata_url=f"https://login.microsoftonline.com/{MICROSOFT_CLIENT_TENANT_ID.value}/v2.0/.well-known/openid-configuration?appid={MICROSOFT_CLIENT_ID.value}",
                 client_kwargs={
                     "scope": MICROSOFT_OAUTH_SCOPE.value,
                 },
@@ -601,14 +608,21 @@ def load_oauth_providers():
     ):
 
         def oidc_oauth_register(client):
+            client_kwargs = {
+                "scope": OAUTH_SCOPES.value,
+            }
+
+            if OAUTH_CODE_CHALLENGE_METHOD.value and OAUTH_CODE_CHALLENGE_METHOD.value == "S256":
+                client_kwargs["code_challenge_method"] = "S256"
+            elif OAUTH_CODE_CHALLENGE_METHOD.value:
+                raise Exception('Code challenge methods other than "%s" not supported. Given: "%s"' % ("S256", OAUTH_CODE_CHALLENGE_METHOD.value))
+
             client.register(
                 name="oidc",
                 client_id=OAUTH_CLIENT_ID.value,
                 client_secret=OAUTH_CLIENT_SECRET.value,
                 server_metadata_url=OPENID_PROVIDER_URL.value,
-                client_kwargs={
-                    "scope": OAUTH_SCOPES.value,
-                },
+                client_kwargs=client_kwargs,
                 redirect_uri=OPENID_REDIRECT_URI.value,
             )
 
@@ -2141,6 +2155,18 @@ PERPLEXITY_API_KEY = PersistentConfig(
     os.getenv("PERPLEXITY_API_KEY", ""),
 )
 
+SOUGOU_API_SID = PersistentConfig(
+    "SOUGOU_API_SID",
+    "rag.web.search.sougou_api_sid",
+    os.getenv("SOUGOU_API_SID", ""),
+)
+
+SOUGOU_API_SK = PersistentConfig(
+    "SOUGOU_API_SK",
+    "rag.web.search.sougou_api_sk",
+    os.getenv("SOUGOU_API_SK", ""),
+)
+
 RAG_WEB_SEARCH_RESULT_COUNT = PersistentConfig(
     "RAG_WEB_SEARCH_RESULT_COUNT",
     "rag.web.search.result_count",
@@ -2472,6 +2498,24 @@ AUDIO_STT_MODEL = PersistentConfig(
     os.getenv("AUDIO_STT_MODEL", ""),
 )
 
+AUDIO_STT_AZURE_API_KEY = PersistentConfig(
+    "AUDIO_STT_AZURE_API_KEY",
+    "audio.stt.azure.api_key",
+    os.getenv("AUDIO_STT_AZURE_API_KEY", ""),
+)
+
+AUDIO_STT_AZURE_REGION = PersistentConfig(
+    "AUDIO_STT_AZURE_REGION",
+    "audio.stt.azure.region",
+    os.getenv("AUDIO_STT_AZURE_REGION", ""),
+)
+
+AUDIO_STT_AZURE_LOCALES = PersistentConfig(
+    "AUDIO_STT_AZURE_LOCALES",
+    "audio.stt.azure.locales",
+    os.getenv("AUDIO_STT_AZURE_LOCALES", ""),
+)
+
 AUDIO_TTS_OPENAI_API_BASE_URL = PersistentConfig(
     "AUDIO_TTS_OPENAI_API_BASE_URL",
     "audio.tts.openai.api_base_url",

+ 1 - 0
backend/open_webui/constants.py

@@ -31,6 +31,7 @@ class ERROR_MESSAGES(str, Enum):
     USERNAME_TAKEN = (
         "Uh-oh! This username is already registered. Please choose another username."
     )
+    PASSWORD_TOO_LONG = "Uh-oh! The password you entered is too long. Please make sure your password is less than 72 bytes long."
     COMMAND_TAKEN = "Uh-oh! This command is already registered. Please choose another command string."
     FILE_EXISTS = "Uh-oh! This file is already registered. Please choose another file."
 

+ 36 - 0
backend/open_webui/env.py

@@ -326,6 +326,20 @@ REDIS_URL = os.environ.get("REDIS_URL", "")
 REDIS_SENTINEL_HOSTS = os.environ.get("REDIS_SENTINEL_HOSTS", "")
 REDIS_SENTINEL_PORT = os.environ.get("REDIS_SENTINEL_PORT", "26379")
 
+####################################
+# UVICORN WORKERS
+####################################
+
+# Number of uvicorn worker processes for handling requests
+UVICORN_WORKERS = os.environ.get("UVICORN_WORKERS", "1")
+try:
+    UVICORN_WORKERS = int(UVICORN_WORKERS)
+    if UVICORN_WORKERS < 1:
+        UVICORN_WORKERS = 1
+except ValueError:
+    UVICORN_WORKERS = 1
+    log.info(f"Invalid UVICORN_WORKERS value, defaulting to {UVICORN_WORKERS}")
+
 ####################################
 # WEBUI_AUTH (Required for security)
 ####################################
@@ -411,6 +425,21 @@ else:
     except Exception:
         AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST = 10
 
+
+AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = os.environ.get(
+    "AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA", "10"
+)
+
+if AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA == "":
+    AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = None
+else:
+    try:
+        AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = int(
+            AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA
+        )
+    except Exception:
+        AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA = 10
+
 ####################################
 # OFFLINE_MODE
 ####################################
@@ -463,3 +492,10 @@ OTEL_TRACES_SAMPLER = os.environ.get(
 
 PIP_OPTIONS = os.getenv("PIP_OPTIONS", "").split()
 PIP_PACKAGE_INDEX_OPTIONS = os.getenv("PIP_PACKAGE_INDEX_OPTIONS", "").split()
+
+
+####################################
+# PROGRESSIVE WEB APP OPTIONS
+####################################
+
+EXTERNAL_PWA_MANIFEST_URL = os.environ.get("EXTERNAL_PWA_MANIFEST_URL")

+ 50 - 30
backend/open_webui/main.py

@@ -148,6 +148,9 @@ from open_webui.config import (
     AUDIO_STT_MODEL,
     AUDIO_STT_OPENAI_API_BASE_URL,
     AUDIO_STT_OPENAI_API_KEY,
+    AUDIO_STT_AZURE_API_KEY,
+    AUDIO_STT_AZURE_REGION,
+    AUDIO_STT_AZURE_LOCALES,
     AUDIO_TTS_API_KEY,
     AUDIO_TTS_ENGINE,
     AUDIO_TTS_MODEL,
@@ -225,6 +228,8 @@ from open_webui.config import (
     BRAVE_SEARCH_API_KEY,
     EXA_API_KEY,
     PERPLEXITY_API_KEY,
+    SOUGOU_API_SID,
+    SOUGOU_API_SK,
     KAGI_SEARCH_API_KEY,
     MOJEEK_SEARCH_API_KEY,
     BOCHA_SEARCH_API_KEY,
@@ -341,6 +346,7 @@ from open_webui.env import (
     RESET_CONFIG_ON_START,
     OFFLINE_MODE,
     ENABLE_OTEL,
+    EXTERNAL_PWA_MANIFEST_URL,
 )
 
 
@@ -427,6 +433,7 @@ async def lifespan(app: FastAPI):
 
 
 app = FastAPI(
+    title="Open WebUI",
     docs_url="/docs" if ENV == "dev" else None,
     openapi_url="/openapi.json" if ENV == "dev" else None,
     redoc_url=None,
@@ -566,6 +573,7 @@ app.state.config.LDAP_CIPHERS = LDAP_CIPHERS
 app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
 app.state.SIGNOUT_REDIRECT_URI = SIGNOUT_REDIRECT_URI
+app.state.EXTERNAL_PWA_MANIFEST_URL = EXTERNAL_PWA_MANIFEST_URL
 
 app.state.USER_COUNT = None
 app.state.TOOLS = {}
@@ -653,6 +661,8 @@ app.state.config.BING_SEARCH_V7_ENDPOINT = BING_SEARCH_V7_ENDPOINT
 app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY = BING_SEARCH_V7_SUBSCRIPTION_KEY
 app.state.config.EXA_API_KEY = EXA_API_KEY
 app.state.config.PERPLEXITY_API_KEY = PERPLEXITY_API_KEY
+app.state.config.SOUGOU_API_SID = SOUGOU_API_SID
+app.state.config.SOUGOU_API_SK = SOUGOU_API_SK
 
 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
@@ -780,6 +790,10 @@ app.state.config.STT_MODEL = AUDIO_STT_MODEL
 app.state.config.WHISPER_MODEL = WHISPER_MODEL
 app.state.config.DEEPGRAM_API_KEY = DEEPGRAM_API_KEY
 
+app.state.config.AUDIO_STT_AZURE_API_KEY = AUDIO_STT_AZURE_API_KEY
+app.state.config.AUDIO_STT_AZURE_REGION = AUDIO_STT_AZURE_REGION
+app.state.config.AUDIO_STT_AZURE_LOCALES = AUDIO_STT_AZURE_LOCALES
+
 app.state.config.TTS_OPENAI_API_BASE_URL = AUDIO_TTS_OPENAI_API_BASE_URL
 app.state.config.TTS_OPENAI_API_KEY = AUDIO_TTS_OPENAI_API_KEY
 app.state.config.TTS_ENGINE = AUDIO_TTS_ENGINE
@@ -1055,6 +1069,7 @@ async def chat_completion(
     model_item = form_data.pop("model_item", {})
     tasks = form_data.pop("background_tasks", None)
 
+    metadata = {}
     try:
         if not model_item.get("direct", False):
             model_id = form_data.get("model", None)
@@ -1110,13 +1125,15 @@ async def chat_completion(
 
     except Exception as e:
         log.debug(f"Error processing chat payload: {e}")
-        Chats.upsert_message_to_chat_by_id_and_message_id(
-            metadata["chat_id"],
-            metadata["message_id"],
-            {
-                "error": {"content": str(e)},
-            },
-        )
+        if metadata.get("chat_id") and metadata.get("message_id"):
+            # Update the chat message with the error
+            Chats.upsert_message_to_chat_by_id_and_message_id(
+                metadata["chat_id"],
+                metadata["message_id"],
+                {
+                    "error": {"content": str(e)},
+                },
+            )
 
         raise HTTPException(
             status_code=status.HTTP_400_BAD_REQUEST,
@@ -1392,29 +1409,32 @@ async def oauth_callback(provider: str, request: Request, response: Response):
 
 @app.get("/manifest.json")
 async def get_manifest_json():
-    return {
-        "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.",
-        "start_url": "/",
-        "display": "standalone",
-        "background_color": "#343541",
-        "orientation": "natural",
-        "icons": [
-            {
-                "src": "/static/logo.png",
-                "type": "image/png",
-                "sizes": "500x500",
-                "purpose": "any",
-            },
-            {
-                "src": "/static/logo.png",
-                "type": "image/png",
-                "sizes": "500x500",
-                "purpose": "maskable",
-            },
-        ],
-    }
+    if app.state.EXTERNAL_PWA_MANIFEST_URL:
+        return requests.get(app.state.EXTERNAL_PWA_MANIFEST_URL).json()
+    else:
+        return {
+            "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.",
+            "start_url": "/",
+            "display": "standalone",
+            "background_color": "#343541",
+            "orientation": "natural",
+            "icons": [
+                {
+                    "src": "/static/logo.png",
+                    "type": "image/png",
+                    "sizes": "500x500",
+                    "purpose": "any",
+                },
+                {
+                    "src": "/static/logo.png",
+                    "type": "image/png",
+                    "sizes": "500x500",
+                    "purpose": "maskable",
+                },
+            ],
+        }
 
 
 @app.get("/opensearch.xml")

+ 1 - 1
backend/open_webui/retrieval/loaders/youtube.py

@@ -110,7 +110,7 @@ class YoutubeLoader:
 
         transcript = " ".join(
             map(
-                lambda transcript_piece: transcript_piece["text"].strip(" "),
+                lambda transcript_piece: transcript_piece.text.strip(" "),
                 transcript_pieces,
             )
         )

+ 8 - 0
backend/open_webui/retrieval/utils.py

@@ -77,6 +77,7 @@ def query_doc(
     collection_name: str, query_embedding: list[float], k: int, user: UserModel = None
 ):
     try:
+        log.debug(f"query_doc:doc {collection_name}")
         result = VECTOR_DB_CLIENT.search(
             collection_name=collection_name,
             vectors=[query_embedding],
@@ -94,6 +95,7 @@ def query_doc(
 
 def get_doc(collection_name: str, user: UserModel = None):
     try:
+        log.debug(f"get_doc:doc {collection_name}")
         result = VECTOR_DB_CLIENT.get(collection_name=collection_name)
 
         if result:
@@ -116,6 +118,7 @@ def query_doc_with_hybrid_search(
     r: float,
 ) -> dict:
     try:
+        log.debug(f"query_doc_with_hybrid_search:doc {collection_name}")
         bm25_retriever = BM25Retriever.from_texts(
             texts=collection_result.documents[0],
             metadatas=collection_result.metadatas[0],
@@ -168,6 +171,7 @@ def query_doc_with_hybrid_search(
         )
         return result
     except Exception as e:
+        log.exception(f"Error querying doc {collection_name} with hybrid search: {e}")
         raise e
 
 
@@ -257,6 +261,7 @@ def query_collection(
 ) -> dict:
     results = []
     for query in queries:
+        log.debug(f"query_collection:query {query}")
         query_embedding = embedding_function(query, prefix=RAG_EMBEDDING_QUERY_PREFIX)
         for collection_name in collection_names:
             if collection_name:
@@ -292,6 +297,7 @@ def query_collection_with_hybrid_search(
     collection_results = {}
     for collection_name in collection_names:
         try:
+            log.debug(f"query_collection_with_hybrid_search:VECTOR_DB_CLIENT.get:collection {collection_name}")
             collection_results[collection_name] = VECTOR_DB_CLIENT.get(
                 collection_name=collection_name
             )
@@ -613,6 +619,7 @@ def generate_openai_batch_embeddings(
     user: UserModel = None,
 ) -> Optional[list[list[float]]]:
     try:
+        log.debug(f"generate_openai_batch_embeddings:model {model} batch size: {len(texts)}")
         json_data = {"input": texts, "model": model}
         if isinstance(RAG_EMBEDDING_PREFIX_FIELD_NAME, str) and isinstance(prefix, str):
             json_data[RAG_EMBEDDING_PREFIX_FIELD_NAME] = prefix
@@ -655,6 +662,7 @@ def generate_ollama_batch_embeddings(
     user: UserModel = None,
 ) -> Optional[list[list[float]]]:
     try:
+        log.debug(f"generate_ollama_batch_embeddings:model {model} batch size: {len(texts)}")
         json_data = {"input": texts, "model": model}
         if isinstance(RAG_EMBEDDING_PREFIX_FIELD_NAME, str) and isinstance(prefix, str):
             json_data[RAG_EMBEDDING_PREFIX_FIELD_NAME] = prefix

+ 8 - 8
backend/open_webui/retrieval/web/duckduckgo.py

@@ -3,6 +3,7 @@ from typing import Optional
 
 from open_webui.retrieval.web.main import SearchResult, get_filtered_results
 from duckduckgo_search import DDGS
+from duckduckgo_search.exceptions import RatelimitException
 from open_webui.env import SRC_LOG_LEVELS
 
 log = logging.getLogger(__name__)
@@ -22,16 +23,15 @@ def search_duckduckgo(
         list[SearchResult]: A list of search results
     """
     # Use the DDGS context manager to create a DDGS object
+    search_results = []
     with DDGS() as ddgs:
         # Use the ddgs.text() method to perform the search
-        ddgs_gen = ddgs.text(
-            query, safesearch="moderate", max_results=count, backend="api"
-        )
-        # Check if there are search results
-        if ddgs_gen:
-            # Convert the search results into a list
-            search_results = [r for r in ddgs_gen]
-
+        try:
+            search_results = ddgs.text(
+                query, safesearch="moderate", max_results=count, backend="lite"
+            )
+        except RatelimitException as e:
+            log.error(f"RatelimitException: {e}")
     if filter_list:
         search_results = get_filtered_results(search_results, filter_list)
 

+ 60 - 0
backend/open_webui/retrieval/web/sougou.py

@@ -0,0 +1,60 @@
+import logging
+import json
+from typing import Optional, List
+
+
+from open_webui.retrieval.web.main import SearchResult, get_filtered_results
+from open_webui.env import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_sougou(
+    sougou_api_sid: str,
+    sougou_api_sk: str,
+    query: str,
+    count: int,
+    filter_list: Optional[List[str]] = None,
+) -> List[SearchResult]:
+    from tencentcloud.common.common_client import CommonClient
+    from tencentcloud.common import credential
+    from tencentcloud.common.exception.tencent_cloud_sdk_exception import (
+        TencentCloudSDKException,
+    )
+    from tencentcloud.common.profile.client_profile import ClientProfile
+    from tencentcloud.common.profile.http_profile import HttpProfile
+
+    try:
+        cred = credential.Credential(sougou_api_sid, sougou_api_sk)
+        http_profile = HttpProfile()
+        http_profile.endpoint = "tms.tencentcloudapi.com"
+        client_profile = ClientProfile()
+        client_profile.http_profile = http_profile
+        params = json.dumps({"Query": query, "Cnt": 20})
+        common_client = CommonClient(
+            "tms", "2020-12-29", cred, "", profile=client_profile
+        )
+        results = [
+            json.loads(page)
+            for page in common_client.call_json("SearchPro", json.loads(params))[
+                "Response"
+            ]["Pages"]
+        ]
+        sorted_results = sorted(
+            results, key=lambda x: x.get("scour", 0.0), reverse=True
+        )
+        if filter_list:
+            sorted_results = get_filtered_results(sorted_results, filter_list)
+
+        return [
+            SearchResult(
+                link=result.get("url"),
+                title=result.get("title"),
+                snippet=result.get("passage"),
+            )
+            for result in sorted_results[:count]
+        ]
+    except TencentCloudSDKException as err:
+        log.error(f"Error in Sougou search: {err}")
+        return []

+ 135 - 11
backend/open_webui/routers/audio.py

@@ -50,6 +50,8 @@ router = APIRouter()
 # Constants
 MAX_FILE_SIZE_MB = 25
 MAX_FILE_SIZE = MAX_FILE_SIZE_MB * 1024 * 1024  # Convert MB to bytes
+AZURE_MAX_FILE_SIZE_MB = 200
+AZURE_MAX_FILE_SIZE = AZURE_MAX_FILE_SIZE_MB * 1024 * 1024  # Convert MB to bytes
 
 log = logging.getLogger(__name__)
 log.setLevel(SRC_LOG_LEVELS["AUDIO"])
@@ -68,8 +70,8 @@ from pydub import AudioSegment
 from pydub.utils import mediainfo
 
 
-def is_mp4_audio(file_path):
-    """Check if the given file is an MP4 audio file."""
+def get_audio_format(file_path):
+    """Check if the given file needs to be converted to a different format."""
     if not os.path.isfile(file_path):
         log.error(f"File not found: {file_path}")
         return False
@@ -80,13 +82,17 @@ def is_mp4_audio(file_path):
         and info.get("codec_type") == "audio"
         and info.get("codec_tag_string") == "mp4a"
     ):
-        return True
-    return False
+        return "mp4"
+    elif info.get("format_name") == "ogg":
+        return "ogg"
+    elif info.get("format_name") == "matroska,webm":
+        return "webm"
+    return None
 
 
-def convert_mp4_to_wav(file_path, output_path):
-    """Convert MP4 audio file to WAV format."""
-    audio = AudioSegment.from_file(file_path, format="mp4")
+def convert_audio_to_wav(file_path, output_path, conversion_type):
+    """Convert MP4/OGG audio file to WAV format."""
+    audio = AudioSegment.from_file(file_path, format=conversion_type)
     audio.export(output_path, format="wav")
     log.info(f"Converted {file_path} to {output_path}")
 
@@ -141,6 +147,9 @@ class STTConfigForm(BaseModel):
     MODEL: str
     WHISPER_MODEL: str
     DEEPGRAM_API_KEY: str
+    AZURE_API_KEY: str
+    AZURE_REGION: str
+    AZURE_LOCALES: str
 
 
 class AudioConfigUpdateForm(BaseModel):
@@ -169,6 +178,9 @@ async def get_audio_config(request: Request, user=Depends(get_admin_user)):
             "MODEL": request.app.state.config.STT_MODEL,
             "WHISPER_MODEL": request.app.state.config.WHISPER_MODEL,
             "DEEPGRAM_API_KEY": request.app.state.config.DEEPGRAM_API_KEY,
+            "AZURE_API_KEY": request.app.state.config.AUDIO_STT_AZURE_API_KEY,
+            "AZURE_REGION": request.app.state.config.AUDIO_STT_AZURE_REGION,
+            "AZURE_LOCALES": request.app.state.config.AUDIO_STT_AZURE_LOCALES,
         },
     }
 
@@ -195,6 +207,9 @@ async def update_audio_config(
     request.app.state.config.STT_MODEL = form_data.stt.MODEL
     request.app.state.config.WHISPER_MODEL = form_data.stt.WHISPER_MODEL
     request.app.state.config.DEEPGRAM_API_KEY = form_data.stt.DEEPGRAM_API_KEY
+    request.app.state.config.AUDIO_STT_AZURE_API_KEY = form_data.stt.AZURE_API_KEY
+    request.app.state.config.AUDIO_STT_AZURE_REGION = form_data.stt.AZURE_REGION
+    request.app.state.config.AUDIO_STT_AZURE_LOCALES = form_data.stt.AZURE_LOCALES
 
     if request.app.state.config.STT_ENGINE == "":
         request.app.state.faster_whisper_model = set_faster_whisper_model(
@@ -220,6 +235,9 @@ async def update_audio_config(
             "MODEL": request.app.state.config.STT_MODEL,
             "WHISPER_MODEL": request.app.state.config.WHISPER_MODEL,
             "DEEPGRAM_API_KEY": request.app.state.config.DEEPGRAM_API_KEY,
+            "AZURE_API_KEY": request.app.state.config.AUDIO_STT_AZURE_API_KEY,
+            "AZURE_REGION": request.app.state.config.AUDIO_STT_AZURE_REGION,
+            "AZURE_LOCALES": request.app.state.config.AUDIO_STT_AZURE_LOCALES,
         },
     }
 
@@ -496,10 +514,15 @@ def transcribe(request: Request, file_path):
         log.debug(data)
         return data
     elif request.app.state.config.STT_ENGINE == "openai":
-        if is_mp4_audio(file_path):
-            os.rename(file_path, file_path.replace(".wav", ".mp4"))
-            # Convert MP4 audio file to WAV format
-            convert_mp4_to_wav(file_path.replace(".wav", ".mp4"), file_path)
+        audio_format = get_audio_format(file_path)
+        if audio_format:
+            os.rename(file_path, file_path.replace(".wav", f".{audio_format}"))
+            # Convert unsupported audio file to WAV format
+            convert_audio_to_wav(
+                file_path.replace(".wav", f".{audio_format}"),
+                file_path,
+                audio_format,
+            )
 
         r = None
         try:
@@ -598,6 +621,107 @@ def transcribe(request: Request, file_path):
                     detail = f"External: {e}"
             raise Exception(detail if detail else "Open WebUI: Server Connection Error")
 
+    elif request.app.state.config.STT_ENGINE == "azure":
+        # Check file exists and size
+        if not os.path.exists(file_path):
+            raise HTTPException(
+                status_code=400,
+                detail="Audio file not found"
+            )
+
+        # Check file size (Azure has a larger limit of 200MB)
+        file_size = os.path.getsize(file_path)
+        if file_size > AZURE_MAX_FILE_SIZE:
+            raise HTTPException(
+                status_code=400,
+                detail=f"File size exceeds Azure's limit of {AZURE_MAX_FILE_SIZE_MB}MB",
+            )
+
+        api_key = request.app.state.config.AUDIO_STT_AZURE_API_KEY
+        region = request.app.state.config.AUDIO_STT_AZURE_REGION
+        locales = request.app.state.config.AUDIO_STT_AZURE_LOCALES
+
+        # IF NO LOCALES, USE DEFAULTS
+        if len(locales) < 2:
+            locales = ['en-US', 'es-ES', 'es-MX', 'fr-FR', 'hi-IN', 
+                       'it-IT','de-DE', 'en-GB', 'en-IN', 'ja-JP', 
+                       'ko-KR', 'pt-BR', 'zh-CN']
+            locales = ','.join(locales)
+
+
+        if not api_key or not region:
+            raise HTTPException(
+                status_code=400,
+                detail="Azure API key and region are required for Azure STT",
+            )
+
+        r = None
+        try:
+            # Prepare the request
+            data = {'definition': json.dumps({
+                                                "locales": locales.split(','),
+                                                "diarization": {"maxSpeakers": 3,"enabled": True}
+                                              } if locales else {}
+                                              )
+            }
+            url = f"https://{region}.api.cognitive.microsoft.com/speechtotext/transcriptions:transcribe?api-version=2024-11-15"
+            
+            # Use context manager to ensure file is properly closed
+            with open(file_path, 'rb') as audio_file:
+                r = requests.post(
+                    url=url,
+                    files={'audio': audio_file},
+                    data=data,
+                    headers={
+                        'Ocp-Apim-Subscription-Key': api_key,
+                    },
+                )
+
+            r.raise_for_status()
+            response = r.json()
+
+            # Extract transcript from response
+            if not response.get('combinedPhrases'):
+                raise ValueError("No transcription found in response")
+
+            # Get the full transcript from combinedPhrases
+            transcript = response['combinedPhrases'][0].get('text', '').strip()
+            if not transcript:
+                raise ValueError("Empty transcript in response")
+
+            data = {"text": transcript}
+
+            # Save transcript to json file (consistent with other providers)
+            transcript_file = f"{file_dir}/{id}.json"
+            with open(transcript_file, "w") as f:
+                json.dump(data, f)
+
+            log.debug(data)
+            return data
+
+        except (KeyError, IndexError, ValueError) as e:
+            log.exception("Error parsing Azure response")
+            raise HTTPException(
+                status_code=500,
+                detail=f"Failed to parse Azure response: {str(e)}",
+            )
+        except requests.exceptions.RequestException as e:
+            log.exception(e)
+            detail = None
+
+            try:
+                if r is not None and r.status_code != 200:
+                    res = r.json()
+                    if "error" in res:
+                        detail = f"External: {res['error'].get('message', '')}"
+            except Exception:
+                detail = f"External: {e}"
+
+            raise HTTPException(
+                status_code=getattr(r, 'status_code', 500) if r else 500,
+                detail=detail if detail else "Open WebUI: Server Connection Error",
+            )
+
 
 def compress_audio(file_path):
     if os.path.getsize(file_path) > MAX_FILE_SIZE:

+ 12 - 3
backend/open_webui/routers/auths.py

@@ -231,11 +231,13 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
 
         entry = connection_app.entries[0]
         username = str(entry[f"{LDAP_ATTRIBUTE_FOR_USERNAME}"]).lower()
-        email = str(entry[f"{LDAP_ATTRIBUTE_FOR_MAIL}"])
-        if not email or email == "" or email == "[]":
+        email = entry[f"{LDAP_ATTRIBUTE_FOR_MAIL}"]
+        if not email:
             raise HTTPException(400, "User does not have a valid email address.")
-        else:
+        elif isinstance(email, str):
             email = email.lower()
+        elif isinstance(email, list):
+            email = email[0].lower()
 
         cn = str(entry["cn"])
         user_dn = entry.entry_dn
@@ -455,6 +457,13 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
             # Disable signup after the first user is created
             request.app.state.config.ENABLE_SIGNUP = False
 
+        # The password passed to bcrypt must be 72 bytes or fewer. If it is longer, it will be truncated before hashing.
+        if len(form_data.password.encode("utf-8")) > 72:
+            raise HTTPException(
+                status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.PASSWORD_TOO_LONG,
+            )
+
         hashed = get_password_hash(form_data.password)
         user = Auths.insert_new_auth(
             form_data.email.lower(),

+ 42 - 0
backend/open_webui/routers/files.py

@@ -1,6 +1,7 @@
 import logging
 import os
 import uuid
+from fnmatch import fnmatch
 from pathlib import Path
 from typing import Optional
 from urllib.parse import quote
@@ -177,6 +178,47 @@ async def list_files(user=Depends(get_verified_user), content: bool = Query(True
     return files
 
 
+############################
+# Search Files
+############################
+
+
+@router.get("/search", response_model=list[FileModelResponse])
+async def search_files(
+    filename: str = Query(
+        ...,
+        description="Filename pattern to search for. Supports wildcards such as '*.txt'",
+    ),
+    content: bool = Query(True),
+    user=Depends(get_verified_user),
+):
+    """
+    Search for files by filename with support for wildcard patterns.
+    """
+    # Get files according to user role
+    if user.role == "admin":
+        files = Files.get_files()
+    else:
+        files = Files.get_files_by_user_id(user.id)
+
+    # Get matching files
+    matching_files = [
+        file for file in files if fnmatch(file.filename.lower(), filename.lower())
+    ]
+
+    if not matching_files:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="No files found matching the pattern.",
+        )
+
+    if not content:
+        for file in matching_files:
+            del file.data["content"]
+
+    return matching_files
+
+
 ############################
 # Delete All Files
 ############################

+ 69 - 0
backend/open_webui/routers/knowledge.py

@@ -159,6 +159,72 @@ async def create_new_knowledge(
             status_code=status.HTTP_400_BAD_REQUEST,
             detail=ERROR_MESSAGES.FILE_EXISTS,
         )
+    
+
+
+############################
+# ReindexKnowledgeFiles
+############################
+
+
+@router.post("/reindex", response_model=bool)
+async def reindex_knowledge_files(
+    request: Request,
+    user=Depends(get_verified_user)
+):
+    if user.role != "admin":
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.UNAUTHORIZED,
+        )
+    
+    knowledge_bases = Knowledges.get_knowledge_bases()
+         
+    log.info(f"Starting reindexing for {len(knowledge_bases)} knowledge bases")
+    
+    for knowledge_base in knowledge_bases:
+        try:
+            files = Files.get_files_by_ids(knowledge_base.data.get("file_ids", []))
+
+            try:
+                if VECTOR_DB_CLIENT.has_collection(collection_name=knowledge_base.id):
+                    VECTOR_DB_CLIENT.delete_collection(
+                        collection_name=knowledge_base.id
+                    )
+            except Exception as e:
+                log.error(f"Error deleting collection {knowledge_base.id}: {str(e)}")
+                raise HTTPException(
+                    status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+                    detail=f"Error deleting vector DB collection"
+                )
+            
+            failed_files = []
+            for file in files:
+                try:
+                    process_file(
+                        request,
+                        ProcessFileForm(file_id=file.id, collection_name=knowledge_base.id),
+                        user=user,
+                    )
+                except Exception as e:
+                    log.error(f"Error processing file {file.filename} (ID: {file.id}): {str(e)}")
+                    failed_files.append({"file_id": file.id, "error": str(e)})
+                    continue
+          
+        except Exception as e:
+            log.error(f"Error processing knowledge base {knowledge_base.id}: {str(e)}")
+            raise HTTPException(
+                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+                detail=f"Error processing knowledge base"
+            )
+        
+        if failed_files:
+            log.warning(f"Failed to process {len(failed_files)} files in knowledge base {knowledge_base.id}")
+            for failed in failed_files:
+                log.warning(f"File ID: {failed['file_id']}, Error: {failed['error']}")
+    
+    log.info("Reindexing completed successfully")
+    return True
 
 
 ############################
@@ -676,3 +742,6 @@ def add_files_to_knowledge_batch(
     return KnowledgeFilesResponse(
         **knowledge.model_dump(), files=Files.get_files_by_ids(existing_file_ids)
     )
+
+
+

+ 25 - 0
backend/open_webui/routers/retrieval.py

@@ -60,6 +60,7 @@ from open_webui.retrieval.web.tavily import search_tavily
 from open_webui.retrieval.web.bing import search_bing
 from open_webui.retrieval.web.exa import search_exa
 from open_webui.retrieval.web.perplexity import search_perplexity
+from open_webui.retrieval.web.sougou import search_sougou
 
 from open_webui.retrieval.utils import (
     get_embedding_function,
@@ -411,6 +412,8 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
                 "bing_search_v7_subscription_key": request.app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
                 "exa_api_key": request.app.state.config.EXA_API_KEY,
                 "perplexity_api_key": request.app.state.config.PERPLEXITY_API_KEY,
+                "sougou_api_sid": request.app.state.config.SOUGOU_API_SID,
+                "sougou_api_sk": request.app.state.config.SOUGOU_API_SK,
                 "result_count": request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
                 "trust_env": request.app.state.config.RAG_WEB_SEARCH_TRUST_ENV,
                 "concurrent_requests": request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
@@ -478,6 +481,8 @@ class WebSearchConfig(BaseModel):
     bing_search_v7_subscription_key: Optional[str] = None
     exa_api_key: Optional[str] = None
     perplexity_api_key: Optional[str] = None
+    sougou_api_sid: Optional[str] = None
+    sougou_api_sk: Optional[str] = None
     result_count: Optional[int] = None
     concurrent_requests: Optional[int] = None
     trust_env: Optional[bool] = None
@@ -640,6 +645,12 @@ async def update_rag_config(
         request.app.state.config.PERPLEXITY_API_KEY = (
             form_data.web.search.perplexity_api_key
         )
+        request.app.state.config.SOUGOU_API_SID = (
+            form_data.web.search.sougou_api_sid
+        )
+        request.app.state.config.SOUGOU_API_SK = (
+            form_data.web.search.sougou_api_sk
+        )
 
         request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = (
             form_data.web.search.result_count
@@ -712,6 +723,8 @@ async def update_rag_config(
                 "bing_search_v7_subscription_key": request.app.state.config.BING_SEARCH_V7_SUBSCRIPTION_KEY,
                 "exa_api_key": request.app.state.config.EXA_API_KEY,
                 "perplexity_api_key": request.app.state.config.PERPLEXITY_API_KEY,
+                "sougou_api_sid": request.app.state.config.SOUGOU_API_SID,
+                "sougou_api_sk": request.app.state.config.SOUGOU_API_SK,
                 "result_count": request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
                 "concurrent_requests": request.app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
                 "trust_env": request.app.state.config.RAG_WEB_SEARCH_TRUST_ENV,
@@ -1267,6 +1280,7 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
     - TAVILY_API_KEY
     - EXA_API_KEY
     - PERPLEXITY_API_KEY
+    - SOUGOU_API_SID + SOUGOU_API_SK
     - SEARCHAPI_API_KEY + SEARCHAPI_ENGINE (by default `google`)
     - SERPAPI_API_KEY + SERPAPI_ENGINE (by default `google`)
     Args:
@@ -1438,6 +1452,17 @@ def search_web(request: Request, engine: str, query: str) -> list[SearchResult]:
             request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
             request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
         )
+    elif engine == 'sougou':
+        if request.app.state.config.SOUGOU_API_SID and request.app.state.config.SOUGOU_API_SK:
+            return search_sougou(
+                request.app.state.config.SOUGOU_API_SID,
+                request.app.state.config.SOUGOU_API_SK,
+                query,
+                request.app.state.config.RAG_WEB_SEARCH_RESULT_COUNT,
+                request.app.state.config.RAG_WEB_SEARCH_DOMAIN_FILTER_LIST,
+            )
+        else:
+            raise Exception("No SOUGOU_API_SID or SOUGOU_API_SK found in environment variables")
     else:
         raise Exception("No search engine API key found in environment variables")
 

+ 14 - 16
backend/open_webui/routers/tools.py

@@ -10,11 +10,11 @@ from open_webui.models.tools import (
     ToolUserResponse,
     Tools,
 )
-from open_webui.utils.plugin import load_tools_module_by_id, replace_imports
+from open_webui.utils.plugin import load_tool_module_by_id, replace_imports
 from open_webui.config import CACHE_DIR
 from open_webui.constants import ERROR_MESSAGES
 from fastapi import APIRouter, Depends, HTTPException, Request, status
-from open_webui.utils.tools import get_tools_specs
+from open_webui.utils.tools import get_tool_specs
 from open_webui.utils.auth import get_admin_user, get_verified_user
 from open_webui.utils.access_control import has_access, has_permission
 from open_webui.env import SRC_LOG_LEVELS
@@ -45,7 +45,7 @@ async def get_tools(request: Request, user=Depends(get_verified_user)):
         )
 
     tools = Tools.get_tools()
-    for idx, server in enumerate(request.app.state.TOOL_SERVERS):
+    for server in request.app.state.TOOL_SERVERS:
         tools.append(
             ToolUserResponse(
                 **{
@@ -60,7 +60,7 @@ async def get_tools(request: Request, user=Depends(get_verified_user)):
                         .get("description", ""),
                     },
                     "access_control": request.app.state.config.TOOL_SERVER_CONNECTIONS[
-                        idx
+                        server["idx"]
                     ]
                     .get("config", {})
                     .get("access_control", None),
@@ -137,15 +137,15 @@ async def create_new_tools(
     if tools is None:
         try:
             form_data.content = replace_imports(form_data.content)
-            tools_module, frontmatter = load_tools_module_by_id(
+            tool_module, frontmatter = load_tool_module_by_id(
                 form_data.id, content=form_data.content
             )
             form_data.meta.manifest = frontmatter
 
             TOOLS = request.app.state.TOOLS
-            TOOLS[form_data.id] = tools_module
+            TOOLS[form_data.id] = tool_module
 
-            specs = get_tools_specs(TOOLS[form_data.id])
+            specs = get_tool_specs(TOOLS[form_data.id])
             tools = Tools.insert_new_tool(user.id, form_data, specs)
 
             tool_cache_dir = CACHE_DIR / "tools" / form_data.id
@@ -226,15 +226,13 @@ async def update_tools_by_id(
 
     try:
         form_data.content = replace_imports(form_data.content)
-        tools_module, frontmatter = load_tools_module_by_id(
-            id, content=form_data.content
-        )
+        tool_module, frontmatter = load_tool_module_by_id(id, content=form_data.content)
         form_data.meta.manifest = frontmatter
 
         TOOLS = request.app.state.TOOLS
-        TOOLS[id] = tools_module
+        TOOLS[id] = tool_module
 
-        specs = get_tools_specs(TOOLS[id])
+        specs = get_tool_specs(TOOLS[id])
 
         updated = {
             **form_data.model_dump(exclude={"id"}),
@@ -332,7 +330,7 @@ async def get_tools_valves_spec_by_id(
         if id in request.app.state.TOOLS:
             tools_module = request.app.state.TOOLS[id]
         else:
-            tools_module, _ = load_tools_module_by_id(id)
+            tools_module, _ = load_tool_module_by_id(id)
             request.app.state.TOOLS[id] = tools_module
 
         if hasattr(tools_module, "Valves"):
@@ -375,7 +373,7 @@ async def update_tools_valves_by_id(
     if id in request.app.state.TOOLS:
         tools_module = request.app.state.TOOLS[id]
     else:
-        tools_module, _ = load_tools_module_by_id(id)
+        tools_module, _ = load_tool_module_by_id(id)
         request.app.state.TOOLS[id] = tools_module
 
     if not hasattr(tools_module, "Valves"):
@@ -431,7 +429,7 @@ async def get_tools_user_valves_spec_by_id(
         if id in request.app.state.TOOLS:
             tools_module = request.app.state.TOOLS[id]
         else:
-            tools_module, _ = load_tools_module_by_id(id)
+            tools_module, _ = load_tool_module_by_id(id)
             request.app.state.TOOLS[id] = tools_module
 
         if hasattr(tools_module, "UserValves"):
@@ -455,7 +453,7 @@ async def update_tools_user_valves_by_id(
         if id in request.app.state.TOOLS:
             tools_module = request.app.state.TOOLS[id]
         else:
-            tools_module, _ = load_tools_module_by_id(id)
+            tools_module, _ = load_tool_module_by_id(id)
             request.app.state.TOOLS[id] = tools_module
 
         if hasattr(tools_module, "UserValves"):

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

@@ -897,12 +897,16 @@ async def process_chat_payload(request, form_data, user, metadata, model):
     # If context is not empty, insert it into the messages
     if len(sources) > 0:
         context_string = ""
-        for source_idx, source in enumerate(sources):
+        citated_file_idx = {}
+        for _, source in enumerate(sources, 1):
             if "document" in source:
-                for doc_idx, doc_context in enumerate(source["document"]):
-                    context_string += (
-                        f'<source id="{source_idx + 1}">{doc_context}</source>\n'
-                    )
+                for doc_context, doc_meta in zip(
+                    source["document"], source["metadata"]
+                ):
+                    file_id = doc_meta.get("file_id")
+                    if file_id not in citated_file_idx:
+                        citated_file_idx[file_id] = len(citated_file_idx) + 1
+                    context_string += f'<source id="{citated_file_idx[file_id]}">{doc_context}</source>\n'
 
         context_string = context_string.strip()
         prompt = get_last_user_message(form_data["messages"])
@@ -1609,6 +1613,9 @@ async def process_chat_response(
                             )
 
                             if data:
+                                if "event" in data:
+                                    await event_emitter(data.get("event", {}))
+
                                 if "selected_model_id" in data:
                                     model_id = data["selected_model_id"]
                                     Chats.upsert_message_to_chat_by_id_and_message_id(
@@ -1653,14 +1660,27 @@ async def process_chat_response(
                                             )
 
                                             if tool_call_index is not None:
-                                                if (
-                                                    len(response_tool_calls)
-                                                    <= tool_call_index
-                                                ):
+                                                # Check if the tool call already exists
+                                                current_response_tool_call = None
+                                                for (
+                                                    response_tool_call
+                                                ) in response_tool_calls:
+                                                    if (
+                                                        response_tool_call.get("index")
+                                                        == tool_call_index
+                                                    ):
+                                                        current_response_tool_call = (
+                                                            response_tool_call
+                                                        )
+                                                        break
+
+                                                if current_response_tool_call is None:
+                                                    # Add the new tool call
                                                     response_tool_calls.append(
                                                         delta_tool_call
                                                     )
                                                 else:
+                                                    # Update the existing tool call
                                                     delta_name = delta_tool_call.get(
                                                         "function", {}
                                                     ).get("name")
@@ -1671,16 +1691,14 @@ async def process_chat_response(
                                                     )
 
                                                     if delta_name:
-                                                        response_tool_calls[
-                                                            tool_call_index
-                                                        ]["function"][
-                                                            "name"
-                                                        ] += delta_name
+                                                        current_response_tool_call[
+                                                            "function"
+                                                        ]["name"] += delta_name
 
                                                     if delta_arguments:
-                                                        response_tool_calls[
-                                                            tool_call_index
-                                                        ]["function"][
+                                                        current_response_tool_call[
+                                                            "function"
+                                                        ][
                                                             "arguments"
                                                         ] += delta_arguments
 

+ 1 - 1
backend/open_webui/utils/plugin.py

@@ -68,7 +68,7 @@ def replace_imports(content):
     return content
 
 
-def load_tools_module_by_id(tool_id, content=None):
+def load_tool_module_by_id(tool_id, content=None):
 
     if content is None:
         tool = Tools.get_tool_by_id(tool_id)

+ 71 - 27
backend/open_webui/utils/tools.py

@@ -4,19 +4,39 @@ import re
 import inspect
 import aiohttp
 import asyncio
-
-from typing import Any, Awaitable, Callable, get_type_hints, Dict, List, Union, Optional
+import yaml
+
+from pydantic import BaseModel
+from pydantic.fields import FieldInfo
+from typing import (
+    Any,
+    Awaitable,
+    Callable,
+    get_type_hints,
+    get_args,
+    get_origin,
+    Dict,
+    List,
+    Tuple,
+    Union,
+    Optional,
+    Type,
+)
 from functools import update_wrapper, partial
 
 
 from fastapi import Request
 from pydantic import BaseModel, Field, create_model
-from langchain_core.utils.function_calling import convert_to_openai_function
+
+from langchain_core.utils.function_calling import (
+    convert_to_openai_function as convert_pydantic_model_to_openai_function_spec,
+)
 
 
 from open_webui.models.tools import Tools
 from open_webui.models.users import UserModel
-from open_webui.utils.plugin import load_tools_module_by_id
+from open_webui.utils.plugin import load_tool_module_by_id
+from open_webui.env import AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA
 
 import copy
 
@@ -55,7 +75,12 @@ def get_tools(
                 tool_server_connection = (
                     request.app.state.config.TOOL_SERVER_CONNECTIONS[server_idx]
                 )
-                tool_server_data = request.app.state.TOOL_SERVERS[server_idx]
+                tool_server_data = None
+                for server in request.app.state.TOOL_SERVERS:
+                    if server["idx"] == server_idx:
+                        tool_server_data = server
+                        break
+                assert tool_server_data is not None
                 specs = tool_server_data.get("specs", [])
 
                 for spec in specs:
@@ -112,7 +137,7 @@ def get_tools(
         else:
             module = request.app.state.TOOLS.get(tool_id, None)
             if module is None:
-                module, _ = load_tools_module_by_id(tool_id)
+                module, _ = load_tool_module_by_id(tool_id)
                 request.app.state.TOOLS[tool_id] = module
 
             extra_params["__id__"] = tool_id
@@ -233,7 +258,7 @@ def parse_docstring(docstring):
     return param_descriptions
 
 
-def function_to_pydantic_model(func: Callable) -> type[BaseModel]:
+def convert_function_to_pydantic_model(func: Callable) -> type[BaseModel]:
     """
     Converts a Python function's type hints and docstring to a Pydantic model,
     including support for nested types, default values, and descriptions.
@@ -250,45 +275,57 @@ def function_to_pydantic_model(func: Callable) -> type[BaseModel]:
     parameters = signature.parameters
 
     docstring = func.__doc__
-    descriptions = parse_docstring(docstring)
 
-    tool_description = parse_description(docstring)
+    description = parse_description(docstring)
+    function_descriptions = parse_docstring(docstring)
 
     field_defs = {}
     for name, param in parameters.items():
+
         type_hint = type_hints.get(name, Any)
         default_value = param.default if param.default is not param.empty else ...
-        description = descriptions.get(name, None)
-        if not description:
+
+        description = function_descriptions.get(name, None)
+
+        if description:
+            field_defs[name] = type_hint, Field(default_value, description=description)
+        else:
             field_defs[name] = type_hint, default_value
-            continue
-        field_defs[name] = type_hint, Field(default_value, description=description)
 
     model = create_model(func.__name__, **field_defs)
-    model.__doc__ = tool_description
+    model.__doc__ = description
 
     return model
 
 
-def get_callable_attributes(tool: object) -> list[Callable]:
+def get_functions_from_tool(tool: object) -> list[Callable]:
     return [
         getattr(tool, func)
         for func in dir(tool)
-        if callable(getattr(tool, func))
-        and not func.startswith("__")
-        and not inspect.isclass(getattr(tool, func))
+        if callable(
+            getattr(tool, func)
+        )  # checks if the attribute is callable (a method or function).
+        and not func.startswith(
+            "__"
+        )  # filters out special (dunder) methods like init, str, etc. — these are usually built-in functions of an object that you might not need to use directly.
+        and not inspect.isclass(
+            getattr(tool, func)
+        )  # ensures that the callable is not a class itself, just a method or function.
     ]
 
 
-def get_tools_specs(tool_class: object) -> list[dict]:
-    function_model_list = map(
-        function_to_pydantic_model, get_callable_attributes(tool_class)
+def get_tool_specs(tool_module: object) -> list[dict]:
+    function_models = map(
+        convert_function_to_pydantic_model, get_functions_from_tool(tool_module)
     )
-    return [
-        convert_to_openai_function(function_model)
-        for function_model in function_model_list
+
+    specs = [
+        convert_pydantic_model_to_openai_function_spec(function_model)
+        for function_model in function_models
     ]
 
+    return specs
+
 
 def resolve_schema(schema, components):
     """
@@ -393,14 +430,21 @@ async def get_tool_server_data(token: str, url: str) -> Dict[str, Any]:
 
     error = None
     try:
-        async with aiohttp.ClientSession() as session:
+        timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA)
+        async with aiohttp.ClientSession(timeout=timeout) as session:
             async with session.get(url, headers=headers) as response:
                 if response.status != 200:
                     error_body = await response.json()
                     raise Exception(error_body)
-                res = await response.json()
+
+                # Check if URL ends with .yaml or .yml to determine format
+                if url.lower().endswith((".yaml", ".yml")):
+                    text_content = await response.text()
+                    res = yaml.safe_load(text_content)
+                else:
+                    res = await response.json()
     except Exception as err:
-        print("Error:", err)
+        log.exception(f"Could not fetch tool server spec from {url}")
         if isinstance(err, dict) and "detail" in err:
             error = err["detail"]
         else:

+ 19 - 16
backend/requirements.txt

@@ -18,7 +18,7 @@ alembic==1.14.0
 peewee==3.17.9
 peewee-migrate==1.12.2
 psycopg2-binary==2.9.9
-pgvector==0.3.5
+pgvector==0.4.0
 PyMySQL==1.1.1
 bcrypt==4.3.0
 
@@ -44,7 +44,7 @@ langchain==0.3.19
 langchain-community==0.3.18
 
 fake-useragent==2.1.0
-chromadb==0.6.2
+chromadb==0.6.3
 pymilvus==2.5.0
 qdrant-client~=1.12.0
 opensearch-py==2.8.0
@@ -93,12 +93,12 @@ authlib==1.4.1
 
 black==25.1.0
 langfuse==2.44.0
-youtube-transcript-api==0.6.3
+youtube-transcript-api==1.0.3
 pytube==15.0.0
 
 extract_msg
 pydub
-duckduckgo-search~=7.3.2
+duckduckgo-search~=8.0.0
 
 ## Google Drive
 google-api-python-client
@@ -113,7 +113,7 @@ pytest-docker~=3.1.1
 googleapis-common-protos==1.63.2
 google-cloud-storage==2.19.0
 
-azure-identity==1.20.0
+azure-identity==1.21.0
 azure-storage-blob==12.24.1
 
 
@@ -123,15 +123,18 @@ ldap3==2.9.1
 ## Firecrawl
 firecrawl-py==1.12.0
 
+# Sougou API SDK(Tencentcloud SDK)
+tencentcloud-sdk-python==3.0.1336
+
 ## Trace
-opentelemetry-api==1.30.0
-opentelemetry-sdk==1.30.0
-opentelemetry-exporter-otlp==1.30.0
-opentelemetry-instrumentation==0.51b0
-opentelemetry-instrumentation-fastapi==0.51b0
-opentelemetry-instrumentation-sqlalchemy==0.51b0
-opentelemetry-instrumentation-redis==0.51b0
-opentelemetry-instrumentation-requests==0.51b0
-opentelemetry-instrumentation-logging==0.51b0
-opentelemetry-instrumentation-httpx==0.51b0
-opentelemetry-instrumentation-aiohttp-client==0.51b0
+opentelemetry-api==1.31.1
+opentelemetry-sdk==1.31.1
+opentelemetry-exporter-otlp==1.31.1
+opentelemetry-instrumentation==0.52b1
+opentelemetry-instrumentation-fastapi==0.52b1
+opentelemetry-instrumentation-sqlalchemy==0.52b1
+opentelemetry-instrumentation-redis==0.52b1
+opentelemetry-instrumentation-requests==0.52b1
+opentelemetry-instrumentation-logging==0.52b1
+opentelemetry-instrumentation-httpx==0.52b1
+opentelemetry-instrumentation-aiohttp-client==0.52b1

+ 1 - 1
backend/start.sh

@@ -65,4 +65,4 @@ if [ -n "$SPACE_ID" ]; then
   export WEBUI_URL=${SPACE_HOST}
 fi
 
-WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" exec uvicorn open_webui.main:app --host "$HOST" --port "$PORT" --forwarded-allow-ips '*'
+WEBUI_SECRET_KEY="$WEBUI_SECRET_KEY" exec uvicorn open_webui.main:app --host "$HOST" --port "$PORT" --forwarded-allow-ips '*' --workers "${UVICORN_WORKERS:-1}"

+ 2 - 1
backend/start_windows.bat

@@ -41,5 +41,6 @@ IF "%WEBUI_SECRET_KEY%%WEBUI_JWT_SECRET_KEY%" == " " (
 
 :: Execute uvicorn
 SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%"
-uvicorn open_webui.main:app --host "%HOST%" --port "%PORT%" --forwarded-allow-ips '*' --ws auto
+IF "%UVICORN_WORKERS%"=="" SET UVICORN_WORKERS=1
+uvicorn open_webui.main:app --host "%HOST%" --port "%PORT%" --forwarded-allow-ips '*' --workers %UVICORN_WORKERS% --ws auto
 :: For ssl user uvicorn open_webui.main:app --host "%HOST%" --port "%PORT%" --forwarded-allow-ips '*' --ssl-keyfile "key.pem" --ssl-certfile "cert.pem" --ws auto

+ 60 - 26
package-lock.json

@@ -23,12 +23,13 @@
 				"@tiptap/extension-highlight": "^2.10.0",
 				"@tiptap/extension-placeholder": "^2.10.0",
 				"@tiptap/extension-typography": "^2.10.0",
-				"@tiptap/pm": "^2.10.0",
+				"@tiptap/pm": "^2.11.7",
 				"@tiptap/starter-kit": "^2.10.0",
 				"@xyflow/svelte": "^0.1.19",
 				"async": "^3.2.5",
 				"bits-ui": "^0.19.7",
 				"codemirror": "^6.0.1",
+				"codemirror-lang-elixir": "^4.0.0",
 				"codemirror-lang-hcl": "^0.0.0-beta.2",
 				"crc-32": "^1.2.2",
 				"dayjs": "^1.11.10",
@@ -69,7 +70,8 @@
 				"turndown": "^7.2.0",
 				"undici": "^7.3.0",
 				"uuid": "^9.0.1",
-				"vite-plugin-static-copy": "^2.2.0"
+				"vite-plugin-static-copy": "^2.2.0",
+				"yaml": "^2.7.1"
 			},
 			"devDependencies": {
 				"@sveltejs/adapter-auto": "3.2.2",
@@ -3042,9 +3044,9 @@
 			}
 		},
 		"node_modules/@tiptap/pm": {
-			"version": "2.10.0",
-			"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.10.0.tgz",
-			"integrity": "sha512-ohshlWf4MlW6D3rQkNQnhmiQ2w4pwRoQcJmTPt8UJoIDGkeKmZh494fQp4Aeh80XuGd81SsCv//1HJeyaeHJYQ==",
+			"version": "2.11.7",
+			"resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.7.tgz",
+			"integrity": "sha512-7gEEfz2Q6bYKXM07vzLUD0vqXFhC5geWRA6LCozTiLdVFDdHWiBrvb2rtkL5T7mfLq03zc1QhH7rI3F6VntOEA==",
 			"license": "MIT",
 			"dependencies": {
 				"prosemirror-changeset": "^2.2.1",
@@ -3061,10 +3063,10 @@
 				"prosemirror-schema-basic": "^1.2.3",
 				"prosemirror-schema-list": "^1.4.1",
 				"prosemirror-state": "^1.4.3",
-				"prosemirror-tables": "^1.6.1",
+				"prosemirror-tables": "^1.6.4",
 				"prosemirror-trailing-node": "^3.0.0",
 				"prosemirror-transform": "^1.10.2",
-				"prosemirror-view": "^1.36.0"
+				"prosemirror-view": "^1.37.0"
 			},
 			"funding": {
 				"type": "github",
@@ -4625,6 +4627,15 @@
 				"@codemirror/view": "^6.0.0"
 			}
 		},
+		"node_modules/codemirror-lang-elixir": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/codemirror-lang-elixir/-/codemirror-lang-elixir-4.0.0.tgz",
+			"integrity": "sha512-mzFesxo/t6KOxwnkqVd34R/q7yk+sMtHh6vUKGAvjwHmpL7bERHB+vQAsmU/nqrndkwVeJEHWGw/z/ybfdiudA==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"lezer-elixir": "^1.0.0"
+			}
+		},
 		"node_modules/codemirror-lang-hcl": {
 			"version": "0.0.0-beta.2",
 			"resolved": "https://registry.npmjs.org/codemirror-lang-hcl/-/codemirror-lang-hcl-0.0.0-beta.2.tgz",
@@ -7611,6 +7622,15 @@
 				"node": ">= 0.8.0"
 			}
 		},
+		"node_modules/lezer-elixir": {
+			"version": "1.1.2",
+			"resolved": "https://registry.npmjs.org/lezer-elixir/-/lezer-elixir-1.1.2.tgz",
+			"integrity": "sha512-K3yPMJcNhqCL6ugr5NkgOC1g37rcOM38XZezO9lBXy0LwWFd8zdWXfmRbY829vZVk0OGCQoI02yDWp9FF2OWZA==",
+			"dependencies": {
+				"@lezer/highlight": "^1.2.0",
+				"@lezer/lr": "^1.3.0"
+			}
+		},
 		"node_modules/lightningcss": {
 			"version": "1.29.1",
 			"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz",
@@ -9495,6 +9515,16 @@
 				"node": ">=10"
 			}
 		},
+		"node_modules/postcss-load-config/node_modules/yaml": {
+			"version": "1.10.2",
+			"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+			"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+			"dev": true,
+			"license": "ISC",
+			"engines": {
+				"node": ">= 6"
+			}
+		},
 		"node_modules/postcss-safe-parser": {
 			"version": "6.0.0",
 			"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz",
@@ -9783,9 +9813,10 @@
 			}
 		},
 		"node_modules/prosemirror-model": {
-			"version": "1.23.0",
-			"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.23.0.tgz",
-			"integrity": "sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==",
+			"version": "1.25.0",
+			"resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.0.tgz",
+			"integrity": "sha512-/8XUmxWf0pkj2BmtqZHYJipTBMHIdVjuvFzMvEoxrtyGNmfvdhBiRwYt/eFwy2wA9DtBW3RLqvZnjurEkHaFCw==",
+			"license": "MIT",
 			"dependencies": {
 				"orderedmap": "^2.0.0"
 			}
@@ -9819,16 +9850,16 @@
 			}
 		},
 		"node_modules/prosemirror-tables": {
-			"version": "1.6.1",
-			"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.1.tgz",
-			"integrity": "sha512-p8WRJNA96jaNQjhJolmbxTzd6M4huRE5xQ8OxjvMhQUP0Nzpo4zz6TztEiwk6aoqGBhz9lxRWR1yRZLlpQN98w==",
+			"version": "1.6.4",
+			"resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.4.tgz",
+			"integrity": "sha512-TkDY3Gw52gRFRfRn2f4wJv5WOgAOXLJA2CQJYIJ5+kdFbfj3acR4JUW6LX2e1hiEBiUwvEhzH5a3cZ5YSztpIA==",
 			"license": "MIT",
 			"dependencies": {
-				"prosemirror-keymap": "^1.1.2",
-				"prosemirror-model": "^1.8.1",
-				"prosemirror-state": "^1.3.1",
-				"prosemirror-transform": "^1.2.1",
-				"prosemirror-view": "^1.13.3"
+				"prosemirror-keymap": "^1.2.2",
+				"prosemirror-model": "^1.24.1",
+				"prosemirror-state": "^1.4.3",
+				"prosemirror-transform": "^1.10.2",
+				"prosemirror-view": "^1.37.2"
 			}
 		},
 		"node_modules/prosemirror-trailing-node": {
@@ -9856,9 +9887,9 @@
 			}
 		},
 		"node_modules/prosemirror-view": {
-			"version": "1.36.0",
-			"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.36.0.tgz",
-			"integrity": "sha512-U0GQd5yFvV5qUtT41X1zCQfbw14vkbbKwLlQXhdylEmgpYVHkefXYcC4HHwWOfZa3x6Y8wxDLUBv7dxN5XQ3nA==",
+			"version": "1.39.1",
+			"resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.39.1.tgz",
+			"integrity": "sha512-GhLxH1xwnqa5VjhJ29LfcQITNDp+f1jzmMPXQfGW9oNrF0lfjPzKvV5y/bjIQkyKpwCX3Fp+GA4dBpMMk8g+ZQ==",
 			"license": "MIT",
 			"dependencies": {
 				"prosemirror-model": "^1.20.0",
@@ -13011,12 +13042,15 @@
 			}
 		},
 		"node_modules/yaml": {
-			"version": "1.10.2",
-			"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
-			"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
-			"dev": true,
+			"version": "2.7.1",
+			"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
+			"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
+			"license": "ISC",
+			"bin": {
+				"yaml": "bin.mjs"
+			},
 			"engines": {
-				"node": ">= 6"
+				"node": ">= 14"
 			}
 		},
 		"node_modules/yauzl": {

+ 4 - 2
package.json

@@ -66,13 +66,14 @@
 		"@tiptap/extension-highlight": "^2.10.0",
 		"@tiptap/extension-placeholder": "^2.10.0",
 		"@tiptap/extension-typography": "^2.10.0",
-		"@tiptap/pm": "^2.10.0",
+		"@tiptap/pm": "^2.11.7",
 		"@tiptap/starter-kit": "^2.10.0",
 		"@xyflow/svelte": "^0.1.19",
 		"async": "^3.2.5",
 		"bits-ui": "^0.19.7",
 		"codemirror": "^6.0.1",
 		"codemirror-lang-hcl": "^0.0.0-beta.2",
+		"codemirror-lang-elixir": "^4.0.0",
 		"crc-32": "^1.2.2",
 		"dayjs": "^1.11.10",
 		"dompurify": "^3.1.6",
@@ -112,7 +113,8 @@
 		"turndown": "^7.2.0",
 		"undici": "^7.3.0",
 		"uuid": "^9.0.1",
-		"vite-plugin-static-copy": "^2.2.0"
+		"vite-plugin-static-copy": "^2.2.0",
+		"yaml": "^2.7.1"
 	},
 	"engines": {
 		"node": ">=18.13.0 <=22.x.x",

+ 3 - 1
pyproject.toml

@@ -104,7 +104,7 @@ dependencies = [
 
     "extract_msg",
     "pydub",
-    "duckduckgo-search~=7.3.2",
+    "duckduckgo-search~=8.0.0",
 
     "google-api-python-client",
     "google-auth-httplib2",
@@ -125,6 +125,8 @@ dependencies = [
 
     "firecrawl-py==1.12.0",
 
+    "tencentcloud-sdk-python==3.0.1336",
+
     "gcp-storage-emulator>=2024.8.3",
 ]
 readme = "README.md"

+ 0 - 4
src/app.css

@@ -91,10 +91,6 @@ textarea::placeholder {
 	-webkit-app-region: no-drag;
 }
 
-iframe {
-	@apply rounded-lg;
-}
-
 li p {
 	display: inline;
 }

+ 1 - 1
src/app.html

@@ -9,7 +9,7 @@
 		<link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png" />
 		<meta name="apple-mobile-web-app-title" content="Open WebUI" />
 
-		<link rel="manifest" href="/manifest.json" />
+		<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
 		<meta
 			name="viewport"
 			content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover"

+ 11 - 3
src/lib/apis/index.ts

@@ -2,6 +2,7 @@ import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
 import { convertOpenApiToToolPayload } from '$lib/utils';
 import { getOpenAIModelsDirect } from './openai';
 
+import { parse } from 'yaml';
 import { toast } from 'svelte-sonner';
 
 export const getModels = async (
@@ -271,8 +272,15 @@ export const getToolServerData = async (token: string, url: string) => {
 		}
 	})
 		.then(async (res) => {
-			if (!res.ok) throw await res.json();
-			return res.json();
+			// Check if URL ends with .yaml or .yml to determine format
+			if (url.toLowerCase().endsWith('.yaml') || url.toLowerCase().endsWith('.yml')) {
+				if (!res.ok) throw await res.text();
+				const text = await res.text();
+				return parse(text);
+			} else {
+				if (!res.ok) throw await res.json();
+				return res.json();
+			}
 		})
 		.catch((err) => {
 			console.log(err);
@@ -305,7 +313,7 @@ export const getToolServersData = async (i18n, servers: object[]) => {
 				.filter((server) => server?.config?.enable)
 				.map(async (server) => {
 					const data = await getToolServerData(
-						server?.key,
+						(server?.auth_type ?? 'bearer') === 'bearer' ? server?.key : localStorage.token,
 						server?.url + '/' + (server?.path ?? 'openapi.json')
 					).catch((err) => {
 						toast.error(

+ 29 - 0
src/lib/apis/knowledge/index.ts

@@ -345,3 +345,32 @@ export const deleteKnowledgeById = async (token: string, id: string) => {
 
 	return res;
 };
+
+
+export const reindexKnowledgeFiles = async (token: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/reindex`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};

+ 46 - 9
src/lib/components/admin/Settings/Audio.svelte

@@ -39,6 +39,9 @@
 	let STT_ENGINE = '';
 	let STT_MODEL = '';
 	let STT_WHISPER_MODEL = '';
+	let STT_AZURE_API_KEY = '';
+	let STT_AZURE_REGION = '';
+	let STT_AZURE_LOCALES = '';
 	let STT_DEEPGRAM_API_KEY = '';
 
 	let STT_WHISPER_MODEL_LOADING = false;
@@ -103,12 +106,15 @@
 				AZURE_SPEECH_OUTPUT_FORMAT: TTS_AZURE_SPEECH_OUTPUT_FORMAT
 			},
 			stt: {
-				OPENAI_API_BASE_URL: STT_OPENAI_API_BASE_URL,
-				OPENAI_API_KEY: STT_OPENAI_API_KEY,
-				ENGINE: STT_ENGINE,
-				MODEL: STT_MODEL,
-				WHISPER_MODEL: STT_WHISPER_MODEL,
-				DEEPGRAM_API_KEY: STT_DEEPGRAM_API_KEY
+			    OPENAI_API_BASE_URL: STT_OPENAI_API_BASE_URL,
+			    OPENAI_API_KEY: STT_OPENAI_API_KEY,
+			    ENGINE: STT_ENGINE,
+			    MODEL: STT_MODEL,
+			    WHISPER_MODEL: STT_WHISPER_MODEL,
+			    DEEPGRAM_API_KEY: STT_DEEPGRAM_API_KEY,
+			    AZURE_API_KEY: STT_AZURE_API_KEY,
+			    AZURE_REGION: STT_AZURE_REGION,
+			    AZURE_LOCALES: STT_AZURE_LOCALES
 			}
 		});
 
@@ -144,10 +150,13 @@
 
 			STT_OPENAI_API_BASE_URL = res.stt.OPENAI_API_BASE_URL;
 			STT_OPENAI_API_KEY = res.stt.OPENAI_API_KEY;
-
+			
 			STT_ENGINE = res.stt.ENGINE;
 			STT_MODEL = res.stt.MODEL;
 			STT_WHISPER_MODEL = res.stt.WHISPER_MODEL;
+			STT_AZURE_API_KEY = res.stt.AZURE_API_KEY;
+			STT_AZURE_REGION = res.stt.AZURE_REGION;
+			STT_AZURE_LOCALES = res.stt.AZURE_LOCALES;
 			STT_DEEPGRAM_API_KEY = res.stt.DEEPGRAM_API_KEY;
 		}
 
@@ -180,7 +189,8 @@
 							<option value="openai">OpenAI</option>
 							<option value="web">{$i18n.t('Web API')}</option>
 							<option value="deepgram">Deepgram</option>
-						</select>
+							<option value="azure">Azure AI Speech</option>
+							</select>
 					</div>
 				</div>
 
@@ -248,8 +258,35 @@
 							</a>
 						</div>
 					</div>
+				{:else if STT_ENGINE === 'azure'}
+				<div>
+				<div class="mt-1 flex gap-2 mb-1">
+				    <SensitiveInput placeholder={$i18n.t('API Key')} bind:value={STT_AZURE_API_KEY} required />
+				    <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-hidden"
+				        placeholder={$i18n.t('Azure Region')}
+				        bind:value={STT_AZURE_REGION}
+				        required
+				    />
+				</div>
+				
+				<hr class="border-gray-100 dark:border-gray-850 my-2" />
+				
+				<div>
+				<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Language Locales')}</div>
+				<div class="flex w-full">
+				    <div class="flex-1">
+				        <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"
+				            bind:value={STT_AZURE_LOCALES}
+				            placeholder={$i18n.t('e.g., en-US,ja-JP (leave blank for auto-detect)')}
+				        />
+				    </div>
+				</div>
+				</div>
+				</div>
 				{:else if STT_ENGINE === ''}
-					<div>
+				<div>
 						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
 
 						<div class="flex w-full">

+ 34 - 5
src/lib/components/admin/Settings/Documents.svelte

@@ -13,17 +13,16 @@
 		updateEmbeddingConfig,
 		getRerankingConfig,
 		updateRerankingConfig,
-		resetUploadDir,
 		getRAGConfig,
 		updateRAGConfig
 	} from '$lib/apis/retrieval';
 
-	import { knowledge, models } from '$lib/stores';
-	import { getKnowledgeBases } from '$lib/apis/knowledge';
-	import { uploadDir, deleteAllFiles, deleteFileById } from '$lib/apis/files';
+	import {  reindexKnowledgeFiles} from '$lib/apis/knowledge';
+	import {  deleteAllFiles } from '$lib/apis/files';
 
 	import ResetUploadDirConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
 	import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+	import ReindexKnowledgeFilesConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
 	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Switch from '$lib/components/common/Switch.svelte';
@@ -31,12 +30,12 @@
 
 	const i18n = getContext('i18n');
 
-	let scanDirLoading = false;
 	let updateEmbeddingModelLoading = false;
 	let updateRerankingModelLoading = false;
 
 	let showResetConfirm = false;
 	let showResetUploadDirConfirm = false;
+	let showReindexConfirm = false;
 
 	let embeddingEngine = '';
 	let embeddingModel = '';
@@ -333,6 +332,21 @@
 	}}
 />
 
+
+<ReindexKnowledgeFilesConfirmDialog
+	bind:show={showReindexConfirm}
+	on:confirm={async () => {
+		const res = await reindexKnowledgeFiles(localStorage.token).catch((error) => {
+			toast.error(`${error}`);
+			return null;
+		});
+
+		if (res) {
+			toast.success($i18n.t('Success'));
+		}
+	}}
+/>
+
 <form
 	class="flex flex-col h-full justify-between space-y-3 text-sm"
 	on:submit|preventDefault={() => {
@@ -950,6 +964,21 @@
 						</button>
 					</div>
 				</div>
+				<div class="  mb-2.5 flex w-full justify-between">
+					<div class=" self-center text-xs font-medium">
+						{$i18n.t('Reindex Knowledge Base Vectors')}
+					</div>
+					<div class="flex items-center relative">
+						<button
+							class="text-xs"
+							on:click={() => {
+								showReindexConfirm = true;
+							}}
+						>
+							{$i18n.t('Reindex')}
+						</button>
+					</div>
+				</div>
 			</div>
 		</div>
 	</div>

+ 33 - 3
src/lib/components/admin/Settings/WebSearch.svelte

@@ -30,7 +30,8 @@
 		'jina',
 		'bing',
 		'exa',
-		'perplexity'
+		'perplexity',
+		'sougou'
 	];
 
 	let youtubeLanguage = 'en';
@@ -404,6 +405,31 @@
 									/>
 								</div>
 							</div>
+							{:else if webConfig.search.engine === 'sougou'}
+								<div class="mb-2.5 flex w-full flex-col">
+									<div>
+										<div class=" self-center text-xs font-medium mb-1">
+											{$i18n.t('Sougou Search API sID')}
+										</div>
+	
+										<SensitiveInput
+											placeholder={$i18n.t('Enter Sougou Search API sID')}
+											bind:value={webConfig.search.sougou_api_sid}
+										/>
+									</div>
+								</div>
+								<div class="mb-2.5 flex w-full flex-col">
+									<div>
+										<div class=" self-center text-xs font-medium mb-1">
+											{$i18n.t('Sougou Search API SK')}
+										</div>
+	
+										<SensitiveInput
+											placeholder={$i18n.t('Enter Sougou Search API SK')}
+											bind:value={webConfig.search.sougou_api_sk}
+										/>
+									</div>
+								</div>
 						{/if}
 					{/if}
 
@@ -481,8 +507,12 @@
 						<div class="flex items-center relative">
 							<Tooltip
 								content={webConfig.search.trust_env
-									? 'Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents'
-									: 'Use no proxy to fetch page contents.'}
+									? $i18n.t(
+											'Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.'
+										)
+									: $i18n.t(
+											'Use no proxy to fetch page contents.'
+										)}
 							>
 								<Switch bind:state={webConfig.search.trust_env} />
 							</Tooltip>

+ 22 - 21
src/lib/components/chat/Chat.svelte

@@ -401,6 +401,7 @@
 		if (!$chatId) {
 			chatIdUnsubscriber = chatId.subscribe(async (value) => {
 				if (!value) {
+					await tick(); // Wait for DOM updates
 					await initNewChat();
 				}
 			});
@@ -1927,7 +1928,7 @@
 		: ' '} w-full max-w-full flex flex-col"
 	id="chat-container"
 >
-	{#if chatIdProp === '' || (!loading && chatIdProp)}
+	{#if !loading}
 		{#if $settings?.backgroundImageUrl ?? null}
 			<div
 				class="absolute {$showSidebar
@@ -1941,27 +1942,27 @@
 			/>
 		{/if}
 
-		<Navbar
-			bind:this={navbarElement}
-			chat={{
-				id: $chatId,
-				chat: {
-					title: $chatTitle,
-					models: selectedModels,
-					system: $settings.system ?? undefined,
-					params: params,
-					history: history,
-					timestamp: Date.now()
-				}
-			}}
-			title={$chatTitle}
-			bind:selectedModels
-			shareEnabled={!!history.currentId}
-			{initNewChat}
-		/>
-
 		<PaneGroup direction="horizontal" class="w-full h-full">
-			<Pane defaultSize={50} class="h-full flex w-full relative">
+			<Pane defaultSize={50} class="h-full flex relative max-w-full flex-col">
+				<Navbar
+					bind:this={navbarElement}
+					chat={{
+						id: $chatId,
+						chat: {
+							title: $chatTitle,
+							models: selectedModels,
+							system: $settings.system ?? undefined,
+							params: params,
+							history: history,
+							timestamp: Date.now()
+						}
+					}}
+					title={$chatTitle}
+					bind:selectedModels
+					shareEnabled={!!history.currentId}
+					{initNewChat}
+				/>
+
 				{#if !history.currentId && !$chatId && selectedModels.length <= 1 && ($banners.length > 0 || ($config?.license_metadata?.type ?? null) === 'trial' || (($config?.license_metadata?.seats ?? null) !== null && $config?.user_count > $config?.license_metadata?.seats))}
 					<div class="absolute top-12 left-0 right-0 w-full z-30">
 						<div class=" flex flex-col gap-1 w-full">

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

@@ -223,14 +223,14 @@
 				showControls.set(false);
 			}}
 			collapsible={true}
-			class="pt-8"
+			class=" z-10 "
 		>
 			{#if $showControls}
-				<div class="pr-4 pb-8 flex max-h-full min-h-full">
+				<div class="flex max-h-full min-h-full">
 					<div
 						class="w-full {($showOverview || $showArtifacts) && !$showCallOverlay
 							? ' '
-							: '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"
+							: 'px-4 py-4 bg-white dark:shadow-lg dark:bg-gray-850  border border-gray-100 dark:border-gray-850'} z-40 pointer-events-auto overflow-y-auto scrollbar-hidden"
 					>
 						{#if $showCallOverlay}
 							<div class="w-full h-full flex justify-center">

+ 13 - 18
src/lib/components/chat/MessageInput.svelte

@@ -1033,7 +1033,7 @@
 									{/if}
 								</div>
 
-								<div class=" flex justify-between mt-1.5 mb-2.5 mx-0.5 max-w-full" dir="ltr">
+								<div class=" flex justify-between mt-1 mb-2.5 mx-0.5 max-w-full" dir="ltr">
 									<div class="ml-1 self-end flex items-center flex-1 max-w-[80%] gap-0.5">
 										<InputMenu
 											bind:selectedToolIds
@@ -1102,7 +1102,7 @@
 											</button>
 										</InputMenu>
 
-										<div class="flex gap-[2px] items-center overflow-x-auto scrollbar-none flex-1">
+										<div class="flex gap-1 items-center overflow-x-auto scrollbar-none flex-1">
 											{#if toolServers.length + selectedToolIds.length > 0}
 												<Tooltip
 													content={$i18n.t('{{COUNT}} Available Tools', {
@@ -1132,10 +1132,10 @@
 														<button
 															on:click|preventDefault={() => (webSearchEnabled = !webSearchEnabled)}
 															type="button"
-															class="px-1.5 @xl: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 ||
+															class="px-1.5 @xl: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 border {webSearchEnabled ||
 															($settings?.webSearch ?? false) === 'always'
-																? 'bg-blue-100 dark:bg-blue-500/20 text-blue-500 dark:text-blue-400'
-																: 'bg-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800'}"
+																? 'bg-blue-100 dark:bg-blue-500/20 border-blue-400/20 text-blue-500 dark:text-blue-400'
+																: 'bg-transparent border-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" />
 															<span
@@ -1152,9 +1152,9 @@
 															on:click|preventDefault={() =>
 																(imageGenerationEnabled = !imageGenerationEnabled)}
 															type="button"
-															class="px-1.5 @xl: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-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
+															class="px-1.5 @xl: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 border {imageGenerationEnabled
+																? 'bg-gray-50 dark:bg-gray-400/10 border-gray-100 dark:border-gray-700 text-gray-600 dark:text-gray-400'
+																: 'bg-transparent border-transparent text-gray-600 dark:text-gray-300  hover:bg-gray-100 dark:hover:bg-gray-800 '}"
 														>
 															<Photo className="size-5" strokeWidth="1.75" />
 															<span
@@ -1171,9 +1171,9 @@
 															on:click|preventDefault={() =>
 																(codeInterpreterEnabled = !codeInterpreterEnabled)}
 															type="button"
-															class="px-1.5 @xl: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-transparent text-gray-600 dark:text-gray-300 border-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 '}"
+															class="px-1.5 @xl: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 border {codeInterpreterEnabled
+																? 'bg-gray-50 dark:bg-gray-400/10 border-gray-100  dark:border-gray-700 text-gray-600 dark:text-gray-400  '
+																: 'bg-transparent border-transparent text-gray-600 dark:text-gray-300  hover:bg-gray-100 dark:hover:bg-gray-800 '}"
 														>
 															<CommandLine className="size-5" strokeWidth="1.75" />
 															<span
@@ -1242,10 +1242,7 @@
 												<div class=" flex items-center">
 													<Tooltip content={$i18n.t('Call')}>
 														<button
-															class=" {webSearchEnabled ||
-															($settings?.webSearch ?? false) === 'always'
-																? 'bg-blue-500 text-white hover:bg-blue-400 '
-																: 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100'} transition rounded-full p-1.5 self-center"
+															class=" bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full p-1.5 self-center"
 															type="button"
 															on:click={async () => {
 																if (selectedModels.length > 1) {
@@ -1311,9 +1308,7 @@
 														<button
 															id="send-message-button"
 															class="{!(prompt === '' && files.length === 0)
-																? webSearchEnabled || ($settings?.webSearch ?? false) === 'always'
-																	? 'bg-blue-500 text-white hover:bg-blue-400 '
-																	: 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
+																? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
 																: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 self-center"
 															type="submit"
 															disabled={prompt === '' && files.length === 0}

+ 2 - 1
src/lib/components/chat/Messages/ContentRenderer.svelte

@@ -4,7 +4,7 @@
 	const dispatch = createEventDispatcher();
 
 	import Markdown from './Markdown.svelte';
-	import { chatId, mobile, showArtifacts, showControls, showOverview } from '$lib/stores';
+	import { chatId, mobile, settings, showArtifacts, showControls, showOverview } from '$lib/stores';
 	import FloatingButtons from '../ContentRenderer/FloatingButtons.svelte';
 	import { createMessagesList } from '$lib/utils';
 
@@ -161,6 +161,7 @@
 			const { lang, code } = e.detail;
 
 			if (
+				($settings?.detectArtifacts ?? true) &&
 				(['html', 'svg'].includes(lang) || (lang === 'xml' && code.includes('svg'))) &&
 				!$mobile &&
 				$chatId

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

@@ -192,7 +192,7 @@
 							<div>
 								<button
 									id="save-edit-message-button"
-									class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
+									class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border border-gray-100 dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
 									on:click={() => {
 										editMessageConfirmHandler(false);
 									}}

+ 64 - 59
src/lib/components/chat/ModelSelector/Selector.svelte

@@ -84,47 +84,49 @@
 		}
 	);
 
-	$: filteredItems = searchValue
-		? fuse
-				.search(searchValue)
-				.map((e) => {
-					return e.item;
-				})
-				.filter((item) => {
-					if (selectedTag === '') {
-						return true;
-					}
-					return (item.model?.tags ?? []).map((tag) => tag.name).includes(selectedTag);
-				})
-				.filter((item) => {
-					if (selectedConnectionType === '') {
-						return true;
-					} else if (selectedConnectionType === 'ollama') {
-						return item.model?.owned_by === 'ollama';
-					} else if (selectedConnectionType === 'openai') {
-						return item.model?.owned_by === 'openai';
-					} else if (selectedConnectionType === 'direct') {
-						return item.model?.direct;
-					}
-				})
-		: items
-				.filter((item) => {
-					if (selectedTag === '') {
-						return true;
-					}
-					return (item.model?.tags ?? []).map((tag) => tag.name).includes(selectedTag);
-				})
-				.filter((item) => {
-					if (selectedConnectionType === '') {
-						return true;
-					} else if (selectedConnectionType === 'ollama') {
-						return item.model?.owned_by === 'ollama';
-					} else if (selectedConnectionType === 'openai') {
-						return item.model?.owned_by === 'openai';
-					} else if (selectedConnectionType === 'direct') {
-						return item.model?.direct;
-					}
-				});
+	$: filteredItems = (
+		searchValue
+			? fuse
+					.search(searchValue)
+					.map((e) => {
+						return e.item;
+					})
+					.filter((item) => {
+						if (selectedTag === '') {
+							return true;
+						}
+						return (item.model?.tags ?? []).map((tag) => tag.name).includes(selectedTag);
+					})
+					.filter((item) => {
+						if (selectedConnectionType === '') {
+							return true;
+						} else if (selectedConnectionType === 'ollama') {
+							return item.model?.owned_by === 'ollama';
+						} else if (selectedConnectionType === 'openai') {
+							return item.model?.owned_by === 'openai';
+						} else if (selectedConnectionType === 'direct') {
+							return item.model?.direct;
+						}
+					})
+			: items
+					.filter((item) => {
+						if (selectedTag === '') {
+							return true;
+						}
+						return (item.model?.tags ?? []).map((tag) => tag.name).includes(selectedTag);
+					})
+					.filter((item) => {
+						if (selectedConnectionType === '') {
+							return true;
+						} else if (selectedConnectionType === 'ollama') {
+							return item.model?.owned_by === 'ollama';
+						} else if (selectedConnectionType === 'openai') {
+							return item.model?.owned_by === 'openai';
+						} else if (selectedConnectionType === 'direct') {
+							return item.model?.direct;
+						}
+					})
+	).filter((item) => !(item.model?.info?.meta?.hidden ?? false));
 
 	$: if (selectedTag || selectedConnectionType) {
 		resetView();
@@ -282,7 +284,10 @@
 		ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
 
 		if (items) {
-			tags = items.flatMap((item) => item.model?.tags ?? []).map((tag) => tag.name);
+			tags = items
+				.filter((item) => !(item.model?.info?.meta?.hidden ?? false))
+				.flatMap((item) => item.model?.tags ?? [])
+				.map((tag) => tag.name);
 
 			// Remove duplicates and sort
 			tags = Array.from(new Set(tags)).sort((a, b) => a.localeCompare(b));
@@ -373,7 +378,7 @@
 				</div>
 			{/if}
 
-			<div class="px-3 mb-2 max-h-64 overflow-y-auto scrollbar-hidden group relative">
+			<div class="px-3 max-h-64 overflow-y-auto scrollbar-hidden group relative">
 				{#if tags && items.filter((item) => !(item.model?.info?.meta?.hidden ?? false)).length > 0}
 					<div
 						class=" flex w-full sticky top-0 z-10 bg-white dark:bg-gray-850 overflow-x-auto scrollbar-none"
@@ -388,18 +393,20 @@
 							class="flex gap-1 w-fit text-center text-sm font-medium rounded-full bg-transparent px-1.5 pb-0.5"
 							bind:this={tagsContainerElement}
 						>
-							<button
-								class="min-w-fit outline-none p-1.5 {selectedTag === '' &&
-								selectedConnectionType === ''
-									? ''
-									: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
-								on:click={() => {
-									selectedConnectionType = '';
-									selectedTag = '';
-								}}
-							>
-								{$i18n.t('All')}
-							</button>
+							{#if (items.find((item) => item.model?.owned_by === 'ollama') && items.find((item) => item.model?.owned_by === 'openai')) || items.find((item) => item.model?.direct) || tags.length > 0}
+								<button
+									class="min-w-fit outline-none p-1.5 {selectedTag === '' &&
+									selectedConnectionType === ''
+										? ''
+										: 'text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'} transition capitalize"
+									on:click={() => {
+										selectedConnectionType = '';
+										selectedTag = '';
+									}}
+								>
+									{$i18n.t('All')}
+								</button>
+							{/if}
 
 							{#if items.find((item) => item.model?.owned_by === 'ollama') && items.find((item) => item.model?.owned_by === 'openai')}
 								<button
@@ -457,7 +464,7 @@
 					</div>
 				{/if}
 
-				{#each filteredItems.filter((item) => !(item.model?.info?.meta?.hidden ?? false)) as item, index}
+				{#each filteredItems as item, index}
 					<button
 						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-hidden transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg cursor-pointer data-highlighted:bg-muted {index ===
@@ -739,9 +746,7 @@
 			</div>
 
 			{#if showTemporaryChatControl}
-				<hr class="border-gray-100 dark:border-gray-800" />
-
-				<div class="flex items-center mx-2 my-2">
+				<div class="flex items-center mx-2 mb-2">
 					<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-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 () => {

+ 30 - 0
src/lib/components/chat/Settings/Interface.svelte

@@ -32,6 +32,8 @@
 	let showUsername = false;
 	let notificationSound = true;
 
+	let detectArtifacts = true;
+
 	let richTextInput = true;
 	let promptAutocomplete = false;
 
@@ -176,6 +178,11 @@
 		saveSettings({ autoTags });
 	};
 
+	const toggleDetectArtifacts = async () => {
+		detectArtifacts = !detectArtifacts;
+		saveSettings({ detectArtifacts });
+	};
+
 	const toggleRichTextInput = async () => {
 		richTextInput = !richTextInput;
 		saveSettings({ richTextInput });
@@ -242,6 +249,7 @@
 		titleAutoGenerate = $settings?.title?.auto ?? true;
 		autoTags = $settings.autoTags ?? true;
 
+		detectArtifacts = $settings.detectArtifacts ?? true;
 		responseAutoCopy = $settings.responseAutoCopy ?? false;
 
 		showUsername = $settings.showUsername ?? false;
@@ -537,6 +545,28 @@
 				</div>
 			</div>
 
+			<div>
+				<div class=" py-0.5 flex w-full justify-between">
+					<div class=" self-center text-xs">
+						{$i18n.t('Detect Artifacts Automatically')}
+					</div>
+
+					<button
+						class="p-1 px-3 text-xs flex rounded-sm transition"
+						on:click={() => {
+							toggleDetectArtifacts();
+						}}
+						type="button"
+					>
+						{#if detectArtifacts === true}
+							<span class="ml-2 self-center">{$i18n.t('On')}</span>
+						{:else}
+							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+						{/if}
+					</button>
+				</div>
+			</div>
+
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
 					<div class=" self-center text-xs">

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

@@ -15,7 +15,7 @@
 
 	let selectedTools = [];
 
-	$: selectedTools = $tools.filter((tool) => selectedToolIds.includes(tool.id));
+	$: selectedTools = ($tools ?? []).filter((tool) => selectedToolIds.includes(tool.id));
 
 	const i18n = getContext('i18n');
 </script>

+ 11 - 1
src/lib/components/common/CodeEditor.svelte

@@ -98,6 +98,16 @@
 			}
 		})
 	);
+	languages.push(
+		LanguageDescription.of({
+			name: 'Elixir',
+			extensions: ['ex', 'exs'],
+			load() {
+				return import('codemirror-lang-elixir').then((m) => m.elixir());
+			}
+		})
+	);
+
 	const getLang = async () => {
 		const language = languages.find((l) => l.alias.includes(lang));
 		return await language?.load();
@@ -232,4 +242,4 @@
 	});
 </script>
 
-<div id="code-textarea-{id}" class="h-full w-full" />
+<div id="code-textarea-{id}" class="h-full w-full text-sm" />

+ 1 - 1
src/lib/components/common/Collapsible.svelte

@@ -73,7 +73,7 @@
 				return JSON.stringify(parsed, null, 2);
 			} else {
 				// It's a primitive value like a number, boolean, etc.
-				return String(parsed);
+				return `${JSON.stringify(String(parsed))}`;
 			}
 		} catch (e) {
 			// Not valid JSON, return as-is

+ 2 - 0
src/lib/components/layout/Sidebar/ChatItem.svelte

@@ -83,6 +83,8 @@
 			currentChatPage.set(1);
 			await chats.set(await getChatList(localStorage.token, $currentChatPage));
 			await pinnedChats.set(await getPinnedChatList(localStorage.token));
+
+			dispatch('change');
 		}
 	};
 

+ 4 - 0
src/lib/i18n/locales/ar-BH/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "رفض عندما لا ينبغي أن يكون",
 	"Regenerate": "تجديد",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "ملاحظات الإصدار",
 	"Relevance": "",
 	"Remove": "إزالة",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar أستخدم",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Initials أستخدم",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (أولاما)",
 	"use_mmap (Ollama)": "use_mmap (أولاما)",
 	"user": "مستخدم",

+ 4 - 0
src/lib/i18n/locales/ar/translation.json

@@ -878,6 +878,8 @@
 	"References from": "مراجع من",
 	"Refused when it shouldn't have": "رفض عندما لا ينبغي أن يكون",
 	"Regenerate": "تجديد",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "ملاحظات الإصدار",
 	"Relevance": "الصلة",
 	"Remove": "إزالة",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar أستخدم",
 	"Use groups to group your users and assign permissions.": "استخدم المجموعات لتجميع المستخدمين وتحديد الصلاحيات.",
 	"Use Initials": "Initials أستخدم",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (أولاما)",
 	"use_mmap (Ollama)": "use_mmap (أولاما)",
 	"user": "مستخدم",

+ 4 - 0
src/lib/i18n/locales/bg-BG/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Препратки от",
 	"Refused when it shouldn't have": "Отказано, когато не трябва да бъде",
 	"Regenerate": "Регенериране",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Бележки по изданието",
 	"Relevance": "Релевантност",
 	"Remove": "Изтриване",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Използвайте Gravatar",
 	"Use groups to group your users and assign permissions.": "Използвайте групи, за да групирате вашите потребители и да присвоите разрешения.",
 	"Use Initials": "Използвайте инициали",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "потребител",

+ 4 - 0
src/lib/i18n/locales/bn-BD/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "যদি উপযুক্ত নয়, তবে রেজিগেনেট করা হচ্ছে",
 	"Regenerate": "রেজিগেনেট করুন",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "রিলিজ নোটসমূহ",
 	"Relevance": "",
 	"Remove": "রিমুভ করুন",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar ব্যবহার করুন",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "নামের আদ্যক্ষর ব্যবহার করুন",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (ওলামা)",
 	"use_mmap (Ollama)": "use_mmap (ওলামা)",
 	"user": "ব্যবহারকারী",

+ 4 - 0
src/lib/i18n/locales/bo-TB/translation.json

@@ -878,6 +878,8 @@
 	"References from": "ནས་ལུང་འདྲེན།",
 	"Refused when it shouldn't have": "མི་དགོས་དུས་ཁས་མ་བླངས།",
 	"Regenerate": "བསྐྱར་བཟོ།",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "འགྲེམས་སྤེལ་མཆན་བུ།",
 	"Relevance": "འབྲེལ་ཡོད་རང་བཞིན།",
 	"Remove": "འདོར་བ།",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar བེད་སྤྱོད།",
 	"Use groups to group your users and assign permissions.": "ཁྱེད་ཀྱི་བེད་སྤྱོད་མཁན་ཚོགས་པ་བཟོ་བ་དང་དབང་ཚད་སྤྲོད་པར་ཚོགས་པ་བེད་སྤྱོད་གཏོང་བ།",
 	"Use Initials": "མིང་གི་ཡིག་འབྲུ་མགོ་མ་བེད་སྤྱོད།",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "བེད་སྤྱོད་མཁན།",

+ 41 - 37
src/lib/i18n/locales/ca-ES/translation.json

@@ -6,7 +6,7 @@
 	"(latest)": "(últim)",
 	"(Ollama)": "(Ollama)",
 	"{{ models }}": "{{ models }}",
-	"{{COUNT}} Available Tools": "",
+	"{{COUNT}} Available Tools": "{{COUNT}} eines disponibles",
 	"{{COUNT}} hidden lines": "{{COUNT}} línies ocultes",
 	"{{COUNT}} Replies": "{{COUNT}} respostes",
 	"{{user}}'s Chats": "Els xats de {{user}}",
@@ -108,10 +108,10 @@
 	"Attribute for Username": "Atribut per al Nom d'usuari",
 	"Audio": "Àudio",
 	"August": "Agost",
-	"Auth": "",
+	"Auth": "Autenticació",
 	"Authenticate": "Autenticar",
 	"Authentication": "Autenticació",
-	"Auto": "",
+	"Auto": "Automàtic",
 	"Auto-Copy Response to Clipboard": "Copiar la resposta automàticament al porta-retalls",
 	"Auto-playback response": "Reproduir la resposta automàticament",
 	"Autocomplete Generation": "Generació automàtica",
@@ -121,7 +121,7 @@
 	"AUTOMATIC1111 Base URL": "URL Base d'AUTOMATIC1111",
 	"AUTOMATIC1111 Base URL is required.": "Es requereix l'URL Base d'AUTOMATIC1111.",
 	"Available list": "Llista de disponibles",
-	"Available Tools": "",
+	"Available Tools": "Eines disponibles",
 	"available!": "disponible!",
 	"Awful": "Terrible",
 	"Azure AI Speech": "Azure AI Speech",
@@ -220,10 +220,10 @@
 	"Confirm your new password": "Confirma la teva nova contrasenya",
 	"Connect to your own OpenAI compatible API endpoints.": "Connecta als teus propis punts de connexió de l'API compatible amb OpenAI",
 	"Connect to your own OpenAPI compatible external tool servers.": "Connecta als teus propis servidors d'eines externs compatibles amb OpenAPI",
-	"Connection failed": "",
-	"Connection successful": "",
+	"Connection failed": "La connexió ha fallat",
+	"Connection successful": "Connexió correcta",
 	"Connections": "Connexions",
-	"Connections saved successfully": "",
+	"Connections saved successfully": "Les connexions s'han desat correctament",
 	"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "Restringeix l'esforç de raonament dels models de raonament. Només aplicable a models de raonament de proveïdors específics que donen suport a l'esforç de raonament.",
 	"Contact Admin for WebUI Access": "Posat en contacte amb l'administrador per accedir a WebUI",
 	"Content": "Contingut",
@@ -308,7 +308,7 @@
 	"Direct Connections": "Connexions directes",
 	"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Les connexions directes permeten als usuaris connectar-se als seus propis endpoints d'API compatibles amb OpenAI.",
 	"Direct Connections settings updated": "Configuració de les connexions directes actualitzada",
-	"Direct Tool Servers": "",
+	"Direct Tool Servers": "Servidors d'eines directes",
 	"Disabled": "Deshabilitat",
 	"Discover a function": "Descobrir una funció",
 	"Discover a model": "Descobrir un model",
@@ -385,7 +385,7 @@
 	"Enable Mirostat sampling for controlling perplexity.": "Permetre el mostreig de Mirostat per controlar la perplexitat",
 	"Enable New Sign Ups": "Permetre nous registres",
 	"Enabled": "Habilitat",
-	"Enforce Temporary Chat": "",
+	"Enforce Temporary Chat": "Forçar els xats temporals",
 	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Assegura't que els teus fitxers CSV inclouen 4 columnes en aquest ordre: Nom, Correu electrònic, Contrasenya, Rol.",
 	"Enter {{role}} message here": "Introdueix aquí el missatge de {{role}}",
 	"Enter a detail about yourself for your LLMs to recall": "Introdueix un detall sobre tu què els teus models de llenguatge puguin recordar",
@@ -418,7 +418,7 @@
 	"Enter Kagi Search API Key": "Introdueix la clau API de Kagi Search",
 	"Enter Key Behavior": "Introdueix el comportament de clau",
 	"Enter language codes": "Introdueix els codis de llenguatge",
-	"Enter Mistral API Key": "",
+	"Enter Mistral API Key": "Entra la clau API de Mistral",
 	"Enter Model ID": "Introdueix l'identificador del model",
 	"Enter model tag (e.g. {{modelTag}})": "Introdueix l'etiqueta del model (p. ex. {{modelTag}})",
 	"Enter Mojeek Search API Key": "Introdueix la clau API de Mojeek Search",
@@ -443,7 +443,7 @@
 	"Enter server port": "Introdueix el port del servidor",
 	"Enter stop sequence": "Introdueix la seqüència de parada",
 	"Enter system prompt": "Introdueix la indicació de sistema",
-	"Enter system prompt here": "",
+	"Enter system prompt here": "Entra la indicació de sistema aquí",
 	"Enter Tavily API Key": "Introdueix la clau API de Tavily",
 	"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Entra la URL pública de WebUI. Aquesta URL s'utilitzarà per generar els enllaços en les notificacions.",
 	"Enter Tika Server URL": "Introdueix l'URL del servidor Tika",
@@ -457,7 +457,7 @@
 	"Enter Your Email": "Introdueix el teu correu electrònic",
 	"Enter Your Full Name": "Introdueix el teu nom complet",
 	"Enter your message": "Introdueix el teu missatge",
-	"Enter your name": "",
+	"Enter your name": "Entra el teu nom",
 	"Enter your new password": "Introdueix la teva nova contrasenya",
 	"Enter Your Password": "Introdueix la teva contrasenya",
 	"Enter Your Role": "Introdueix el teu rol",
@@ -477,7 +477,7 @@
 	"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "S'ha superat el nombre de places a la vostra llicència. Poseu-vos en contacte amb el servei d'assistència per augmentar el nombre de places.",
 	"Exclude": "Excloure",
 	"Execute code for analysis": "Executar el codi per analitzar-lo",
-	"Executing **{{NAME}}**...": "",
+	"Executing **{{NAME}}**...": "Executant **{{NAME}}**...",
 	"Expand": "Expandir",
 	"Experimental": "Experimental",
 	"Explain": "Explicar",
@@ -502,7 +502,7 @@
 	"Failed to create API Key.": "No s'ha pogut crear la clau API.",
 	"Failed to fetch models": "No s'han pogut obtenir els models",
 	"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
-	"Failed to save connections": "",
+	"Failed to save connections": "No s'han pogut desar les connexions",
 	"Failed to save models configuration": "No s'ha pogut desar la configuració dels models",
 	"Failed to update settings": "No s'han pogut actualitzar les preferències",
 	"Failed to upload file.": "No s'ha pogut pujar l'arxiu.",
@@ -535,7 +535,7 @@
 	"Forge new paths": "Crea nous camins",
 	"Form": "Formulari",
 	"Format your variables using brackets like this:": "Formata les teves variables utilitzant claudàtors així:",
-	"Forwards system user session credentials to authenticate": "",
+	"Forwards system user session credentials to authenticate": "Envia les credencials de l'usuari del sistema per autenticar",
 	"Frequency Penalty": "Penalització per freqüència",
 	"Full Context Mode": "Mode de context complert",
 	"Function": "Funció",
@@ -581,7 +581,7 @@
 	"Hex Color": "Color hexadecimal",
 	"Hex Color - Leave empty for default color": "Color hexadecimal - Deixar buit per a color per defecte",
 	"Hide": "Amaga",
-	"Hide Model": "",
+	"Hide Model": "Amagar el model",
 	"Home": "Inici",
 	"Host": "Servidor",
 	"How can I help you today?": "Com et puc ajudar avui?",
@@ -641,7 +641,7 @@
 	"Knowledge Access": "Accés al coneixement",
 	"Knowledge created successfully.": "Coneixement creat correctament.",
 	"Knowledge deleted successfully.": "Coneixement eliminat correctament.",
-	"Knowledge Public Sharing": "",
+	"Knowledge Public Sharing": "Compartir públicament el Coneixement",
 	"Knowledge reset successfully.": "Coneixement restablert correctament.",
 	"Knowledge updated successfully": "Coneixement actualitzat correctament.",
 	"Kokoro.js (Browser)": "Kokoro.js (Navegador)",
@@ -655,7 +655,7 @@
 	"LDAP": "LDAP",
 	"LDAP server updated": "Servidor LDAP actualitzat",
 	"Leaderboard": "Tauler de classificació",
-	"Learn more about OpenAPI tool servers.": "",
+	"Learn more about OpenAPI tool servers.": "Aprèn més sobre els servidors d'eines OpenAPI",
 	"Leave empty for unlimited": "Deixar-ho buit per il·limitat",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "Deixar-ho buit per incloure tots els models del punt de connexió \"{{url}}/api/tags\"",
 	"Leave empty to include all models from \"{{url}}/models\" endpoint": "Deixar-ho buit per incloure tots els models del punt de connexió \"{{url}}/models\"",
@@ -706,16 +706,16 @@
 	"Mirostat": "Mirostat",
 	"Mirostat Eta": "Eta de Mirostat",
 	"Mirostat Tau": "Tau de Mirostat",
-	"Mistral OCR": "",
-	"Mistral OCR API Key required.": "",
+	"Mistral OCR": "Mistral OCR",
+	"Mistral OCR API Key required.": "És necessària la clau API de Mistral OCR",
 	"Model": "Model",
 	"Model '{{modelName}}' has been successfully downloaded.": "El model '{{modelName}}' s'ha descarregat correctament.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "El model '{{modelTag}}' ja està en cua per ser descarregat.",
 	"Model {{modelId}} not found": "No s'ha trobat el model {{modelId}}",
 	"Model {{modelName}} is not vision capable": "El model {{modelName}} no és capaç de visió",
 	"Model {{name}} is now {{status}}": "El model {{name}} ara és {{status}}",
-	"Model {{name}} is now hidden": "",
-	"Model {{name}} is now visible": "",
+	"Model {{name}} is now hidden": "El model {{name}} està ara amagat",
+	"Model {{name}} is now visible": "El model {{name}} està ara visible",
 	"Model accepts image inputs": "El model accepta entrades d'imatge",
 	"Model created successfully!": "Model creat correctament",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "S'ha detectat el camí del sistema de fitxers del model. És necessari un nom curt del model per actualitzar, no es pot continuar.",
@@ -731,7 +731,7 @@
 	"Models": "Models",
 	"Models Access": "Accés als models",
 	"Models configuration saved successfully": "La configuració dels models s'ha desat correctament",
-	"Models Public Sharing": "",
+	"Models Public Sharing": "Compartició pública de models",
 	"Mojeek Search API Key": "Clau API de Mojeek Search",
 	"more": "més",
 	"More": "Més",
@@ -794,7 +794,7 @@
 	"Open file": "Obrir arxiu",
 	"Open in full screen": "Obrir en pantalla complerta",
 	"Open new chat": "Obre un xat nou",
-	"Open WebUI can use tools provided by any OpenAPI server.": "",
+	"Open WebUI can use tools provided by any OpenAPI server.": "Open WebUI pot utilitzar eines de servidors OpenAPI.",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI utilitza faster-whisper internament.",
 	"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Open WebUI utilitza incrustacions de SpeechT5 i CMU Arctic.",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La versió d'Open WebUI (v{{OPEN_WEBUI_VERSION}}) és inferior a la versió requerida (v{{REQUIRED_VERSION}})",
@@ -804,7 +804,7 @@
 	"OpenAI API Key is required.": "Es requereix la clau API d'OpenAI.",
 	"OpenAI API settings updated": "Configuració de l'API d'OpenAI actualitzada",
 	"OpenAI URL/Key required.": "URL/Clau d'OpenAI requerides.",
-	"openapi.json Path": "",
+	"openapi.json Path": "openapi.json",
 	"or": "o",
 	"Organize your users": "Organitza els teus usuaris",
 	"Other": "Altres",
@@ -836,8 +836,8 @@
 	"Please carefully review the following warnings:": "Si us plau, revisa els següents avisos amb cura:",
 	"Please do not close the settings page while loading the model.": "No tanquis la pàgina de configuració mentre carregues el model.",
 	"Please enter a prompt": "Si us plau, entra una indicació",
-	"Please enter a valid path": "",
-	"Please enter a valid URL": "",
+	"Please enter a valid path": "Si us plau, entra un camí vàlid",
+	"Please enter a valid URL": "Si us plau, entra una URL vàlida",
 	"Please fill in all fields.": "Emplena tots els camps, si us plau.",
 	"Please select a model first.": "Si us plau, selecciona un model primer",
 	"Please select a model.": "Si us plau, selecciona un model.",
@@ -853,14 +853,14 @@
 	"Profile Image": "Imatge de perfil",
 	"Prompt": "Indicació",
 	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Indicació (p.ex. Digues-me quelcom divertit sobre l'Imperi Romà)",
-	"Prompt Autocompletion": "",
+	"Prompt Autocompletion": "Completar automàticament la indicació",
 	"Prompt Content": "Contingut de la indicació",
 	"Prompt created successfully": "Indicació creada correctament",
 	"Prompt suggestions": "Suggeriments d'indicacions",
 	"Prompt updated successfully": "Indicació actualitzada correctament",
 	"Prompts": "Indicacions",
 	"Prompts Access": "Accés a les indicacions",
-	"Prompts Public Sharing": "",
+	"Prompts Public Sharing": "Compartició pública de indicacions",
 	"Public": "Públic",
 	"Pull \"{{searchValue}}\" from Ollama.com": "Obtenir \"{{searchValue}}\" de Ollama.com",
 	"Pull a model from Ollama.com": "Obtenir un model d'Ollama.com",
@@ -878,6 +878,8 @@
 	"References from": "Referències de",
 	"Refused when it shouldn't have": "Refusat quan no hauria d'haver estat",
 	"Regenerate": "Regenerar",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Notes de la versió",
 	"Relevance": "Rellevància",
 	"Remove": "Eliminar",
@@ -993,11 +995,11 @@
 	"Share": "Compartir",
 	"Share Chat": "Compartir el xat",
 	"Share to Open WebUI Community": "Compartir amb la comunitat OpenWebUI",
-	"Sharing Permissions": "",
+	"Sharing Permissions": "Compartir els permisos",
 	"Show": "Mostrar",
 	"Show \"What's New\" modal on login": "Veure 'Què hi ha de nou' a l'entrada",
 	"Show Admin Details in Account Pending Overlay": "Mostrar els detalls de l'administrador a la superposició del compte pendent",
-	"Show Model": "",
+	"Show Model": "Mostrar el model",
 	"Show shortcuts": "Mostrar dreceres",
 	"Show your support!": "Mostra el teu suport!",
 	"Showcased creativity": "Creativitat mostrada",
@@ -1060,7 +1062,7 @@
 	"Thinking...": "Pensant...",
 	"This action cannot be undone. Do you wish to continue?": "Aquesta acció no es pot desfer. Vols continuar?",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Aquest canal es va crear el dia {{createdAt}}. Aquest és el començament del canal {{channelName}}.",
-	"This chat won’t appear in history and your messages will not be saved.": "",
+	"This chat won’t appear in history and your messages will not be saved.": "Aquest xat no apareixerà a l'historial i els teus missatges no es desaran.",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Això assegura que les teves converses valuoses queden desades de manera segura a la teva base de dades. Gràcies!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Aquesta és una funció experimental, és possible que no funcioni com s'espera i està subjecta a canvis en qualsevol moment.",
 	"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "Aquesta opció controla quants tokens es conserven en actualitzar el context. Per exemple, si s'estableix en 2, es conservaran els darrers 2 tokens del context de conversa. Preservar el context pot ajudar a mantenir la continuïtat d'una conversa, però pot reduir la capacitat de respondre a nous temes.",
@@ -1108,7 +1110,7 @@
 	"Tool ID": "ID de l'eina",
 	"Tool imported successfully": "Eina importada correctament",
 	"Tool Name": "Nom de l'eina",
-	"Tool Servers": "",
+	"Tool Servers": "Servidors d'eines",
 	"Tool updated successfully": "Eina actualitzada correctament",
 	"Tools": "Eines",
 	"Tools Access": "Accés a les eines",
@@ -1116,7 +1118,7 @@
 	"Tools Function Calling Prompt": "Indicació per a la crida de funcions",
 	"Tools have a function calling system that allows arbitrary code execution": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari",
 	"Tools have a function calling system that allows arbitrary code execution.": "Les eines disposen d'un sistema de crida a funcions que permet execució de codi arbitrari.",
-	"Tools Public Sharing": "",
+	"Tools Public Sharing": "Compartició pública d'eines",
 	"Top K": "Top K",
 	"Top K Reranker": "Top K Reranker",
 	"Top P": "Top P",
@@ -1158,12 +1160,14 @@
 	"Use Gravatar": "Utilitzar Gravatar",
 	"Use groups to group your users and assign permissions.": "Utilitza grups per agrupar els usuaris i assignar permisos.",
 	"Use Initials": "Utilitzar inicials",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "usuari",
 	"User": "Usuari",
 	"User location successfully retrieved.": "Ubicació de l'usuari obtinguda correctament",
-	"User Webhooks": "",
+	"User Webhooks": "Webhooks d'usuari",
 	"Username": "Nom d'usuari",
 	"Users": "Usuaris",
 	"Using the default arena model with all models. Click the plus button to add custom models.": "S'utilitza el model d'Arena predeterminat amb tots els models. Clica el botó més per afegir models personalitzats.",
@@ -1178,7 +1182,7 @@
 	"Version": "Versió",
 	"Version {{selectedVersion}} of {{totalVersions}}": "Versió {{selectedVersion}} de {{totalVersions}}",
 	"View Replies": "Veure les respostes",
-	"View Result from **{{NAME}}**": "",
+	"View Result from **{{NAME}}**": "Veure el resultat de **{{NAME}}**",
 	"Visibility": "Visibilitat",
 	"Voice": "Veu",
 	"Voice Input": "Entrada de veu",
@@ -1196,7 +1200,7 @@
 	"Webhook URL": "URL del webhook",
 	"WebUI Settings": "Preferències de WebUI",
 	"WebUI URL": "URL de WebUI",
-	"WebUI will make requests to \"{{url}}\"": "",
+	"WebUI will make requests to \"{{url}}\"": "WebUI farà peticions a \"{{url}}\"",
 	"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI farà peticions a \"{{url}}/api/chat\"",
 	"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI farà peticions a \"{{url}}/chat/completions\"",
 	"What are you trying to achieve?": "Què intentes aconseguir?",

+ 4 - 0
src/lib/i18n/locales/ceb-PH/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "",
 	"Regenerate": "",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Release Notes",
 	"Relevance": "",
 	"Remove": "",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Paggamit sa Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "",
 	"use_mmap (Ollama)": "",
 	"user": "tiggamit",

+ 4 - 0
src/lib/i18n/locales/cs-CZ/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Reference z",
 	"Refused when it shouldn't have": "Odmítnuto, když nemělo být.",
 	"Regenerate": "Regenerovat",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Záznamy o vydání",
 	"Relevance": "Relevance",
 	"Remove": "Odebrat",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Použití Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Použijte iniciály",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "uživatel",

+ 4 - 0
src/lib/i18n/locales/da-DK/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Afvist, når den ikke burde have været det",
 	"Regenerate": "Regenerer",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Udgivelsesnoter",
 	"Relevance": "",
 	"Remove": "Fjern",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Brug Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Brug initialer",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "bruger",

+ 4 - 0
src/lib/i18n/locales/de-DE/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Referenzen aus",
 	"Refused when it shouldn't have": "Abgelehnt, obwohl es nicht hätte abgelehnt werden sollen",
 	"Regenerate": "Neu generieren",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Veröffentlichungshinweise",
 	"Relevance": "Relevanz",
 	"Remove": "Entfernen",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar verwenden",
 	"Use groups to group your users and assign permissions.": "Nutzen Sie Gruppen, um Ihre Benutzer zu gruppieren und Berechtigungen zuzuweisen.",
 	"Use Initials": "Initialen verwenden",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "Benutzer",

+ 4 - 0
src/lib/i18n/locales/dg-DG/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "",
 	"Regenerate": "",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Release Borks",
 	"Relevance": "",
 	"Remove": "",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Use Gravatar much avatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Use Initials much initial",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "",
 	"use_mmap (Ollama)": "",
 	"user": "user much user",

+ 4 - 0
src/lib/i18n/locales/el-GR/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Αναφορές από",
 	"Refused when it shouldn't have": "Αρνήθηκε όταν δεν έπρεπε",
 	"Regenerate": "Αναγεννήστε",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Σημειώσεις Έκδοσης",
 	"Relevance": "Σχετικότητα",
 	"Remove": "Αφαίρεση",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Χρησιμοποιήστε Gravatar",
 	"Use groups to group your users and assign permissions.": "Χρησιμοποιήστε ομάδες για να ομαδοποιήσετε τους χρήστες σας και να αναθέσετε δικαιώματα.",
 	"Use Initials": "Χρησιμοποιήστε Αρχικά",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "user",

+ 4 - 0
src/lib/i18n/locales/en-GB/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "",
 	"Regenerate": "",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "",
 	"Relevance": "",
 	"Remove": "",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "",
 	"use_mmap (Ollama)": "",
 	"user": "",

+ 8 - 0
src/lib/i18n/locales/en-US/translation.json

@@ -424,6 +424,8 @@
 	"Enter Mojeek Search API Key": "",
 	"Enter Number of Steps (e.g. 50)": "",
 	"Enter Perplexity API Key": "",
+	"Enter Sougou Search API sID": "",
+	"Enter Sougou Search API SK": "",
 	"Enter proxy URL (e.g. https://user:password@host:port)": "",
 	"Enter reasoning effort": "",
 	"Enter Sampler (e.g. Euler a)": "",
@@ -822,6 +824,8 @@
 	"Permission denied when accessing microphone: {{error}}": "",
 	"Permissions": "",
 	"Perplexity API Key": "",
+	"Sougou Search API sID": "",
+	"Sougou Search API SK": "",
 	"Personalization": "",
 	"Pin": "",
 	"Pinned": "",
@@ -878,6 +882,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "",
 	"Regenerate": "",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "",
 	"Relevance": "",
 	"Remove": "",
@@ -1158,6 +1164,8 @@
 	"Use Gravatar": "",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "",
 	"use_mmap (Ollama)": "",
 	"user": "",

+ 4 - 0
src/lib/i18n/locales/es-ES/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Referencias desde",
 	"Refused when it shouldn't have": "Rechazado cuando no debería haberlo hecho",
 	"Regenerate": "Regenerar",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Notas de la Versión",
 	"Relevance": "Relevancia",
 	"Remove": "Eliminar",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Usar Gravatar",
 	"Use groups to group your users and assign permissions.": "Usar grupos para agrupar a usuarios y asignar permisos.",
 	"Use Initials": "Usar Iniciales",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "usuario",

+ 4 - 0
src/lib/i18n/locales/et-EE/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Viited allikast",
 	"Refused when it shouldn't have": "Keeldus, kui ei oleks pidanud",
 	"Regenerate": "Regenereeri",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Väljalaskemärkmed",
 	"Relevance": "Asjakohasus",
 	"Remove": "Eemalda",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Kasuta Gravatari",
 	"Use groups to group your users and assign permissions.": "Kasutage gruppe oma kasutajate grupeerimiseks ja õiguste määramiseks.",
 	"Use Initials": "Kasuta initsiaale",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "kasutaja",

+ 4 - 0
src/lib/i18n/locales/eu-ES/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Erreferentziak hemendik",
 	"Refused when it shouldn't have": "Ukatu duenean ukatu behar ez zuenean",
 	"Regenerate": "Bersortu",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Bertsio oharrak",
 	"Relevance": "Garrantzia",
 	"Remove": "Kendu",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Erabili Gravatar",
 	"Use groups to group your users and assign permissions.": "Erabili taldeak zure erabiltzaileak taldekatu eta baimenak esleitzeko.",
 	"Use Initials": "Erabili inizialak",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "erabiltzailea",

+ 4 - 0
src/lib/i18n/locales/fa-IR/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "رد شده زمانی که باید نباشد",
 	"Regenerate": "ری\u200cسازی",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "یادداشت\u200cهای انتشار",
 	"Relevance": "ارتباط",
 	"Remove": "حذف",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "استفاده از گراواتار",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "استفاده از سرواژه",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (اولاما)",
 	"use_mmap (Ollama)": "use_mmap (اولاما)",
 	"user": "کاربر",

+ 4 - 0
src/lib/i18n/locales/fi-FI/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Viitteet lähteistä",
 	"Refused when it shouldn't have": "Kieltäytyi, vaikka ei olisi pitänyt",
 	"Regenerate": "Uudelleentuota",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Julkaisutiedot",
 	"Relevance": "Relevanssi",
 	"Remove": "Poista",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Käytä Gravataria",
 	"Use groups to group your users and assign permissions.": "Käytä ryhmiä jäsentääksesi käyttäjiä ja antaaksesi käyttöoikeuksia.",
 	"Use Initials": "Käytä alkukirjaimia",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "käyttäjä",

+ 4 - 0
src/lib/i18n/locales/fr-CA/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Refusé alors qu'il n'aurait pas dû l'être",
 	"Regenerate": "Regénérer",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Notes de publication",
 	"Relevance": "",
 	"Remove": "Retirer",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Utilisez Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Utiliser les initiales",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "utiliser mmap (Ollama)",
 	"user": "utilisateur",

+ 4 - 0
src/lib/i18n/locales/fr-FR/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Références de",
 	"Refused when it shouldn't have": "Refusé alors qu'il n'aurait pas dû l'être",
 	"Regenerate": "Regénérer",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Notes de mise à jour",
 	"Relevance": "Pertinence",
 	"Remove": "Retirer",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Utiliser Gravatar",
 	"Use groups to group your users and assign permissions.": "Utilisez des groupes pour regrouper vos utilisateurs et attribuer des permissions.",
 	"Use Initials": "Utiliser les initiales",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "Utiliser mlock (Ollama)",
 	"use_mmap (Ollama)": "Utiliser mmap (Ollama)",
 	"user": "utilisateur",

+ 4 - 0
src/lib/i18n/locales/he-IL/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "נדחה כאשר לא היה צריך",
 	"Regenerate": "הפק מחדש",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "הערות שחרור",
 	"Relevance": "",
 	"Remove": "הסר",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "שימוש ב Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "שימוש ב initials",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (אולמה)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "משתמש",

+ 4 - 0
src/lib/i18n/locales/hi-IN/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "जब ऐसा नहीं होना चाहिए था तो मना कर दिया",
 	"Regenerate": "पुनः जेनरेट",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "रिलीज नोट्स",
 	"Relevance": "",
 	"Remove": "हटा दें",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar का प्रयोग करें",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "प्रथमाक्षर का प्रयोग करें",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (ओलामा)",
 	"use_mmap (Ollama)": "use_mmap (ओलामा)",
 	"user": "उपयोगकर्ता",

+ 4 - 0
src/lib/i18n/locales/hr-HR/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Odbijen kada nije trebao biti",
 	"Regenerate": "Regeneriraj",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Bilješke o izdanju",
 	"Relevance": "",
 	"Remove": "Ukloni",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Koristi Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Koristi inicijale",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "korisnik",

File diff suppressed because it is too large
+ 313 - 313
src/lib/i18n/locales/hu-HU/translation.json


+ 4 - 0
src/lib/i18n/locales/id-ID/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Menolak ketika seharusnya tidak",
 	"Regenerate": "Regenerasi",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Catatan Rilis",
 	"Relevance": "",
 	"Remove": "Hapus",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gunakan Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Gunakan Inisial",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "pengguna",

+ 4 - 0
src/lib/i18n/locales/ie-GA/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Tagairtí ó",
 	"Refused when it shouldn't have": "Diúltaíodh nuair nár chóir dó",
 	"Regenerate": "Athghiniúint",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Nótaí Scaoilte",
 	"Relevance": "Ábharthacht",
 	"Remove": "Bain",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Úsáid Gravatar",
 	"Use groups to group your users and assign permissions.": "Úsáid grúpaí chun d'úsáideoirí a ghrúpáil agus ceadanna a shannadh",
 	"Use Initials": "Úsáid ceannlitreacha",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "úsáideoir",

+ 4 - 0
src/lib/i18n/locales/it-IT/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Rifiutato quando non avrebbe dovuto",
 	"Regenerate": "Rigenera",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Note di rilascio",
 	"Relevance": "",
 	"Remove": "Rimuovi",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Usa Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Usa iniziali",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "utente",

+ 4 - 0
src/lib/i18n/locales/ja-JP/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "拒否すべきでないのに拒否した",
 	"Regenerate": "再生成",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "リリースノート",
 	"Relevance": "",
 	"Remove": "削除",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar を使用する",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "初期値を使用する",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "",
 	"use_mmap (Ollama)": "",
 	"user": "ユーザー",

+ 4 - 0
src/lib/i18n/locales/ka-GE/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "უარა, როგორც უნდა იყოს",
 	"Regenerate": "თავიდან გენერაცია",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "გამოცემის შენიშვნები",
 	"Relevance": "შესაბამისობა",
 	"Remove": "წაშლა",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar-ის გამოყენება",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "ინიციალების გამოყენება",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "მომხმარებელი",

+ 4 - 0
src/lib/i18n/locales/ko-KR/translation.json

@@ -878,6 +878,8 @@
 	"References from": "출처",
 	"Refused when it shouldn't have": "허용되지 않았지만 허용되어야 합니다.",
 	"Regenerate": "재생성",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "릴리스 노트",
 	"Relevance": "관련도",
 	"Remove": "삭제",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar 사용",
 	"Use groups to group your users and assign permissions.": "그룹을 사용하여 사용자를 그룹화하고 권한을 할당하세요.",
 	"Use Initials": "초성 사용",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (올라마)",
 	"use_mmap (Ollama)": "use_mmap (올라마)",
 	"user": "사용자",

+ 4 - 0
src/lib/i18n/locales/lt-LT/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Atmesta kai neturėtų būti atmesta",
 	"Regenerate": "Generuoti iš naujo",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Naujovės",
 	"Relevance": "",
 	"Remove": "Pašalinti",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Naudoti Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Naudotojo inicialai",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "naudotojas",

+ 4 - 0
src/lib/i18n/locales/ms-MY/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Menolak dimana ia tidak sepatutnya",
 	"Regenerate": "Jana semula",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Nota Keluaran",
 	"Relevance": "",
 	"Remove": "Hapuskan",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gunakan Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Gunakan nama pendek",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "se_mmap (Ollama)",
 	"user": "pengguna",

+ 4 - 0
src/lib/i18n/locales/nb-NO/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Henviser fra",
 	"Refused when it shouldn't have": "Avvist når det ikke burde ha blitt det",
 	"Regenerate": "Generer på nytt",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Utgivelsesnotater",
 	"Relevance": "Relevans",
 	"Remove": "Fjern",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Bruk Gravatar",
 	"Use groups to group your users and assign permissions.": "Bruk grupper til å samle brukere og tildele tillatelser.",
 	"Use Initials": "Bruk initialer",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "bruker",

+ 4 - 0
src/lib/i18n/locales/nl-NL/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Referenties van",
 	"Refused when it shouldn't have": "Geweigerd terwijl het niet had moeten",
 	"Regenerate": "Regenereren",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Release-opmerkingen",
 	"Relevance": "Relevantie",
 	"Remove": "Verwijderen",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gebruik Gravatar",
 	"Use groups to group your users and assign permissions.": "Gebruik groepen om gebruikers te groeperen en rechten aan te wijzen",
 	"Use Initials": "Gebruik initialen",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "gebruiker",

+ 4 - 0
src/lib/i18n/locales/pa-IN/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "ਜਦੋਂ ਇਹ ਨਹੀਂ ਹੋਣਾ ਚਾਹੀਦਾ ਸੀ ਤਾਂ ਇਨਕਾਰ ਕੀਤਾ",
 	"Regenerate": "ਮੁੜ ਬਣਾਓ",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "ਰਿਲੀਜ਼ ਨੋਟਸ",
 	"Relevance": "",
 	"Remove": "ਹਟਾਓ",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "ਗ੍ਰਾਵਾਟਾਰ ਵਰਤੋ",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "ਸ਼ੁਰੂਆਤੀ ਅੱਖਰ ਵਰਤੋ",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (ਓਲਾਮਾ)",
 	"use_mmap (Ollama)": "use_mmap (ਓਲਾਮਾ)",
 	"user": "ਉਪਭੋਗਤਾ",

+ 4 - 0
src/lib/i18n/locales/pl-PL/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Odniesienia do",
 	"Refused when it shouldn't have": "Odmówił, gdy nie powinien",
 	"Regenerate": "Wygeneruj ponownie",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Notatki do wydania",
 	"Relevance": "Trafność",
 	"Remove": "Usuń",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Użyj Gravatara",
 	"Use groups to group your users and assign permissions.": "Wykorzystaj grupy do grupowania użytkowników i przypisywania uprawnień.",
 	"Use Initials": "Użyj inicjałów",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "użyj_mlock (Ollama)",
 	"use_mmap (Ollama)": "użyj_mmap (Ollama)",
 	"user": "użytkownik",

+ 4 - 0
src/lib/i18n/locales/pt-BR/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Referências de",
 	"Refused when it shouldn't have": "Recusado quando não deveria",
 	"Regenerate": "Gerar novamente",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Notas de Lançamento",
 	"Relevance": "Relevância",
 	"Remove": "Remover",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Usar Gravatar",
 	"Use groups to group your users and assign permissions.": "Use grupos para agrupar seus usuários e atribuir permissões.",
 	"Use Initials": "Usar Iniciais",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "usuário",

+ 4 - 0
src/lib/i18n/locales/pt-PT/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Recusado quando não deveria",
 	"Regenerate": "Regenerar",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Notas de Lançamento",
 	"Relevance": "",
 	"Remove": "Remover",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Usar Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Usar Iniciais",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "utilizador",

+ 4 - 0
src/lib/i18n/locales/ro-RO/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Referințe din",
 	"Refused when it shouldn't have": "Refuzat când nu ar fi trebuit",
 	"Regenerate": "Regenerare",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Note de Lansare",
 	"Relevance": "Relevanță",
 	"Remove": "Înlătură",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Folosește Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Folosește Inițialele",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "utilizator",

+ 61 - 57
src/lib/i18n/locales/ru-RU/translation.json

@@ -6,7 +6,7 @@
 	"(latest)": "(последняя)",
 	"(Ollama)": "",
 	"{{ models }}": "{{ модели }}",
-	"{{COUNT}} Available Tools": "",
+	"{{COUNT}} Available Tools": "{{COUNT}} доступных инструментов",
 	"{{COUNT}} hidden lines": "{{COUNT}} скрытых строк",
 	"{{COUNT}} Replies": "{{COUNT}} Ответов",
 	"{{user}}'s Chats": "Чаты {{user}}'а",
@@ -49,10 +49,10 @@
 	"Adjusting these settings will apply changes universally to all users.": "Изменения в этих настройках будут применены для всех пользователей.",
 	"admin": "админ",
 	"Admin": "Админ",
-	"Admin Panel": "Админ панель",
+	"Admin Panel": "Панель администратора",
 	"Admin Settings": "Настройки администратора",
 	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "Администраторы всегда имеют доступ ко всем инструментам; пользователям нужны инструменты, назначенные для каждой модели в рабочем пространстве.",
-	"Advanced Parameters": "Расширенные Параметры",
+	"Advanced Parameters": "Расширенные параметры",
 	"Advanced Params": "Расширенные параметры",
 	"All": "Все",
 	"All Documents": "Все документы",
@@ -108,10 +108,10 @@
 	"Attribute for Username": "Атрибут для имени пользователя",
 	"Audio": "Аудио",
 	"August": "Август",
-	"Auth": "",
+	"Auth": "Вход",
 	"Authenticate": "Аутентификация",
 	"Authentication": "Аутентификация",
-	"Auto": "",
+	"Auto": "Автоматически",
 	"Auto-Copy Response to Clipboard": "Автоматическое копирование ответа в буфер обмена",
 	"Auto-playback response": "Автоматическое воспроизведение ответа",
 	"Autocomplete Generation": "Генерация автозаполнения",
@@ -121,7 +121,7 @@
 	"AUTOMATIC1111 Base URL": "Базовый URL адрес AUTOMATIC1111",
 	"AUTOMATIC1111 Base URL is required.": "Необходим базовый адрес URL AUTOMATIC1111.",
 	"Available list": "Список доступных",
-	"Available Tools": "",
+	"Available Tools": "Доступные инструменты",
 	"available!": "доступно!",
 	"Awful": "Ужасно",
 	"Azure AI Speech": "Azure AI Speech",
@@ -220,10 +220,10 @@
 	"Confirm your new password": "Подтвердите свой новый пароль",
 	"Connect to your own OpenAI compatible API endpoints.": "Подключитесь к своим собственным энд-поинтам API, совместимым с OpenAI.",
 	"Connect to your own OpenAPI compatible external tool servers.": "Подключитесь к вашим собственным внешним инструментальным серверам, совместимым с OpenAPI.",
-	"Connection failed": "",
-	"Connection successful": "",
+	"Connection failed": "Подключение не удалось",
+	"Connection successful": "Успешное подключение",
 	"Connections": "Подключения",
-	"Connections saved successfully": "",
+	"Connections saved successfully": "Подключение успешно сохранено",
 	"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort.": "Ограничивает усилия по обоснованию для моделей обоснования. Применимо только к моделям обоснования от конкретных поставщиков, которые поддерживают усилия по обоснованию.",
 	"Contact Admin for WebUI Access": "Обратитесь к администратору для получения доступа к WebUI",
 	"Content": "Содержание",
@@ -254,7 +254,7 @@
 	"Create Admin Account": "Создать Аккаунт Администратора",
 	"Create Channel": "Создать Канал",
 	"Create Group": "Создать Группу",
-	"Create Knowledge": "Создать Знания",
+	"Create Knowledge": "Создать Знание",
 	"Create new key": "Создать новый ключ",
 	"Create new secret key": "Создать новый секретный ключ",
 	"Created at": "Создано",
@@ -304,11 +304,11 @@
 	"Describe your knowledge base and objectives": "Опишите свою базу знаний и цели",
 	"Description": "Описание",
 	"Didn't fully follow instructions": "Не полностью следует инструкциям",
-	"Direct": "",
+	"Direct": "Прямое",
 	"Direct Connections": "Прямые подключения",
 	"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "Прямые подключения позволяют пользователям подключаться к своим собственным конечным точкам API, совместимым с OpenAI.",
 	"Direct Connections settings updated": "Настройки прямых подключений обновлены",
-	"Direct Tool Servers": "",
+	"Direct Tool Servers": "Прямые сервера инструментов",
 	"Disabled": "Отключено",
 	"Discover a function": "Найти функцию",
 	"Discover a model": "Найти модель",
@@ -329,7 +329,7 @@
 	"Do not install functions from sources you do not fully trust.": "Не устанавливайте функции из источников, которым вы не полностью доверяете.",
 	"Do not install tools from sources you do not fully trust.": "Не устанавливайте инструменты из источников, которым вы не полностью доверяете.",
 	"Docling": "",
-	"Docling Server URL required.": "",
+	"Docling Server URL required.": "Необходим URL сервера Docling",
 	"Document": "Документ",
 	"Document Intelligence": "Интеллектуальный анализ документов",
 	"Document Intelligence endpoint and key required.": "Требуется энд-поинт анализа документов и ключ.",
@@ -350,7 +350,7 @@
 	"Draw": "Рисовать",
 	"Drop any files here to add to the conversation": "Перетащите сюда файлы, чтобы добавить их в разговор",
 	"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "например, '30s','10m'. Допустимые единицы времени: 's', 'm', 'h'.",
-	"e.g. \"json\" or a JSON schema": "",
+	"e.g. \"json\" or a JSON schema": "например, \"json\" или схему JSON",
 	"e.g. 60": "например, 60",
 	"e.g. A filter to remove profanity from text": "например, фильтр для удаления ненормативной лексики из текста",
 	"e.g. My Filter": "например, мой фильтр",
@@ -385,7 +385,7 @@
 	"Enable Mirostat sampling for controlling perplexity.": "Включите выборку Mirostat для контроля путаницы.",
 	"Enable New Sign Ups": "Разрешить новые регистрации",
 	"Enabled": "Включено",
-	"Enforce Temporary Chat": "",
+	"Enforce Temporary Chat": "Принудительный временный чат",
 	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Убедитесь, что ваш CSV-файл включает в себя 4 столбца в следующем порядке: Имя, Электронная почта, Пароль, Роль.",
 	"Enter {{role}} message here": "Введите сообщение {{role}} здесь",
 	"Enter a detail about yourself for your LLMs to recall": "Введите детали о себе, чтобы LLMs могли запомнить",
@@ -402,7 +402,7 @@
 	"Enter Chunk Size": "Введите размер фрагмента",
 	"Enter comma-seperated \"token:bias_value\" pairs (example: 5432:100, 413:-100)": "Введите пары \"token:bias_value\", разделенные запятыми (пример: 5432:100, 413:-100).",
 	"Enter description": "Введите описание",
-	"Enter Docling Server URL": "",
+	"Enter Docling Server URL": "Введите URL сервера Docling",
 	"Enter Document Intelligence Endpoint": "Введите энд-поинт анализа документов",
 	"Enter Document Intelligence Key": "Введите ключ для анализа документов",
 	"Enter domains separated by commas (e.g., example.com,site.org)": "Введите домены, разделенные запятыми (например, example.com,site.org)",
@@ -418,7 +418,7 @@
 	"Enter Kagi Search API Key": "Введите ключ API поиска Kagi",
 	"Enter Key Behavior": "Введите ключ поведения",
 	"Enter language codes": "Введите коды языков",
-	"Enter Mistral API Key": "",
+	"Enter Mistral API Key": "Введите ключ API для Mistral",
 	"Enter Model ID": "Введите ID модели",
 	"Enter model tag (e.g. {{modelTag}})": "Введите тег модели (например, {{modelTag}})",
 	"Enter Mojeek Search API Key": "Введите ключ API поиска Mojeek",
@@ -443,21 +443,21 @@
 	"Enter server port": "Введите порт сервера",
 	"Enter stop sequence": "Введите последовательность остановки",
 	"Enter system prompt": "Введите системный промпт",
-	"Enter system prompt here": "",
+	"Enter system prompt here": "Введите системный промпт здесь",
 	"Enter Tavily API Key": "Введите ключ API Tavily",
 	"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Введите общедоступный URL вашего WebUI. Этот URL будет использоваться для создания ссылок в уведомлениях.",
 	"Enter Tika Server URL": "Введите URL-адрес сервера Tika",
 	"Enter timeout in seconds": "Введите время ожидания в секундах",
 	"Enter to Send": "Enter для отправки",
 	"Enter Top K": "Введите Top K",
-	"Enter Top K Reranker": "",
+	"Enter Top K Reranker": "Введите Top K переоценщика",
 	"Enter URL (e.g. http://127.0.0.1:7860/)": "Введите URL-адрес (например, http://127.0.0.1:7860/)",
 	"Enter URL (e.g. http://localhost:11434)": "Введите URL-адрес (например, http://localhost:11434)",
 	"Enter your current password": "Введите ваш текущий пароль",
 	"Enter Your Email": "Введите вашу электронную почту",
 	"Enter Your Full Name": "Введите ваше полное имя",
 	"Enter your message": "Введите ваше сообщение",
-	"Enter your name": "",
+	"Enter your name": "Введите ваше имя",
 	"Enter your new password": "Введите свой новый пароль",
 	"Enter Your Password": "Введите ваш пароль",
 	"Enter Your Role": "Введите вашу роль",
@@ -477,7 +477,7 @@
 	"Exceeded the number of seats in your license. Please contact support to increase the number of seats.": "Превышено количество мест в вашей лицензии. Пожалуйста, свяжитесь со службой поддержки, чтобы увеличить количество мест.",
 	"Exclude": "Исключать",
 	"Execute code for analysis": "Выполнить код для анализа",
-	"Executing **{{NAME}}**...": "",
+	"Executing **{{NAME}}**...": "Выполняю **{{NAME}}**...",
 	"Expand": "Расширить",
 	"Experimental": "Экспериментальное",
 	"Explain": "Объяснить",
@@ -495,14 +495,14 @@
 	"Export Prompts": "Экспортировать промпты",
 	"Export to CSV": "Экспортировать в CSV",
 	"Export Tools": "Экспортировать инструменты",
-	"External": "",
+	"External": "Внешнее",
 	"External Models": "Внешние модели",
 	"Failed to add file.": "Не удалось добавить файл.",
-	"Failed to connect to {{URL}} OpenAPI tool server": "",
+	"Failed to connect to {{URL}} OpenAPI tool server": "Не удалось подключиться к серверу инструмента OpenAI {{URL}}",
 	"Failed to create API Key.": "Не удалось создать ключ API.",
 	"Failed to fetch models": "Не удалось получить модели",
 	"Failed to read clipboard contents": "Не удалось прочитать содержимое буфера обмена",
-	"Failed to save connections": "",
+	"Failed to save connections": "Не удалось сохранить подключения",
 	"Failed to save models configuration": "Не удалось сохранить конфигурацию моделей",
 	"Failed to update settings": "Не удалось обновить настройки",
 	"Failed to upload file.": "Не удалось загрузить файл.",
@@ -535,7 +535,7 @@
 	"Forge new paths": "Прокладывайте новые пути",
 	"Form": "Форма",
 	"Format your variables using brackets like this:": "Отформатируйте переменные, используя такие : скобки",
-	"Forwards system user session credentials to authenticate": "",
+	"Forwards system user session credentials to authenticate": "Перенаправляет учетные данные сеанса системного пользователя для проверки подлинности",
 	"Frequency Penalty": "Штраф за частоту",
 	"Full Context Mode": "Режим полного контекста",
 	"Function": "Функция",
@@ -581,7 +581,7 @@
 	"Hex Color": "Цвет Hex",
 	"Hex Color - Leave empty for default color": "Цвет Hex - оставьте пустым значение цвета по умолчанию",
 	"Hide": "Скрыть",
-	"Hide Model": "",
+	"Hide Model": "Скрыть модель",
 	"Home": "Домой",
 	"Host": "Хост",
 	"How can I help you today?": "Чем я могу помочь вам сегодня?",
@@ -612,14 +612,14 @@
 	"Include `--api` flag when running stable-diffusion-webui": "Добавьте флаг `--api` при запуске stable-diffusion-webui",
 	"Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive.": "Влияет на то, насколько быстро алгоритм реагирует на обратную связь из сгенерированного текста. Более низкая скорость обучения приведет к более медленной корректировке, в то время как более высокая скорость обучения сделает алгоритм более отзывчивым.",
 	"Info": "Информация",
-	"Inject the entire content as context for comprehensive processing, this is recommended for complex queries.": "",
+	"Inject the entire content as context for comprehensive processing, this is recommended for complex queries.": "Вводите весь контент в качестве контекста для комплексной обработки, это рекомендуется для сложных запросов.",
 	"Input commands": "Введите команды",
 	"Install from Github URL": "Установка с URL-адреса Github",
 	"Instant Auto-Send After Voice Transcription": "Мгновенная автоматическая отправка после расшифровки голоса",
 	"Integration": "Интеграция",
 	"Interface": "Интерфейс",
 	"Invalid file format.": "Неверный формат файла.",
-	"Invalid JSON schema": "",
+	"Invalid JSON schema": "Недопустимая схема JSON",
 	"Invalid Tag": "Недопустимый тег",
 	"is typing...": "печатает...",
 	"January": "Январь",
@@ -641,7 +641,7 @@
 	"Knowledge Access": "Доступ к Знаниям",
 	"Knowledge created successfully.": "Знания созданы успешно.",
 	"Knowledge deleted successfully.": "Знания успешно удалены.",
-	"Knowledge Public Sharing": "",
+	"Knowledge Public Sharing": "Публичный обмен знаниями",
 	"Knowledge reset successfully.": "Знания успешно сброшены.",
 	"Knowledge updated successfully": "Знания успешно обновлены",
 	"Kokoro.js (Browser)": "Kokoro.js (Браузер)",
@@ -655,10 +655,10 @@
 	"LDAP": "",
 	"LDAP server updated": "LDAP сервер обновлен",
 	"Leaderboard": "Таблица Лидеров",
-	"Learn more about OpenAPI tool servers.": "",
+	"Learn more about OpenAPI tool servers.": "Узнайте больше о серверах инструментов OpenAPI.",
 	"Leave empty for unlimited": "Оставьте пустым для неограниченного",
-	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
-	"Leave empty to include all models from \"{{url}}/models\" endpoint": "",
+	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "Оставьте пустым, чтобы включить все модели из конечной точки \"{{url}}/api/tags\"",
+	"Leave empty to include all models from \"{{url}}/models\" endpoint": "Оставьте поле пустым, чтобы включить все модели из конечной точки \"{{url}}/models\"",
 	"Leave empty to include all models or select specific models": "Оставьте поле пустым, чтобы включить все модели или выбрать конкретные модели",
 	"Leave empty to use the default prompt, or enter a custom prompt": "Оставьте пустым, чтобы использовать промпт по умолчанию, или введите пользовательский промпт",
 	"Leave model field empty to use the default model.": "Оставьте поле model пустым, чтобы использовать модель по умолчанию.",
@@ -668,8 +668,8 @@
 	"Llama.cpp": "",
 	"LLMs can make mistakes. Verify important information.": "LLMs могут допускать ошибки. Проверяйте важную информацию.",
 	"Loader": "Загрузчик",
-	"Loading Kokoro.js...": "",
-	"Local": "",
+	"Loading Kokoro.js...": "Загрузка Kokoro.js",
+	"Local": "Локально",
 	"Local Models": "Локальные модели",
 	"Location access not allowed": "Доступ к местоположению запрещен",
 	"Logit Bias": "",
@@ -707,15 +707,15 @@
 	"Mirostat Eta": "Mirostat Eta",
 	"Mirostat Tau": "Mirostat Tau",
 	"Mistral OCR": "",
-	"Mistral OCR API Key required.": "",
+	"Mistral OCR API Key required.": "Требуется API ключ Mistral OCR.",
 	"Model": "Модель",
 	"Model '{{modelName}}' has been successfully downloaded.": "Модель '{{modelName}}' успешно загружена.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' уже находится в очереди на загрузку.",
 	"Model {{modelId}} not found": "Модель {{modelId}} не найдена",
 	"Model {{modelName}} is not vision capable": "Модель {{modelName}} не поддерживает зрение",
 	"Model {{name}} is now {{status}}": "Модель {{name}} теперь {{status}}",
-	"Model {{name}} is now hidden": "",
-	"Model {{name}} is now visible": "",
+	"Model {{name}} is now hidden": "Модель {{name}} теперь скрыта",
+	"Model {{name}} is now visible": "Модель {{name}} теперь видна",
 	"Model accepts image inputs": "Модель принимает изображения как входные данные",
 	"Model created successfully!": "Модель успешно создана!",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Обнаружен путь к файловой системе модели. Для обновления требуется краткое имя модели, не удается продолжить.",
@@ -731,7 +731,7 @@
 	"Models": "Модели",
 	"Models Access": "Доступ к Моделям",
 	"Models configuration saved successfully": "Конфигурация модели успешно сохранена.",
-	"Models Public Sharing": "",
+	"Models Public Sharing": "Публичный обмен моделями",
 	"Mojeek Search API Key": "Ключ API для поиска Mojeek",
 	"more": "больше",
 	"More": "Больше",
@@ -794,7 +794,7 @@
 	"Open file": "Открыть файл",
 	"Open in full screen": "Открыть на весь экран",
 	"Open new chat": "Открыть новый чат",
-	"Open WebUI can use tools provided by any OpenAPI server.": "",
+	"Open WebUI can use tools provided by any OpenAPI server.": "Open WebUI может использовать инструменты, предоставляемые любым сервером OpenAPI.",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI использует более быстрый внутренний интерфейс whisper.",
 	"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "В Open WebUI используются встраиваемые движки генерации речи SpeechT5 и CMU Arctic.",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Версия Open WebUI (v{{OPEN_WEBUI_VERSION}}) ниже требуемой версии (v{{REQUIRED_VERSION}})",
@@ -804,7 +804,7 @@
 	"OpenAI API Key is required.": "Требуется ключ API OpenAI.",
 	"OpenAI API settings updated": "Настройки OpenAI API обновлены",
 	"OpenAI URL/Key required.": "Требуется URL-адрес API OpenAI или ключ API.",
-	"openapi.json Path": "",
+	"openapi.json Path": "Путь openapi.json",
 	"or": "или",
 	"Organize your users": "Организуйте своих пользователей",
 	"Other": "Прочее",
@@ -836,8 +836,8 @@
 	"Please carefully review the following warnings:": "Пожалуйста, внимательно ознакомьтесь со следующими предупреждениями:",
 	"Please do not close the settings page while loading the model.": "Пожалуйста, не закрывайте страницу настроек во время загрузки модели.",
 	"Please enter a prompt": "Пожалуйста, введите подсказку",
-	"Please enter a valid path": "",
-	"Please enter a valid URL": "",
+	"Please enter a valid path": "Пожалуйста, введите правильный путь",
+	"Please enter a valid URL": "Пожалуйста, введите правильный URL",
 	"Please fill in all fields.": "Пожалуйста, заполните все поля.",
 	"Please select a model first.": "Пожалуйста, сначала выберите модель.",
 	"Please select a model.": "Пожалуйста, выберите модель.",
@@ -849,7 +849,7 @@
 	"Presence Penalty": "Штраф за присутствие",
 	"Previous 30 days": "Предыдущие 30 дней",
 	"Previous 7 days": "Предыдущие 7 дней",
-	"Private": "",
+	"Private": "Частное",
 	"Profile Image": "Изображение профиля",
 	"Prompt": "Промпт",
 	"Prompt (e.g. Tell me a fun fact about the Roman Empire)": "Промпт (например, Расскажи мне интересный факт о Римской империи)",
@@ -860,8 +860,8 @@
 	"Prompt updated successfully": "Промпт успешно обновлён",
 	"Prompts": "Промпты",
 	"Prompts Access": "Доступ к промптам",
-	"Prompts Public Sharing": "",
-	"Public": "",
+	"Prompts Public Sharing": "Публичный обмен промптами",
+	"Public": "Публичное",
 	"Pull \"{{searchValue}}\" from Ollama.com": "Загрузить \"{{searchValue}}\" с Ollama.com",
 	"Pull a model from Ollama.com": "Загрузить модель с Ollama.com",
 	"Query Generation Prompt": "Запрос на генерацию промпта",
@@ -878,6 +878,8 @@
 	"References from": "Отсылки к",
 	"Refused when it shouldn't have": "Отказано в доступе, когда это не должно было произойти",
 	"Regenerate": "Перегенерировать",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Примечания к выпуску",
 	"Relevance": "Актуальность",
 	"Remove": "Удалить",
@@ -993,11 +995,11 @@
 	"Share": "Поделиться",
 	"Share Chat": "Поделиться чатом",
 	"Share to Open WebUI Community": "Поделиться с сообществом OpenWebUI",
-	"Sharing Permissions": "",
+	"Sharing Permissions": "Разрешения на общий доступ",
 	"Show": "Показать",
 	"Show \"What's New\" modal on login": "Показывать окно «Что нового» при входе в систему",
 	"Show Admin Details in Account Pending Overlay": "Показывать данные администратора в оверлее ожидающей учетной записи",
-	"Show Model": "",
+	"Show Model": "Показать модель",
 	"Show shortcuts": "Показать горячие клавиши",
 	"Show your support!": "Поддержите нас!",
 	"Showcased creativity": "Продемонстрирован творческий подход",
@@ -1024,11 +1026,11 @@
 	"Suggested": "Предложено",
 	"Support": "Поддержать",
 	"Support this plugin:": "Поддержите этот плагин",
-	"Sync directory": "",
+	"Sync directory": "Каталог синхронизации",
 	"System": "Система",
 	"System Instructions": "Системные инструкции",
 	"System Prompt": "Системный промпт",
-	"Tags": "",
+	"Tags": "Теги",
 	"Tags Generation": "Генерация тегов",
 	"Tags Generation Prompt": "Промпт для генерации тегов",
 	"Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting.": "Выборка без хвостов используется для уменьшения влияния менее вероятных токенов на выходные данные. Более высокое значение (например, 2.0) еще больше уменьшит влияние, в то время как значение 1.0 отключает эту настройку.",
@@ -1059,8 +1061,8 @@
 	"Theme": "Тема",
 	"Thinking...": "Думаю...",
 	"This action cannot be undone. Do you wish to continue?": "Это действие нельзя отменить. Вы хотите продолжить?",
-	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
-	"This chat won’t appear in history and your messages will not be saved.": "",
+	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Этот канал был создан {{createdAt}}. Это самое начало канала {{channelName}}.",
+	"This chat won’t appear in history and your messages will not be saved.": "Этот чат не появится в истории, и ваши сообщения не будут сохранены.",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Это обеспечивает сохранение ваших ценных разговоров в безопасной базе данных на вашем сервере. Спасибо!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Это экспериментальная функция, она может работать не так, как ожидалось, и может быть изменена в любое время.",
 	"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "Этот параметр определяет, сколько токенов сохраняется при обновлении контекста. Например, если задано значение 2, будут сохранены последние 2 токена контекста беседы. Сохранение контекста может помочь сохранить непрерывность беседы, но может уменьшить возможность отвечать на новые темы.",
@@ -1074,7 +1076,7 @@
 	"This will reset the knowledge base and sync all files. Do you wish to continue?": "Это сбросит базу знаний и синхронизирует все файлы. Хотите продолжить?",
 	"Thorough explanation": "Подробное объяснение",
 	"Thought for {{DURATION}}": "Рассуждаю {{DURATION}}",
-	"Thought for {{DURATION}} seconds": "Рассуждаю {{DURATION}} секунд(ы)",
+	"Thought for {{DURATION}} seconds": "Рассуждал {{DURATION}} секунд",
 	"Tika": "Tika",
 	"Tika Server URL required.": "Требуется URL-адрес сервера Tika.",
 	"Tiktoken": "",
@@ -1108,7 +1110,7 @@
 	"Tool ID": "ID Инструмента",
 	"Tool imported successfully": "Инструмент успешно импортирован",
 	"Tool Name": "Имя Инструмента",
-	"Tool Servers": "",
+	"Tool Servers": "Сервер Инструмента",
 	"Tool updated successfully": "Инструмент успешно обновлен",
 	"Tools": "Инструменты",
 	"Tools Access": "Доступ к инструментам",
@@ -1116,7 +1118,7 @@
 	"Tools Function Calling Prompt": "Промпт на вызов функции Инструменты",
 	"Tools have a function calling system that allows arbitrary code execution": "Инструменты имеют систему вызова функций, которая позволяет выполнять произвольный код",
 	"Tools have a function calling system that allows arbitrary code execution.": "Инструменты имеют систему вызова функций, которая позволяет выполнять произвольный код.",
-	"Tools Public Sharing": "",
+	"Tools Public Sharing": "Публичный обмен инструментами",
 	"Top K": "Top K",
 	"Top K Reranker": "",
 	"Top P": "Top P",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Использовать Gravatar",
 	"Use groups to group your users and assign permissions.": "Используйте группы, чтобы группировать пользователей и назначать разрешения.",
 	"Use Initials": "Использовать инициалы",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "пользователь",
@@ -1174,11 +1178,11 @@
 	"Valves updated successfully": "Вентили успешно обновлены",
 	"variable": "переменная",
 	"variable to have them replaced with clipboard content.": "переменную, чтобы заменить их содержимым буфера обмена.",
-	"Verify Connection": "",
+	"Verify Connection": "Проверить подключение",
 	"Version": "Версия",
 	"Version {{selectedVersion}} of {{totalVersions}}": "Версия {{selectedVersion}} из {{totalVersions}}",
 	"View Replies": "С ответами",
-	"View Result from **{{NAME}}**": "",
+	"View Result from **{{NAME}}**": "Просмотр результата от **{{NAME}}**",
 	"Visibility": "Видимость",
 	"Voice": "Голос",
 	"Voice Input": "Ввод голоса",
@@ -1196,7 +1200,7 @@
 	"Webhook URL": "URL-адрес веб-хука",
 	"WebUI Settings": "Настройки WebUI",
 	"WebUI URL": "",
-	"WebUI will make requests to \"{{url}}\"": "",
+	"WebUI will make requests to \"{{url}}\"": "WebUI будет отправлять запросы к \"{{url}}\"",
 	"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI будет отправлять запросы к \"{{url}}/api/chat\"",
 	"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI будет отправлять запросы к \"{{url}}/chat/completions\"",
 	"What are you trying to achieve?": "Чего вы пытаетесь достичь?",

+ 4 - 0
src/lib/i18n/locales/sk-SK/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Referencie z",
 	"Refused when it shouldn't have": "Odmietnuté, keď nemalo byť.",
 	"Regenerate": "Regenerovať",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Záznamy o vydaní",
 	"Relevance": "Relevancia",
 	"Remove": "Odstrániť",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Použiť Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "Použiť iniciály",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "používateľ",

+ 4 - 0
src/lib/i18n/locales/sr-RS/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Референце од",
 	"Refused when it shouldn't have": "Одбијено када није требало",
 	"Regenerate": "Поново створи",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Напомене о издању",
 	"Relevance": "Примењивост",
 	"Remove": "Уклони",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Користи Граватар",
 	"Use groups to group your users and assign permissions.": "Користите групе да бисте разврстали ваше кориснике и доделили овлашћења.",
 	"Use Initials": "Користи иницијале",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "корисник",

+ 4 - 0
src/lib/i18n/locales/sv-SE/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "Avvisades när det inte borde ha gjort det",
 	"Regenerate": "Regenerera",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Versionsinformation",
 	"Relevance": "",
 	"Remove": "Ta bort",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Använd Gravatar",
 	"Use groups to group your users and assign permissions.": "Använd grupper för att gruppera dina användare och tilldela behörigheter.",
 	"Use Initials": "Använd initialer",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "användare",

+ 4 - 0
src/lib/i18n/locales/th-TH/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "ปฏิเสธเมื่อไม่ควรทำ",
 	"Regenerate": "สร้างใหม่",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "บันทึกรุ่น",
 	"Relevance": "",
 	"Remove": "ลบ",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "ใช้ Gravatar",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "ใช้ตัวย่อ",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "ผู้ใช้",

+ 4 - 0
src/lib/i18n/locales/tk-TW/translation.json

@@ -878,6 +878,8 @@
 	"References from": "",
 	"Refused when it shouldn't have": "",
 	"Regenerate": "",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "",
 	"Relevance": "",
 	"Remove": "",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "",
 	"use_mmap (Ollama)": "",
 	"user": "",

+ 4 - 0
src/lib/i18n/locales/tr-TR/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Referanslar arasından",
 	"Refused when it shouldn't have": "Reddedilmemesi gerekirken reddedildi",
 	"Regenerate": "Tekrar Oluştur",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Sürüm Notları",
 	"Relevance": "İlgili",
 	"Remove": "Kaldır",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Gravatar Kullan",
 	"Use groups to group your users and assign permissions.": "Kullanıcılarınızı gruplamak ve izinler atamak için grupları kullanın.",
 	"Use Initials": "Baş Harfleri Kullan",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "kullanıcı",

+ 4 - 0
src/lib/i18n/locales/uk-UA/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Посилання з",
 	"Refused when it shouldn't have": "Відмовив, коли не мав би",
 	"Regenerate": "Регенерувати",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Нотатки до випуску",
 	"Relevance": "Актуальність",
 	"Remove": "Видалити",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Змінити аватар",
 	"Use groups to group your users and assign permissions.": "Використовуйте групи, щоб об’єднувати користувачів і призначати дозволи.",
 	"Use Initials": "Використовувати ініціали",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "користувач",

+ 4 - 0
src/lib/i18n/locales/ur-PK/translation.json

@@ -878,6 +878,8 @@
 	"References from": "سے حوالہ جات",
 	"Refused when it shouldn't have": "جب انکار نہیں ہونا چاہیے تھا، انکار کر دیا",
 	"Regenerate": "دوبارہ تخلیق کریں",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "ریلیز نوٹس",
 	"Relevance": "موزونیت",
 	"Remove": "ہٹا دیں",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "گراویٹر استعمال کریں",
 	"Use groups to group your users and assign permissions.": "",
 	"Use Initials": "ابتدائیات استعمال کریں",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "استعمال کریں_mlock (Ollama)",
 	"use_mmap (Ollama)": "استعمال_mmap (Ollama)",
 	"user": "صارف",

+ 4 - 0
src/lib/i18n/locales/vi-VN/translation.json

@@ -878,6 +878,8 @@
 	"References from": "Tham khảo từ",
 	"Refused when it shouldn't have": "Từ chối trả lời mà nhẽ không nên làm vậy",
 	"Regenerate": "Tạo sinh lại câu trả lời",
+	"Reindex": "",
+	"Reindex Knowledge Base Vectors": "",
 	"Release Notes": "Mô tả những cập nhật mới",
 	"Relevance": "Mức độ liên quan",
 	"Remove": "Xóa",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "Sử dụng Gravatar",
 	"Use groups to group your users and assign permissions.": "Sử dụng nhóm để nhóm người dùng của bạn và gán quyền.",
 	"Use Initials": "Sử dụng tên viết tắt",
+	"Use no proxy to fetch page contents.": "",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "",
 	"use_mlock (Ollama)": "use_mlock (Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "Người sử dụng",

+ 20 - 12
src/lib/i18n/locales/zh-CN/translation.json

@@ -111,11 +111,11 @@
 	"Auth": "授权",
 	"Authenticate": "认证",
 	"Authentication": "身份验证",
-	"Auto": "",
+	"Auto": "自动",
 	"Auto-Copy Response to Clipboard": "自动复制回复到剪贴板",
 	"Auto-playback response": "自动念出回复内容",
-	"Autocomplete Generation": "输入框内容猜测补全",
-	"Autocomplete Generation Input Max Length": "输入框内容猜测补全输入最大长度",
+	"Autocomplete Generation": "输入框内容自动补全",
+	"Autocomplete Generation Input Max Length": "输入框内容自动补全输入最大长度",
 	"Automatic1111": "Automatic1111",
 	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 Api 鉴权字符串",
 	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 基础地址",
@@ -154,7 +154,7 @@
 	"Channel Name": "频道名称",
 	"Channels": "频道",
 	"Character": "字符",
-	"Character limit for autocomplete generation input": "输入框内容猜测补全输入的字符限制",
+	"Character limit for autocomplete generation input": "输入框内容自动补全输入的字符限制",
 	"Chart new frontiers": "开拓新领域",
 	"Chat": "对话",
 	"Chat Background Image": "对话背景图片",
@@ -375,14 +375,14 @@
 	"Embedding Model Engine": "语义向量模型引擎",
 	"Embedding model set to \"{{embedding_model}}\"": "语义向量模型设置为 \"{{embedding_model}}\"",
 	"Enable API Key": "启用 API 密钥",
-	"Enable autocomplete generation for chat messages": "启用聊天消息的输入框内容猜测补全",
+	"Enable autocomplete generation for chat messages": "启用聊天消息的输入框内容自动补全",
 	"Enable Code Execution": "启用代码执行",
 	"Enable Code Interpreter": "启用代码解释器",
 	"Enable Community Sharing": "启用分享至社区",
 	"Enable Memory Locking (mlock) to prevent model data from being swapped out of RAM. This option locks the model's working set of pages into RAM, ensuring that they will not be swapped out to disk. This can help maintain performance by avoiding page faults and ensuring fast data access.": "启用内存锁定(mlock)以防止模型数据被交换出RAM。此选项将模型的工作集页面锁定在RAM中,确保它们不会被交换到磁盘。这可以通过避免页面错误和确保快速数据访问来帮助维持性能。",
 	"Enable Memory Mapping (mmap) to load model data. This option allows the system to use disk storage as an extension of RAM by treating disk files as if they were in RAM. This can improve model performance by allowing for faster data access. However, it may not work correctly with all systems and can consume a significant amount of disk space.": "启用内存映射(mmap)以加载模型数据。此选项允许系统通过将磁盘文件视为在RAM中来使用磁盘存储作为RAM的扩展。这可以通过更快的数据访问来提高模型性能。然而,它可能无法在所有系统上正常工作,并且可能会消耗大量磁盘空间。",
 	"Enable Message Rating": "启用回复评价",
-	"Enable Mirostat sampling for controlling perplexity.": "启用Mirostat采样以控制困惑度",
+	"Enable Mirostat sampling for controlling perplexity.": "启用 Mirostat 采样以控制困惑度",
 	"Enable New Sign Ups": "允许新用户注册",
 	"Enabled": "启用",
 	"Enforce Temporary Chat": "强制临时聊天",
@@ -424,6 +424,8 @@
 	"Enter Mojeek Search API Key": "输入 Mojeek Search API 密钥",
 	"Enter Number of Steps (e.g. 50)": "输入步骤数 (Steps) (例如:50)",
 	"Enter Perplexity API Key": "输入 Perplexity API 密钥",
+	"Enter Sougou Search API sID": "输入搜狗搜索 API 的 Secret ID",
+	"Enter Sougou Search API SK": "输入搜狗搜索 API 的 Secret Key",
 	"Enter proxy URL (e.g. https://user:password@host:port)": "输入代理 URL (例如:https://用户名:密码@主机名:端口)",
 	"Enter reasoning effort": "设置推理努力",
 	"Enter Sampler (e.g. Euler a)": "输入 Sampler (例如:Euler a)",
@@ -655,7 +657,7 @@
 	"LDAP": "LDAP",
 	"LDAP server updated": "LDAP 服务器已更新",
 	"Leaderboard": "排行榜",
-	"Learn more about OpenAPI tool servers.": "",
+	"Learn more about OpenAPI tool servers.": "进一步了解 OpenAPI 工具服务器。",
 	"Leave empty for unlimited": "留空表示无限制",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "留空以包含来自 \"{{url}}/api/tags\" 端点的所有模型",
 	"Leave empty to include all models from \"{{url}}/models\" endpoint": "留空以包含来自 \"{{url}}/models\" 端点的所有模型",
@@ -687,7 +689,7 @@
 	"Manage Pipelines": "管理 Pipeline",
 	"Manage Tool Servers": "管理工具服务器",
 	"March": "三月",
-	"Max Tokens (num_predict)": "最大Token数量 (num_predict)",
+	"Max Tokens (num_predict)": "最大 Token 数量 (num_predict)",
 	"Max Upload Count": "最大上传数量",
 	"Max Upload Size": "最大上传大小",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "最多可以同时下载 3 个模型,请稍后重试。",
@@ -771,7 +773,7 @@
 	"Notifications": "桌面通知",
 	"November": "十一月",
 	"num_gpu (Ollama)": "num_gpu (Ollama)",
-	"num_thread (Ollama)": "num_thread(Ollama)",
+	"num_thread (Ollama)": "num_thread (Ollama)",
 	"OAuth ID": "OAuth ID",
 	"October": "十月",
 	"Off": "关闭",
@@ -794,7 +796,7 @@
 	"Open file": "打开文件",
 	"Open in full screen": "全屏打开",
 	"Open new chat": "打开新对话",
-	"Open WebUI can use tools provided by any OpenAPI server.": "",
+	"Open WebUI can use tools provided by any OpenAPI server.": "Open WebUI 可使用任何 OpenAPI 服务器提供的工具。",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI 使用内置 faster-whisper。",
 	"Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.": "Open WebUI 使用 SpeechT5 和 CMU Arctic speaker embedding。",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "当前 Open WebUI 版本 (v{{OPEN_WEBUI_VERSION}}) 低于所需的版本 (v{{REQUIRED_VERSION}})",
@@ -822,6 +824,8 @@
 	"Permission denied when accessing microphone: {{error}}": "申请麦克风权限被拒绝:{{error}}",
 	"Permissions": "权限",
 	"Perplexity API Key": "Perplexity API 密钥",
+	"Sougou Search API sID": "搜狗搜索 API 的 Secret ID",
+	"Sougou Search API SK": "搜狗搜索 API 的 Secret Key",
 	"Personalization": "个性化",
 	"Pin": "置顶",
 	"Pinned": "已置顶",
@@ -846,7 +850,7 @@
 	"Positive attitude": "积极的态度",
 	"Prefix ID": "Prefix ID",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "Prefix ID 用于通过为模型 ID 添加前缀来避免与其他连接发生冲突 - 留空则禁用此功能",
-	"Presence Penalty": "重复惩罚(Presence Penalty)",
+	"Presence Penalty": "重复惩罚 (Presence Penalty)",
 	"Previous 30 days": "过去 30 天",
 	"Previous 7 days": "过去 7 天",
 	"Private": "私有",
@@ -878,6 +882,8 @@
 	"References from": "来自",
 	"Refused when it shouldn't have": "无理拒绝",
 	"Regenerate": "重新生成",
+	"Reindex": "重建索引",
+	"Reindex Knowledge Base Vectors": "重建知识库向量",
 	"Release Notes": "更新日志",
 	"Relevance": "相关性",
 	"Remove": "移除",
@@ -885,7 +891,7 @@
 	"Rename": "重命名",
 	"Reorder Models": "重新排序模型",
 	"Repeat Last N": "重复最后 N 次",
-	"Repeat Penalty (Ollama)": "重复惩罚(Ollama)",
+	"Repeat Penalty (Ollama)": "重复惩罚 (Ollama)",
 	"Reply in Thread": "在主题中回复",
 	"Request Mode": "请求模式",
 	"Reranking Model": "重排模型",
@@ -1158,6 +1164,8 @@
 	"Use Gravatar": "使用来自 Gravatar 的头像",
 	"Use groups to group your users and assign permissions.": "使用权限组来组织用户并分配权限。",
 	"Use Initials": "使用首个字符作为头像",
+	"Use no proxy to fetch page contents.": "不使用代理获取页面内容。",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "使用由 http_proxy 和 https_proxy 环境变量指定的代理获取页面内容。",
 	"use_mlock (Ollama)": "use_mlock(Ollama)",
 	"use_mmap (Ollama)": "use_mmap (Ollama)",
 	"user": "用户",

+ 4 - 0
src/lib/i18n/locales/zh-TW/translation.json

@@ -878,6 +878,8 @@
 	"References from": "引用來源",
 	"Refused when it shouldn't have": "不應拒絕時拒絕了",
 	"Regenerate": "重新產生",
+	"Reindex": "重新索引",
+	"Reindex Knowledge Base Vectors": "重新索引知識庫向量",
 	"Release Notes": "釋出説明",
 	"Relevance": "相關性",
 	"Remove": "移除",
@@ -1158,6 +1160,8 @@
 	"Use Gravatar": "使用 Gravatar",
 	"Use groups to group your users and assign permissions.": "使用群組來組織您的使用者並分配權限。",
 	"Use Initials": "使用姓名縮寫",
+	"Use no proxy to fetch page contents.": "不使用代理擷取頁面內容。",
+	"Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents.": "使用 http_proxy 和 https_proxy 環境變數指定的代理擷取頁面內容。",
 	"use_mlock (Ollama)": "使用 mlock (Ollama)",
 	"use_mmap (Ollama)": "使用 mmap (Ollama)",
 	"user": "使用者",

+ 59 - 53
src/routes/(app)/+layout.svelte

@@ -45,6 +45,7 @@
 	import AccountPending from '$lib/components/layout/Overlay/AccountPending.svelte';
 	import UpdateInfoToast from '$lib/components/layout/UpdateInfoToast.svelte';
 	import { get } from 'svelte/store';
+	import Spinner from '$lib/components/common/Spinner.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -254,65 +255,70 @@
 	<div
 		class=" text-gray-700 dark:text-gray-100 bg-white dark:bg-gray-900 h-screen max-h-[100dvh] overflow-auto flex flex-row justify-end"
 	>
-		{#if loaded}
-			{#if !['user', 'admin'].includes($user?.role)}
-				<AccountPending />
-			{:else if localDBChats.length > 0}
-				<div class="fixed w-full h-full flex z-50">
-					<div
-						class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
-					>
-						<div class="m-auto pb-44 flex flex-col justify-center">
-							<div class="max-w-md">
-								<div class="text-center dark:text-white text-2xl font-medium z-50">
-									Important Update<br /> Action Required for Chat Log Storage
-								</div>
-
-								<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
-									{$i18n.t(
-										"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through"
-									)}
-									<span class="font-semibold dark:text-white"
-										>{$i18n.t('Settings')} > {$i18n.t('Chats')} > {$i18n.t('Import Chats')}</span
-									>. {$i18n.t(
-										'This ensures that your valuable conversations are securely saved to your backend database. Thank you!'
-									)}
-								</div>
-
-								<div class=" mt-6 mx-auto relative group w-fit">
-									<button
-										class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
-										on:click={async () => {
-											let blob = new Blob([JSON.stringify(localDBChats)], {
-												type: 'application/json'
-											});
-											saveAs(blob, `chat-export-${Date.now()}.json`);
-
-											const tx = DB.transaction('chats', 'readwrite');
-											await Promise.all([tx.store.clear(), tx.done]);
-											await deleteDB('Chats');
-
-											localDBChats = [];
-										}}
-									>
-										Download & Delete
-									</button>
-
-									<button
-										class="text-xs text-center w-full mt-2 text-gray-400 underline"
-										on:click={async () => {
-											localDBChats = [];
-										}}>{$i18n.t('Close')}</button
-									>
-								</div>
+		{#if !['user', 'admin'].includes($user?.role)}
+			<AccountPending />
+		{:else if localDBChats.length > 0}
+			<div class="fixed w-full h-full flex z-50">
+				<div
+					class="absolute w-full h-full backdrop-blur-md bg-white/20 dark:bg-gray-900/50 flex justify-center"
+				>
+					<div class="m-auto pb-44 flex flex-col justify-center">
+						<div class="max-w-md">
+							<div class="text-center dark:text-white text-2xl font-medium z-50">
+								Important Update<br /> Action Required for Chat Log Storage
+							</div>
+
+							<div class=" mt-4 text-center text-sm dark:text-gray-200 w-full">
+								{$i18n.t(
+									"Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through"
+								)}
+								<span class="font-semibold dark:text-white"
+									>{$i18n.t('Settings')} > {$i18n.t('Chats')} > {$i18n.t('Import Chats')}</span
+								>. {$i18n.t(
+									'This ensures that your valuable conversations are securely saved to your backend database. Thank you!'
+								)}
+							</div>
+
+							<div class=" mt-6 mx-auto relative group w-fit">
+								<button
+									class="relative z-20 flex px-5 py-2 rounded-full bg-white border border-gray-100 dark:border-none hover:bg-gray-100 transition font-medium text-sm"
+									on:click={async () => {
+										let blob = new Blob([JSON.stringify(localDBChats)], {
+											type: 'application/json'
+										});
+										saveAs(blob, `chat-export-${Date.now()}.json`);
+
+										const tx = DB.transaction('chats', 'readwrite');
+										await Promise.all([tx.store.clear(), tx.done]);
+										await deleteDB('Chats');
+
+										localDBChats = [];
+									}}
+								>
+									Download & Delete
+								</button>
+
+								<button
+									class="text-xs text-center w-full mt-2 text-gray-400 underline"
+									on:click={async () => {
+										localDBChats = [];
+									}}>{$i18n.t('Close')}</button
+								>
 							</div>
 						</div>
 					</div>
 				</div>
-			{/if}
+			</div>
+		{/if}
 
-			<Sidebar />
+		<Sidebar />
+
+		{#if loaded}
 			<slot />
+		{:else}
+			<div class="w-full flex-1 h-full flex items-center justify-center">
+				<Spinner />
+			</div>
 		{/if}
 	</div>
 </div>

+ 2 - 1
src/routes/+layout.svelte

@@ -211,8 +211,9 @@
 		console.log('executeTool', data, toolServer);
 
 		if (toolServer) {
+			console.log(toolServer);
 			const res = await executeToolServer(
-				toolServer.key,
+				(toolServer?.auth_type ?? 'bearer') === 'bearer' ? toolServer?.key : localStorage.token,
 				toolServer.url,
 				data?.name,
 				data?.params,

Some files were not shown because too many files changed in this diff