Преглед изворни кода

Merge pull request #5599 from open-webui/dev

0.3.24
Timothy Jaeryang Baek пре 10 месеци
родитељ
комит
7ec72679f0
80 измењених фајлова са 1659 додато и 872 уклоњено
  1. 29 0
      CHANGELOG.md
  2. 1 1
      backend/open_webui/apps/audio/main.py
  3. 81 62
      backend/open_webui/apps/socket/main.py
  4. 59 0
      backend/open_webui/apps/socket/utils.py
  5. 2 1
      backend/open_webui/apps/webui/utils.py
  6. 1 0
      backend/open_webui/env.py
  7. 2 1
      backend/open_webui/main.py
  8. 1 1
      backend/requirements.txt
  9. 73 73
      package-lock.json
  10. 1 1
      package.json
  11. 1 1
      pyproject.toml
  12. 220 156
      src/lib/components/chat/Chat.svelte
  13. 8 3
      src/lib/components/chat/ChatControls.svelte
  14. 14 8
      src/lib/components/chat/MessageInput.svelte
  15. 45 5
      src/lib/components/chat/MessageInput/Commands/Prompts.svelte
  16. 177 216
      src/lib/components/chat/Messages.svelte
  17. 23 13
      src/lib/components/chat/Messages/CitationsModal.svelte
  18. 15 1
      src/lib/components/chat/Messages/CodeBlock.svelte
  19. 1 1
      src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte
  20. 166 0
      src/lib/components/chat/Messages/Message.svelte
  21. 139 123
      src/lib/components/chat/Messages/MultiResponseMessages.svelte
  22. 31 21
      src/lib/components/chat/Messages/ResponseMessage.svelte
  23. 39 15
      src/lib/components/chat/Messages/UserMessage.svelte
  24. 24 5
      src/lib/components/chat/Overview/Node.svelte
  25. 31 2
      src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
  26. 2 0
      src/lib/components/chat/Settings/General.svelte
  27. 0 30
      src/lib/components/chat/Settings/Interface.svelte
  28. 19 3
      src/lib/components/common/Loader.svelte
  29. 3 1
      src/lib/components/common/Tooltip.svelte
  30. 1 1
      src/lib/components/layout/Sidebar.svelte
  31. 17 1
      src/lib/components/layout/Sidebar/UserMenu.svelte
  32. 44 0
      src/lib/components/layout/UpdateInfoToast.svelte
  33. 1 1
      src/lib/components/workspace/Documents.svelte
  34. 17 3
      src/lib/components/workspace/Models.svelte
  35. 3 1
      src/lib/constants.ts
  36. 4 0
      src/lib/i18n/locales/ar-BH/translation.json
  37. 4 0
      src/lib/i18n/locales/bg-BG/translation.json
  38. 4 0
      src/lib/i18n/locales/bn-BD/translation.json
  39. 4 0
      src/lib/i18n/locales/ca-ES/translation.json
  40. 4 0
      src/lib/i18n/locales/ceb-PH/translation.json
  41. 4 0
      src/lib/i18n/locales/de-DE/translation.json
  42. 4 0
      src/lib/i18n/locales/dg-DG/translation.json
  43. 4 0
      src/lib/i18n/locales/en-GB/translation.json
  44. 4 0
      src/lib/i18n/locales/en-US/translation.json
  45. 4 0
      src/lib/i18n/locales/es-ES/translation.json
  46. 4 0
      src/lib/i18n/locales/fa-IR/translation.json
  47. 4 0
      src/lib/i18n/locales/fi-FI/translation.json
  48. 4 0
      src/lib/i18n/locales/fr-CA/translation.json
  49. 4 0
      src/lib/i18n/locales/fr-FR/translation.json
  50. 4 0
      src/lib/i18n/locales/he-IL/translation.json
  51. 4 0
      src/lib/i18n/locales/hi-IN/translation.json
  52. 4 0
      src/lib/i18n/locales/hr-HR/translation.json
  53. 4 0
      src/lib/i18n/locales/id-ID/translation.json
  54. 4 0
      src/lib/i18n/locales/it-IT/translation.json
  55. 4 0
      src/lib/i18n/locales/ja-JP/translation.json
  56. 4 0
      src/lib/i18n/locales/ka-GE/translation.json
  57. 4 0
      src/lib/i18n/locales/ko-KR/translation.json
  58. 4 0
      src/lib/i18n/locales/lt-LT/translation.json
  59. 4 0
      src/lib/i18n/locales/ms-MY/translation.json
  60. 4 0
      src/lib/i18n/locales/nb-NO/translation.json
  61. 4 0
      src/lib/i18n/locales/nl-NL/translation.json
  62. 4 0
      src/lib/i18n/locales/pa-IN/translation.json
  63. 4 0
      src/lib/i18n/locales/pl-PL/translation.json
  64. 4 0
      src/lib/i18n/locales/pt-BR/translation.json
  65. 4 0
      src/lib/i18n/locales/pt-PT/translation.json
  66. 4 0
      src/lib/i18n/locales/ro-RO/translation.json
  67. 4 0
      src/lib/i18n/locales/ru-RU/translation.json
  68. 4 0
      src/lib/i18n/locales/sr-RS/translation.json
  69. 4 0
      src/lib/i18n/locales/sv-SE/translation.json
  70. 4 0
      src/lib/i18n/locales/th-TH/translation.json
  71. 4 0
      src/lib/i18n/locales/tk-TW/translation.json
  72. 119 115
      src/lib/i18n/locales/tr-TR/translation.json
  73. 4 0
      src/lib/i18n/locales/uk-UA/translation.json
  74. 4 0
      src/lib/i18n/locales/vi-VN/translation.json
  75. 5 1
      src/lib/i18n/locales/zh-CN/translation.json
  76. 5 1
      src/lib/i18n/locales/zh-TW/translation.json
  77. 47 1
      src/lib/utils/index.ts
  78. 28 1
      src/routes/(app)/+layout.svelte
  79. 11 1
      src/routes/+layout.svelte
  80. 1 1
      src/routes/s/[id]/+page.svelte

+ 29 - 0
CHANGELOG.md

@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.3.24] - 2024-09-24
+
+### Added
+
+- **🚀 Rendering Optimization**: Significantly improved message rendering performance, enhancing user experience and webui responsiveness.
+- **💖 Favorite Response Feature in Chat Overview**: Users can now mark responses as favorite directly from the chat overview, enhancing ease of retrieval and organization of preferred responses.
+- **💬 Create Message Pairs with Shortcut**: Implemented creation of new message pairs using Cmd/Ctrl+Shift+Enter, making conversation editing faster and more intuitive.
+- **🌍 Expanded User Prompt Variables**: Added weekday, timezone, and language information variables to user prompts to match system prompt variables.
+- **🎵 Enhanced Audio Support**: Now includes support for 'audio/x-m4a' files, broadening compatibility with audio content within the platform.
+- **🔏 Model URL Search Parameter**: Added an ability to select a model directly via URL parameters, streamlining navigation and model access.
+- **📄 Enhanced PDF Citations**: PDF citations now open at the associated page, streamlining reference checks and document handling.
+- **🔧Use of Redis in Sockets**: Enhanced socket implementation to fully support Redis, enabling effective stateless instances suitable for scalable load balancing.
+- **🌍 Stream Individual Model Responses**: Allows specific models to have individualized streaming settings, enhancing performance and customization.
+- **🕒 Display Model Hash and Last Modified Timestamp for Ollama Models**: Provides critical model details directly in the Models workspace for enhanced tracking.
+- **❗ Update Info Notification for Admins**: Ensures administrators receive immediate updates upon login, keeping them informed of the latest changes and system statuses.
+
+### Fixed
+
+- **🗑️ Temporary File Handling On Windows**: Fixed an issue causing errors when accessing a temporary file being used by another process, Tools & Functions should now work as intended.
+- **🔓 Authentication Toggle Issue**: Resolved the malfunction where setting 'WEBUI_AUTH=False' did not appropriately disable authentication, ensuring that user experience and system security settings function as configured.
+- **🔧 Save As Copy Issue for Many Model Chats**: Resolved an error preventing users from save messages as copies in many model chats.
+- **🔒 Sidebar Closure on Mobile**: Resolved an issue where the mobile sidebar remained open after menu engagement, improving user interface responsivity and comfort.
+- **🛡️ Tooltip XSS Vulnerability**: Resolved a cross-site scripting (XSS) issue within tooltips, ensuring enhanced security and data integrity during user interactions.
+
+### Changed
+
+- **↩️ Deprecated Interface Stream Response Settings**: Moved to advanced parameters to streamline interface settings and enhance user clarity.
+- **⚙️ Renamed 'speedRate' to 'playbackRate'**: Standardizes terminology, improving usability and understanding in media settings.
+
 ## [0.3.23] - 2024-09-21
 
 ### Added

+ 1 - 1
backend/open_webui/apps/audio/main.py

@@ -360,7 +360,7 @@ def transcribe(
 ):
     log.info(f"file.content_type: {file.content_type}")
 
-    if file.content_type not in ["audio/mpeg", "audio/wav", "audio/ogg"]:
+    if file.content_type not in ["audio/mpeg", "audio/wav", "audio/ogg", "audio/x-m4a"]:
         raise HTTPException(
             status_code=status.HTTP_400_BAD_REQUEST,
             detail=ERROR_MESSAGES.FILE_NOT_SUPPORTED,

+ 81 - 62
backend/open_webui/apps/socket/main.py

@@ -1,6 +1,9 @@
 import asyncio
-
 import socketio
+import logging
+import sys
+import time
+
 from open_webui.apps.webui.models.users import Users
 from open_webui.env import (
     ENABLE_WEBSOCKET_SUPPORT,
@@ -8,6 +11,17 @@ from open_webui.env import (
     WEBSOCKET_REDIS_URL,
 )
 from open_webui.utils.utils import decode_token
+from open_webui.apps.socket.utils import RedisDict
+
+from open_webui.env import (
+    GLOBAL_LOG_LEVEL,
+    SRC_LOG_LEVELS,
+)
+
+
+logging.basicConfig(stream=sys.stdout, level=GLOBAL_LOG_LEVEL)
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["SOCKET"])
 
 
 if WEBSOCKET_MANAGER == "redis":
@@ -38,13 +52,72 @@ app = socketio.ASGIApp(sio, socketio_path="/ws/socket.io")
 
 # Dictionary to maintain the user pool
 
-SESSION_POOL = {}
-USER_POOL = {}
-USAGE_POOL = {}
+if WEBSOCKET_MANAGER == "redis":
+    SESSION_POOL = RedisDict("open-webui:session_pool", redis_url=WEBSOCKET_REDIS_URL)
+    USER_POOL = RedisDict("open-webui:user_pool", redis_url=WEBSOCKET_REDIS_URL)
+    USAGE_POOL = RedisDict("open-webui:usage_pool", redis_url=WEBSOCKET_REDIS_URL)
+else:
+    SESSION_POOL = {}
+    USER_POOL = {}
+    USAGE_POOL = {}
+
+
 # Timeout duration in seconds
 TIMEOUT_DURATION = 3
 
 
+async def periodic_usage_pool_cleanup():
+    while True:
+        now = int(time.time())
+        for model_id, connections in list(USAGE_POOL.items()):
+            # Creating a list of sids to remove if they have timed out
+            expired_sids = [
+                sid
+                for sid, details in connections.items()
+                if now - details["updated_at"] > TIMEOUT_DURATION
+            ]
+
+            for sid in expired_sids:
+                del connections[sid]
+
+            if not connections:
+                log.debug(f"Cleaning up model {model_id} from usage pool")
+                del USAGE_POOL[model_id]
+            else:
+                USAGE_POOL[model_id] = connections
+
+            # Emit updated usage information after cleaning
+            await sio.emit("usage", {"models": get_models_in_use()})
+
+        await asyncio.sleep(TIMEOUT_DURATION)
+
+
+# Start the cleanup task when your app starts
+asyncio.create_task(periodic_usage_pool_cleanup())
+
+
+def get_models_in_use():
+    # List models that are currently in use
+    models_in_use = list(USAGE_POOL.keys())
+    return models_in_use
+
+
+@sio.on("usage")
+async def usage(sid, data):
+    model_id = data["model"]
+    # Record the timestamp for the last update
+    current_time = int(time.time())
+
+    # Store the new usage data and task
+    USAGE_POOL[model_id] = {
+        **(USAGE_POOL[model_id] if model_id in USAGE_POOL else {}),
+        sid: {"updated_at": current_time},
+    }
+
+    # Broadcast the usage data to all clients
+    await sio.emit("usage", {"models": get_models_in_use()})
+
+
 @sio.event
 async def connect(sid, environ, auth):
     user = None
@@ -62,8 +135,7 @@ async def connect(sid, environ, auth):
                 USER_POOL[user.id] = [sid]
 
             # print(f"user {user.name}({user.id}) connected with session ID {sid}")
-
-            await sio.emit("user-count", {"count": len(set(USER_POOL))})
+            await sio.emit("user-count", {"count": len(USER_POOL.items())})
             await sio.emit("usage", {"models": get_models_in_use()})
 
 
@@ -91,65 +163,12 @@ async def user_join(sid, data):
 
     # print(f"user {user.name}({user.id}) connected with session ID {sid}")
 
-    await sio.emit("user-count", {"count": len(set(USER_POOL))})
+    await sio.emit("user-count", {"count": len(USER_POOL.items())})
 
 
 @sio.on("user-count")
 async def user_count(sid):
-    await sio.emit("user-count", {"count": len(set(USER_POOL))})
-
-
-def get_models_in_use():
-    # Aggregate all models in use
-    models_in_use = []
-    for model_id, data in USAGE_POOL.items():
-        models_in_use.append(model_id)
-
-    return models_in_use
-
-
-@sio.on("usage")
-async def usage(sid, data):
-    model_id = data["model"]
-
-    # Cancel previous callback if there is one
-    if model_id in USAGE_POOL:
-        USAGE_POOL[model_id]["callback"].cancel()
-
-    # Store the new usage data and task
-
-    if model_id in USAGE_POOL:
-        USAGE_POOL[model_id]["sids"].append(sid)
-        USAGE_POOL[model_id]["sids"] = list(set(USAGE_POOL[model_id]["sids"]))
-
-    else:
-        USAGE_POOL[model_id] = {"sids": [sid]}
-
-    # Schedule a task to remove the usage data after TIMEOUT_DURATION
-    USAGE_POOL[model_id]["callback"] = asyncio.create_task(
-        remove_after_timeout(sid, model_id)
-    )
-
-    # Broadcast the usage data to all clients
-    await sio.emit("usage", {"models": get_models_in_use()})
-
-
-async def remove_after_timeout(sid, model_id):
-    try:
-        await asyncio.sleep(TIMEOUT_DURATION)
-        if model_id in USAGE_POOL:
-            # print(USAGE_POOL[model_id]["sids"])
-            USAGE_POOL[model_id]["sids"].remove(sid)
-            USAGE_POOL[model_id]["sids"] = list(set(USAGE_POOL[model_id]["sids"]))
-
-            if len(USAGE_POOL[model_id]["sids"]) == 0:
-                del USAGE_POOL[model_id]
-
-            # Broadcast the usage data to all clients
-            await sio.emit("usage", {"models": get_models_in_use()})
-    except asyncio.CancelledError:
-        # Task was cancelled due to new 'usage' event
-        pass
+    await sio.emit("user-count", {"count": len(USER_POOL.items())})
 
 
 @sio.event
@@ -158,7 +177,7 @@ async def disconnect(sid):
         user_id = SESSION_POOL[sid]
         del SESSION_POOL[sid]
 
-        USER_POOL[user_id].remove(sid)
+        USER_POOL[user_id] = [_sid for _sid in USER_POOL[user_id] if _sid != sid]
 
         if len(USER_POOL[user_id]) == 0:
             del USER_POOL[user_id]

+ 59 - 0
backend/open_webui/apps/socket/utils.py

@@ -0,0 +1,59 @@
+import json
+import redis
+
+
+class RedisDict:
+    def __init__(self, name, redis_url):
+        self.name = name
+        self.redis = redis.Redis.from_url(redis_url, decode_responses=True)
+
+    def __setitem__(self, key, value):
+        serialized_value = json.dumps(value)
+        self.redis.hset(self.name, key, serialized_value)
+
+    def __getitem__(self, key):
+        value = self.redis.hget(self.name, key)
+        if value is None:
+            raise KeyError(key)
+        return json.loads(value)
+
+    def __delitem__(self, key):
+        result = self.redis.hdel(self.name, key)
+        if result == 0:
+            raise KeyError(key)
+
+    def __contains__(self, key):
+        return self.redis.hexists(self.name, key)
+
+    def __len__(self):
+        return self.redis.hlen(self.name)
+
+    def keys(self):
+        return self.redis.hkeys(self.name)
+
+    def values(self):
+        return [json.loads(v) for v in self.redis.hvals(self.name)]
+
+    def items(self):
+        return [(k, json.loads(v)) for k, v in self.redis.hgetall(self.name).items()]
+
+    def get(self, key, default=None):
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+    def clear(self):
+        self.redis.delete(self.name)
+
+    def update(self, other=None, **kwargs):
+        if other is not None:
+            for k, v in other.items() if hasattr(other, "items") else other:
+                self[k] = v
+        for k, v in kwargs.items():
+            self[k] = v
+
+    def setdefault(self, key, default=None):
+        if key not in self:
+            self[key] = default
+        return self[key]

+ 2 - 1
backend/open_webui/apps/webui/utils.py

@@ -87,7 +87,7 @@ def load_toolkit_module_by_id(toolkit_id, content=None):
     # Create a temporary file and use it to define `__file__` so
     # that it works as expected from the module's perspective.
     temp_file = tempfile.NamedTemporaryFile(delete=False)
-
+    temp_file.close()
     try:
         with open(temp_file.name, "w", encoding="utf-8") as f:
             f.write(content)
@@ -131,6 +131,7 @@ def load_function_module_by_id(function_id, content=None):
     # Create a temporary file and use it to define `__file__` so
     # that it works as expected from the module's perspective.
     temp_file = tempfile.NamedTemporaryFile(delete=False)
+    temp_file.close()
     try:
         with open(temp_file.name, "w", encoding="utf-8") as f:
             f.write(content)

+ 1 - 0
backend/open_webui/env.py

@@ -84,6 +84,7 @@ log_sources = [
     "OPENAI",
     "RAG",
     "WEBHOOK",
+    "SOCKET",
 ]
 
 SRC_LOG_LEVELS = {}

+ 2 - 1
backend/open_webui/main.py

@@ -2329,10 +2329,11 @@ async def get_manifest_json():
     return {
         "name": WEBUI_NAME,
         "short_name": 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": "portrait-primary",
+        "orientation": "any",
         "icons": [
             {
                 "src": "/static/logo.png",

+ 1 - 1
backend/requirements.txt

@@ -69,7 +69,7 @@ rank-bm25==0.2.2
 faster-whisper==1.0.3
 
 PyJWT[crypto]==2.9.0
-authlib==1.3.1
+authlib==1.3.2
 
 black==24.8.0
 langfuse==2.44.0

+ 73 - 73
package-lock.json

@@ -1,12 +1,12 @@
 {
 	"name": "open-webui",
-	"version": "0.3.23",
+	"version": "0.3.24",
 	"lockfileVersion": 3,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "open-webui",
-			"version": "0.3.23",
+			"version": "0.3.24",
 			"dependencies": {
 				"@codemirror/lang-javascript": "^6.2.2",
 				"@codemirror/lang-python": "^6.1.6",
@@ -1167,9 +1167,9 @@
 			}
 		},
 		"node_modules/@rollup/rollup-android-arm-eabi": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz",
-			"integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz",
+			"integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==",
 			"cpu": [
 				"arm"
 			],
@@ -1179,9 +1179,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-android-arm64": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz",
-			"integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz",
+			"integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==",
 			"cpu": [
 				"arm64"
 			],
@@ -1191,9 +1191,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-darwin-arm64": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz",
-			"integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz",
+			"integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==",
 			"cpu": [
 				"arm64"
 			],
@@ -1203,9 +1203,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-darwin-x64": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz",
-			"integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz",
+			"integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==",
 			"cpu": [
 				"x64"
 			],
@@ -1215,9 +1215,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz",
-			"integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz",
+			"integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==",
 			"cpu": [
 				"arm"
 			],
@@ -1227,9 +1227,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-arm-musleabihf": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz",
-			"integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz",
+			"integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==",
 			"cpu": [
 				"arm"
 			],
@@ -1239,9 +1239,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-arm64-gnu": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz",
-			"integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz",
+			"integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==",
 			"cpu": [
 				"arm64"
 			],
@@ -1251,9 +1251,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-arm64-musl": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz",
-			"integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz",
+			"integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==",
 			"cpu": [
 				"arm64"
 			],
@@ -1263,9 +1263,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz",
-			"integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz",
+			"integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==",
 			"cpu": [
 				"ppc64"
 			],
@@ -1275,9 +1275,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-riscv64-gnu": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz",
-			"integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz",
+			"integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==",
 			"cpu": [
 				"riscv64"
 			],
@@ -1287,9 +1287,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-s390x-gnu": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz",
-			"integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz",
+			"integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==",
 			"cpu": [
 				"s390x"
 			],
@@ -1299,9 +1299,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-x64-gnu": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz",
-			"integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz",
+			"integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==",
 			"cpu": [
 				"x64"
 			],
@@ -1311,9 +1311,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-linux-x64-musl": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz",
-			"integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz",
+			"integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==",
 			"cpu": [
 				"x64"
 			],
@@ -1323,9 +1323,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-win32-arm64-msvc": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz",
-			"integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz",
+			"integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==",
 			"cpu": [
 				"arm64"
 			],
@@ -1335,9 +1335,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-win32-ia32-msvc": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz",
-			"integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz",
+			"integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==",
 			"cpu": [
 				"ia32"
 			],
@@ -1347,9 +1347,9 @@
 			]
 		},
 		"node_modules/@rollup/rollup-win32-x64-msvc": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz",
-			"integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz",
+			"integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==",
 			"cpu": [
 				"x64"
 			],
@@ -7906,9 +7906,9 @@
 			"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
 		},
 		"node_modules/rollup": {
-			"version": "4.20.0",
-			"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz",
-			"integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==",
+			"version": "4.22.4",
+			"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz",
+			"integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==",
 			"dependencies": {
 				"@types/estree": "1.0.5"
 			},
@@ -7920,22 +7920,22 @@
 				"npm": ">=8.0.0"
 			},
 			"optionalDependencies": {
-				"@rollup/rollup-android-arm-eabi": "4.20.0",
-				"@rollup/rollup-android-arm64": "4.20.0",
-				"@rollup/rollup-darwin-arm64": "4.20.0",
-				"@rollup/rollup-darwin-x64": "4.20.0",
-				"@rollup/rollup-linux-arm-gnueabihf": "4.20.0",
-				"@rollup/rollup-linux-arm-musleabihf": "4.20.0",
-				"@rollup/rollup-linux-arm64-gnu": "4.20.0",
-				"@rollup/rollup-linux-arm64-musl": "4.20.0",
-				"@rollup/rollup-linux-powerpc64le-gnu": "4.20.0",
-				"@rollup/rollup-linux-riscv64-gnu": "4.20.0",
-				"@rollup/rollup-linux-s390x-gnu": "4.20.0",
-				"@rollup/rollup-linux-x64-gnu": "4.20.0",
-				"@rollup/rollup-linux-x64-musl": "4.20.0",
-				"@rollup/rollup-win32-arm64-msvc": "4.20.0",
-				"@rollup/rollup-win32-ia32-msvc": "4.20.0",
-				"@rollup/rollup-win32-x64-msvc": "4.20.0",
+				"@rollup/rollup-android-arm-eabi": "4.22.4",
+				"@rollup/rollup-android-arm64": "4.22.4",
+				"@rollup/rollup-darwin-arm64": "4.22.4",
+				"@rollup/rollup-darwin-x64": "4.22.4",
+				"@rollup/rollup-linux-arm-gnueabihf": "4.22.4",
+				"@rollup/rollup-linux-arm-musleabihf": "4.22.4",
+				"@rollup/rollup-linux-arm64-gnu": "4.22.4",
+				"@rollup/rollup-linux-arm64-musl": "4.22.4",
+				"@rollup/rollup-linux-powerpc64le-gnu": "4.22.4",
+				"@rollup/rollup-linux-riscv64-gnu": "4.22.4",
+				"@rollup/rollup-linux-s390x-gnu": "4.22.4",
+				"@rollup/rollup-linux-x64-gnu": "4.22.4",
+				"@rollup/rollup-linux-x64-musl": "4.22.4",
+				"@rollup/rollup-win32-arm64-msvc": "4.22.4",
+				"@rollup/rollup-win32-ia32-msvc": "4.22.4",
+				"@rollup/rollup-win32-x64-msvc": "4.22.4",
 				"fsevents": "~2.3.2"
 			}
 		},
@@ -8724,11 +8724,11 @@
 			}
 		},
 		"node_modules/svelte-sonner": {
-			"version": "0.3.19",
-			"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.19.tgz",
-			"integrity": "sha512-jpPOgLtHwRaB6Vqo2dUQMv15/yUV/BQWTjKpEqQ11uqRSHKjAYUKZyGrHB2cQsGmyjR0JUzBD58btpgNqINQ/Q==",
+			"version": "0.3.28",
+			"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.28.tgz",
+			"integrity": "sha512-K3AmlySeFifF/cKgsYNv5uXqMVNln0NBAacOYgmkQStLa/UoU0LhfAACU6Gr+YYC8bOCHdVmFNoKuDbMEsppJg==",
 			"peerDependencies": {
-				"svelte": ">=3 <5"
+				"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1"
 			}
 		},
 		"node_modules/svelte/node_modules/estree-walker": {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 	"name": "open-webui",
-	"version": "0.3.23",
+	"version": "0.3.24",
 	"private": true,
 	"scripts": {
 		"dev": "npm run pyodide:fetch && vite dev --host",

+ 1 - 1
pyproject.toml

@@ -76,7 +76,7 @@ dependencies = [
     "faster-whisper==1.0.3",
 
     "PyJWT[crypto]==2.9.0",
-    "authlib==1.3.1",
+    "authlib==1.3.2",
 
     "black==24.8.0",
     "langfuse==2.44.0",

+ 220 - 156
src/lib/components/chat/Chat.svelte

@@ -5,6 +5,8 @@
 	import { PaneGroup, Pane, PaneResizer } from 'paneforge';
 
 	import { getContext, onDestroy, onMount, tick } from 'svelte';
+	const i18n: Writable<i18nType> = getContext('i18n');
+
 	import { goto } from '$app/navigation';
 	import { page } from '$app/stores';
 
@@ -67,11 +69,9 @@
 	import Navbar from '$lib/components/layout/Navbar.svelte';
 	import ChatControls from './ChatControls.svelte';
 	import EventConfirmDialog from '../common/ConfirmDialog.svelte';
-	import EllipsisVertical from '../icons/EllipsisVertical.svelte';
-
-	const i18n: Writable<i18nType> = getContext('i18n');
 
 	export let chatIdProp = '';
+
 	let loaded = false;
 	const eventTarget = new EventTarget();
 	let controlPane;
@@ -89,11 +89,10 @@
 	let eventConfirmationInputValue = '';
 	let eventCallback = null;
 
-	let showModelSelector = true;
+	let chatIdUnsubscriber: Unsubscriber | undefined;
 
 	let selectedModels = [''];
 	let atSelectedModel: Model | undefined;
-
 	let selectedModelIds = [];
 	$: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels;
 
@@ -104,35 +103,17 @@
 	let tags = [];
 
 	let title = '';
-	let prompt = '';
-
-	let chatFiles = [];
-	let files = [];
-	let messages = [];
 	let history = {
 		messages: {},
 		currentId: null
 	};
 
+	// Chat Input
+	let prompt = '';
+	let chatFiles = [];
+	let files = [];
 	let params = {};
 
-	let chatIdUnsubscriber: Unsubscriber | undefined;
-
-	$: if (history.currentId !== null) {
-		let _messages = [];
-		let currentMessage = history.messages[history.currentId];
-		while (currentMessage) {
-			_messages.unshift({ ...currentMessage });
-			currentMessage =
-				currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
-		}
-
-		// This is most likely causing the performance issue
-		messages = _messages;
-	} else {
-		messages = [];
-	}
-
 	$: if (chatIdProp) {
 		(async () => {
 			console.log(chatIdProp);
@@ -150,6 +131,7 @@
 	}
 
 	const showMessage = async (message) => {
+		const _chatId = JSON.parse(JSON.stringify($chatId));
 		let _messageId = JSON.parse(JSON.stringify(message.id));
 
 		let messageChildrenIds = history.messages[_messageId].childrenIds;
@@ -169,6 +151,9 @@
 		if (messageElement) {
 			messageElement.scrollIntoView({ behavior: 'smooth' });
 		}
+
+		await tick();
+		saveChatHandler(_chatId);
 	};
 
 	const chatEventHandler = async (event, cb) => {
@@ -226,7 +211,7 @@
 				console.log('Unknown message type', data);
 			}
 
-			messages = messages;
+			history.messages[event.message_id] = message;
 		}
 	};
 
@@ -308,6 +293,9 @@
 				showOverview.set(false);
 			}
 		});
+
+		const chatInput = document.getElementById('chat-textarea');
+		chatInput?.focus();
 	});
 
 	onDestroy(() => {
@@ -329,7 +317,6 @@
 		autoScroll = true;
 
 		title = '';
-		messages = [];
 		history = {
 			messages: {},
 			currentId: null
@@ -340,6 +327,8 @@
 
 		if ($page.url.searchParams.get('models')) {
 			selectedModels = $page.url.searchParams.get('models')?.split(',');
+		} else if ($page.url.searchParams.get('model')) {
+			selectedModels = $page.url.searchParams.get('model')?.split(',');
 		} else if ($settings?.models) {
 			selectedModels = $settings?.models;
 		} else if ($config?.default_models) {
@@ -424,8 +413,8 @@
 				autoScroll = true;
 				await tick();
 
-				if (messages.length > 0) {
-					history.messages[messages.at(-1).id].done = true;
+				if (history.currentId) {
+					history.messages[history.currentId].done = true;
 				}
 				await tick();
 
@@ -444,8 +433,12 @@
 	};
 
 	const createMessagesList = (responseMessageId) => {
+		if (responseMessageId === null) {
+			return [];
+		}
+
 		const message = history.messages[responseMessageId];
-		if (message.parentId) {
+		if (message?.parentId) {
 			return [...createMessagesList(message.parentId), message];
 		} else {
 			return [message];
@@ -506,6 +499,8 @@
 	};
 
 	const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
+		const messages = createMessagesList(responseMessageId);
+
 		const res = await chatAction(localStorage.token, actionId, {
 			model: modelId,
 			messages: messages.map((m) => ({
@@ -564,6 +559,66 @@
 		}, 1000);
 	};
 
+	const createMessagePair = async (userPrompt) => {
+		prompt = '';
+		if (selectedModels.length === 0) {
+			toast.error($i18n.t('Model not selected'));
+		} else {
+			const modelId = selectedModels[0];
+			const model = $models.filter((m) => m.id === modelId).at(0);
+
+			const messages = createMessagesList(history.currentId);
+			const parentMessage = messages.length !== 0 ? messages.at(-1) : null;
+
+			const userMessageId = uuidv4();
+			const responseMessageId = uuidv4();
+
+			const userMessage = {
+				id: userMessageId,
+				parentId: parentMessage ? parentMessage.id : null,
+				childrenIds: [responseMessageId],
+				role: 'user',
+				content: userPrompt ? userPrompt : `[PROMPT] ${userMessageId}`,
+				timestamp: Math.floor(Date.now() / 1000)
+			};
+
+			const responseMessage = {
+				id: responseMessageId,
+				parentId: userMessageId,
+				childrenIds: [],
+				role: 'assistant',
+				content: `[RESPONSE] ${responseMessageId}`,
+				done: true,
+
+				model: modelId,
+				modelName: model.name ?? model.id,
+				modelIdx: 0,
+				timestamp: Math.floor(Date.now() / 1000)
+			};
+
+			if (parentMessage) {
+				parentMessage.childrenIds.push(userMessageId);
+				history.messages[parentMessage.id] = parentMessage;
+			}
+			history.messages[userMessageId] = userMessage;
+			history.messages[responseMessageId] = responseMessage;
+
+			history.currentId = responseMessageId;
+
+			await tick();
+
+			if (autoScroll) {
+				scrollToBottom();
+			}
+
+			if (messages.length === 0) {
+				await initChatHandler();
+			} else {
+				await saveChatHandler($chatId);
+			}
+		}
+	};
+
 	//////////////////////////
 	// Chat functions
 	//////////////////////////
@@ -571,6 +626,7 @@
 	const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
 		let _responses = [];
 		console.log('submitPrompt', $chatId);
+		const messages = createMessagesList(history.currentId);
 
 		selectedModels = selectedModels.map((modelId) =>
 			$models.map((m) => m.id).includes(modelId) ? modelId : ''
@@ -664,8 +720,16 @@
 		parentId: string,
 		{ modelId = null, modelIdx = null, newChat = false } = {}
 	) => {
-		let _responses: string[] = [];
+		// Create new chat if newChat is true and first user message
+		if (
+			newChat &&
+			history.messages[history.currentId].parentId === null &&
+			history.messages[history.currentId].role === 'user'
+		) {
+			await initChatHandler();
+		}
 
+		let _responses: string[] = [];
 		// If modelId is provided, use it, else use selected model
 		let selectedModelIds = modelId
 			? [modelId]
@@ -710,38 +774,14 @@
 		}
 		await tick();
 
-		// Create new chat if only one message in messages
-		if (newChat && messages.length == 2) {
-			if (!$temporaryChatEnabled) {
-				chat = await createNewChat(localStorage.token, {
-					id: $chatId,
-					title: $i18n.t('New Chat'),
-					models: selectedModels,
-					system: $settings.system ?? undefined,
-					params: params,
-					messages: messages,
-					history: history,
-					tags: [],
-					timestamp: Date.now()
-				});
-
-				currentChatPage.set(1);
-				await chats.set(await getChatList(localStorage.token, $currentChatPage));
-				await chatId.set(chat.id);
-			} else {
-				await chatId.set('local');
-			}
-			await tick();
-		}
-
 		const _chatId = JSON.parse(JSON.stringify($chatId));
-
 		await Promise.all(
 			selectedModelIds.map(async (modelId, _modelIdx) => {
 				console.log('modelId', modelId);
 				const model = $models.filter((m) => m.id === modelId).at(0);
 
 				if (model) {
+					const messages = createMessagesList(parentId);
 					// If there are image files, check if model is vision capable
 					const hasImages = messages.some((message) =>
 						message.files?.some((file) => file.type === 'image')
@@ -840,7 +880,7 @@
 						}`
 					}
 				: undefined,
-			...messages
+			...createMessagesList(responseMessageId)
 		]
 			.filter((message) => message?.content?.trim())
 			.map((message) => {
@@ -891,7 +931,7 @@
 				}
 			];
 			files.push(...model.info.meta.knowledge);
-			messages = messages; // Trigger Svelte update
+			history.messages[responseMessageId] = responseMessage;
 		}
 		files.push(
 			...(userMessage?.files ?? []).filter((item) =>
@@ -912,7 +952,11 @@
 
 		await tick();
 
-		const stream = $settings?.streamResponse ?? true;
+		const stream =
+			model?.info?.params?.stream_response ??
+			$settings?.params?.stream_response ??
+			params?.stream_response ??
+			true;
 		const [res, controller] = await generateChatCompletion(localStorage.token, {
 			stream: stream,
 			model: model.id,
@@ -965,7 +1009,7 @@
 					const { value, done } = await reader.read();
 					if (done || stopResponseFlag || _chatId !== $chatId) {
 						responseMessage.done = true;
-						messages = messages;
+						history.messages[responseMessageId] = responseMessage;
 
 						if (stopResponseFlag) {
 							controller.abort('User: Stop Response');
@@ -1032,7 +1076,7 @@
 											);
 										}
 
-										messages = messages;
+										history.messages[responseMessageId] = responseMessage;
 									}
 								} else {
 									responseMessage.done = true;
@@ -1055,7 +1099,8 @@
 										eval_count: data.eval_count,
 										eval_duration: data.eval_duration
 									};
-									messages = messages;
+
+									history.messages[responseMessageId] = responseMessage;
 
 									if ($settings.notificationEnabled && !document.hasFocus()) {
 										const notification = new Notification(`${model.id}`, {
@@ -1124,7 +1169,7 @@
 				);
 			}
 
-			messages = messages;
+			history.messages[responseMessageId] = responseMessage;
 		}
 		await saveChatHandler(_chatId);
 
@@ -1157,7 +1202,8 @@
 			scrollToBottom();
 		}
 
-		if (messages.length == 2 && messages.at(1).content !== '' && selectedModels[0] === model.id) {
+		const messages = createMessagesList(responseMessageId);
+		if (messages.length == 2 && messages.at(-1).content !== '' && selectedModels[0] === model.id) {
 			window.history.replaceState(history.state, '', `/c/${_chatId}`);
 			const _title = await generateChatTitle(userPrompt);
 			await setChatTitle(_chatId, _title);
@@ -1185,7 +1231,7 @@
 				}
 			];
 			files.push(...model.info.meta.knowledge);
-			messages = messages; // Trigger Svelte update
+			history.messages[responseMessageId] = responseMessage;
 		}
 		files.push(
 			...(userMessage?.files ?? []).filter((item) =>
@@ -1206,7 +1252,12 @@
 		await tick();
 
 		try {
-			const stream = $settings?.streamResponse ?? true;
+			const stream =
+				model?.info?.params?.stream_response ??
+				$settings?.params?.stream_response ??
+				params?.stream_response ??
+				true;
+
 			const [res, controller] = await generateOpenAIChatCompletion(
 				localStorage.token,
 				{
@@ -1236,7 +1287,7 @@
 									}`
 								}
 							: undefined,
-						...messages
+						...createMessagesList(responseMessageId)
 					]
 						.filter((message) => message?.content?.trim())
 						.map((message, idx, arr) => ({
@@ -1314,7 +1365,7 @@
 						}
 						if (done || stopResponseFlag || _chatId !== $chatId) {
 							responseMessage.done = true;
-							messages = messages;
+							history.messages[responseMessageId] = responseMessage;
 
 							if (stopResponseFlag) {
 								controller.abort('User: Stop Response');
@@ -1369,7 +1420,7 @@
 								);
 							}
 
-							messages = messages;
+							history.messages[responseMessageId] = responseMessage;
 						}
 
 						if (autoScroll) {
@@ -1410,7 +1461,7 @@
 
 		await saveChatHandler(_chatId);
 
-		messages = messages;
+		history.messages[responseMessageId] = responseMessage;
 
 		stopResponseFlag = false;
 		await tick();
@@ -1441,9 +1492,9 @@
 			scrollToBottom();
 		}
 
+		const messages = createMessagesList(responseMessageId);
 		if (messages.length == 2 && selectedModels[0] === model.id) {
 			window.history.replaceState(history.state, '', `/c/${_chatId}`);
-
 			const _title = await generateChatTitle(userPrompt);
 			await setChatTitle(_chatId, _title);
 		}
@@ -1493,7 +1544,7 @@
 			);
 		}
 
-		messages = messages;
+		history.messages[responseMessage.id] = responseMessage;
 	};
 
 	const stopResponse = () => {
@@ -1504,7 +1555,7 @@
 	const regenerateResponse = async (message) => {
 		console.log('regenerateResponse');
 
-		if (messages.length != 0) {
+		if (history.currentId) {
 			let userMessage = history.messages[message.parentId];
 			let userPrompt = userMessage.content;
 
@@ -1522,11 +1573,11 @@
 		}
 	};
 
-	const continueGeneration = async () => {
-		console.log('continueGeneration');
+	const continueResponse = async () => {
+		console.log('continueResponse');
 		const _chatId = JSON.parse(JSON.stringify($chatId));
 
-		if (messages.length != 0 && messages.at(-1).done == true) {
+		if (history.currentId && history.messages[history.currentId].done == true) {
 			const responseMessage = history.messages[history.currentId];
 			responseMessage.done = false;
 			await tick();
@@ -1554,6 +1605,53 @@
 		}
 	};
 
+	const mergeResponses = async (messageId, responses, _chatId) => {
+		console.log('mergeResponses', messageId, responses);
+		const message = history.messages[messageId];
+		const mergedResponse = {
+			status: true,
+			content: ''
+		};
+		message.merged = mergedResponse;
+		history.messages[messageId] = message;
+
+		try {
+			const [res, controller] = await generateMoACompletion(
+				localStorage.token,
+				message.model,
+				history.messages[message.parentId].content,
+				responses
+			);
+
+			if (res && res.ok && res.body) {
+				const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
+				for await (const update of textStream) {
+					const { value, done, citations, error, usage } = update;
+					if (error || done) {
+						break;
+					}
+
+					if (mergedResponse.content == '' && value == '\n') {
+						continue;
+					} else {
+						mergedResponse.content += value;
+						history.messages[messageId] = message;
+					}
+
+					if (autoScroll) {
+						scrollToBottom();
+					}
+				}
+
+				await saveChatHandler(_chatId);
+			} else {
+				console.error(res);
+			}
+		} catch (e) {
+			console.error(e);
+		}
+	};
+
 	const generateChatTitle = async (userPrompt) => {
 		if ($settings?.title?.auto ?? true) {
 			const title = await generateTitle(
@@ -1596,7 +1694,7 @@
 				description: $i18n.t('Generating search query')
 			}
 		];
-		messages = messages;
+		history.messages[responseMessageId] = responseMessage;
 
 		const prompt = userMessage.content;
 		let searchQuery = await generateSearchQuery(
@@ -1616,7 +1714,7 @@
 				action: 'web_search',
 				description: $i18n.t('No search query generated')
 			});
-			messages = messages;
+			history.messages[responseMessageId] = responseMessage;
 			return;
 		}
 
@@ -1625,7 +1723,7 @@
 			action: 'web_search',
 			description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
 		});
-		messages = messages;
+		history.messages[responseMessageId] = responseMessage;
 
 		const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
 			console.log(error);
@@ -1653,8 +1751,7 @@
 				type: 'web_search_results',
 				urls: results.filenames
 			});
-
-			messages = messages;
+			history.messages[responseMessageId] = responseMessage;
 		} else {
 			responseMessage.statusHistory.push({
 				done: true,
@@ -1662,7 +1759,7 @@
 				action: 'web_search',
 				description: 'No search results found'
 			});
-			messages = messages;
+			history.messages[responseMessageId] = responseMessage;
 		}
 	};
 
@@ -1672,13 +1769,34 @@
 		});
 	};
 
+	const initChatHandler = async () => {
+		if (!$temporaryChatEnabled) {
+			chat = await createNewChat(localStorage.token, {
+				id: $chatId,
+				title: $i18n.t('New Chat'),
+				models: selectedModels,
+				system: $settings.system ?? undefined,
+				params: params,
+				history: history,
+				tags: [],
+				timestamp: Date.now()
+			});
+
+			currentChatPage.set(1);
+			await chats.set(await getChatList(localStorage.token, $currentChatPage));
+			await chatId.set(chat.id);
+		} else {
+			await chatId.set('local');
+		}
+		await tick();
+	};
+
 	const saveChatHandler = async (_chatId) => {
 		if ($chatId == _chatId) {
 			if (!$temporaryChatEnabled) {
 				chat = await updateChatById(localStorage.token, _chatId, {
-					messages: messages,
-					history: history,
 					models: selectedModels,
+					history: history,
 					params: params,
 					files: chatFiles
 				});
@@ -1688,52 +1806,6 @@
 			}
 		}
 	};
-	const mergeResponses = async (messageId, responses, _chatId) => {
-		console.log('mergeResponses', messageId, responses);
-		const message = history.messages[messageId];
-		const mergedResponse = {
-			status: true,
-			content: ''
-		};
-		message.merged = mergedResponse;
-		messages = messages;
-
-		try {
-			const [res, controller] = await generateMoACompletion(
-				localStorage.token,
-				message.model,
-				history.messages[message.parentId].content,
-				responses
-			);
-
-			if (res && res.ok && res.body) {
-				const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
-				for await (const update of textStream) {
-					const { value, done, citations, error, usage } = update;
-					if (error || done) {
-						break;
-					}
-
-					if (mergedResponse.content == '' && value == '\n') {
-						continue;
-					} else {
-						mergedResponse.content += value;
-						messages = messages;
-					}
-
-					if (autoScroll) {
-						scrollToBottom();
-					}
-				}
-
-				await saveChatHandler(_chatId);
-			} else {
-				console.error(res);
-			}
-		} catch (e) {
-			console.error(e);
-		}
-	};
 </script>
 
 <svelte:head>
@@ -1784,18 +1856,11 @@
 			/>
 		{/if}
 
-		<Navbar
-			{title}
-			bind:selectedModels
-			bind:showModelSelector
-			shareEnabled={messages.length > 0}
-			{chat}
-			{initNewChat}
-		/>
+		<Navbar {chat} {title} bind:selectedModels shareEnabled={!!history.currentId} {initNewChat} />
 
 		<PaneGroup direction="horizontal" class="w-full h-full">
 			<Pane defaultSize={50} class="h-full flex w-full relative">
-				{#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1}
+				{#if $banners.length > 0 && !history.currentId && !$chatId && selectedModels.length <= 1}
 					<div class="absolute top-3 left-0 right-0 w-full z-20">
 						<div class=" flex flex-col gap-1 w-full">
 							{#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
@@ -1834,31 +1899,31 @@
 						<div class=" h-full w-full flex flex-col {chatIdProp ? 'py-4' : 'pt-2 pb-4'}">
 							<Messages
 								chatId={$chatId}
-								{selectedModels}
-								{processing}
 								bind:history
-								bind:messages
 								bind:autoScroll
 								bind:prompt
-								bottomPadding={files.length > 0}
+								{selectedModels}
 								{sendPrompt}
-								{continueGeneration}
+								{showMessage}
+								{continueResponse}
 								{regenerateResponse}
 								{mergeResponses}
 								{chatActionHandler}
-								{showMessage}
+								bottomPadding={files.length > 0}
 							/>
 						</div>
 					</div>
 
 					<div class="">
 						<MessageInput
+							{history}
 							bind:files
 							bind:prompt
 							bind:autoScroll
 							bind:selectedToolIds
 							bind:webSearchEnabled
 							bind:atSelectedModel
+							{selectedModels}
 							availableToolIds={selectedModelIds.reduce((a, e, i, arr) => {
 								const model = $models.find((m) => m.id === e);
 								if (model?.info?.meta?.toolIds ?? false) {
@@ -1867,10 +1932,9 @@
 								return a;
 							}, [])}
 							transparentBackground={$settings?.backgroundImageUrl ?? false}
-							{selectedModels}
-							{messages}
 							{submitPrompt}
 							{stopResponse}
+							{createMessagePair}
 							on:call={async () => {
 								await showControls.set(true);
 							}}
@@ -1880,6 +1944,13 @@
 			</Pane>
 
 			<ChatControls
+				bind:history
+				bind:chatFiles
+				bind:params
+				bind:files
+				bind:pane={controlPane}
+				chatId={$chatId}
+				modelId={selectedModelIds?.at(0) ?? null}
 				models={selectedModelIds.reduce((a, e, i, arr) => {
 					const model = $models.find((m) => m.id === e);
 					if (model) {
@@ -1887,16 +1958,9 @@
 					}
 					return a;
 				}, [])}
-				bind:history
-				bind:chatFiles
-				bind:params
-				bind:files
-				bind:pane={controlPane}
 				{submitPrompt}
 				{stopResponse}
 				{showMessage}
-				modelId={selectedModelIds?.at(0) ?? null}
-				chatId={$chatId}
 				{eventTarget}
 			/>
 		</PaneGroup>

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

@@ -115,9 +115,9 @@
 		{/if}
 	{:else}
 		<!-- if $showControls -->
-		<PaneResizer class="relative flex w-2 items-center justify-center bg-background">
+		<PaneResizer class="relative flex w-2 items-center justify-center bg-background group">
 			<div class="z-10 flex h-7 w-5 items-center justify-center rounded-sm">
-				<EllipsisVertical />
+				<EllipsisVertical className="size-4 invisible group-hover:visible" />
 			</div>
 		</PaneResizer>
 		<Pane
@@ -128,7 +128,6 @@
 					: 30
 				: 0}
 			onResize={(size) => {
-				console.log(size);
 				if (size === 0) {
 					showControls.set(false);
 				} else {
@@ -164,6 +163,12 @@
 							<Overview
 								{history}
 								on:nodeclick={(e) => {
+									if (e.detail.node.data.message.favorite) {
+										history.messages[e.detail.node.data.message.id].favorite = true;
+									} else {
+										history.messages[e.detail.node.data.message.id].favorite = null;
+									}
+
 									showMessage(e.detail.node.data.message);
 								}}
 								on:close={() => {

+ 14 - 8
src/lib/components/chat/MessageInput.svelte

@@ -41,6 +41,7 @@
 	export let transparentBackground = false;
 
 	export let submitPrompt: Function;
+	export let createMessagePair: Function;
 	export let stopResponse: Function;
 
 	export let autoScroll = false;
@@ -61,15 +62,14 @@
 	let user = null;
 	let chatInputPlaceholder = '';
 
-	export let files = [];
+	export let history;
 
+	export let prompt = '';
+	export let files = [];
 	export let availableToolIds = [];
 	export let selectedToolIds = [];
 	export let webSearchEnabled = false;
 
-	export let prompt = '';
-	export let messages = [];
-
 	let visionCapableModels = [];
 	$: visionCapableModels = [...(atSelectedModel ? [atSelectedModel] : selectedModels)].filter(
 		(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.vision ?? true
@@ -107,7 +107,7 @@
 		files = [...files, fileItem];
 
 		// Check if the file is an audio file and transcribe/convert it to text file
-		if (['audio/mpeg', 'audio/wav', 'audio/ogg'].includes(file['type'])) {
+		if (['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/x-m4a'].includes(file['type'])) {
 			const res = await transcribeAudio(localStorage.token, file).catch((error) => {
 				toast.error(error);
 				return null;
@@ -272,7 +272,7 @@
 	<div class=" -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
 		<div class="flex flex-col max-w-6xl px-2.5 md:px-6 w-full">
 			<div class="relative">
-				{#if autoScroll === false && messages.length > 0}
+				{#if autoScroll === false && history?.currentId}
 					<div
 						class=" absolute -top-12 left-0 right-0 flex justify-center z-30 pointer-events-none"
 					>
@@ -555,6 +555,12 @@
 										const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
 										const commandsContainerElement = document.getElementById('commands-container');
 
+										// Command/Ctrl + Shift + Enter to submit a message pair
+										if (isCtrlPressed && e.key === 'Enter' && e.shiftKey) {
+											e.preventDefault();
+											createMessagePair(prompt);
+										}
+
 										// Check if Ctrl + R is pressed
 										if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
 											e.preventDefault();
@@ -692,7 +698,7 @@
 								/>
 
 								<div class="self-end mb-2 flex space-x-1 mr-1">
-									{#if messages.length == 0 || messages.at(-1).done == true}
+									{#if !history?.currentId || history.messages[history.currentId]?.done == true}
 										<Tooltip content={$i18n.t('Record voice')}>
 											<button
 												id="voice-input-button"
@@ -744,7 +750,7 @@
 							</div>
 						</div>
 						<div class="flex items-end w-10">
-							{#if messages.length == 0 || messages.at(-1).done == true}
+							{#if !history.currentId || history.messages[history.currentId]?.done == true}
 								{#if prompt === ''}
 									<div class=" flex items-center mb-1">
 										<Tooltip content={$i18n.t('Call')}>

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

@@ -1,6 +1,14 @@
 <script lang="ts">
 	import { prompts } from '$lib/stores';
-	import { findWordIndices } from '$lib/utils';
+	import {
+		findWordIndices,
+		getUserPosition,
+		getFormattedDate,
+		getFormattedTime,
+		getCurrentDateTime,
+		getUserTimezone,
+		getWeekday
+	} from '$lib/utils';
 	import { tick, getContext } from 'svelte';
 	import { toast } from 'svelte-sonner';
 
@@ -39,8 +47,6 @@
 				return '{{CLIPBOARD}}';
 			});
 
-			console.log(clipboardText);
-
 			const clipboardItems = await navigator.clipboard.read();
 
 			let imageUrl = null;
@@ -50,7 +56,6 @@
 					if (type.startsWith('image/')) {
 						const blob = await item.getType(type);
 						imageUrl = URL.createObjectURL(blob);
-						console.log(`Image URL (${type}): ${imageUrl}`);
 					}
 				}
 			}
@@ -65,7 +70,42 @@
 				];
 			}
 
-			text = command.content.replaceAll('{{CLIPBOARD}}', clipboardText);
+			text = text.replaceAll('{{CLIPBOARD}}', clipboardText);
+		}
+
+		if (command.content.includes('{{USER_LOCATION}}')) {
+			const location = await getUserPosition();
+			text = text.replaceAll('{{USER_LOCATION}}', String(location));
+		}
+
+		if (command.content.includes('{{USER_LANGUAGE}}')) {
+			const language = localStorage.getItem('locale') || 'en-US';
+			text = text.replaceAll('{{USER_LANGUAGE}}', language);
+		}
+
+		if (command.content.includes('{{CURRENT_DATE}}')) {
+			const date = getFormattedDate();
+			text = text.replaceAll('{{CURRENT_DATE}}', date);
+		}
+
+		if (command.content.includes('{{CURRENT_TIME}}')) {
+			const time = getFormattedTime();
+			text = text.replaceAll('{{CURRENT_TIME}}', time);
+		}
+
+		if (command.content.includes('{{CURRENT_DATETIME}}')) {
+			const dateTime = getCurrentDateTime();
+			text = text.replaceAll('{{CURRENT_DATETIME}}', dateTime);
+		}
+
+		if (command.content.includes('{{CURRENT_TIMEZONE}}')) {
+			const timezone = getUserTimezone();
+			text = text.replaceAll('{{CURRENT_TIMEZONE}}', timezone);
+		}
+
+		if (command.content.includes('{{CURRENT_WEEKDAY}}')) {
+			const weekday = getWeekday();
+			text = text.replaceAll('{{CURRENT_WEEKDAY}}', weekday);
 		}
 
 		prompt = text;

+ 177 - 216
src/lib/components/chat/Messages.svelte

@@ -7,31 +7,63 @@
 	import { getChatList, updateChatById } from '$lib/apis/chats';
 	import { copyToClipboard, findWordIndices } from '$lib/utils';
 
-	import UserMessage from './Messages/UserMessage.svelte';
-	import ResponseMessage from './Messages/ResponseMessage.svelte';
 	import Placeholder from './Messages/Placeholder.svelte';
-	import MultiResponseMessages from './Messages/MultiResponseMessages.svelte';
+	import Message from './Messages/Message.svelte';
+	import Loader from '../common/Loader.svelte';
+	import Spinner from '../common/Spinner.svelte';
 
 	const i18n = getContext('i18n');
 
 	export let chatId = '';
-	export let readOnly = false;
+	export let user = $_user;
+
+	export let prompt;
+	export let history = {};
+	export let selectedModels;
+
+	let messages = [];
+
 	export let sendPrompt: Function;
-	export let continueGeneration: Function;
+	export let continueResponse: Function;
 	export let regenerateResponse: Function;
 	export let mergeResponses: Function;
 	export let chatActionHandler: Function;
 	export let showMessage: Function = () => {};
 
-	export let user = $_user;
-	export let prompt;
-	export let processing = '';
+	export let readOnly = false;
+
 	export let bottomPadding = false;
 	export let autoScroll;
-	export let history = {};
-	export let messages = [];
 
-	export let selectedModels;
+	let messagesCount = 20;
+	let messagesLoading = false;
+
+	const loadMoreMessages = async () => {
+		// scroll slightly down to disable continuous loading
+		const element = document.getElementById('messages-container');
+		element.scrollTop = element.scrollTop + 100;
+
+		messagesLoading = true;
+		messagesCount += 20;
+
+		await tick();
+
+		messagesLoading = false;
+	};
+
+	$: if (history.currentId) {
+		let _messages = [];
+
+		let message = history.messages[history.currentId];
+		while (message && _messages.length <= messagesCount) {
+			_messages.unshift({ ...message });
+			message = message.parentId !== null ? history.messages[message.parentId] : null;
+		}
+
+		messages = _messages;
+	} else {
+		messages = [];
+	}
 
 	$: if (autoScroll && bottomPadding) {
 		(async () => {
@@ -45,56 +77,9 @@
 		element.scrollTop = element.scrollHeight;
 	};
 
-	const copyToClipboardWithToast = async (text) => {
-		const res = await copyToClipboard(text);
-		if (res) {
-			toast.success($i18n.t('Copying to clipboard was successful!'));
-		}
-	};
-
-	const confirmEditMessage = async (messageId, content, submit = true) => {
-		if (submit) {
-			let userPrompt = content;
-			let userMessageId = uuidv4();
-
-			let userMessage = {
-				id: userMessageId,
-				parentId: history.messages[messageId].parentId,
-				childrenIds: [],
-				role: 'user',
-				content: userPrompt,
-				...(history.messages[messageId].files && { files: history.messages[messageId].files }),
-				models: selectedModels
-			};
-
-			let messageParentId = history.messages[messageId].parentId;
-
-			if (messageParentId !== null) {
-				history.messages[messageParentId].childrenIds = [
-					...history.messages[messageParentId].childrenIds,
-					userMessageId
-				];
-			}
-
-			history.messages[userMessageId] = userMessage;
-			history.currentId = userMessageId;
-
-			await tick();
-			await sendPrompt(userPrompt, userMessageId);
-		} else {
-			history.messages[messageId].content = content;
-			await tick();
-			await updateChatById(localStorage.token, chatId, {
-				messages: messages,
-				history: history
-			});
-		}
-	};
-
-	const updateChatMessages = async () => {
+	const updateChatHistory = async () => {
 		await tick();
 		await updateChatById(localStorage.token, chatId, {
-			messages: messages,
 			history: history
 		});
 
@@ -102,49 +87,6 @@
 		await chats.set(await getChatList(localStorage.token, $currentChatPage));
 	};
 
-	const confirmEditResponseMessage = async (messageId, content) => {
-		history.messages[messageId].originalContent = history.messages[messageId].content;
-		history.messages[messageId].content = content;
-
-		await updateChatMessages();
-	};
-
-	const saveNewResponseMessage = async (message, content) => {
-		const responseMessageId = uuidv4();
-		const parentId = message.parentId;
-
-		const responseMessage = {
-			...message,
-			id: responseMessageId,
-			parentId: parentId,
-			childrenIds: [],
-			content: content,
-			timestamp: Math.floor(Date.now() / 1000) // Unix epoch
-		};
-
-		history.messages[responseMessageId] = responseMessage;
-		history.currentId = responseMessageId;
-
-		// Append messageId to childrenIds of parent message
-		if (parentId !== null) {
-			history.messages[parentId].childrenIds = [
-				...history.messages[parentId].childrenIds,
-				responseMessageId
-			];
-		}
-
-		await updateChatMessages();
-	};
-
-	const rateMessage = async (messageId, rating) => {
-		history.messages[messageId].annotation = {
-			...history.messages[messageId].annotation,
-			rating: rating
-		};
-
-		await updateChatMessages();
-	};
-
 	const showPreviousMessage = async (message) => {
 		if (message.parentId !== null) {
 			let messageId =
@@ -243,7 +185,89 @@
 		}
 	};
 
-	const deleteMessageHandler = async (messageId) => {
+	const rateMessage = async (messageId, rating) => {
+		history.messages[messageId].annotation = {
+			...history.messages[messageId].annotation,
+			rating: rating
+		};
+
+		await updateChatHistory();
+	};
+
+	const editMessage = async (messageId, content, submit = true) => {
+		if (history.messages[messageId].role === 'user') {
+			if (submit) {
+				// New user message
+				let userPrompt = content;
+				let userMessageId = uuidv4();
+
+				let userMessage = {
+					id: userMessageId,
+					parentId: history.messages[messageId].parentId,
+					childrenIds: [],
+					role: 'user',
+					content: userPrompt,
+					...(history.messages[messageId].files && { files: history.messages[messageId].files }),
+					models: selectedModels
+				};
+
+				let messageParentId = history.messages[messageId].parentId;
+
+				if (messageParentId !== null) {
+					history.messages[messageParentId].childrenIds = [
+						...history.messages[messageParentId].childrenIds,
+						userMessageId
+					];
+				}
+
+				history.messages[userMessageId] = userMessage;
+				history.currentId = userMessageId;
+
+				await tick();
+				await sendPrompt(userPrompt, userMessageId);
+			} else {
+				// Edit user message
+				history.messages[messageId].content = content;
+				await updateChatHistory();
+			}
+		} else {
+			if (submit) {
+				// New response message
+				const responseMessageId = uuidv4();
+				const message = history.messages[messageId];
+				const parentId = message.parentId;
+
+				const responseMessage = {
+					...message,
+					id: responseMessageId,
+					parentId: parentId,
+					childrenIds: [],
+					content: content,
+					timestamp: Math.floor(Date.now() / 1000) // Unix epoch
+				};
+
+				history.messages[responseMessageId] = responseMessage;
+				history.currentId = responseMessageId;
+
+				// Append messageId to childrenIds of parent message
+				if (parentId !== null) {
+					history.messages[parentId].childrenIds = [
+						...history.messages[parentId].childrenIds,
+						responseMessageId
+					];
+				}
+
+				await updateChatHistory();
+			} else {
+				// Edit response message
+				history.messages[messageId].originalContent = history.messages[messageId].content;
+				history.messages[messageId].content = content;
+				await updateChatHistory();
+			}
+		}
+	};
+
+	const deleteMessage = async (messageId) => {
 		const messageToDelete = history.messages[messageId];
 		const parentMessageId = messageToDelete.parentId;
 		const childMessageIds = messageToDelete.childrenIds ?? [];
@@ -278,15 +302,12 @@
 		showMessage({ id: parentMessageId });
 
 		// Update the chat
-		await updateChatById(localStorage.token, chatId, {
-			messages: messages,
-			history: history
-		});
+		await updateChatHistory();
 	};
 </script>
 
 <div class="h-full flex">
-	{#if messages.length == 0}
+	{#if Object.keys(history?.messages ?? {}).length == 0}
 		<Placeholder
 			modelIds={selectedModels}
 			submitPrompt={async (p) => {
@@ -327,115 +348,55 @@
 	{:else}
 		<div class="w-full pt-2">
 			{#key chatId}
-				{#each messages as message, messageIdx (message.id)}
-					<div class=" w-full {messageIdx === messages.length - 1 ? ' pb-12' : ''}">
-						<div
-							class="flex flex-col justify-between px-5 mb-3 {($settings?.widescreenMode ?? null)
-								? 'max-w-full'
-								: 'max-w-5xl'} mx-auto rounded-lg group"
+				<div class="w-full">
+					{#if messages.at(0)?.parentId !== null}
+						<Loader
+							on:visible={(e) => {
+								console.log('visible');
+								if (!messagesLoading) {
+									loadMoreMessages();
+								}
+							}}
 						>
-							{#if message.role === 'user'}
-								<UserMessage
-									on:delete={() => deleteMessageHandler(message.id)}
-									{user}
-									{readOnly}
-									{message}
-									isFirstMessage={messageIdx === 0}
-									siblings={message.parentId !== null
-										? (history.messages[message.parentId]?.childrenIds ?? [])
-										: (Object.values(history.messages)
-												.filter((message) => message.parentId === null)
-												.map((message) => message.id) ?? [])}
-									{confirmEditMessage}
-									{showPreviousMessage}
-									{showNextMessage}
-									copyToClipboard={copyToClipboardWithToast}
-								/>
-							{:else if (history.messages[message.parentId]?.models?.length ?? 1) === 1}
-								{#key message.id}
-									<ResponseMessage
-										{message}
-										siblings={history.messages[message.parentId]?.childrenIds ?? []}
-										isLastMessage={messageIdx + 1 === messages.length}
-										{readOnly}
-										{updateChatMessages}
-										{confirmEditResponseMessage}
-										{saveNewResponseMessage}
-										{showPreviousMessage}
-										{showNextMessage}
-										{rateMessage}
-										copyToClipboard={copyToClipboardWithToast}
-										{continueGeneration}
-										{regenerateResponse}
-										on:action={async (e) => {
-											console.log('action', e);
-											if (typeof e.detail === 'string') {
-												await chatActionHandler(chatId, e.detail, message.model, message.id);
-											} else {
-												const { id, event } = e.detail;
-												await chatActionHandler(chatId, id, message.model, message.id, event);
-											}
-										}}
-										on:save={async (e) => {
-											console.log('save', e);
-
-											const message = e.detail;
-											history.messages[message.id] = message;
-											await updateChatById(localStorage.token, chatId, {
-												messages: messages,
-												history: history
-											});
-										}}
-									/>
-								{/key}
-							{:else}
-								{#key message.parentId}
-									<MultiResponseMessages
-										bind:history
-										isLastMessage={messageIdx + 1 === messages.length}
-										{messages}
-										{readOnly}
-										{chatId}
-										parentMessage={history.messages[message.parentId]}
-										{messageIdx}
-										{updateChatMessages}
-										{confirmEditResponseMessage}
-										{rateMessage}
-										copyToClipboard={copyToClipboardWithToast}
-										{continueGeneration}
-										{mergeResponses}
-										{regenerateResponse}
-										on:action={async (e) => {
-											console.log('action', e);
-											if (typeof e.detail === 'string') {
-												await chatActionHandler(chatId, e.detail, message.model, message.id);
-											} else {
-												const { id, event } = e.detail;
-												await chatActionHandler(chatId, id, message.model, message.id, event);
-											}
-										}}
-										on:change={async () => {
-											await updateChatById(localStorage.token, chatId, {
-												messages: messages,
-												history: history
-											});
-
-											if (autoScroll) {
-												const element = document.getElementById('messages-container');
-												autoScroll =
-													element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
-												setTimeout(() => {
-													scrollToBottom();
-												}, 100);
-											}
-										}}
-									/>
-								{/key}
-							{/if}
-						</div>
-					</div>
-				{/each}
-
+							<div class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2">
+								<Spinner className=" size-4" />
+								<div class=" ">Loading...</div>
+							</div>
+						</Loader>
+					{/if}
+
+					{#each messages as message, messageIdx (message.id)}
+						<Message
+							{chatId}
+							bind:history
+							messageId={message.id}
+							idx={messageIdx}
+							{user}
+							{showPreviousMessage}
+							{showNextMessage}
+							{editMessage}
+							{deleteMessage}
+							{rateMessage}
+							{regenerateResponse}
+							{continueResponse}
+							{mergeResponses}
+							{updateChatHistory}
+							{chatActionHandler}
+							{readOnly}
+							on:scroll={() => {
+								if (autoScroll) {
+									const element = document.getElementById('messages-container');
+									autoScroll =
+										element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
+									setTimeout(() => {
+										scrollToBottom();
+									}, 100);
+								}
+							}}
+						/>
+					{/each}
+				</div>
+				<div class="pb-12" />
 				{#if bottomPadding}
 					<div class="  pb-6" />
 				{/if}

+ 23 - 13
src/lib/components/chat/Messages/CitationsModal.svelte

@@ -1,6 +1,7 @@
 <script lang="ts">
 	import { getContext, onMount, tick } from 'svelte';
 	import Modal from '$lib/components/common/Modal.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	const i18n = getContext('i18n');
 
 	export let show = false;
@@ -55,19 +56,28 @@
 						</div>
 
 						{#if document.source?.name}
-							<div class="text-sm dark:text-gray-400">
-								<a
-									href={document?.metadata?.file_id
-										? `/api/v1/files/${document?.metadata?.file_id}/content`
-										: document.source.name.includes('http')
-											? document.source.name
-											: `#`}
-									target="_blank"
-								>
-									{document?.metadata?.name ?? document.source.name}
-								</a>
-								{document?.metadata?.page ? `(page ${document.metadata.page + 1})` : ''}
-							</div>
+							<Tooltip
+								content={$i18n.t('Open file')}
+								placement="left"
+								tippyOptions={{ duration: [500, 0], animation: 'perspective' }}
+							>
+								<div class="text-sm dark:text-gray-400">
+									<a
+										class="hover:text-gray-500 hover:dark:text-gray-100 underline"
+										href={document?.metadata?.file_id
+											? `/api/v1/files/${document?.metadata?.file_id}/content${document?.metadata?.page !== undefined ? `#page=${document.metadata.page + 1}` : ''}`
+											: document.source.name.includes('http')
+												? document.source.name
+												: `#`}
+										target="_blank"
+									>
+										{document?.metadata?.name ?? document.source.name}
+									</a>
+									{document?.metadata?.page
+										? `(${$i18n.t('page')} ${document.metadata.page + 1})`
+										: ''}
+								</div>
+							</Tooltip>
 						{:else}
 							<div class="text-sm dark:text-gray-400">
 								{$i18n.t('No source available')}

+ 15 - 1
src/lib/components/chat/Messages/CodeBlock.svelte

@@ -20,6 +20,8 @@
 	export let lang = '';
 	export let code = '';
 
+	let _token = null;
+
 	let mermaidHtml = null;
 
 	let highlightedCode = null;
@@ -226,7 +228,7 @@ __builtins__.input = input`);
 		}
 	};
 
-	$: if (token.raw) {
+	const render = async () => {
 		if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) {
 			(async () => {
 				await drawMermaidDiagram();
@@ -242,9 +244,21 @@ __builtins__.input = input`);
 			// Set a new timeout to debounce the code highlighting
 			debounceTimeout = setTimeout(highlightCode, 10);
 		}
+	};
+
+	$: if (token) {
+		if (JSON.stringify(token) !== JSON.stringify(_token)) {
+			console.log('hi');
+			_token = token;
+		}
+	}
+
+	$: if (_token) {
+		render();
 	}
 
 	onMount(async () => {
+		console.log('codeblock', lang, code);
 		if (document.documentElement.classList.contains('dark')) {
 			mermaid.initialize({
 				startOnLoad: true,

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

@@ -19,7 +19,7 @@
 </script>
 
 <!-- {JSON.stringify(tokens)} -->
-{#each tokens as token, tokenIdx}
+{#each tokens as token, tokenIdx (tokenIdx)}
 	{#if token.type === 'hr'}
 		<hr />
 	{:else if token.type === 'heading'}

+ 166 - 0
src/lib/components/chat/Messages/Message.svelte

@@ -0,0 +1,166 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+
+	import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import { settings } from '$lib/stores';
+	import { copyToClipboard } from '$lib/utils';
+
+	import MultiResponseMessages from './MultiResponseMessages.svelte';
+	import ResponseMessage from './ResponseMessage.svelte';
+	import UserMessage from './UserMessage.svelte';
+	import { updateChatById } from '$lib/apis/chats';
+
+	export let chatId;
+	export let idx = 0;
+
+	export let history;
+	export let messageId;
+
+	export let user;
+
+	export let updateChatHistory;
+	export let chatActionHandler;
+
+	export let showPreviousMessage;
+	export let showNextMessage;
+
+	export let editMessage;
+	export let deleteMessage;
+	export let rateMessage;
+
+	export let regenerateResponse;
+	export let continueResponse;
+
+	// MultiResponseMessages
+	export let mergeResponses;
+
+	export let autoScroll = false;
+	export let readOnly = false;
+
+	onMount(() => {
+		// console.log('message', idx);
+	});
+</script>
+
+<div
+	class="flex flex-col justify-between px-5 mb-3 w-full {($settings?.widescreenMode ?? null)
+		? 'max-w-full'
+		: 'max-w-5xl'} mx-auto rounded-lg group"
+>
+	{#if history.messages[messageId]}
+		{#if history.messages[messageId].role === 'user'}
+			<UserMessage
+				{user}
+				{history}
+				{messageId}
+				isFirstMessage={idx === 0}
+				siblings={history.messages[messageId].parentId !== null
+					? (history.messages[history.messages[messageId].parentId]?.childrenIds ?? [])
+					: (Object.values(history.messages)
+							.filter((message) => message.parentId === null)
+							.map((message) => message.id) ?? [])}
+				{showPreviousMessage}
+				{showNextMessage}
+				{editMessage}
+				on:delete={() => deleteMessage(messageId)}
+				{readOnly}
+			/>
+		{:else if (history.messages[history.messages[messageId].parentId]?.models?.length ?? 1) === 1}
+			<ResponseMessage
+				{history}
+				{messageId}
+				isLastMessage={messageId === history.currentId}
+				siblings={history.messages[history.messages[messageId].parentId]?.childrenIds ?? []}
+				{showPreviousMessage}
+				{showNextMessage}
+				{editMessage}
+				{rateMessage}
+				{continueResponse}
+				{regenerateResponse}
+				on:action={async (e) => {
+					console.log('action', e);
+					const message = history.messages[messageId];
+					if (typeof e.detail === 'string') {
+						await chatActionHandler(chatId, e.detail, message.model, message.id);
+					} else {
+						const { id, event } = e.detail;
+						await chatActionHandler(chatId, id, message.model, message.id, event);
+					}
+				}}
+				on:update={async (e) => {
+					console.log('update', e);
+					updateChatHistory();
+				}}
+				on:save={async (e) => {
+					console.log('save', e);
+
+					const message = e.detail;
+					if (message) {
+						history.messages[message.id] = message;
+						await updateChatById(localStorage.token, chatId, {
+							history: history
+						});
+					} else {
+						await updateChatById(localStorage.token, chatId, {
+							history: history
+						});
+					}
+				}}
+				{readOnly}
+			/>
+		{:else}
+			<MultiResponseMessages
+				bind:history
+				{chatId}
+				{messageId}
+				isLastMessage={messageId === history?.currentId}
+				{rateMessage}
+				{editMessage}
+				{continueResponse}
+				{regenerateResponse}
+				{mergeResponses}
+				on:action={async (e) => {
+					console.log('action', e);
+					const message = history.messages[messageId];
+					if (typeof e.detail === 'string') {
+						await chatActionHandler(chatId, e.detail, message.model, message.id);
+					} else {
+						const { id, event } = e.detail;
+						await chatActionHandler(chatId, id, message.model, message.id, event);
+					}
+				}}
+				on:update={async (e) => {
+					console.log('update', e);
+					updateChatHistory();
+				}}
+				on:save={async (e) => {
+					console.log('save', e);
+
+					const message = e.detail;
+					if (message) {
+						history.messages[message.id] = message;
+						await updateChatById(localStorage.token, chatId, {
+							history: history
+						});
+					} else {
+						await updateChatById(localStorage.token, chatId, {
+							history: history
+						});
+					}
+				}}
+				on:change={async () => {
+					await tick();
+					await updateChatById(localStorage.token, chatId, {
+						history: history
+					});
+
+					dispatch('scroll');
+				}}
+				{readOnly}
+			/>
+		{/if}
+	{/if}
+</div>

+ 139 - 123
src/lib/components/chat/Messages/MultiResponseMessages.svelte

@@ -20,40 +20,39 @@
 	const i18n = getContext('i18n');
 
 	export let chatId;
-
 	export let history;
-	export let messages = [];
-	export let messageIdx;
+	export let messageId;
 
-	export let parentMessage;
 	export let isLastMessage;
-
 	export let readOnly = false;
 
-	export let updateChatMessages: Function;
-	export let confirmEditResponseMessage: Function;
+	export let editMessage: Function;
 	export let rateMessage: Function;
 
-	export let copyToClipboard: Function;
-	export let continueGeneration: Function;
-	export let mergeResponses: Function;
+	export let continueResponse: Function;
 	export let regenerateResponse: Function;
+	export let mergeResponses: Function;
 
 	const dispatch = createEventDispatcher();
 
 	let currentMessageId;
-	let groupedMessages = {};
-	let groupedMessagesIdx = {};
-
-	$: if (parentMessage) {
-		initHandler();
+	let parentMessage;
+	let groupedMessageIds = {};
+	let groupedMessageIdsIdx = {};
+
+	let message = JSON.parse(JSON.stringify(history.messages[messageId]));
+	$: if (history.messages) {
+		if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
+			message = JSON.parse(JSON.stringify(history.messages[messageId]));
+		}
 	}
 
 	const showPreviousMessage = (modelIdx) => {
-		groupedMessagesIdx[modelIdx] = Math.max(0, groupedMessagesIdx[modelIdx] - 1);
-		let messageId = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]].id;
+		groupedMessageIdsIdx[modelIdx] = Math.max(0, groupedMessageIdsIdx[modelIdx] - 1);
 
+		let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]];
 		console.log(messageId);
+
 		let messageChildrenIds = history.messages[messageId].childrenIds;
 
 		while (messageChildrenIds.length !== 0) {
@@ -66,12 +65,12 @@
 	};
 
 	const showNextMessage = (modelIdx) => {
-		groupedMessagesIdx[modelIdx] = Math.min(
-			groupedMessages[modelIdx].messages.length - 1,
-			groupedMessagesIdx[modelIdx] + 1
+		groupedMessageIdsIdx[modelIdx] = Math.min(
+			groupedMessageIds[modelIdx].messageIds.length - 1,
+			groupedMessageIdsIdx[modelIdx] + 1
 		);
 
-		let messageId = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]].id;
+		let messageId = groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]];
 		console.log(messageId);
 
 		let messageChildrenIds = history.messages[messageId].childrenIds;
@@ -86,33 +85,43 @@
 	};
 
 	const initHandler = async () => {
+		console.log('multiresponse:initHandler');
 		await tick();
-		currentMessageId = messages[messageIdx].id;
 
-		groupedMessages = parentMessage?.models.reduce((a, model, modelIdx) => {
+		currentMessageId = messageId;
+		parentMessage = history.messages[messageId].parentId
+			? history.messages[history.messages[messageId].parentId]
+			: null;
+
+		groupedMessageIds = parentMessage?.models.reduce((a, model, modelIdx) => {
 			// Find all messages that are children of the parent message and have the same model
-			let modelMessages = parentMessage?.childrenIds
+			let modelMessageIds = parentMessage?.childrenIds
 				.map((id) => history.messages[id])
-				.filter((m) => m?.modelIdx === modelIdx);
+				.filter((m) => m?.modelIdx === modelIdx)
+				.map((m) => m.id);
 
-			if (modelMessages.length === 0) {
-				modelMessages = parentMessage?.childrenIds
+			// Legacy support for messages that don't have a modelIdx
+			// Find all messages that are children of the parent message and have the same model
+			if (modelMessageIds.length === 0) {
+				let modelMessages = parentMessage?.childrenIds
 					.map((id) => history.messages[id])
 					.filter((m) => m?.model === model);
 
 				modelMessages.forEach((m) => {
 					m.modelIdx = modelIdx;
 				});
+
+				modelMessageIds = modelMessages.map((m) => m.id);
 			}
 
 			return {
 				...a,
-				[modelIdx]: { messages: modelMessages }
+				[modelIdx]: { messageIds: modelMessageIds }
 			};
 		}, {});
 
-		groupedMessagesIdx = parentMessage?.models.reduce((a, model, modelIdx) => {
-			const idx = groupedMessages[modelIdx].messages.findIndex((m) => m.id === currentMessageId);
+		groupedMessageIdsIdx = parentMessage?.models.reduce((a, model, modelIdx) => {
+			const idx = groupedMessageIds[modelIdx].messageIds.findIndex((id) => id === messageId);
 			if (idx !== -1) {
 				return {
 					...a,
@@ -121,39 +130,53 @@
 			} else {
 				return {
 					...a,
-					[modelIdx]: 0
+					[modelIdx]: groupedMessageIds[modelIdx].messageIds.length - 1
 				};
 			}
 		}, {});
+
+		console.log(groupedMessageIds, groupedMessageIdsIdx);
+
+		await tick();
 	};
 
 	const mergeResponsesHandler = async () => {
-		const responses = Object.keys(groupedMessages).map((modelIdx) => {
-			const { messages } = groupedMessages[modelIdx];
-			return messages[groupedMessagesIdx[modelIdx]].content;
+		const responses = Object.keys(groupedMessageIds).map((modelIdx) => {
+			const { messageIds } = groupedMessageIds[modelIdx];
+			const messageId = messageIds[groupedMessageIdsIdx[modelIdx]];
+
+			return history.messages[messageId].content;
 		});
-		mergeResponses(currentMessageId, responses, chatId);
+		mergeResponses(messageId, responses, chatId);
 	};
 
 	onMount(async () => {
-		initHandler();
+		await initHandler();
+		await tick();
+
+		const messageElement = document.getElementById(`message-${messageId}`);
+		console.log(messageElement);
+		if (messageElement) {
+			messageElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
+		}
 	});
 </script>
 
-<div>
-	<div
-		class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
-		id="responses-container-{chatId}-{parentMessage.id}"
-	>
-		{#key currentMessageId}
-			{#each Object.keys(groupedMessages) as modelIdx}
-				{#if groupedMessagesIdx[modelIdx] !== undefined && groupedMessages[modelIdx].messages.length > 0}
+{#if parentMessage}
+	<div>
+		<div
+			class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
+			id="responses-container-{chatId}-{parentMessage.id}"
+		>
+			{#each Object.keys(groupedMessageIds) as modelIdx}
+				{#if groupedMessageIdsIdx[modelIdx] !== undefined && groupedMessageIds[modelIdx].messageIds.length > 0}
 					<!-- svelte-ignore a11y-no-static-element-interactions -->
 					<!-- svelte-ignore a11y-click-events-have-key-events -->
-					{@const message = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]]}
+					{@const _messageId =
+						groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]}
 
 					<div
-						class=" snap-center w-full max-w-full m-1 border {history.messages[currentMessageId]
+						class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
 							?.modelIdx == modelIdx
 							? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
 									$mobile ? 'min-w-full' : 'min-w-[32rem]'
@@ -162,17 +185,13 @@
 									$mobile ? 'min-w-full' : 'min-w-80'
 								}`} transition-all p-5 rounded-2xl"
 						on:click={() => {
-							if (currentMessageId != message.id) {
-								currentMessageId = message.id;
-								let messageId = message.id;
-								console.log(messageId);
-								//
-								let messageChildrenIds = history.messages[messageId].childrenIds;
+							if (messageId != _messageId) {
+								let messageChildrenIds = history.messages[_messageId].childrenIds;
 								while (messageChildrenIds.length !== 0) {
 									messageId = messageChildrenIds.at(-1);
-									messageChildrenIds = history.messages[messageId].childrenIds;
+									messageChildrenIds = history.messages[_messageId].childrenIds;
 								}
-								history.currentId = messageId;
+								history.currentId = _messageId;
 								dispatch('change');
 							}
 						}}
@@ -180,95 +199,92 @@
 						{#key history.currentId}
 							{#if message}
 								<ResponseMessage
-									{message}
-									siblings={groupedMessages[modelIdx].messages.map((m) => m.id)}
+									{history}
+									messageId={_messageId}
 									isLastMessage={true}
-									{updateChatMessages}
-									{confirmEditResponseMessage}
+									siblings={groupedMessageIds[modelIdx].messageIds}
 									showPreviousMessage={() => showPreviousMessage(modelIdx)}
 									showNextMessage={() => showNextMessage(modelIdx)}
-									{readOnly}
 									{rateMessage}
-									{copyToClipboard}
-									{continueGeneration}
+									{editMessage}
+									{continueResponse}
 									regenerateResponse={async (message) => {
 										regenerateResponse(message);
 										await tick();
-										groupedMessagesIdx[modelIdx] = groupedMessages[modelIdx].messages.length - 1;
+										groupedMessageIdsIdx[modelIdx] =
+											groupedMessageIds[modelIdx].messageIds.length - 1;
 									}}
 									on:action={async (e) => {
 										dispatch('action', e.detail);
 									}}
+									on:update={async (e) => {
+										dispatch('update', e.detail);
+									}}
 									on:save={async (e) => {
-										console.log('save', e);
-
-										const message = e.detail;
-										history.messages[message.id] = message;
-										await updateChatById(localStorage.token, chatId, {
-											messages: messages,
-											history: history
-										});
+										dispatch('save', e.detail);
 									}}
+									{readOnly}
 								/>
 							{/if}
 						{/key}
 					</div>
 				{/if}
 			{/each}
-		{/key}
-	</div>
-
-	{#if !readOnly && isLastMessage}
-		{#if !Object.keys(groupedMessages).find((modelIdx) => {
-			const { messages } = groupedMessages[modelIdx];
-			return !messages[groupedMessagesIdx[modelIdx]]?.done ?? false;
-		})}
-			<div class="flex justify-end">
-				<div class="w-full">
-					{#if history.messages[currentMessageId]?.merged?.status}
-						{@const message = history.messages[currentMessageId]?.merged}
-
-						<div class="w-full rounded-xl pl-5 pr-2 py-2">
-							<Name>
-								Merged Response
-
-								{#if message.timestamp}
-									<span
-										class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
-									>
-										{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
-									</span>
-								{/if}
-							</Name>
-
-							<div class="mt-1 markdown-prose w-full min-w-full">
-								{#if (message?.content ?? '') === ''}
-									<Skeleton />
-								{:else}
-									<Markdown id={`merged`} content={message.content ?? ''} />
-								{/if}
+		</div>
+
+		{#if !readOnly && isLastMessage}
+			{#if !Object.keys(groupedMessageIds).find((modelIdx) => {
+				const { messageIds } = groupedMessageIds[modelIdx];
+				const _messageId = messageIds[groupedMessageIdsIdx[modelIdx]];
+				return !history.messages[_messageId]?.done ?? false;
+			})}
+				<div class="flex justify-end">
+					<div class="w-full">
+						{#if history.messages[messageId]?.merged?.status}
+							{@const message = history.messages[messageId]?.merged}
+
+							<div class="w-full rounded-xl pl-5 pr-2 py-2">
+								<Name>
+									Merged Response
+
+									{#if message.timestamp}
+										<span
+											class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
+										>
+											{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
+										</span>
+									{/if}
+								</Name>
+
+								<div class="mt-1 markdown-prose w-full min-w-full">
+									{#if (message?.content ?? '') === ''}
+										<Skeleton />
+									{:else}
+										<Markdown id={`merged`} content={message.content ?? ''} />
+									{/if}
+								</div>
 							</div>
-						</div>
-					{/if}
-				</div>
+						{/if}
+					</div>
 
-				<div class=" flex-shrink-0 text-gray-600 dark:text-gray-500 mt-1">
-					<Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
-						<button
-							type="button"
-							id="merge-response-button"
-							class="{true
-								? 'visible'
-								: 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
-							on:click={() => {
-								mergeResponsesHandler();
-							}}
-						>
-							<Merge className=" size-5 " />
-						</button>
-					</Tooltip>
+					<div class=" flex-shrink-0 text-gray-600 dark:text-gray-500 mt-1">
+						<Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
+							<button
+								type="button"
+								id="merge-response-button"
+								class="{true
+									? 'visible'
+									: 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
+								on:click={() => {
+									mergeResponsesHandler();
+								}}
+							>
+								<Merge className=" size-5 " />
+							</button>
+						</Tooltip>
+					</div>
 				</div>
-			</div>
+			{/if}
 		{/if}
-	{/if}
-</div>
+	</div>
+{/if}

+ 31 - 21
src/lib/components/chat/Messages/ResponseMessage.svelte

@@ -13,6 +13,7 @@
 	import { synthesizeOpenAISpeech } from '$lib/apis/audio';
 	import { imageGenerations } from '$lib/apis/images';
 	import {
+		copyToClipboard as _copyToClipboard,
 		approximateToHumanReadable,
 		extractParagraphsForAudio,
 		extractSentencesForAudio,
@@ -76,25 +77,30 @@
 		annotation?: { type: string; rating: number };
 	}
 
-	export let message: MessageType;
-	export let siblings;
-
-	export let isLastMessage = true;
+	export let history;
+	export let messageId;
 
-	export let readOnly = false;
+	let message: MessageType = JSON.parse(JSON.stringify(history.messages[messageId]));
+	$: if (history.messages) {
+		if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
+			message = JSON.parse(JSON.stringify(history.messages[messageId]));
+		}
+	}
 
-	export let updateChatMessages: Function;
-	export let confirmEditResponseMessage: Function;
-	export let saveNewResponseMessage: Function = () => {};
+	export let siblings;
 
 	export let showPreviousMessage: Function;
 	export let showNextMessage: Function;
+
+	export let editMessage: Function;
 	export let rateMessage: Function;
 
-	export let copyToClipboard: Function;
-	export let continueGeneration: Function;
+	export let continueResponse: Function;
 	export let regenerateResponse: Function;
 
+	export let isLastMessage = true;
+	export let readOnly = false;
+
 	let model = null;
 	$: model = $models.find((m) => m.id === message.model);
 
@@ -111,6 +117,13 @@
 
 	let showRateComment = false;
 
+	const copyToClipboard = async (text) => {
+		const res = await _copyToClipboard(text);
+		if (res) {
+			toast.success($i18n.t('Copying to clipboard was successful!'));
+		}
+	};
+
 	const playAudio = (idx: number) => {
 		return new Promise<void>((res) => {
 			speakingIdx = idx;
@@ -260,11 +273,7 @@
 	};
 
 	const editMessageConfirmHandler = async () => {
-		if (editedContent === '') {
-			editedContent = ' ';
-		}
-
-		confirmEditResponseMessage(message.id, editedContent);
+		editMessage(message.id, editedContent ? editedContent : '', false);
 
 		edit = false;
 		editedContent = '';
@@ -272,8 +281,8 @@
 		await tick();
 	};
 
-	const saveNewMessageHandler = async () => {
-		saveNewResponseMessage(message, editedContent);
+	const saveAsCopyHandler = async () => {
+		editMessage(message.id, editedContent ? editedContent : '');
 
 		edit = false;
 		editedContent = '';
@@ -313,6 +322,8 @@
 	}
 
 	onMount(async () => {
+		console.log('ResponseMessage mounted');
+
 		await tick();
 	});
 </script>
@@ -424,7 +435,7 @@
 											id="save-new-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"
 											on:click={() => {
-												saveNewMessageHandler();
+												saveAsCopyHandler();
 											}}
 										>
 											{$i18n.t('Save As Copy')}
@@ -909,7 +920,7 @@
 													? 'visible'
 													: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
 												on:click={() => {
-													continueGeneration();
+													continueResponse();
 
 													(model?.actions ?? [])
 														.filter((action) => action?.__webui__ ?? false)
@@ -1028,8 +1039,7 @@
 							bind:show={showRateComment}
 							bind:message
 							on:submit={(e) => {
-								updateChatMessages();
-
+								dispatch('update');
 								(model?.actions ?? [])
 									.filter((action) => action?.__webui__ ?? false)
 									.forEach((action) => {

+ 39 - 15
src/lib/components/chat/Messages/UserMessage.svelte

@@ -1,38 +1,58 @@
 <script lang="ts">
 	import dayjs from 'dayjs';
+	import { toast } from 'svelte-sonner';
+	import { tick, createEventDispatcher, getContext, onMount } from 'svelte';
+
+	import { models, settings } from '$lib/stores';
+	import { user as _user } from '$lib/stores';
+	import {
+		copyToClipboard as _copyToClipboard,
+		processResponseContent,
+		replaceTokens
+	} from '$lib/utils';
 
-	import { tick, createEventDispatcher, getContext } from 'svelte';
 	import Name from './Name.svelte';
 	import ProfileImage from './ProfileImage.svelte';
-	import { models, settings } from '$lib/stores';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
-
-	import { user as _user } from '$lib/stores';
-	import { getFileContentById } from '$lib/apis/files';
 	import FileItem from '$lib/components/common/FileItem.svelte';
-	import { marked } from 'marked';
-	import { processResponseContent, replaceTokens } from '$lib/utils';
-	import MarkdownTokens from './Markdown/MarkdownTokens.svelte';
 	import Markdown from './Markdown.svelte';
 
 	const i18n = getContext('i18n');
 
 	const dispatch = createEventDispatcher();
-
 	export let user;
-	export let message;
+
+	export let history;
+	export let messageId;
+
 	export let siblings;
-	export let isFirstMessage: boolean;
-	export let readOnly: boolean;
 
-	export let confirmEditMessage: Function;
 	export let showPreviousMessage: Function;
 	export let showNextMessage: Function;
-	export let copyToClipboard: Function;
+
+	export let editMessage: Function;
+
+	export let isFirstMessage: boolean;
+	export let readOnly: boolean;
 
 	let edit = false;
 	let editedContent = '';
 	let messageEditTextAreaElement: HTMLTextAreaElement;
+
+	let message = JSON.parse(JSON.stringify(history.messages[messageId]));
+	$: if (history.messages) {
+		if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
+			message = JSON.parse(JSON.stringify(history.messages[messageId]));
+		}
+	}
+
+	const copyToClipboard = async (text) => {
+		const res = await _copyToClipboard(text);
+		if (res) {
+			toast.success($i18n.t('Copying to clipboard was successful!'));
+		}
+	};
+
 	const editMessageHandler = async () => {
 		edit = true;
 		editedContent = message.content;
@@ -46,7 +66,7 @@
 	};
 
 	const editMessageConfirmHandler = async (submit = true) => {
-		confirmEditMessage(message.id, editedContent, submit);
+		editMessage(message.id, editedContent, submit);
 
 		edit = false;
 		editedContent = '';
@@ -60,6 +80,10 @@
 	const deleteMessageHandler = async () => {
 		dispatch('delete', message.id);
 	};
+
+	onMount(() => {
+		console.log('UserMessage mounted');
+	});
 </script>
 
 <div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">

+ 24 - 5
src/lib/components/chat/Overview/Node.svelte

@@ -4,13 +4,14 @@
 
 	import ProfileImageBase from '../Messages/ProfileImageBase.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import Heart from '$lib/components/icons/Heart.svelte';
 
 	type $$Props = NodeProps;
 	export let data: $$Props['data'];
 </script>
 
 <div
-	class="px-4 py-3 shadow-md rounded-xl dark:bg-black bg-white border dark:border-gray-900 w-60 h-20"
+	class="px-4 py-3 shadow-md rounded-xl dark:bg-black bg-white border dark:border-gray-900 w-60 h-20 group"
 >
 	<Tooltip
 		content={data?.message?.error ? data.message.error.content : data.message.content}
@@ -24,8 +25,10 @@
 					className={'size-5 -translate-y-[1px]'}
 				/>
 				<div class="ml-2">
-					<div class="text-xs text-black dark:text-white font-medium">
-						{data?.user?.name ?? 'User'}
+					<div class=" flex justify-between items-center">
+						<div class="text-xs text-black dark:text-white font-medium line-clamp-1">
+							{data?.user?.name ?? 'User'}
+						</div>
 					</div>
 
 					{#if data?.message?.error}
@@ -43,8 +46,24 @@
 				/>
 
 				<div class="ml-2">
-					<div class="text-xs text-black dark:text-white font-medium">
-						{data?.model?.name ?? data?.message?.model ?? 'Assistant'}
+					<div class=" flex justify-between items-center">
+						<div class="text-xs text-black dark:text-white font-medium line-clamp-1">
+							{data?.model?.name ?? data?.message?.model ?? 'Assistant'}
+						</div>
+
+						<button
+							class={data?.message?.favorite ? '' : 'invisible group-hover:visible'}
+							on:click={() => {
+								data.message.favorite = !(data?.message?.favorite ?? false);
+							}}
+						>
+							<Heart
+								className="size-3 {data?.message?.favorite
+									? 'fill-red-500 stroke-red-500'
+									: 'hover:fill-red-500 hover:stroke-red-500'} "
+								strokeWidth="2.5"
+							/>
+						</button>
 					</div>
 
 					{#if data?.message?.error}

+ 31 - 2
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte

@@ -10,6 +10,7 @@
 
 	export let params = {
 		// Advanced
+		stream_response: null, // Set stream responses for this model individually
 		seed: null,
 		stop: null,
 		temperature: null,
@@ -42,6 +43,35 @@
 </script>
 
 <div class=" space-y-1 text-xs pb-safe-bottom">
+	<div>
+		<div class=" py-0.5 flex w-full justify-between">
+			<div class=" self-center text-xs font-medium">
+				{$i18n.t('Stream Chat Response')}
+			</div>
+
+			<button
+				class="p-1 px-3 text-xs flex rounded transition"
+				on:click={() => {
+					params.stream_response =
+						(params?.stream_response ?? null) === null
+							? true
+							: params.stream_response
+								? false
+								: null;
+				}}
+				type="button"
+			>
+				{#if params.stream_response === true}
+					<span class="ml-2 self-center">{$i18n.t('On')}</span>
+				{:else if params.stream_response === false}
+					<span class="ml-2 self-center">{$i18n.t('Off')}</span>
+				{:else}
+					<span class="ml-2 self-center">{$i18n.t('Default')}</span>
+				{/if}
+			</button>
+		</div>
+	</div>
+
 	<div class=" py-0.5 w-full justify-between">
 		<div class="flex w-full justify-between">
 			<div class=" self-center text-xs font-medium">{$i18n.t('Seed')}</div>
@@ -734,7 +764,7 @@
 						id="steps-range"
 						type="range"
 						min="-2"
-						max="16000"
+						max="131072"
 						step="1"
 						bind:value={params.max_tokens}
 						class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
@@ -746,7 +776,6 @@
 						type="number"
 						class=" bg-transparent text-center w-14"
 						min="-2"
-						max="16000"
 						step="1"
 					/>
 				</div>

+ 2 - 0
src/lib/components/chat/Settings/General.svelte

@@ -45,6 +45,7 @@
 
 	let params = {
 		// Advanced
+		stream_response: null,
 		seed: null,
 		temperature: null,
 		frequency_penalty: null,
@@ -327,6 +328,7 @@
 				saveSettings({
 					system: system !== '' ? system : undefined,
 					params: {
+						stream_response: params.stream_response !== null ? params.stream_response : undefined,
 						seed: (params.seed !== null ? params.seed : undefined) ?? undefined,
 						stop: params.stop ? params.stop.split(',').filter((e) => e) : undefined,
 						temperature: params.temperature !== null ? params.temperature : undefined,

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

@@ -36,18 +36,11 @@
 	let voiceInterruption = false;
 	let hapticFeedback = false;
 
-	let streamResponse = true;
-
 	const toggleSplitLargeChunks = async () => {
 		splitLargeChunks = !splitLargeChunks;
 		saveSettings({ splitLargeChunks: splitLargeChunks });
 	};
 
-	const toggleStreamResponse = async () => {
-		streamResponse = !streamResponse;
-		saveSettings({ streamResponse: streamResponse });
-	};
-
 	const togglesScrollOnBranchChange = async () => {
 		scrollOnBranchChange = !scrollOnBranchChange;
 		saveSettings({ scrollOnBranchChange: scrollOnBranchChange });
@@ -165,7 +158,6 @@
 		userLocation = $settings.userLocation ?? false;
 
 		hapticFeedback = $settings.hapticFeedback ?? false;
-		streamResponse = $settings?.streamResponse ?? true;
 
 		defaultModelId = $settings?.models?.at(0) ?? '';
 		if ($config?.default_models) {
@@ -319,28 +311,6 @@
 				</div>
 			</div>
 
-			<div>
-				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
-						{$i18n.t('Stream Chat Response')}
-					</div>
-
-					<button
-						class="p-1 px-3 text-xs flex rounded transition"
-						on:click={() => {
-							toggleStreamResponse();
-						}}
-						type="button"
-					>
-						{#if streamResponse === 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">

+ 19 - 3
src/lib/components/common/Loader.svelte

@@ -1,16 +1,24 @@
 <script lang="ts">
-	import { createEventDispatcher, onMount } from 'svelte';
+	import { createEventDispatcher, onDestroy, onMount } from 'svelte';
 	const dispatch = createEventDispatcher();
 
 	let loaderElement: HTMLElement;
 
+	let observer;
+	let intervalId;
+
 	onMount(() => {
-		const observer = new IntersectionObserver(
+		observer = new IntersectionObserver(
 			(entries, observer) => {
 				entries.forEach((entry) => {
 					if (entry.isIntersecting) {
-						dispatch('visible');
+						intervalId = setInterval(() => {
+							dispatch('visible');
+						}, 100);
+						// dispatch('visible');
 						// observer.unobserve(loaderElement); // Stop observing until content is loaded
+					} else {
+						clearInterval(intervalId);
 					}
 				});
 			},
@@ -23,6 +31,14 @@
 
 		observer.observe(loaderElement);
 	});
+
+	onDestroy(() => {
+		observer.disconnect();
+
+		if (intervalId) {
+			clearInterval(intervalId);
+		}
+	});
 </script>
 
 <div bind:this={loaderElement}>

+ 3 - 1
src/lib/components/common/Tooltip.svelte

@@ -13,6 +13,7 @@
 	export let className = 'flex';
 	export let theme = '';
 	export let allowHTML = true;
+	export let tippyOptions = {};
 
 	let tooltipElement;
 	let tooltipInstance;
@@ -28,7 +29,8 @@
 				touch: touch,
 				...(theme !== '' ? { theme } : { theme: 'dark' }),
 				arrow: false,
-				offset: [0, 4]
+				offset: [0, 4],
+				...tippyOptions
 			});
 		}
 	} else if (tooltipInstance && content === '') {

+ 1 - 1
src/lib/components/layout/Sidebar.svelte

@@ -461,7 +461,7 @@
 				</div>
 			{/if}
 
-			{#if $pinnedChats.length > 0}
+			{#if !search && $pinnedChats.length > 0}
 				<div class="pl-2 py-2 flex flex-col space-y-1">
 					<div class="">
 						<div class="w-full pl-2.5 text-xs text-gray-500 dark:text-gray-500 font-medium pb-1.5">

+ 17 - 1
src/lib/components/layout/Sidebar/UserMenu.svelte

@@ -5,7 +5,7 @@
 	import { flyAndScale } from '$lib/utils/transitions';
 	import { goto } from '$app/navigation';
 	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
-	import { showSettings, activeUserCount, USAGE_POOL } from '$lib/stores';
+	import { showSettings, activeUserCount, USAGE_POOL, mobile, showSidebar } from '$lib/stores';
 	import { fade, slide } from 'svelte/transition';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 
@@ -41,6 +41,10 @@
 				on:click={async () => {
 					await showSettings.set(true);
 					show = false;
+
+					if ($mobile) {
+						showSidebar.set(false);
+					}
 				}}
 			>
 				<div class=" self-center mr-3">
@@ -72,6 +76,10 @@
 				on:click={() => {
 					dispatch('show', 'archived-chat');
 					show = false;
+
+					if ($mobile) {
+						showSidebar.set(false);
+					}
 				}}
 			>
 				<div class=" self-center mr-3">
@@ -86,6 +94,10 @@
 					on:click={() => {
 						goto('/playground');
 						show = false;
+
+						if ($mobile) {
+							showSidebar.set(false);
+						}
 					}}
 				>
 					<div class=" self-center mr-3">
@@ -112,6 +124,10 @@
 					on:click={() => {
 						goto('/admin');
 						show = false;
+
+						if ($mobile) {
+							showSidebar.set(false);
+						}
 					}}
 				>
 					<div class=" self-center mr-3">

+ 44 - 0
src/lib/components/layout/UpdateInfoToast.svelte

@@ -0,0 +1,44 @@
+<script lang="ts">
+	import { getContext, createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+	const i18n = getContext('i18n');
+
+	import { WEBUI_VERSION } from '$lib/constants';
+	import XMark from '../icons/XMark.svelte';
+
+	export let version = {
+		current: WEBUI_VERSION,
+		latest: WEBUI_VERSION
+	};
+</script>
+
+<div
+	class="flex items-start bg-[--info-bg] border border-[--info-border] text-[--info-text] rounded-lg px-3.5 py-3 text-xs"
+>
+	<div class="flex-1 font-medium">
+		{$i18n.t(`A new version (v{{LATEST_VERSION}}) is now available.`, {
+			LATEST_VERSION: version.latest
+		})}
+
+		<a
+			href="https://github.com/open-webui/open-webui/releases/tag/v{version.latest}"
+			target="_blank"
+			class="underline"
+		>
+			{$i18n.t('Update for the latest features and improvements.')}</a
+		>
+	</div>
+
+	<div class=" flex-shrink-0 pl-2">
+		<button
+			class=" hover:text-blue-900 dark:hover:text-blue-300 transition"
+			on:click={() => {
+				console.log('closeToast');
+				dispatch('closeToast');
+			}}
+		>
+			<XMark />
+		</button>
+	</div>
+</div>

+ 1 - 1
src/lib/components/workspace/Documents.svelte

@@ -55,7 +55,7 @@
 	const uploadDoc = async (file, tags?: object) => {
 		console.log(file);
 		// Check if the file is an audio file and transcribe/convert it to text file
-		if (['audio/mpeg', 'audio/wav', 'audio/ogg'].includes(file['type'])) {
+		if (['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/x-m4a'].includes(file['type'])) {
 			const transcribeRes = await transcribeAudio(localStorage.token, file).catch((error) => {
 				toast.error(error);
 				return null;

+ 17 - 3
src/lib/components/workspace/Models.svelte

@@ -1,4 +1,6 @@
 <script lang="ts">
+	import { marked } from 'marked';
+
 	import { toast } from 'svelte-sonner';
 	import Sortable from 'sortablejs';
 
@@ -389,9 +391,21 @@
 				<div
 					class=" flex-1 self-center {(model?.info?.meta?.hidden ?? false) ? 'text-gray-500' : ''}"
 				>
-					<div class="  font-semibold line-clamp-1">{model.name}</div>
-					<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
-						{!!model?.info?.meta?.description ? model?.info?.meta?.description : model.id}
+					<Tooltip
+						content={marked.parse(
+							model?.ollama?.digest
+								? `${model?.ollama?.digest} *(${model?.ollama?.modified_at})*`
+								: ''
+						)}
+						className=" w-fit"
+						placement="top-start"
+					>
+						<div class="  font-semibold line-clamp-1">{model.name}</div>
+					</Tooltip>
+					<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1 text-gray-500">
+						{!!model?.info?.meta?.description
+							? model?.info?.meta?.description
+							: (model?.ollama?.digest ?? model.id)}
 					</div>
 				</div>
 			</a>

+ 3 - 1
src/lib/constants.ts

@@ -31,7 +31,9 @@ export const SUPPORTED_FILE_TYPE = [
 	'application/x-javascript',
 	'text/markdown',
 	'audio/mpeg',
-	'audio/wav'
+	'audio/wav',
+	'audio/ogg',
+	'audio/x-m4a'
 ];
 
 export const SUPPORTED_FILE_EXTENSIONS = [

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "دردشات {{user}}",
 	"{{webUIName}} Backend Required": "{{webUIName}} مطلوب",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "يتم استخدام نموذج المهمة عند تنفيذ مهام مثل إنشاء عناوين للدردشات واستعلامات بحث الويب",
 	"a user": "مستخدم",
 	"About": "عن",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "خطاء! يبدو أن عنوان URL غير صالح. يرجى التحقق مرة أخرى والمحاولة مرة أخرى.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "خطاء! أنت تستخدم طريقة غير مدعومة (الواجهة الأمامية فقط). يرجى تقديم واجهة WebUI من الواجهة الخلفية.",
+	"Open file": "",
 	"Open new chat": "فتح محادثة جديده",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "آخر",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "الباسورد",
 	"PDF document (.pdf)": "PDF ملف (.pdf)",
 	"PDF Extract Images (OCR)": "PDF أستخرج الصور (OCR)",
@@ -703,6 +706,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "تحديث ونسخ الرابط",
+	"Update for the latest features and improvements.": "",
 	"Update password": "تحديث كلمة المرور",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}'s чатове",
 	"{{webUIName}} Backend Required": "{{webUIName}} Изисква се Бекенд",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Моделът на задачите се използва при изпълнение на задачи като генериране на заглавия за чатове и заявки за търсене в мрежата",
 	"a user": "потребител",
 	"About": "Относно",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Изглежда URL адресът е невалиден. Моля, проверете отново и опитайте пак.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Използвате неподдържан метод (само фронтенд). Моля, сервирайте WebUI от бекенда.",
+	"Open file": "",
 	"Open new chat": "Отвори нов чат",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Other",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Парола",
 	"PDF document (.pdf)": "PDF документ (.pdf)",
 	"PDF Extract Images (OCR)": "PDF Extract Images (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Обнови и копирай връзка",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Обновяване на парола",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}র চ্যাটস",
 	"{{webUIName}} Backend Required": "{{webUIName}} ব্যাকএন্ড আবশ্যক",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "চ্যাট এবং ওয়েব অনুসন্ধান প্রশ্নের জন্য শিরোনাম তৈরি করার মতো কাজগুলি সম্পাদন করার সময় একটি টাস্ক মডেল ব্যবহার করা হয়",
 	"a user": "একজন ব্যাবহারকারী",
 	"About": "সম্পর্কে",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "ওহ, মনে হচ্ছে ইউআরএলটা ইনভ্যালিড। দয়া করে আর চেক করে চেষ্টা করুন।",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "আপনি একটা আনসাপোর্টেড পদ্ধতি (শুধু ফ্রন্টএন্ড) ব্যবহার করছেন। দয়া করে WebUI ব্যাকএন্ড থেকে চালনা করুন।",
+	"Open file": "",
 	"Open new chat": "নতুন চ্যাট খুলুন",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "অন্যান্য",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "পাসওয়ার্ড",
 	"PDF document (.pdf)": "PDF ডকুমেন্ট (.pdf)",
 	"PDF Extract Images (OCR)": "পিডিএফ এর ছবি থেকে লেখা বের করুন (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "আপডেট এবং লিংক কপি করুন",
+	"Update for the latest features and improvements.": "",
 	"Update password": "পাসওয়ার্ড আপডেট করুন",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Els xats de {{user}}",
 	"{{webUIName}} Backend Required": "El Backend de {{webUIName}} és necessari",
 	"*Prompt node ID(s) are required for image generation": "*Els identificadors de nodes d'indicacions són necessaris per a la generació d'imatges",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un model de tasca s'utilitza quan es realitzen tasques com ara generar títols per a xats i consultes de cerca per a la web",
 	"a user": "un usuari",
 	"About": "Sobre",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ui! Sembla que l'URL no és vàlida. Si us plau, revisa-la i torna-ho a provar.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Ui! Hi ha hagut un error en la resposta anterior. Torna a provar-ho o contacta amb un administrador",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ui! Estàs utilitzant un mètode no suportat (només frontend). Si us plau, serveix la WebUI des del backend.",
+	"Open file": "",
 	"Open new chat": "Obre un xat nou",
 	"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}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Altres",
 	"Output format": "Format de sortida",
 	"Overview": "Vista general",
+	"page": "",
 	"Password": "Contrasenya",
 	"PDF document (.pdf)": "Document PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Extreu imatges del PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "Alliberar",
 	"Update": "Actualitzar",
 	"Update and Copy Link": "Actualitzar i copiar l'enllaç",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Actualitzar la contrasenya",
 	"Updated at": "Actualitzat",
 	"Upload": "Pujar",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} gikinahanglan",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"a user": "usa ka user",
 	"About": "Mahitungod sa",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! ",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! ",
+	"Open file": "",
 	"Open new chat": "Ablihi ang bag-ong diskusyon",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "",
@@ -476,6 +478,7 @@
 	"Other": "",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Password",
 	"PDF document (.pdf)": "",
 	"PDF Extract Images (OCR)": "PDF Image Extraction (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
 	"Update password": "I-update ang password",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}s Unterhaltungen",
 	"{{webUIName}} Backend Required": "{{webUIName}}-Backend erforderlich",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Aufgabenmodelle können Unterhaltungstitel oder Websuchanfragen generieren.",
 	"a user": "ein Benutzer",
 	"About": "Über",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppla! Es scheint, dass die URL ungültig ist. Bitte überprüfen Sie diese und versuchen Sie es erneut.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Hoppla! Es gab einen Fehler in der vorherigen Antwort. Bitte versuchen Sie es erneut oder kontaktieren Sie den Administrator.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppla! Sie verwenden eine nicht unterstützte Methode (nur Frontend). Bitte stellen Sie die WebUI vom Backend bereit.",
+	"Open file": "Datei öffnen",
 	"Open new chat": "Neuen Chat öffnen",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Die installierte Open-WebUI-Version (v{{OPEN_WEBUI_VERSION}}) ist niedriger als die erforderliche Version (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Andere",
 	"Output format": "",
 	"Overview": "",
+	"page": "Seite",
 	"Password": "Passwort",
 	"PDF document (.pdf)": "PDF-Dokument (.pdf)",
 	"PDF Extract Images (OCR)": "Text von Bildern aus PDFs extrahieren (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "Lösen",
 	"Update": "Aktualisieren",
 	"Update and Copy Link": "Aktualisieren und Link kopieren",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Passwort aktualisieren",
 	"Updated at": "Aktualisiert am",
 	"Upload": "Hochladen",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Much Required",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"a user": "such user",
 	"About": "Much About",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Looks like the URL is invalid. Please double-check and try again.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.",
+	"Open file": "",
 	"Open new chat": "Open new bark",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "",
@@ -476,6 +478,7 @@
 	"Other": "",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Barkword",
 	"PDF document (.pdf)": "",
 	"PDF Extract Images (OCR)": "PDF Extract Wowmages (OCR)",
@@ -701,6 +704,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Update password much change",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "",
 	"{{webUIName}} Backend Required": "",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"a user": "",
 	"About": "",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "",
+	"Open file": "",
 	"Open new chat": "",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "",
@@ -476,6 +478,7 @@
 	"Other": "",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "",
 	"PDF document (.pdf)": "",
 	"PDF Extract Images (OCR)": "",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
 	"Update password": "",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "",
 	"{{webUIName}} Backend Required": "",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"a user": "",
 	"About": "",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "",
+	"Open file": "",
 	"Open new chat": "",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "",
@@ -476,6 +478,7 @@
 	"Other": "",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "",
 	"PDF document (.pdf)": "",
 	"PDF Extract Images (OCR)": "",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
 	"Update password": "",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Servidor Requerido",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un modelo de tareas se utiliza cuando se realizan tareas como la generación de títulos para chats y consultas de búsqueda web",
 	"a user": "un usuario",
 	"About": "Sobre nosotros",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "¡Ups! Parece que la URL no es válida. Vuelva a verificar e inténtelo nuevamente.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "¡Oops! Hubo un error en la respuesta anterior. Intente de nuevo o póngase en contacto con el administrador.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "¡Ups! Estás utilizando un método no compatible (solo frontend). Por favor ejecute la WebUI desde el backend.",
+	"Open file": "",
 	"Open new chat": "Abrir nuevo chat",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Otro",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Contraseña",
 	"PDF document (.pdf)": "PDF document (.pdf)",
 	"PDF Extract Images (OCR)": "Extraer imágenes de PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "",
 	"Update": "Actualizar",
 	"Update and Copy Link": "Actualizar y copiar enlace",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Actualizar contraseña",
 	"Updated at": "Actualizado en",
 	"Upload": "Subir",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} چت ها",
 	"{{webUIName}} Backend Required": "بکند {{webUIName}} نیاز است.",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "یک مدل وظیفه هنگام انجام وظایف مانند تولید عناوین برای چت ها و نمایش های جستجوی وب استفاده می شود.",
 	"a user": "یک کاربر",
 	"About": "درباره",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "اوه! به نظر می رسد URL نامعتبر است. لطفاً دوباره بررسی کنید و دوباره امتحان کنید.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "اوه! شما از یک روش پشتیبانی نشده (فقط frontend) استفاده می کنید. لطفاً WebUI را از بکند اجرا کنید.",
+	"Open file": "",
 	"Open new chat": "باز کردن گپ جدید",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "دیگر",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "رمز عبور",
 	"PDF document (.pdf)": "PDF سند (.pdf)",
 	"PDF Extract Images (OCR)": "استخراج تصاویر از PDF (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "به روزرسانی و کپی لینک",
+	"Update for the latest features and improvements.": "",
 	"Update password": "به روزرسانی رمزعبور",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}:n keskustelut",
 	"{{webUIName}} Backend Required": "{{webUIName}} backend vaaditaan",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Tehtävämallia käytetään tehtävien suorittamiseen, kuten otsikoiden luomiseen keskusteluille ja verkkohakukyselyille",
 	"a user": "käyttäjä",
 	"About": "Tietoja",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hups! Näyttää siltä, että URL on virheellinen. Tarkista se ja yritä uudelleen.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hupsista! Käytät ei-tuettua menetelmää. WebUI pitää palvella backendista.",
+	"Open file": "",
 	"Open new chat": "Avaa uusi keskustelu",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Muu",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Salasana",
 	"PDF document (.pdf)": "PDF-tiedosto (.pdf)",
 	"PDF Extract Images (OCR)": "PDF-tiedoston kuvien erottelu (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Päivitä ja kopioi linkki",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Päivitä salasana",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Discussions de {{user}}",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un modèle de tâche est utilisé lors de l’exécution de tâches telles que la génération de titres pour les conversations et les requêtes de recherche sur le web.",
 	"a user": "un utilisateur",
 	"About": "À propos",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oups ! Il semble que l'URL soit invalide. Veuillez vérifier à nouveau et réessayer.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Oops ! Il y a eu une erreur dans la réponse précédente. Veuillez réessayer ou contacter l'administrateur.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oups\u00a0! Vous utilisez une méthode non prise en charge (frontend uniquement). Veuillez servir l'interface Web à partir du backend.",
+	"Open file": "",
 	"Open new chat": "Ouvrir une nouvelle discussion",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Autre",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Mot de passe",
 	"PDF document (.pdf)": "Document au format PDF  (.pdf)",
 	"PDF Extract Images (OCR)": "Extraction d'images PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "",
 	"Update": "Mise à jour",
 	"Update and Copy Link": "Mettre à jour et copier le lien",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Mettre à jour le mot de passe",
 	"Updated at": "Mise à jour le",
 	"Upload": "Télécharger",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Conversations de {{user}}",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} requis",
 	"*Prompt node ID(s) are required for image generation": "*Les ID de noeud du prompt sont nécessaires pour la génération d’images",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un modèle de tâche est utilisé lors de l’exécution de tâches telles que la génération de titres pour les conversations et les requêtes de recherche sur le web.",
 	"a user": "un utilisateur",
 	"About": "À propos",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oups ! Il semble que l'URL soit invalide. Veuillez vérifier à nouveau et réessayer.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Oops ! Il y a eu une erreur dans la réponse précédente. Veuillez réessayer ou contacter l'administrateur.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oups\u00a0! Vous utilisez une méthode non prise en charge (frontend uniquement). Veuillez servir l'interface Web à partir du backend.",
+	"Open file": "",
 	"Open new chat": "Ouvrir une nouvelle conversation",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "La version Open WebUI (v{{OPEN_WEBUI_VERSION}}) est inférieure à la version requise (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Autre",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Mot de passe",
 	"PDF document (.pdf)": "Document au format PDF  (.pdf)",
 	"PDF Extract Images (OCR)": "Extraction d'images PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "Désépingler",
 	"Update": "Mise à jour",
 	"Update and Copy Link": "Mettre à jour et copier le lien",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Mettre à jour le mot de passe",
 	"Updated at": "Mise à jour le",
 	"Upload": "Télécharger",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "צ'אטים של {{user}}",
 	"{{webUIName}} Backend Required": "נדרש Backend של {{webUIName}}",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "מודל משימה משמש בעת ביצוע משימות כגון יצירת כותרות עבור צ'אטים ושאילתות חיפוש באינטרנט",
 	"a user": "משתמש",
 	"About": "אודות",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "אופס! נראה שהכתובת URL אינה תקינה. אנא בדוק שוב ונסה שנית.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "אופס! אתה משתמש בשיטה לא נתמכת (רק חזית). אנא שרת את ממשק המשתמש האינטרנטי מהשרת האחורי.",
+	"Open file": "",
 	"Open new chat": "פתח צ'אט חדש",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "אחר",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "סיסמה",
 	"PDF document (.pdf)": "מסמך PDF (.pdf)",
 	"PDF Extract Images (OCR)": "חילוץ תמונות מ-PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "עדכן ושכפל קישור",
+	"Update for the latest features and improvements.": "",
 	"Update password": "עדכן סיסמה",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} की चैट",
 	"{{webUIName}} Backend Required": "{{webUIName}} बैकएंड आवश्यक",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "चैट और वेब खोज क्वेरी के लिए शीर्षक उत्पन्न करने जैसे कार्य करते समय कार्य मॉडल का उपयोग किया जाता है",
 	"a user": "एक उपयोगकर्ता",
 	"About": "हमारे बारे में",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "उफ़! ऐसा लगता है कि यूआरएल अमान्य है. कृपया दोबारा जांचें और पुनः प्रयास करें।",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "उफ़! आप एक असमर्थित विधि (केवल फ्रंटएंड) का उपयोग कर रहे हैं। कृपया बैकएंड से WebUI सर्वे करें।",
+	"Open file": "",
 	"Open new chat": "नई चैट खोलें",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "अन्य",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "पासवर्ड",
 	"PDF document (.pdf)": "PDF दस्तावेज़ (.pdf)",
 	"PDF Extract Images (OCR)": "PDF छवियाँ निकालें (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "अपडेट करें और लिंक कॉपी करें",
+	"Update for the latest features and improvements.": "",
 	"Update password": "पासवर्ड अपडेट करें",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Razgovori korisnika {{user}}",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend je potreban",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model zadatka koristi se pri izvođenju zadataka kao što su generiranje naslova za razgovore i upite za pretraživanje weba",
 	"a user": "korisnik",
 	"About": "O aplikaciji",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Izgleda da je URL nevažeći. Molimo provjerite ponovno i pokušajte ponovo.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Koristite nepodržanu metodu (samo frontend). Molimo poslužite WebUI s backend-a.",
+	"Open file": "",
 	"Open new chat": "Otvorite novi razgovor",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Ostalo",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Lozinka",
 	"PDF document (.pdf)": "PDF dokument (.pdf)",
 	"PDF Extract Images (OCR)": "PDF izdvajanje slika (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Ažuriraj i kopiraj vezu",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Ažuriraj lozinku",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Obrolan {{user}}",
 	"{{webUIName}} Backend Required": "{{webUIName}} Diperlukan Backend",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model tugas digunakan saat melakukan tugas seperti membuat judul untuk obrolan dan kueri penelusuran web",
 	"a user": "seorang pengguna",
 	"About": "Tentang",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Sepertinya URL tidak valid. Mohon periksa ulang dan coba lagi.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Ups! Ada kesalahan pada respons sebelumnya. Silakan coba lagi atau hubungi admin.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Anda menggunakan metode yang tidak didukung (hanya untuk frontend). Silakan sajikan WebUI dari backend.",
+	"Open file": "",
 	"Open new chat": "Buka obrolan baru",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Lainnya",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Kata sandi",
 	"PDF document (.pdf)": "Dokumen PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Ekstrak Gambar PDF (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "Memperbarui",
 	"Update and Copy Link": "Perbarui dan Salin Tautan",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Perbarui kata sandi",
 	"Updated at": "Diperbarui di",
 	"Upload": "Unggah",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} Chat",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend richiesto",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un modello di attività viene utilizzato durante l'esecuzione di attività come la generazione di titoli per chat e query di ricerca Web",
 	"a user": "un utente",
 	"About": "Informazioni",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ops! Sembra che l'URL non sia valido. Si prega di ricontrollare e riprovare.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ops! Stai utilizzando un metodo non supportato (solo frontend). Si prega di servire la WebUI dal backend.",
+	"Open file": "",
 	"Open new chat": "Apri nuova chat",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Altro",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Password",
 	"PDF document (.pdf)": "Documento PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Estrazione immagini PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Aggiorna e copia link",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Aggiorna password",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} のチャット",
 	"{{webUIName}} Backend Required": "{{webUIName}} バックエンドが必要です",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "タスクモデルは、チャットやWeb検索クエリのタイトルの生成などのタスクを実行するときに使用されます",
 	"a user": "ユーザー",
 	"About": "概要",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "おっと! URL が無効なようです。もう一度確認してやり直してください。",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "おっと! サポートされていない方法 (フロントエンドのみ) を使用しています。バックエンドから WebUI を提供してください。",
+	"Open file": "",
 	"Open new chat": "新しいチャットを開く",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "その他",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "パスワード",
 	"PDF document (.pdf)": "PDF ドキュメント (.pdf)",
 	"PDF Extract Images (OCR)": "PDF 画像抽出 (OCR)",
@@ -698,6 +701,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "リンクの更新とコピー",
+	"Update for the latest features and improvements.": "",
 	"Update password": "パスワードを更新",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}-ის ჩათები",
 	"{{webUIName}} Backend Required": "{{webUIName}} საჭიროა ბექენდი",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "დავალების მოდელი გამოიყენება ისეთი ამოცანების შესრულებისას, როგორიცაა ჩეთების სათაურების გენერირება და ვებ – ძიების მოთხოვნები",
 	"a user": "მომხმარებელი",
 	"About": "შესახებ",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "უი! როგორც ჩანს, მისამართი არასწორია. გთხოვთ, გადაამოწმოთ და ისევ სცადოთ.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "უპს! თქვენ იყენებთ მხარდაუჭერელ მეთოდს (მხოლოდ frontend). გთხოვთ, მოემსახუროთ WebUI-ს ბექენდიდან",
+	"Open file": "",
 	"Open new chat": "ახალი მიმოწერის გახსნა",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "სხვა",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "პაროლი",
 	"PDF document (.pdf)": "PDF დოკუმენტი (.pdf)",
 	"PDF Extract Images (OCR)": "PDF იდან ამოღებული სურათები (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "განახლება და ბმულის კოპირება",
+	"Update for the latest features and improvements.": "",
 	"Update password": "პაროლის განახლება",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}의 채팅",
 	"{{webUIName}} Backend Required": "{{webUIName}} 백엔드가 필요합니다.",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "작업 모델은 채팅 및 웹 검색 쿼리에 대한 제목 생성 등의 작업 수행 시 사용됩니다.",
 	"a user": "사용자",
 	"About": "정보",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "이런! URL이 잘못된 것 같습니다. 다시 한번 확인하고 다시 시도해주세요.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "이런! 지원되지 않는 방식(프론트엔드만)을 사용하고 계십니다. 백엔드에서 WebUI를 제공해주세요.",
+	"Open file": "",
 	"Open new chat": "새 채팅 열기",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "기타",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "비밀번호",
 	"PDF document (.pdf)": "PDF 문서(.pdf)",
 	"PDF Extract Images (OCR)": "PDF 이미지 추출(OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "업데이트",
 	"Update and Copy Link": "링크 업데이트 및 복사",
+	"Update for the latest features and improvements.": "",
 	"Update password": "비밀번호 업데이트",
 	"Updated at": "다음에 업데이트됨",
 	"Upload": "업로드",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} susirašinėjimai",
 	"{{webUIName}} Backend Required": "{{webUIName}} būtinas serveris",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Užduočių modelis naudojamas pokalbių pavadinimų ir paieškos užklausų generavimui.",
 	"a user": "naudotojas",
 	"About": "Apie",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Regis nuoroda nevalidi. Prašau patikrtinkite ir pabandykite iš naujo.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Įvyko klaida. Pabandykite iš naujo arba susisiekite su administratoriumi.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Naudojate nepalaikomą (front-end) web ui rėžimą. Prašau serviruokite WebUI iš back-end",
+	"Open file": "",
 	"Open new chat": "Atverti naują pokalbį",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Tortue Chat versija per sena. Reikalinga (v{{REQUIRED_VERSION}}) versija.",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Kita",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Slaptažodis",
 	"PDF document (.pdf)": "PDF dokumentas (.pdf)",
 	"PDF Extract Images (OCR)": "PDF paveikslėlių skaitymas (OCR)",
@@ -701,6 +704,7 @@
 	"Unpin": "Atsemigti",
 	"Update": "Atnaujinti",
 	"Update and Copy Link": "Atnaujinti ir kopijuoti nuorodą",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Atnaujinti slaptažodį",
 	"Updated at": "Atnaujinta",
 	"Upload": "Atnaujinti",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Perbualan {{user}}",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend diperlukan",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model tugas digunakan semasa melaksanakan tugas seperti menjana tajuk untuk perbualan dan pertanyaan carian web.",
 	"a user": "seorang pengguna",
 	"About": "Mengenai",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Maaf, didapati URL tidak sah. Sila semak semula dan cuba lagi.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Maaf, terdapat ralat dalam respons sebelumnya. Sila cuba lagi atau hubungi pentadbir",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Maaf, Anda menggunakan kaedah yang tidak disokong (bahagian 'frontend' sahaja). Sila sediakan WebUI dari 'backend'.",
+	"Open file": "",
 	"Open new chat": "Buka perbualan baru",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI version (v{{OPEN_WEBUI_VERSION}}) adalah lebih rendah daripada versi yang diperlukan iaitu (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Lain-lain",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Kata Laluan",
 	"PDF document (.pdf)": "Dokumen PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Imej Ekstrak PDF (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "Nyahsematkan",
 	"Update": "Kemaskini",
 	"Update and Copy Link": "Kemaskini dan salin pautan",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Kemaskini Kata Laluan",
 	"Updated at": "Dikemaskini pada",
 	"Upload": "Muatnaik",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}s samtaler",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend kreves",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "En oppgavemodell brukes når du utfører oppgaver som å generere titler for samtaler og websøkeforespørsler",
 	"a user": "en bruker",
 	"About": "Om",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Ser ut som URL-en er ugyldig. Vennligst dobbeltsjekk og prøv igjen.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Oops! Det oppstod en feil i forrige svar. Prøv igjen eller kontakt administrator",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Du bruker en ikke-støttet metode (kun frontend). Vennligst server WebUI fra backend.",
+	"Open file": "",
 	"Open new chat": "Åpne ny chat",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI-versjon (v{{OPEN_WEBUI_VERSION}}) er lavere enn nødvendig versjon (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Annet",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Passord",
 	"PDF document (.pdf)": "PDF-dokument (.pdf)",
 	"PDF Extract Images (OCR)": "PDF-ekstraktbilder (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "Løsne",
 	"Update": "Oppdater",
 	"Update and Copy Link": "Oppdater og kopier lenke",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Oppdater passord",
 	"Updated at": "Oppdatert",
 	"Upload": "Last opp",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Verlpicht",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Een taakmodel wordt gebruikt bij het uitvoeren van taken zoals het genereren van titels voor chats en zoekopdrachten op internet",
 	"a user": "een gebruiker",
 	"About": "Over",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Het lijkt erop dat de URL ongeldig is. Controleer het nogmaals en probeer opnieuw.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Je gebruikt een niet-ondersteunde methode (alleen frontend). Serveer de WebUI vanuit de backend.",
+	"Open file": "",
 	"Open new chat": "Open nieuwe chat",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Andere",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Wachtwoord",
 	"PDF document (.pdf)": "PDF document (.pdf)",
 	"PDF Extract Images (OCR)": "PDF Extract Afbeeldingen (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Update en Kopieer Link",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Wijzig wachtwoord",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} ਦੀਆਂ ਗੱਲਾਂ",
 	"{{webUIName}} Backend Required": "{{webUIName}} ਬੈਕਐਂਡ ਲੋੜੀਂਦਾ ਹੈ",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "ਚੈਟਾਂ ਅਤੇ ਵੈੱਬ ਖੋਜ ਪੁੱਛਗਿੱਛਾਂ ਵਾਸਤੇ ਸਿਰਲੇਖ ਤਿਆਰ ਕਰਨ ਵਰਗੇ ਕਾਰਜ ਾਂ ਨੂੰ ਕਰਦੇ ਸਮੇਂ ਇੱਕ ਕਾਰਜ ਮਾਡਲ ਦੀ ਵਰਤੋਂ ਕੀਤੀ ਜਾਂਦੀ ਹੈ",
 	"a user": "ਇੱਕ ਉਪਭੋਗਤਾ",
 	"About": "ਬਾਰੇ",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "ਓਹੋ! ਲੱਗਦਾ ਹੈ ਕਿ URL ਗਲਤ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਜਾਂਚ ਕਰੋ ਅਤੇ ਮੁੜ ਕੋਸ਼ਿਸ਼ ਕਰੋ।",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "ਓਹੋ! ਤੁਸੀਂ ਇੱਕ ਅਣਸਮਰਥਿਤ ਢੰਗ ਵਰਤ ਰਹੇ ਹੋ (ਸਿਰਫ਼ ਫਰੰਟਐਂਡ)। ਕਿਰਪਾ ਕਰਕੇ ਵੈਬਯੂਆਈ ਨੂੰ ਬੈਕਐਂਡ ਤੋਂ ਸਰਵ ਕਰੋ।",
+	"Open file": "",
 	"Open new chat": "ਨਵੀਂ ਗੱਲਬਾਤ ਖੋਲ੍ਹੋ",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "ਓਪਨਏਆਈ",
@@ -476,6 +478,7 @@
 	"Other": "ਹੋਰ",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "ਪਾਸਵਰਡ",
 	"PDF document (.pdf)": "PDF ਡਾਕੂਮੈਂਟ (.pdf)",
 	"PDF Extract Images (OCR)": "PDF ਚਿੱਤਰ ਕੱਢੋ (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "ਅੱਪਡੇਟ ਕਰੋ ਅਤੇ ਲਿੰਕ ਕਾਪੀ ਕਰੋ",
+	"Update for the latest features and improvements.": "",
 	"Update password": "ਪਾਸਵਰਡ ਅੱਪਡੇਟ ਕਰੋ",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} - czaty",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} wymagane",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Model zadań jest używany podczas wykonywania zadań, takich jak generowanie tytułów czatów i zapytań wyszukiwania w Internecie",
 	"a user": "użytkownik",
 	"About": "O nas",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ups! Wygląda na to, że URL jest nieprawidłowy. Sprawdź jeszcze raz i spróbuj ponownie.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Używasz nieobsługiwanej metody (tylko interfejs front-end). Proszę obsłużyć interfejs WebUI z poziomu backendu.",
+	"Open file": "",
 	"Open new chat": "Otwórz nowy czat",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Inne",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Hasło",
 	"PDF document (.pdf)": "Dokument PDF (.pdf)",
 	"PDF Extract Images (OCR)": "PDF Wyodrębnij obrazy (OCR)",
@@ -701,6 +704,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Uaktualnij i skopiuj link",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Aktualizacja hasła",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Chats de {{user}}",
 	"{{webUIName}} Backend Required": "Backend {{webUIName}} necessário",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Um modelo de tarefa é usado ao realizar tarefas como gerar títulos para chats e consultas de pesquisa na web",
 	"a user": "um usuário",
 	"About": "Sobre",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Ops! Parece que a URL é inválida. Por favor, verifique novamente e tente de novo.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Ops! Houve um erro na resposta anterior. Por favor, tente novamente ou entre em contato com o administrador.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ops! Você está usando um método não suportado (somente frontend). Por favor, sirva a WebUI a partir do backend.",
+	"Open file": "",
 	"Open new chat": "Abrir novo chat",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "A versão do Open WebUI (v{{OPEN_WEBUI_VERSION}}) é inferior à versão necessária (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Outro",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Senha",
 	"PDF document (.pdf)": "Documento PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Extrair Imagens do PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "Desfixar",
 	"Update": "Atualizar",
 	"Update and Copy Link": "Atualizar e Copiar Link",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Atualizar senha",
 	"Updated at": "Atualizado em",
 	"Upload": "Fazer upload",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend Necessário",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Um modelo de tarefa é usado ao executar tarefas como gerar títulos para bate-papos e consultas de pesquisa na Web",
 	"a user": "um utilizador",
 	"About": "Acerca de",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Epá! Parece que o URL é inválido. Verifique novamente e tente outra vez.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Epá! Você está a usar um método não suportado (somente frontend). Por favor, sirva o WebUI a partir do backend.",
+	"Open file": "",
 	"Open new chat": "Abrir nova conversa",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Outro",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Senha",
 	"PDF document (.pdf)": "Documento PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Extrair Imagens de PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Atualizar e Copiar Link",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Atualizar senha",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Conversațiile lui {{user}}",
 	"{{webUIName}} Backend Required": "Este necesar backend-ul {{webUIName}}",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Un model de sarcină este utilizat pentru realizarea unor sarcini precum generarea de titluri pentru conversații și interogări de căutare pe web",
 	"a user": "un utilizator",
 	"About": "Despre",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Oops! Se pare că URL-ul este invalid. Vă rugăm să verificați din nou și să încercați din nou.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Oops! A apărut o eroare în răspunsul anterior. Vă rugăm să încercați din nou sau să contactați administratorul.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! Utilizați o metodă nesuportată (doar frontend). Vă rugăm să serviți WebUI din backend.",
+	"Open file": "",
 	"Open new chat": "Deschide conversație nouă",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Versiunea Open WebUI (v{{OPEN_WEBUI_VERSION}}) este mai mică decât versiunea necesară (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Altele",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Parolă",
 	"PDF document (.pdf)": "Document PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Extrage Imagini PDF (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "Anulează Fixarea",
 	"Update": "Actualizează",
 	"Update and Copy Link": "Actualizează și Copiază Link-ul",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Actualizează parola",
 	"Updated at": "Actualizat la",
 	"Upload": "Încărcare",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Чаты {{user}}'а",
 	"{{webUIName}} Backend Required": "Необходимо подключение к серверу {{webUIName}}",
 	"*Prompt node ID(s) are required for image generation": "ID узлов промптов обязательны для генерации изображения",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Модель задач используется при выполнении таких задач, как генерация заголовков для чатов и поисковых запросов в Интернете",
 	"a user": "пользователь",
 	"About": "О программе",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Похоже, что URL-адрес недействителен. Пожалуйста, перепроверьте и попробуйте еще раз.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Упс! В предыдущем ответе была ошибка. Пожалуйста, повторите попытку или свяжитесь с администратором.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Вы используете неподдерживаемый метод (только фронтенд). Пожалуйста, обслуживайте веб-интерфейс из бэкенда.",
+	"Open file": "",
 	"Open new chat": "Открыть новый чат",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Версия Open WebUI (v{{OPEN_WEBUI_VERSION}}) ниже требуемой версии (v{{REQUIRED_VERSION}})",
 	"OpenAI": "Open AI",
@@ -476,6 +478,7 @@
 	"Other": "Прочее",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Пароль",
 	"PDF document (.pdf)": "PDF-документ (.pdf)",
 	"PDF Extract Images (OCR)": "Извлечение изображений из PDF (OCR)",
@@ -701,6 +704,7 @@
 	"Unpin": "Открепить",
 	"Update": "Обновить",
 	"Update and Copy Link": "Обновить и скопировать ссылку",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Обновить пароль",
 	"Updated at": "Обновлено",
 	"Upload": "Загрузить",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Ћаскања корисника {{user}}",
 	"{{webUIName}} Backend Required": "Захтева се {{webUIName}} позадинац",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Модел задатка се користи приликом извршавања задатака као што су генерисање наслова за ћаскања и упите за Веб претрагу",
 	"a user": "корисник",
 	"About": "О нама",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Изгледа да је адреса неважећа. Молимо вас да проверите и покушате поново.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Користите неподржани метод (само фронтенд). Молимо вас да покренете WebUI са бекенда.",
+	"Open file": "",
 	"Open new chat": "Покрени ново ћаскање",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Остало",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Лозинка",
 	"PDF document (.pdf)": "PDF документ (.pdf)",
 	"PDF Extract Images (OCR)": "Извлачење PDF слика (OCR)",
@@ -700,6 +703,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Ажурирај и копирај везу",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Ажурирај лозинку",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend krävs",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "En uppgiftsmodell används när du utför uppgifter som att generera titlar för chattar och webbsökningsfrågor",
 	"a user": "en användare",
 	"About": "Om",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hoppsan! Det ser ut som om URL:en är ogiltig. Dubbelkolla gärna och försök igen.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hoppsan! Du använder en ej stödd metod (endast frontend). Vänligen servera WebUI från backend.",
+	"Open file": "",
 	"Open new chat": "Öppna ny chatt",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Andra",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Lösenord",
 	"PDF document (.pdf)": "PDF-dokument (.pdf)",
 	"PDF Extract Images (OCR)": "PDF Extrahera bilder (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "Uppdatera och kopiera länk",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Uppdatera lösenord",
 	"Updated at": "",
 	"Upload": "",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "การสนทนาของ {{user}}",
 	"{{webUIName}} Backend Required": "ต้องการ Backend ของ {{webUIName}}",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "ใช้โมเดลงานเมื่อทำงานเช่นการสร้างหัวข้อสำหรับการสนทนาและการค้นหาเว็บ",
 	"a user": "ผู้ใช้",
 	"About": "เกี่ยวกับ",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "อุ๊บส์! ดูเหมือนว่า URL ไม่ถูกต้อง กรุณาตรวจสอบและลองใหม่อีกครั้ง",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "อุ๊บส์! มีข้อผิดพลาดในคำตอบก่อนหน้า กรุณาลองใหม่อีกครั้งหรือติดต่อผู้ดูแลระบบ",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "อุ๊บส์! คุณกำลังใช้วิธีที่ไม่รองรับ (เฉพาะเว็บส่วนหน้า) กรุณาให้บริการ WebUI จากเว็บส่วนแบ็กเอนด์",
+	"Open file": "",
 	"Open new chat": "เปิดแชทใหม่",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "เวอร์ชั่น Open WebUI (v{{OPEN_WEBUI_VERSION}}) ต่ำกว่าเวอร์ชั่นที่ต้องการ (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "อื่น ๆ",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "รหัสผ่าน",
 	"PDF document (.pdf)": "เอกสาร PDF (.pdf)",
 	"PDF Extract Images (OCR)": "การแยกรูปภาพจาก PDF (OCR)",
@@ -699,6 +702,7 @@
 	"Unpin": "ยกเลิกการปักหมุด",
 	"Update": "อัปเดต",
 	"Update and Copy Link": "อัปเดตและคัดลอกลิงก์",
+	"Update for the latest features and improvements.": "",
 	"Update password": "อัปเดตรหัสผ่าน",
 	"Updated at": "อัปเดตเมื่อ",
 	"Upload": "อัปโหลด",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "",
 	"{{webUIName}} Backend Required": "",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "",
 	"a user": "",
 	"About": "",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "",
+	"Open file": "",
 	"Open new chat": "",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
 	"OpenAI": "",
@@ -476,6 +478,7 @@
 	"Other": "",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "",
 	"PDF document (.pdf)": "",
 	"PDF Extract Images (OCR)": "",
@@ -699,6 +702,7 @@
 	"Unpin": "",
 	"Update": "",
 	"Update and Copy Link": "",
+	"Update for the latest features and improvements.": "",
 	"Update password": "",
 	"Updated at": "",
 	"Upload": "",

+ 119 - 115
src/lib/i18n/locales/tr-TR/translation.json

@@ -1,37 +1,38 @@
 {
 	"'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.": "'s', 'm', 'h', 'd', 'w' veya süresiz için '-1'.",
 	"(Beta)": "(Beta)",
-	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(e.g. `sh webui.sh --api --api-auth username_password`)",
+	"(e.g. `sh webui.sh --api --api-auth username_password`)": "(örn. `sh webui.sh --api --api-auth username_password`)",
 	"(e.g. `sh webui.sh --api`)": "(örn. `sh webui.sh --api`)",
 	"(latest)": "(en son)",
 	"{{ models }}": "{{ models }}",
 	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Temel modeli silemezsiniz",
-	"{{user}}'s Chats": "{{user}} Sohbetleri",
-	"{{webUIName}} Backend Required": "{{webUIName}} Arkayüz Gerekli",
-	"*Prompt node ID(s) are required for image generation": "",
+	"{{user}}'s Chats": "{{user}}'ın Sohbetleri",
+	"{{webUIName}} Backend Required": "{{webUIName}} Arka-uç Gerekli",
+	"*Prompt node ID(s) are required for image generation": "*Görüntü oluşturma için düğüm kimlikleri gereklidir",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Bir görev modeli, sohbetler ve web arama sorguları için başlık oluşturma gibi görevleri yerine getirirken kullanılır",
 	"a user": "bir kullanıcı",
 	"About": "Hakkında",
 	"Account": "Hesap",
 	"Account Activation Pending": "Hesap Aktivasyonu Bekleniyor",
 	"Accurate information": "Doğru bilgi",
-	"Actions": "",
+	"Actions": "Aksiyonlar",
 	"Active Users": "Aktif Kullanıcılar",
 	"Add": "Ekle",
-	"Add a model id": "Model id ekle",
-	"Add a short description about what this model does": "Bu modelin ne yaptığı hakkında kısa bir açıklama ekle",
+	"Add a model id": "Bir model id ekle",
+	"Add a short description about what this model does": "Bu modelin ne yaptığı hakkında kısa bir açıklama ekleyin",
 	"Add a short title for this prompt": "Bu prompt için kısa bir başlık ekleyin",
 	"Add a tag": "Bir etiket ekleyin",
-	"Add custom prompt": "Özel prompt ekle",
-	"Add Docs": "Dökümanlar Ekle",
+	"Add custom prompt": "Özel prompt ekleyin",
+	"Add Docs": "Belgeler Ekle",
 	"Add Files": "Dosyalar Ekle",
 	"Add Memory": "Bellek Ekle",
-	"Add message": "Mesaj ekle",
+	"Add message": "Mesaj Ekle",
 	"Add Model": "Model Ekle",
-	"Add Tag": "",
-	"Add Tags": "Etiketler ekle",
+	"Add Tag": "Etiket Ekle",
+	"Add Tags": "Etiketler Ekle",
 	"Add User": "Kullanıcı Ekle",
-	"Adjusting these settings will apply changes universally to all users.": "Bu ayarları ayarlamak değişiklikleri tüm kullanıcılara evrensel olarak uygular.",
+	"Adjusting these settings will apply changes universally to all users.": "Bu ayarların yapılması, değişiklikleri tüm kullanıcılara evrensel olarak uygulayacaktır.",
 	"admin": "yönetici",
 	"Admin": "Yönetici",
 	"Admin Panel": "Yönetici Paneli",
@@ -44,9 +45,9 @@
 	"All Users": "Tüm Kullanıcılar",
 	"Allow": "İzin ver",
 	"Allow Chat Deletion": "Sohbet Silmeye İzin Ver",
-	"Allow Chat Editing": "",
+	"Allow Chat Editing": "Soğbet Düzenlemeye İzin Ver",
 	"Allow non-local voices": "Yerel olmayan seslere izin verin",
-	"Allow Temporary Chat": "",
+	"Allow Temporary Chat": "Geçici Sohbetlere İzin Ver",
 	"Allow User Location": "Kullanıcı Konumuna İzin Ver",
 	"Allow Voice Interruption in Call": "Aramada Ses Kesintisine İzin Ver",
 	"alphanumeric characters and hyphens": "alfanumerik karakterler ve tireler",
@@ -73,10 +74,10 @@
 	"AUTOMATIC1111 Api Auth String": "AUTOMATIC1111 API Kimlik Doğrulama Dizesi",
 	"AUTOMATIC1111 Base URL": "AUTOMATIC1111 Temel URL",
 	"AUTOMATIC1111 Base URL is required.": "AUTOMATIC1111 Temel URL gereklidir.",
-	"Available list": "",
+	"Available list": "Mevcut liste",
 	"available!": "mevcut!",
-	"Azure AI Speech": "",
-	"Azure Region": "",
+	"Azure AI Speech": "Azure AI Konuşma",
+	"Azure Region": "Azure Bölgesi",
 	"Back": "Geri",
 	"Bad Response": "Kötü Yanıt",
 	"Banners": "Afişler",
@@ -94,10 +95,10 @@
 	"Change Password": "Parola Değiştir",
 	"Chat": "Sohbet",
 	"Chat Background Image": "Sohbet Arka Plan Resmi",
-	"Chat Bubble UI": "Sohbet Balonu UI",
-	"Chat Controls": "",
+	"Chat Bubble UI": "Sohbet Balonu Arayüzü",
+	"Chat Controls": "Sohbet Kontrolleri",
 	"Chat direction": "Sohbet Yönü",
-	"Chat Overview": "",
+	"Chat Overview": "Sohbet Genel Bakış",
 	"Chats": "Sohbetler",
 	"Check Again": "Tekrar Kontrol Et",
 	"Check for updates": "Güncellemeleri kontrol et",
@@ -115,7 +116,7 @@
 	"Click here to select a csv file.": "Bir CSV dosyası seçmek için buraya tıklayın.",
 	"Click here to select a py file.": "Bir py dosyası seçmek için buraya tıklayın.",
 	"Click here to select documents.": "Belgeleri seçmek için buraya tıklayın.",
-	"Click here to upload a workflow.json file.": "",
+	"Click here to upload a workflow.json file.": "Bir workflow.json dosyası yüklemek için buraya tıklayın.",
 	"click here.": "buraya tıklayın.",
 	"Click on the user role button to change a user's role.": "Bir kullanıcının rolünü değiştirmek için kullanıcı rolü düğmesine tıklayın.",
 	"Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Panoya yazma izni reddedildi. Tarayıcı ayarlarını kontrol ederek gerekli izinleri sağlayabilirsiniz.",
@@ -126,8 +127,8 @@
 	"ComfyUI": "ComfyUI",
 	"ComfyUI Base URL": "ComfyUI Temel URL",
 	"ComfyUI Base URL is required.": "ComfyUI Temel URL gerekli.",
-	"ComfyUI Workflow": "",
-	"ComfyUI Workflow Nodes": "",
+	"ComfyUI Workflow": "ComfyUI İş Akışı",
+	"ComfyUI Workflow Nodes": "ComfyUI İş Akışı Düğümleri",
 	"Command": "Komut",
 	"Concurrent Requests": "Eşzamanlı İstekler",
 	"Confirm": "Onayla",
@@ -136,17 +137,17 @@
 	"Connections": "Bağlantılar",
 	"Contact Admin for WebUI Access": "WebUI Erişimi için Yöneticiyle İletişime Geçin",
 	"Content": "İçerik",
-	"Content Extraction": "",
+	"Content Extraction": "İçerik Çıkarma",
 	"Context Length": "Bağlam Uzunluğu",
 	"Continue Response": "Yanıta Devam Et",
 	"Continue with {{provider}}": "{{provider}} ile devam et",
-	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "",
-	"Controls": "",
-	"Copied": "",
+	"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "Mesaj metninin TTS istekleri için nasıl bölüneceğini kontrol edin. 'Noktalama' cümlelere, 'paragraflar' paragraflara böler ve 'hiçbiri' mesajı tek bir dize olarak tutar.",
+	"Controls": "Kontroller",
+	"Copied": "Kopyalandı",
 	"Copied shared chat URL to clipboard!": "Paylaşılan sohbet URL'si panoya kopyalandı!",
-	"Copied to clipboard": "",
+	"Copied to clipboard": "Panoya kopyalandı",
 	"Copy": "Kopyala",
-	"Copy Code": "",
+	"Copy Code": "Kodu Kopyala",
 	"Copy last code block": "Son kod bloğunu kopyala",
 	"Copy last response": "Son yanıtı kopyala",
 	"Copy Link": "Bağlantıyı Kopyala",
@@ -181,7 +182,7 @@
 	"Delete chat": "Sohbeti sil",
 	"Delete Chat": "Sohbeti Sil",
 	"Delete chat?": "Sohbeti sil?",
-	"Delete Doc": "",
+	"Delete Doc": "Belgeyi Sil",
 	"Delete function?": "Fonksiyonu sil?",
 	"Delete prompt?": "Promptu sil?",
 	"delete this link": "bu bağlantıyı sil",
@@ -191,7 +192,7 @@
 	"Deleted {{name}}": "{{name}} silindi",
 	"Description": "Açıklama",
 	"Didn't fully follow instructions": "Talimatları tam olarak takip etmedi",
-	"Disabled": "",
+	"Disabled": "Devre Dışı",
 	"Discover a function": "Bir fonksiyon keşfedin",
 	"Discover a model": "Bir model keşfedin",
 	"Discover a prompt": "Bir prompt keşfedin",
@@ -203,16 +204,16 @@
 	"Dismissible": "Reddedilebilir",
 	"Display Emoji in Call": "Aramada Emoji Göster",
 	"Display the username instead of You in the Chat": "Sohbet'te Siz yerine kullanıcı adını göster",
-	"Do not install functions from sources you do not fully trust.": "",
-	"Do not install tools from sources you do not fully trust.": "",
+	"Do not install functions from sources you do not fully trust.": "Tamamen güvenmediğiniz kaynaklardan fonksiyonlar yüklemeyin.",
+	"Do not install tools from sources you do not fully trust.": "Tamamen güvenmediğiniz kaynaklardan araçlar yüklemeyin.",
 	"Document": "Belge",
 	"Documentation": "Dökümantasyon",
 	"Documents": "Belgeler",
 	"does not make any external connections, and your data stays securely on your locally hosted server.": "herhangi bir harici bağlantı yapmaz ve verileriniz güvenli bir şekilde yerel olarak barındırılan sunucunuzda kalır.",
 	"Don't Allow": "İzin Verme",
 	"Don't have an account?": "Hesabınız yok mu?",
-	"don't install random functions from sources you don't trust.": "",
-	"don't install random tools from sources you don't trust.": "",
+	"don't install random functions from sources you don't trust.": "Tanımadığınız kaynaklardan rastgele fonksiyonlar yüklemeyin.",
+	"don't install random tools from sources you don't trust.": "Tanımadığınız kaynaklardan rastgele araçlar yüklemeyin.",
 	"Don't like the style": "Tarzını beğenmedim",
 	"Done": "Tamamlandı",
 	"Download": "İndir",
@@ -231,18 +232,18 @@
 	"Embedding Model Engine": "Gömme Modeli Motoru",
 	"Embedding model set to \"{{embedding_model}}\"": "Gömme modeli \"{{embedding_model}}\" olarak ayarlandı",
 	"Enable Community Sharing": "Topluluk Paylaşımını Etkinleştir",
-	"Enable Message Rating": "",
+	"Enable Message Rating": "Mesaj Değerlendirmeyi Etkinleştir",
 	"Enable New Sign Ups": "Yeni Kayıtları Etkinleştir",
 	"Enable Web Search": "Web Aramasını Etkinleştir",
-	"Enable Web Search Query Generation": "",
-	"Enabled": "",
-	"Engine": "",
+	"Enable Web Search Query Generation": "Web Arama Sorgusu Oluşturmayı Etkinleştir",
+	"Enabled": "Etkin",
+	"Engine": "Motor",
 	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "CSV dosyanızın şu sırayla 4 sütun içerdiğinden emin olun: İsim, E-posta, Şifre, Rol.",
 	"Enter {{role}} message here": "Buraya {{role}} mesajını girin",
 	"Enter a detail about yourself for your LLMs to recall": "LLM'lerinizin hatırlaması için kendiniz hakkında bir bilgi girin",
 	"Enter api auth string (e.g. username:password)": "Api auth dizesini girin (örn. kullanıcı adı:parola)",
 	"Enter Brave Search API Key": "Brave Search API Anahtarını Girin",
-	"Enter CFG Scale (e.g. 7.0)": "",
+	"Enter CFG Scale (e.g. 7.0)": "CFG Ölçeğini Girin (örn. 7.0)",
 	"Enter Chunk Overlap": "Chunk Örtüşmesini Girin",
 	"Enter Chunk Size": "Chunk Boyutunu Girin",
 	"Enter Github Raw URL": "Github Raw URL'sini girin",
@@ -250,28 +251,28 @@
 	"Enter Google PSE Engine Id": "Google PSE Engine Id'sini Girin",
 	"Enter Image Size (e.g. 512x512)": "Görüntü Boyutunu Girin (örn. 512x512)",
 	"Enter language codes": "Dil kodlarını girin",
-	"Enter Model ID": "",
+	"Enter Model ID": "Model ID'sini Girin",
 	"Enter model tag (e.g. {{modelTag}})": "Model etiketini girin (örn. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Adım Sayısını Girin (örn. 50)",
-	"Enter Sampler (e.g. Euler a)": "",
-	"Enter Scheduler (e.g. Karras)": "",
+	"Enter Sampler (e.g. Euler a)": "Örnekleyiciyi Girin (örn. Euler a)",
+	"Enter Scheduler (e.g. Karras)": "Zamanlayıcıyı Girin (örn. Karras)",
 	"Enter Score": "Skoru Girin",
-	"Enter SearchApi API Key": "",
-	"Enter SearchApi Engine": "",
+	"Enter SearchApi API Key": "Arama-API Anahtarını Girin",
+	"Enter SearchApi Engine": "Arama-API Motorunu Girin",
 	"Enter Searxng Query URL": "Searxng Sorgu URL'sini girin",
 	"Enter Serper API Key": "Serper API Anahtarını Girin",
 	"Enter Serply API Key": "Serply API Anahtarını Girin",
 	"Enter Serpstack API Key": "Serpstack API Anahtarını Girin",
 	"Enter stop sequence": "Durdurma dizisini girin",
-	"Enter system prompt": "",
+	"Enter system prompt": "Sistem promptunu girin",
 	"Enter Tavily API Key": "Tavily API Anahtarını Girin",
-	"Enter Tika Server URL": "",
+	"Enter Tika Server URL": "Tika Sunucu URL'sini Girin",
 	"Enter Top K": "Top K'yı girin",
 	"Enter URL (e.g. http://127.0.0.1:7860/)": "URL'yi Girin (örn. http://127.0.0.1:7860/)",
 	"Enter URL (e.g. http://localhost:11434)": "URL'yi Girin (e.g. http://localhost:11434)",
 	"Enter Your Email": "E-postanızı Girin",
 	"Enter Your Full Name": "Tam Adınızı Girin",
-	"Enter your message": "",
+	"Enter your message": "Mesajınızı girin",
 	"Enter Your Password": "Parolanızı Girin",
 	"Enter Your Role": "Rolünüzü Girin",
 	"Error": "Hata",
@@ -296,8 +297,8 @@
 	"File": "Dosya",
 	"File Mode": "Dosya Modu",
 	"File not found.": "Dosya bulunamadı.",
-	"File size should not exceed {{maxSize}} MB.": "",
-	"Files": "",
+	"File size should not exceed {{maxSize}} MB.": "Dosya boyutu {{maxSize}} MB'yi aşmamalıdır.",
+	"Files": "Dosyalar",
 	"Filter is now globally disabled": "Filtre artık global olarak devre dışı",
 	"Filter is now globally enabled": "Filtre artık global olarak devrede",
 	"Filters": "Filtreler",
@@ -310,28 +311,28 @@
 	"Frequency Penalty": "Frekans Cezası",
 	"Function created successfully": "Fonksiyon başarıyla oluşturuldu",
 	"Function deleted successfully": "Fonksiyon başarıyla silindi",
-	"Function Description (e.g. A filter to remove profanity from text)": "",
-	"Function ID (e.g. my_filter)": "",
-	"Function is now globally disabled": "",
-	"Function is now globally enabled": "",
-	"Function Name (e.g. My Filter)": "",
+	"Function Description (e.g. A filter to remove profanity from text)": "Fonksiyon Açıklaması (örn. Metinden küfürleri kaldıran bir filtre)",
+	"Function ID (e.g. my_filter)": "Fonksiyon ID'si (örn. my_filter)",
+	"Function is now globally disabled": "Fonksiyon artık global olarak devre dışı",
+	"Function is now globally enabled": "Fonksiyon artık global olarak aktif",
+	"Function Name (e.g. My Filter)": "Fonksiyon Adı (örn. Benim Filtrem)",
 	"Function updated successfully": "Fonksiyon başarıyla güncellendi",
 	"Functions": "Fonksiyonlar",
-	"Functions allow arbitrary code execution": "",
-	"Functions allow arbitrary code execution.": "",
+	"Functions allow arbitrary code execution": "Fonksiyonlar keyfi kod yürütülmesine izin verir",
+	"Functions allow arbitrary code execution.": "Fonksiyonlar keyfi kod yürütülmesine izin verir.",
 	"Functions imported successfully": "Fonksiyonlar başarıyla içe aktarıldı",
 	"General": "Genel",
 	"General Settings": "Genel Ayarlar",
 	"Generate Image": "Görsel Üret",
 	"Generating search query": "Arama sorgusu oluşturma",
 	"Generation Info": "Üretim Bilgisi",
-	"Get up and running with": "",
-	"Global": "Global",
+	"Get up and running with": "ile çalışmaya başlayın",
+	"Global": "Evrensel",
 	"Good Response": "İyi Yanıt",
 	"Google PSE API Key": "Google PSE API Anahtarı",
 	"Google PSE Engine Id": "Google PSE Engine Id",
 	"h:mm a": "h:mm a",
-	"Haptic Feedback": "",
+	"Haptic Feedback": "Dokunsal Geri Bildirim",
 	"has no conversations.": "hiç konuşması yok.",
 	"Hello, {{name}}": "Merhaba, {{name}}",
 	"Help": "Yardım",
@@ -339,7 +340,7 @@
 	"Hide Model": "Modeli Gizle",
 	"How can I help you today?": "Bugün size nasıl yardımcı olabilirim?",
 	"Hybrid Search": "Karma Arama",
-	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "",
+	"I acknowledge that I have read and I understand the implications of my action. I am aware of the risks associated with executing arbitrary code and I have verified the trustworthiness of the source.": "Eylemimin sonuçlarını okuduğumu ve anladığımı kabul ediyorum. Rastgele kod çalıştırmayla ilgili risklerin farkındayım ve kaynağın güvenilirliğini doğruladım.",
 	"Image Generation (Experimental)": "Görüntü Oluşturma (Deneysel)",
 	"Image Generation Engine": "Görüntü Oluşturma Motoru",
 	"Image Settings": "Görüntü Ayarları",
@@ -371,27 +372,27 @@
 	"Keyboard shortcuts": "Klavye kısayolları",
 	"Knowledge": "Bilgi",
 	"Language": "Dil",
-	"large language models, locally.": "",
+	"large language models, locally.": "büyük dil modelleri, yerel olarak.",
 	"Last Active": "Son Aktivite",
 	"Last Modified": "Son Düzenleme",
 	"Leave empty for unlimited": "",
-	"Leave empty to use the default prompt, or enter a custom prompt": "",
+	"Leave empty to use the default prompt, or enter a custom prompt": "Varsayılan promptu kullanmak için boş bırakın veya özel bir prompt girin",
 	"Light": "Açık",
 	"Listening...": "Dinleniyor...",
 	"LLMs can make mistakes. Verify important information.": "LLM'ler hata yapabilir. Önemli bilgileri doğrulayın.",
 	"Local Models": "Yerel Modeller",
-	"LTR": "LTR",
+	"LTR": "Soldan Sağa",
 	"Made by OpenWebUI Community": "OpenWebUI Topluluğu tarafından yapılmıştır",
 	"Make sure to enclose them with": "Değişkenlerinizi şu şekilde biçimlendirin:",
-	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
+	"Make sure to export a workflow.json file as API format from ComfyUI.": "ComfyUI'dan API formatında bir workflow.json dosyası olarak dışa aktardığınızdan emin olun.",
 	"Manage": "Yönet",
 	"Manage Models": "Modelleri Yönet",
 	"Manage Ollama Models": "Ollama Modellerini Yönet",
 	"Manage Pipelines": "Pipelineları Yönet",
 	"March": "Mart",
 	"Max Tokens (num_predict)": "Maksimum Token (num_predict)",
-	"Max Upload Count": "",
-	"Max Upload Size": "",
+	"Max Upload Count": "Maksimum Yükleme Sayısı",
+	"Max Upload Size": "Maksimum Yükleme Boyutu",
 	"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Aynı anda en fazla 3 model indirilebilir. Lütfen daha sonra tekrar deneyin.",
 	"May": "Mayıs",
 	"Memories accessible by LLMs will be shown here.": "LLM'ler tarafından erişilebilen bellekler burada gösterilecektir.",
@@ -400,9 +401,9 @@
 	"Memory cleared successfully": "Bellek başarıyle temizlendi",
 	"Memory deleted successfully": "Bellek başarıyla silindi",
 	"Memory updated successfully": "Bellek başarıyla güncellendi",
-	"Merge Responses": "",
+	"Merge Responses": "Yanıtları Birleştir",
 	"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Bağlantınızı oluşturduktan sonra gönderdiğiniz mesajlar paylaşılmayacaktır. URL'ye sahip kullanıcılar paylaşılan sohbeti görüntüleyebilecektir.",
-	"Min P": "",
+	"Min P": "Min P",
 	"Minimum Score": "Minimum Skor",
 	"Mirostat": "Mirostat",
 	"Mirostat Eta": "Mirostat Eta",
@@ -415,8 +416,8 @@
 	"Model {{modelId}} not found": "{{modelId}} bulunamadı",
 	"Model {{modelName}} is not vision capable": "Model {{modelName}} görüntü yeteneğine sahip değil",
 	"Model {{name}} is now {{status}}": "{{name}} modeli artık {{status}}",
-	"Model {{name}} is now at the top": "",
-	"Model accepts image inputs": "",
+	"Model {{name}} is now at the top": "{{name}} modeli artık en üstte",
+	"Model accepts image inputs": "Model görüntü girdilerini kabul eder",
 	"Model created successfully!": "Model başarıyla oluşturuldu!",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model dosya sistemi yolu algılandı. Güncelleme için model kısa adı gerekli, devam edilemiyor.",
 	"Model ID": "Model ID",
@@ -428,7 +429,7 @@
 	"Modelfile Content": "Model Dosyası İçeriği",
 	"Models": "Modeller",
 	"More": "Daha Fazla",
-	"Move to Top": "",
+	"Move to Top": "En Üste Taşı",
 	"Name": "Ad",
 	"Name Tag": "Ad Etiketi",
 	"Name your model": "Modelinizi Adlandırın",
@@ -465,8 +466,9 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Hop! URL geçersiz gibi görünüyor. Lütfen tekrar kontrol edin ve yeniden deneyin.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Hop! Önceki yanıtta bir hata oluştu. Lütfen tekrar deneyin veya yönetici ile iletişime geçin.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Hop! Desteklenmeyen bir yöntem kullanıyorsunuz (yalnızca önyüz). Lütfen WebUI'yi arkayüzden sunun.",
+	"Open file": "",
 	"Open new chat": "Yeni sohbet aç",
-	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "",
+	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open-WebUI sürümü (v{{OPEN_WEBUI_VERSION}}) gerekli sürümden (v{{REQUIRED_VERSION}}) düşük",
 	"OpenAI": "OpenAI",
 	"OpenAI API": "OpenAI API",
 	"OpenAI API Config": "OpenAI API Konfigürasyonu",
@@ -474,8 +476,9 @@
 	"OpenAI URL/Key required.": "OpenAI URL/Anahtar gereklidir.",
 	"or": "veya",
 	"Other": "Diğer",
-	"Output format": "",
-	"Overview": "",
+	"Output format": "Çıktı formatı",
+	"Overview": "Genel Bakış",
+	"page": "",
 	"Password": "Parola",
 	"PDF document (.pdf)": "PDF belgesi (.pdf)",
 	"PDF Extract Images (OCR)": "PDF Görüntülerini Çıkart (OCR)",
@@ -484,8 +487,8 @@
 	"Permission denied when accessing microphone": "Mikrofona erişim izni reddedildi",
 	"Permission denied when accessing microphone: {{error}}": "Mikrofona erişim izni reddedildi: {{error}}",
 	"Personalization": "Kişiselleştirme",
-	"Pin": "",
-	"Pinned": "",
+	"Pin": "Sabitle",
+	"Pinned": "Sabitlenmiş",
 	"Pipeline deleted successfully": "Pipeline başarıyla silindi",
 	"Pipeline downloaded successfully": "Pipeline başarıyla güncellendi",
 	"Pipelines": "Pipelinelar",
@@ -493,7 +496,7 @@
 	"Pipelines Valves": "Pipeline Valvleri",
 	"Plain text (.txt)": "Düz metin (.txt)",
 	"Playground": "Oyun Alanı",
-	"Please carefully review the following warnings:": "",
+	"Please carefully review the following warnings:": "Lütfen aşağıdaki uyarıları dikkatlice inceleyin:",
 	"Positive attitude": "Olumlu yaklaşım",
 	"Previous 30 days": "Önceki 30 gün",
 	"Previous 7 days": "Önceki 7 gün",
@@ -527,24 +530,24 @@
 	"Reset Vector Storage": "Vektör Depolamayı Sıfırla",
 	"Response AutoCopy to Clipboard": "Yanıtı Panoya Otomatik Kopyala",
 	"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "Web sitesi izinleri reddedildiğinden yanıt bildirimleri etkinleştirilemiyor. Gerekli erişimi sağlamak için lütfen tarayıcı ayarlarınızı ziyaret edin.",
-	"Response splitting": "",
+	"Response splitting": "Yanıt bölme",
 	"Role": "Rol",
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
-	"RTL": "RTL",
-	"Run": "",
-	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "",
+	"RTL": "Sağdan Sola",
+	"Run": "Çalıştır",
+	"Run Llama 2, Code Llama, and other models. Customize and create your own.": "Llama 2, Code Llama ve diğer modelleri çalıştırın. Kendinize özgü hale getirin ve kendi modelinizi oluşturun.",
 	"Running": "Çalışıyor",
 	"Save": "Kaydet",
 	"Save & Create": "Kaydet ve Oluştur",
 	"Save & Update": "Kaydet ve Güncelle",
-	"Save As Copy": "",
-	"Save Tag": "",
+	"Save As Copy": "Kopya Olarak Kaydet",
+	"Save Tag": "Etiketi Kaydet",
 	"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": "Sohbet kayıtlarının doğrudan tarayıcınızın depolama alanına kaydedilmesi artık desteklenmemektedir. Lütfen aşağıdaki butona tıklayarak sohbet kayıtlarınızı indirmek ve silmek için bir dakikanızı ayırın. Endişelenmeyin, sohbet günlüklerinizi arkayüze kolayca yeniden aktarabilirsiniz:",
 	"Scan": "Tarama",
 	"Scan complete!": "Tarama tamamlandı!",
 	"Scan for documents from {{path}}": "{{path}} dizininden belgeleri tarayın",
-	"Scroll to bottom when switching between branches": "",
+	"Scroll to bottom when switching between branches": "Dallar arasında geçiş yaparken en alta kaydır",
 	"Search": "Ara",
 	"Search a model": "Bir model ara",
 	"Search Chats": "Sohbetleri Ara",
@@ -555,12 +558,12 @@
 	"Search Query Generation Prompt": "Arama Sorgusu Üretme Promptu",
 	"Search Result Count": "Arama Sonucu Sayısı",
 	"Search Tools": "Arama Araçları",
-	"SearchApi API Key": "",
-	"SearchApi Engine": "",
+	"SearchApi API Key": "Arama-API API Anahtarı",
+	"SearchApi Engine": "Arama-API Motoru",
 	"Searched {{count}} sites_one": "Arandı {{count}} sites_one",
 	"Searched {{count}} sites_other": "Arandı {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "\"{{searchQuery}}\" aranıyor",
-	"Searching Knowledge for \"{{searchQuery}}\"": "",
+	"Searching Knowledge for \"{{searchQuery}}\"": "\"{{searchQuery}}\" için Bilgi aranıyor",
 	"Searxng Query URL": "Searxng Sorgu URL'si",
 	"See readme.md for instructions": "Yönergeler için readme.md dosyasına bakın",
 	"See what's new": "Yeniliklere göz atın",
@@ -574,27 +577,27 @@
 	"Select a tool": "Bir araç seç",
 	"Select an Ollama instance": "Bir Ollama örneği seçin",
 	"Select Documents": "Bir Doküman Seç",
-	"Select Engine": "",
+	"Select Engine": "Motor Seç",
 	"Select model": "Model seç",
 	"Select only one model to call": "Arama için sadece bir model seç",
 	"Selected model(s) do not support image inputs": "Seçilen model(ler) görüntü girişlerini desteklemiyor",
 	"Send": "Gönder",
 	"Send a Message": "Bir Mesaj Gönder",
 	"Send message": "Mesaj gönder",
-	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "",
+	"Sends `stream_options: { include_usage: true }` in the request.\nSupported providers will return token usage information in the response when set.": "İsteğe `stream_options: { include_usage: true }` gönderir.\nDesteklenen sağlayıcılar, ayarlandığında yanıtta token kullanım bilgilerini döndürecektir.",
 	"September": "Eylül",
 	"Serper API Key": "Serper API Anahtarı",
 	"Serply API Key": "Serply API Anahtarı",
 	"Serpstack API Key": "Serpstack API Anahtarı",
 	"Server connection verified": "Sunucu bağlantısı doğrulandı",
 	"Set as default": "Varsayılan olarak ayarla",
-	"Set CFG Scale": "",
+	"Set CFG Scale": "CFG Ölçeğini Ayarla",
 	"Set Default Model": "Varsayılan Modeli Ayarla",
 	"Set embedding model (e.g. {{model}})": "Gömme modelini ayarlayın (örn. {{model}})",
 	"Set Image Size": "Görüntü Boyutunu Ayarla",
 	"Set reranking model (e.g. {{model}})": "Yeniden sıralama modelini ayarlayın (örn. {{model}})",
-	"Set Sampler": "",
-	"Set Scheduler": "",
+	"Set Sampler": "Örnekleyiciyi Ayarla",
+	"Set Scheduler": "Zamanlayıcıyı Ayarla",
 	"Set Steps": "Adımları Ayarla",
 	"Set Task Model": "Görev Modeli Ayarla",
 	"Set Voice": "Ses Ayarla",
@@ -615,7 +618,7 @@
 	"Sign up": "Kaydol",
 	"Signing in": "Oturum açma",
 	"Source": "Kaynak",
-	"Speech Playback Speed": "",
+	"Speech Playback Speed": "Konuşma Oynatma Hızı",
 	"Speech recognition error: {{error}}": "Konuşma tanıma hatası: {{error}}",
 	"Speech-to-Text Engine": "Konuşmadan Metne Motoru",
 	"Stop Sequence": "Diziyi Durdur",
@@ -627,8 +630,8 @@
 	"Success": "Başarılı",
 	"Successfully updated.": "Başarıyla güncellendi.",
 	"Suggested": "Önerilen",
-	"Support": "",
-	"Support this plugin:": "",
+	"Support": "Destek",
+	"Support this plugin:": "Bu eklentiyi destekleyin:",
 	"System": "Sistem",
 	"System Prompt": "Sistem Promptu",
 	"Tags": "Etiketler",
@@ -637,14 +640,14 @@
 	"Tell us more:": "Bize daha fazlasını anlat:",
 	"Temperature": "Temperature",
 	"Template": "Şablon",
-	"Temporary Chat": "",
+	"Temporary Chat": "Geçici Sohbet",
 	"Text Completion": "Metin Tamamlama",
 	"Text-to-Speech Engine": "Metinden Sese Motoru",
 	"Tfs Z": "Tfs Z",
 	"Thanks for your feedback!": "Geri bildiriminiz için teşekkürler!",
-	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
-	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "",
-	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "",
+	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Bu eklentinin arkasındaki geliştiriciler topluluktan tutkulu gönüllülerdir. Bu eklentinin yararlı olduğunu düşünüyorsanız, gelişimine katkıda bulunmayı düşünün.",
+	"The maximum file size in MB. If the file size exceeds this limit, the file will not be uploaded.": "MB cinsinden maksimum dosya boyutu. Dosya boyutu bu sınırı aşarsa, dosya yüklenmeyecektir.",
+	"The maximum number of files that can be used at once in chat. If the number of files exceeds this limit, the files will not be uploaded.": "Sohbette aynı anda kullanılabilecek maksimum dosya sayısı. Dosya sayısı bu sınırı aşarsa, dosyalar yüklenmeyecektir.",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Puan 0.0 (%0) ile 1.0 (%100) arasında bir değer olmalıdır.",
 	"Theme": "Tema",
 	"Thinking...": "Düşünüyor...",
@@ -654,7 +657,7 @@
 	"This will delete": "Bu silinecek",
 	"Thorough explanation": "Kapsamlı açıklama",
 	"Tika": "",
-	"Tika Server URL required.": "",
+	"Tika Server URL required.": "Tika Sunucu URL'si gereklidir.",
 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "İpucu: Her değiştirmeden sonra sohbet girişinde tab tuşuna basarak birden fazla değişken yuvasını art arda güncelleyin.",
 	"Title": "Başlık",
 	"Title (e.g. Tell me a fun fact)": "Başlık (e.g. Bana ilginç bir bilgi ver)",
@@ -667,7 +670,7 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "WebUI'ye erişmek için lütfen yöneticiyle iletişime geçin. Yöneticiler kullanıcı durumlarını Yönetici Panelinden yönetebilir.",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "Buraya belge eklemek için öncelikle bunları \"Belgeler\" çalışma alanına yükleyin.",
 	"to chat input.": "sohbet girişine.",
-	"To select actions here, add them to the \"Functions\" workspace first.": "",
+	"To select actions here, add them to the \"Functions\" workspace first.": "Burada eylemleri seçmek için öncelikle bunları \"İşlevler\" çalışma alanına ekleyin.",
 	"To select filters here, add them to the \"Functions\" workspace first.": "Filtreleri burada seçmek için öncelikle bunları \"İşlevler\" çalışma alanına ekleyin.",
 	"To select toolkits here, add them to the \"Tools\" workspace first.": "Araçları burada seçmek için öncelikle bunları \"Araçlar\" çalışma alanına ekleyin.",
 	"Today": "Bugün",
@@ -678,13 +681,13 @@
 	"Tool deleted successfully": "Araç başarıyla silindi",
 	"Tool imported successfully": "Araç başarıyla içe aktarıldı",
 	"Tool updated successfully": "Araç başarıyla güncellendi",
-	"Toolkit Description (e.g. A toolkit for performing various operations)": "",
-	"Toolkit ID (e.g. my_toolkit)": "",
-	"Toolkit Name (e.g. My ToolKit)": "",
+	"Toolkit Description (e.g. A toolkit for performing various operations)": "Toolkit Açıklaması (örn. Çeşitli işlemleri gerçekleştirmek için bir toolkit)",
+	"Toolkit ID (e.g. my_toolkit)": "Toolkit ID'si (örn. my_toolkit)",
+	"Toolkit Name (e.g. My ToolKit)": "Toolkit Adı (örn. Benim Toolkitim)",
 	"Tools": "Araçlar",
-	"Tools are a function calling system with arbitrary code execution": "",
-	"Tools have a function calling system that allows arbitrary code execution": "",
-	"Tools have a function calling system that allows arbitrary code execution.": "",
+	"Tools are a function calling system with arbitrary code execution": "Araçlar, keyfi kod yürütme ile bir fonksiyon çağırma sistemine sahiptir",
+	"Tools have a function calling system that allows arbitrary code execution": "Araçlar, keyfi kod yürütme izni veren bir fonksiyon çağırma sistemine sahiptir",
+	"Tools have a function calling system that allows arbitrary code execution.": "Araçlar, keyfi kod yürütme izni veren bir fonksiyon çağırma sistemine sahiptir.",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Ollama'ya erişmede sorun mu yaşıyorsunuz?",
@@ -692,13 +695,14 @@
 	"TTS Settings": "TTS Ayarları",
 	"TTS Voice": "TTS Sesi",
 	"Type": "Tür",
-	"Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (Download) URL'sini Yazın",
+	"Type Hugging Face Resolve (Download) URL": "HuggingFace Çözümleme (İndirme) URL'sini Yazın",
 	"Uh-oh! There was an issue connecting to {{provider}}.": "Ah! {{provider}}'a bağlanırken bir sorun oluştu.",
-	"UI": "UI",
+	"UI": "Arayüz",
 	"Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.": "Bilinmeyen dosya türü '{{file_type}}'. Yine de dosya yükleme işlemine devam ediliyor.",
-	"Unpin": "",
+	"Unpin": "Sabitlemeyi Kaldır",
 	"Update": "Güncelle",
 	"Update and Copy Link": "Güncelle ve Bağlantıyı Kopyala",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Parolayı Güncelle",
 	"Updated at": "Şu tarihte güncellendi:",
 	"Upload": "Yükle",
@@ -726,7 +730,7 @@
 	"Version": "Sürüm",
 	"Voice": "Ses",
 	"Warning": "Uyarı",
-	"Warning:": "",
+	"Warning:": "Uyarı:",
 	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Uyarı: Gömme modelinizi günceller veya değiştirirseniz, tüm belgeleri yeniden içe aktarmanız gerekecektir.",
 	"Web": "Web",
 	"Web API": "Web API",
@@ -744,7 +748,7 @@
 	"Write a summary in 50 words that summarizes [topic or keyword].": "[Konuyu veya anahtar kelimeyi] özetleyen 50 kelimelik bir özet yazın.",
 	"Yesterday": "Dün",
 	"You": "Sen",
-	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "",
+	"You can only chat with a maximum of {{maxCount}} file(s) at a time.": "Aynı anda en fazla {{maxCount}} dosya ile sohbet edebilirsiniz.",
 	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Aşağıdaki 'Yönet' düğmesi aracılığıyla bellekler ekleyerek LLM'lerle etkileşimlerinizi kişiselleştirebilir, onları daha yararlı ve size özel hale getirebilirsiniz.",
 	"You cannot clone a base model": "Bir temel modeli klonlayamazsınız",
 	"You have no archived conversations.": "Arşivlenmiş sohbetleriniz yok.",
@@ -752,7 +756,7 @@
 	"You're a helpful assistant.": "Sen yardımsever bir asistansın.",
 	"You're now logged in.": "Şimdi giriş yaptınız.",
 	"Your account status is currently pending activation.": "Hesap durumunuz şu anda etkinleştirilmeyi bekliyor.",
-	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "",
+	"Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Tüm katkınız doğrudan eklenti geliştiricisine gidecektir; Open WebUI herhangi bir yüzde almaz. Ancak seçilen finansman platformunun kendi ücretleri olabilir.",
 	"Youtube": "Youtube",
 	"Youtube Loader Settings": "Youtube Yükleyici Ayarları"
 }

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "Чати {{user}}а",
 	"{{webUIName}} Backend Required": "Необхідно підключення бекенду {{webUIName}}",
 	"*Prompt node ID(s) are required for image generation": "*Для генерації зображення потрібно вказати ідентифікатор(и) вузла(ів)",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Модель задач використовується при виконанні таких завдань, як генерація заголовків для чатів та пошукових запитів в Інтернеті",
 	"a user": "користувача",
 	"About": "Про програму",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Упс! Схоже, що URL-адреса невірна. Будь ласка, перевірте ще раз та спробуйте ще раз.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Упс! У попередній відповіді сталася помилка. Будь ласка, спробуйте ще раз або зверніться до адміністратора.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Ви використовуєте непідтримуваний метод (тільки для фронтенду). Будь ласка, обслуговуйте WebUI з бекенду.",
+	"Open file": "",
 	"Open new chat": "Відкрити новий чат",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI версія (v{{OPEN_WEBUI_VERSION}}) нижча за необхідну версію (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Інше",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Пароль",
 	"PDF document (.pdf)": "PDF документ (.pdf)",
 	"PDF Extract Images (OCR)": "Розпізнавання зображень з PDF (OCR)",
@@ -701,6 +704,7 @@
 	"Unpin": "Відчепити",
 	"Update": "Оновлення",
 	"Update and Copy Link": "Оновлення та копіювання посилання",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Оновити пароль",
 	"Updated at": "Оновлено на",
 	"Upload": "Завантажити",

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

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}}'s Chats",
 	"{{webUIName}} Backend Required": "{{webUIName}} Yêu cầu Backend",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "Mô hình tác vụ được sử dụng khi thực hiện các tác vụ như tạo tiêu đề cho cuộc trò chuyện và truy vấn tìm kiếm trên web",
 	"a user": "người sử dụng",
 	"About": "Giới thiệu",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "Rất tiếc! URL dường như không hợp lệ. Vui lòng kiểm tra lại và thử lại.",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "Rất tiếc! Đã xảy ra lỗi trong phản hồi trước đó. Vui lòng thử lại hoặc liên hệ với quản trị viên.",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Rất tiếc! Bạn đang sử dụng một phương thức không được hỗ trợ (chỉ dành cho frontend). Vui lòng cung cấp phương thức cho WebUI từ phía backend.",
+	"Open file": "",
 	"Open new chat": "Mở nội dung chat mới",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Phiên bản Open WebUI (v{{OPEN_WEBUI_VERSION}}) hiện thấp hơn phiên bản bắt buộc (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "Khác",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "Mật khẩu",
 	"PDF document (.pdf)": "Tập tin PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Trích xuất ảnh từ PDF (OCR)",
@@ -698,6 +701,7 @@
 	"Unpin": "Bỏ ghim",
 	"Update": "Cập nhật",
 	"Update and Copy Link": "Cập nhật và sao chép link",
+	"Update for the latest features and improvements.": "",
 	"Update password": "Cập nhật mật khẩu",
 	"Updated at": "Cập nhật lúc",
 	"Upload": "",

+ 5 - 1
src/lib/i18n/locales/zh-CN/translation.json

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} 的对话记录",
 	"{{webUIName}} Backend Required": "需要 {{webUIName}} 后端",
 	"*Prompt node ID(s) are required for image generation": "*图片生成需要 Prompt node ID",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "任务模型用于执行生成对话标题和联网搜索查询等任务",
 	"a user": "用户",
 	"About": "关于",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "糟糕!此链接似乎为无效链接。请检查后重试。",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "糟糕!之前的回复出现了错误。请重试或联系管理员。",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "糟糕!你正在使用不被支持的方法(仅前端)。请从后端提供 WebUI 服务。",
+	"Open file": "",
 	"Open new chat": "打开新对话",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "当前 Open WebUI 版本 (v{{OPEN_WEBUI_VERSION}}) 低于所需的版本 (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "其他",
 	"Output format": "输出格式",
 	"Overview": "概述",
+	"page": "",
 	"Password": "密码",
 	"PDF document (.pdf)": "PDF 文档 (.pdf)",
 	"PDF Extract Images (OCR)": "PDF 图像处理 (使用 OCR)",
@@ -614,7 +617,7 @@
 	"Sign up": "注册",
 	"Signing in": "正在登录",
 	"Source": "来源",
-	"Speech Playback Speed": "",
+	"Speech Playback Speed": "语音播放速度",
 	"Speech recognition error: {{error}}": "语音识别错误:{{error}}",
 	"Speech-to-Text Engine": "语音转文本引擎",
 	"Stop Sequence": "停止序列 (Stop Sequence)",
@@ -698,6 +701,7 @@
 	"Unpin": "取消置顶",
 	"Update": "更新",
 	"Update and Copy Link": "更新和复制链接",
+	"Update for the latest features and improvements.": "",
 	"Update password": "更新密码",
 	"Updated at": "更新于",
 	"Upload": "上传",

+ 5 - 1
src/lib/i18n/locales/zh-TW/translation.json

@@ -9,6 +9,7 @@
 	"{{user}}'s Chats": "{{user}} 的對話",
 	"{{webUIName}} Backend Required": "需要 {{webUIName}} 後端",
 	"*Prompt node ID(s) are required for image generation": "",
+	"A new version (v{{LATEST_VERSION}}) is now available.": "",
 	"A task model is used when performing tasks such as generating titles for chats and web search queries": "執行產生對話標題和網頁搜尋查詢等任務時會使用任務模型",
 	"a user": "一位使用者",
 	"About": "關於",
@@ -465,6 +466,7 @@
 	"Oops! Looks like the URL is invalid. Please double-check and try again.": "哎呀!這個 URL 似乎無效。請仔細檢查並再試一次。",
 	"Oops! There was an error in the previous response. Please try again or contact admin.": "哎呀!先前的回應發生錯誤。請重試或聯絡管理員。",
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "哎呀!您使用了不支援的方法(僅限前端)。請從後端提供 WebUI。",
+	"Open file": "",
 	"Open new chat": "開啟新的對話",
 	"Open WebUI version (v{{OPEN_WEBUI_VERSION}}) is lower than required version (v{{REQUIRED_VERSION}})": "Open WebUI 版本 (v{{OPEN_WEBUI_VERSION}}) 低於所需版本 (v{{REQUIRED_VERSION}})",
 	"OpenAI": "OpenAI",
@@ -476,6 +478,7 @@
 	"Other": "其他",
 	"Output format": "",
 	"Overview": "",
+	"page": "",
 	"Password": "密碼",
 	"PDF document (.pdf)": "PDF 文件 (.pdf)",
 	"PDF Extract Images (OCR)": "PDF 影像擷取(OCR 光學文字辨識)",
@@ -615,7 +618,7 @@
 	"Sign up": "註冊",
 	"Signing in": "正在登入",
 	"Source": "來源",
-	"Speech Playback Speed": "",
+	"Speech Playback Speed": "語音播放速度",
 	"Speech recognition error: {{error}}": "語音辨識錯誤:{{error}}",
 	"Speech-to-Text Engine": "語音轉文字 (STT) 引擎",
 	"Stop Sequence": "停止序列",
@@ -699,6 +702,7 @@
 	"Unpin": "取消釘選",
 	"Update": "更新",
 	"Update and Copy Link": "更新並複製連結",
+	"Update for the latest features and improvements.": "",
 	"Update password": "更新密碼",
 	"Updated at": "更新於",
 	"Upload": "上傳",

+ 47 - 1
src/lib/utils/index.ts

@@ -664,6 +664,15 @@ export const promptTemplate = (
 		hour12: true
 	});
 
+	// Get the current weekday
+	const currentWeekday = getWeekday();
+
+	// Get the user's timezone
+	const currentTimezone = getUserTimezone();
+
+	// Get the user's language
+	const userLanguage = localStorage.getItem('locale') || 'en-US';
+
 	// Replace {{CURRENT_DATETIME}} in the template with the formatted datetime
 	template = template.replace('{{CURRENT_DATETIME}}', `${formattedDate} ${currentTime}`);
 
@@ -673,6 +682,15 @@ export const promptTemplate = (
 	// Replace {{CURRENT_TIME}} in the template with the formatted time
 	template = template.replace('{{CURRENT_TIME}}', currentTime);
 
+	// Replace {{CURRENT_WEEKDAY}} in the template with the current weekday
+	template = template.replace('{{CURRENT_WEEKDAY}}', currentWeekday);
+
+	// Replace {{CURRENT_TIMEZONE}} in the template with the user's timezone
+	template = template.replace('{{CURRENT_TIMEZONE}}', currentTimezone);
+
+	// Replace {{USER_LANGUAGE}} in the template with the user's language
+	template = template.replace('{{USER_LANGUAGE}}', userLanguage);
+
 	if (user_name) {
 		// Replace {{USER_NAME}} in the template with the user's name
 		template = template.replace('{{USER_NAME}}', user_name);
@@ -829,6 +847,34 @@ export const bestMatchingLanguage = (supportedLanguages, preferredLanguages, def
 		.map((prefLang) => languages.find((lang) => lang.startsWith(prefLang)))
 		.find(Boolean);
 
-	console.log(languages, preferredLanguages, match, defaultLocale);
 	return match || defaultLocale;
 };
+
+// Get the date in the format YYYY-MM-DD
+export const getFormattedDate = () => {
+	const date = new Date();
+	return date.toISOString().split('T')[0];
+};
+
+// Get the time in the format HH:MM:SS
+export const getFormattedTime = () => {
+	const date = new Date();
+	return date.toTimeString().split(' ')[0];
+};
+
+// Get the current date and time in the format YYYY-MM-DD HH:MM:SS
+export const getCurrentDateTime = () => {
+	return `${getFormattedDate()} ${getFormattedTime()}`;
+};
+
+// Get the user's timezone
+export const getUserTimezone = () => {
+	return Intl.DateTimeFormat().resolvedOptions().timeZone;
+};
+
+// Get the weekday
+export const getWeekday = () => {
+	const date = new Date();
+	const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+	return weekdays[date.getDay()];
+};

+ 28 - 1
src/routes/(app)/+layout.svelte

@@ -9,7 +9,7 @@
 
 	import { goto } from '$app/navigation';
 
-	import { getModels as _getModels } from '$lib/apis';
+	import { getModels as _getModels, getVersionUpdates } from '$lib/apis';
 	import { getAllChatTags } from '$lib/apis/chats';
 
 	import { getPrompts } from '$lib/apis/prompts';
@@ -42,6 +42,10 @@
 	import AccountPending from '$lib/components/layout/Overlay/AccountPending.svelte';
 	import { getFunctions } from '$lib/apis/functions';
 	import { page } from '$app/stores';
+	import { WEBUI_VERSION } from '$lib/constants';
+	import { compareVersion } from '$lib/utils';
+
+	import UpdateInfoToast from '$lib/components/layout/UpdateInfoToast.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -191,11 +195,34 @@
 				temporaryChatEnabled.set(true);
 			}
 
+			if ($user.role === 'admin') {
+				checkForVersionUpdates();
+			}
+
 			await tick();
 		}
 
 		loaded = true;
 	});
+
+	const checkForVersionUpdates = async () => {
+		const version = await getVersionUpdates(localStorage.token).catch((error) => {
+			return {
+				current: WEBUI_VERSION,
+				latest: WEBUI_VERSION
+			};
+		});
+
+		if (compareVersion(version.latest, version.current)) {
+			toast.custom(UpdateInfoToast, {
+				duration: Number.POSITIVE_INFINITY,
+				position: 'bottom-right',
+				componentProps: {
+					version
+				}
+			});
+		}
+	};
 </script>
 
 <SettingsModal bind:show={$showSettings} />

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

@@ -209,4 +209,14 @@
 	<slot />
 {/if}
 
-<Toaster richColors position="top-center" />
+<Toaster
+	theme={$theme.includes('dark')
+		? 'dark'
+		: $theme === 'system'
+			? window.matchMedia('(prefers-color-scheme: dark)').matches
+				? 'dark'
+				: 'light'
+			: 'light'}
+	richColors
+	position="top-center"
+/>

+ 1 - 1
src/routes/s/[id]/+page.svelte

@@ -155,7 +155,7 @@
 							bind:autoScroll
 							bottomPadding={files.length > 0}
 							sendPrompt={() => {}}
-							continueGeneration={() => {}}
+							continueResponse={() => {}}
 							regenerateResponse={() => {}}
 						/>
 					</div>