Browse Source

Merge pull request #15014 from open-webui/dev

0.6.15
Tim Jaeryang Baek 1 week ago
parent
commit
b5f4c85bb1
100 changed files with 2440 additions and 652 deletions
  1. 36 0
      CHANGELOG.md
  2. 57 0
      backend/open_webui/config.py
  3. 1 0
      backend/open_webui/env.py
  4. 50 2
      backend/open_webui/main.py
  5. 32 2
      backend/open_webui/models/groups.py
  6. 38 13
      backend/open_webui/retrieval/loaders/external_document.py
  7. 4 4
      backend/open_webui/retrieval/loaders/main.py
  8. 19 6
      backend/open_webui/routers/audio.py
  9. 103 9
      backend/open_webui/routers/auths.py
  10. 12 3
      backend/open_webui/routers/files.py
  11. 4 4
      backend/open_webui/routers/notes.py
  12. 22 11
      backend/open_webui/routers/ollama.py
  13. 16 8
      backend/open_webui/routers/retrieval.py
  14. 36 1
      backend/open_webui/routers/users.py
  15. 37 51
      backend/open_webui/socket/main.py
  16. BIN
      backend/open_webui/static/apple-touch-icon.png
  17. 0 0
      backend/open_webui/static/custom.css
  18. 1 1
      backend/open_webui/utils/access_control.py
  19. 3 1
      backend/open_webui/utils/auth.py
  20. 32 14
      backend/open_webui/utils/middleware.py
  21. 2 2
      backend/open_webui/utils/oauth.py
  22. 110 0
      backend/open_webui/utils/telemetry/metrics.py
  23. 10 1
      backend/open_webui/utils/telemetry/setup.py
  24. 2 2
      backend/open_webui/utils/tools.py
  25. 2 2
      backend/requirements.txt
  26. 2 2
      package-lock.json
  27. 1 1
      package.json
  28. 2 2
      pyproject.toml
  29. 1 0
      src/app.html
  30. 27 0
      src/lib/apis/index.ts
  31. 27 0
      src/lib/apis/users/index.ts
  32. 71 20
      src/lib/components/AddConnectionModal.svelte
  33. 1 1
      src/lib/components/ChangelogModal.svelte
  34. 80 0
      src/lib/components/admin/Evaluations/FeedbackModal.svelte
  35. 150 10
      src/lib/components/admin/Evaluations/Feedbacks.svelte
  36. 175 12
      src/lib/components/admin/Evaluations/Leaderboard.svelte
  37. 77 0
      src/lib/components/admin/Evaluations/LeaderboardModal.svelte
  38. 190 172
      src/lib/components/admin/Settings/Audio.svelte
  39. 44 0
      src/lib/components/admin/Settings/Documents.svelte
  40. 1 0
      src/lib/components/admin/Users/Groups.svelte
  41. 8 0
      src/lib/components/admin/Users/Groups/Permissions.svelte
  42. 4 5
      src/lib/components/admin/Users/UserList/EditUserModal.svelte
  43. 24 3
      src/lib/components/channel/MessageInput.svelte
  44. 20 7
      src/lib/components/channel/Messages/Message/ProfilePreview.svelte
  45. 1 1
      src/lib/components/chat/Artifacts.svelte
  46. 36 23
      src/lib/components/chat/Chat.svelte
  47. 3 1
      src/lib/components/chat/Controls/Controls.svelte
  48. 46 4
      src/lib/components/chat/MessageInput.svelte
  49. 4 0
      src/lib/components/chat/Messages.svelte
  50. 2 3
      src/lib/components/chat/Messages/Citations.svelte
  51. 1 1
      src/lib/components/chat/Messages/CitationsModal.svelte
  52. 16 1
      src/lib/components/chat/Messages/Markdown/HTMLToken.svelte
  53. 1 1
      src/lib/components/chat/Messages/MultiResponseMessages.svelte
  54. 1 1
      src/lib/components/chat/Messages/ResponseMessage.svelte
  55. 5 3
      src/lib/components/chat/Messages/ResponseMessage/FollowUps.svelte
  56. 16 2
      src/lib/components/chat/Messages/UserMessage.svelte
  57. 5 2
      src/lib/components/chat/ModelSelector/Selector.svelte
  58. 33 17
      src/lib/components/chat/Navbar.svelte
  59. 21 8
      src/lib/components/chat/Placeholder.svelte
  60. 1 1
      src/lib/components/chat/Settings/About.svelte
  61. 1 1
      src/lib/components/chat/Settings/Account.svelte
  62. 1 1
      src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
  63. 1 0
      src/lib/components/chat/Settings/Audio.svelte
  64. 1 1
      src/lib/components/chat/Settings/Chats.svelte
  65. 6 1
      src/lib/components/chat/Settings/Connections.svelte
  66. 4 2
      src/lib/components/chat/Settings/Connections/Connection.svelte
  67. 10 4
      src/lib/components/chat/Settings/General.svelte
  68. 104 35
      src/lib/components/chat/Settings/Interface.svelte
  69. 1 0
      src/lib/components/chat/Settings/Personalization.svelte
  70. 6 1
      src/lib/components/chat/Settings/Tools.svelte
  71. 41 5
      src/lib/components/chat/SettingsModal.svelte
  72. 60 2
      src/lib/components/chat/ShortcutsModal.svelte
  73. 1 1
      src/lib/components/common/Banner.svelte
  74. 10 1
      src/lib/components/common/SensitiveInput.svelte
  75. 2 0
      src/lib/components/common/Switch.svelte
  76. 2 2
      src/lib/components/common/Tags.svelte
  77. 3 0
      src/lib/components/common/Tags/TagInput.svelte
  78. 5 2
      src/lib/components/common/Tags/TagList.svelte
  79. 19 0
      src/lib/components/icons/Code.svelte
  80. 1 0
      src/lib/components/icons/Cog6.svelte
  81. 1 0
      src/lib/components/icons/Minus.svelte
  82. 1 0
      src/lib/components/icons/Plus.svelte
  83. 20 0
      src/lib/components/icons/Settings.svelte
  84. 16 0
      src/lib/components/icons/SignOut.svelte
  85. 19 0
      src/lib/components/icons/UserGroup.svelte
  86. 9 9
      src/lib/components/layout/Navbar/Menu.svelte
  87. 4 13
      src/lib/components/layout/Sidebar.svelte
  88. 77 102
      src/lib/components/layout/Sidebar/UserMenu.svelte
  89. 1 1
      src/lib/components/notes/Notes/NoteMenu.svelte
  90. 1 1
      src/lib/components/notes/RecordMenu.svelte
  91. 28 2
      src/lib/i18n/locales/ar-BH/translation.json
  92. 28 2
      src/lib/i18n/locales/ar/translation.json
  93. 28 2
      src/lib/i18n/locales/bg-BG/translation.json
  94. 28 2
      src/lib/i18n/locales/bn-BD/translation.json
  95. 28 2
      src/lib/i18n/locales/bo-TB/translation.json
  96. 29 3
      src/lib/i18n/locales/ca-ES/translation.json
  97. 28 2
      src/lib/i18n/locales/ceb-PH/translation.json
  98. 28 2
      src/lib/i18n/locales/cs-CZ/translation.json
  99. 29 3
      src/lib/i18n/locales/da-DK/translation.json
  100. 32 6
      src/lib/i18n/locales/de-DE/translation.json

+ 36 - 0
CHANGELOG.md

@@ -5,6 +5,42 @@ 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.6.15] - 2025-06-16
+
+### Added
+
+- 🖼️ **Global Image Compression Option**: Effortlessly set image compression globally so all image uploads and outputs are optimized, speeding up load times and saving bandwidth—perfect for teams dealing with large files or limited network resources.
+- 🎤 **Custom Speech-to-Text Content-Type for Transcription**: Define custom content types for audio transcription, ensuring compatibility with diverse audio sources and unlocking smoother, more accurate transcriptions in advanced setups.
+- 🗂️ **LDAP Group Synchronization (Experimental)**: Automatically sync user groups from your LDAP directory directly into Open WebUI for seamless enterprise access management—simplifies identity integration and governance across your organization.
+- 📈 **OpenTelemetry Metrics via OTLP Exporter (Experimental)**: Gain enterprise-grade analytics and monitor your AI usage in real time with experimental OpenTelemetry Metrics support—connect to any OTLP-compatible backend for instant insights into performance, load, and user interactions.
+- 🕰️ **See User Message Timestamps on Hover (Chat Bubble UI)**: Effortlessly check when any user message was sent by hovering over it in Chat Bubble mode—no more switching screens or digging through logs for context.
+- 🗂️ **Leaderboard Sorting Options**: Sort the leaderboard directly in the UI for a clearer, more actionable view of top performers, models, or tools—making analysis and recognition quick and easy for teams.
+- 🏆 **Evaluation Details Modal in Feedbacks and Leaderboard**: Dive deeper with new modals that display detailed evaluation information when reviewing feedbacks and leaderboard rankings—accelerates learning, progress tracking, and quality improvement.
+- 🔄 **Support for Multiple Pages in External Document Loaders**: Effortlessly extract and work with content spanning multiple pages in external documents, giving you complete flexibility for in-depth research and document workflows.
+- 🌐 **New Accessibility Enhancements Across the Interface**: Benefit from significant accessibility improvements—tab navigation, ARIA roles/labels, better high-contrast text/modes, accessible modals, and more—making Open WebUI more usable and equitable for everyone, including those using assistive technologies.
+- ⚡ **Performance & Stability Upgrades Across Frontend and Backend**: Enjoy a smoother, more reliable experience with numerous behind-the-scenes optimizations and refactoring on both frontend and backend—resulting in faster load times, fewer errors, and even greater stability throughout your workflows.
+- 🌏 **Updated and Expanded Localizations**: Enjoy improved, up-to-date translations for Finnish, German (now with model pinning features), Korean, Russian, Simplified Chinese, Spanish, and more—making every interaction smoother, clearer, and more intuitive for international users.
+
+### Fixed
+
+- 🦾 **Ollama Error Messages More Descriptive**: Receive clearer, more actionable error messages when something goes wrong with Ollama models—making troubleshooting and user support faster and more effective.
+- 🌐 **Bypass Webloader Now Works as Expected**: Resolved an issue where the "bypass webloader" feature failed to function correctly, ensuring web search bypasses operate smoothly and reliably for lighter, faster query results.
+- 🔍 **Prevent Redundant Documents in Citation List**: The expanded citation list no longer shows duplicate documents, offering a cleaner, easier-to-digest reference experience when reviewing sources in knowledge and research workflows.
+- 🛡️ **Trusted Header Email Matching is Now Case-Insensitive**: Fixed a critical authentication issue where email case sensitivity could cause secure headers to mismatch, ensuring robust, seamless login and session management in all environments.
+- ⚙️ **Direct Tool Server Input Accepts Empty Strings**: You can now submit direct tool server commands without unexpected errors when passing empty-string values, improving integration and automation efficiency.
+- 📄 **Citation Page Number for Page 1 is Now Displayed**: Corrected an oversight where references for page 1 documents were missing the page number; citations are now always accurate and fully visible.
+- 📒 **Notes Access Restored**: Fixed an issue where some users could not access their notes—everyone can now view and manage their notes reliably, ensuring seamless documentation and workflow continuity.
+- 🛑 **OAuth Callback Double-Slash Issue Resolved**: Fixed rare cases where an extra slash in OAuth callbacks caused failed logins or redirects, making third-party login integrations more reliable.
+
+### Changed
+
+- 🔑 **Dedicated Permission for System Prompts**: System prompt access is now controlled by its own specific permission instead of being grouped with general chat controls, empowering admins with finer-grained management over who can view or modify system prompts for enhanced security and workflow customization.
+- 🛠️ **YouTube Transcript API and python-pptx Updated**: Enjoy better performance, reliability, and broader compatibility thanks to underlying library upgrades—less friction with media-rich and presentation workflows.
+
+### Removed
+
+- 🗑️ **Console Logging Disabled in Production**: All 'console.log' and 'console.debug' statements are now disabled in production, guaranteeing improved security and cleaner browser logs for end users by removing extraneous technical output.
+
 ## [0.6.14] - 2025-06-10
 
 ### Added

+ 57 - 0
backend/open_webui/config.py

@@ -1077,6 +1077,10 @@ USER_PERMISSIONS_CHAT_CONTROLS = (
     os.environ.get("USER_PERMISSIONS_CHAT_CONTROLS", "True").lower() == "true"
 )
 
+USER_PERMISSIONS_CHAT_SYSTEM_PROMPT = (
+    os.environ.get("USER_PERMISSIONS_CHAT_SYSTEM_PROMPT", "True").lower() == "true"
+)
+
 USER_PERMISSIONS_CHAT_FILE_UPLOAD = (
     os.environ.get("USER_PERMISSIONS_CHAT_FILE_UPLOAD", "True").lower() == "true"
 )
@@ -1162,6 +1166,7 @@ DEFAULT_USER_PERMISSIONS = {
     },
     "chat": {
         "controls": USER_PERMISSIONS_CHAT_CONTROLS,
+        "system_prompt": USER_PERMISSIONS_CHAT_SYSTEM_PROMPT,
         "file_upload": USER_PERMISSIONS_CHAT_FILE_UPLOAD,
         "delete": USER_PERMISSIONS_CHAT_DELETE,
         "edit": USER_PERMISSIONS_CHAT_EDIT,
@@ -2102,6 +2107,27 @@ RAG_FILE_MAX_SIZE = PersistentConfig(
     ),
 )
 
+FILE_IMAGE_COMPRESSION_WIDTH = PersistentConfig(
+    "FILE_IMAGE_COMPRESSION_WIDTH",
+    "file.image_compression_width",
+    (
+        int(os.environ.get("FILE_IMAGE_COMPRESSION_WIDTH"))
+        if os.environ.get("FILE_IMAGE_COMPRESSION_WIDTH")
+        else None
+    ),
+)
+
+FILE_IMAGE_COMPRESSION_HEIGHT = PersistentConfig(
+    "FILE_IMAGE_COMPRESSION_HEIGHT",
+    "file.image_compression_height",
+    (
+        int(os.environ.get("FILE_IMAGE_COMPRESSION_HEIGHT"))
+        if os.environ.get("FILE_IMAGE_COMPRESSION_HEIGHT")
+        else None
+    ),
+)
+
+
 RAG_ALLOWED_FILE_EXTENSIONS = PersistentConfig(
     "RAG_ALLOWED_FILE_EXTENSIONS",
     "rag.file.allowed_extensions",
@@ -2901,6 +2927,18 @@ AUDIO_STT_MODEL = PersistentConfig(
     os.getenv("AUDIO_STT_MODEL", ""),
 )
 
+AUDIO_STT_SUPPORTED_CONTENT_TYPES = PersistentConfig(
+    "AUDIO_STT_SUPPORTED_CONTENT_TYPES",
+    "audio.stt.supported_content_types",
+    [
+        content_type.strip()
+        for content_type in os.environ.get(
+            "AUDIO_STT_SUPPORTED_CONTENT_TYPES", ""
+        ).split(",")
+        if content_type.strip()
+    ],
+)
+
 AUDIO_STT_AZURE_API_KEY = PersistentConfig(
     "AUDIO_STT_AZURE_API_KEY",
     "audio.stt.azure.api_key",
@@ -3075,3 +3113,22 @@ LDAP_VALIDATE_CERT = PersistentConfig(
 LDAP_CIPHERS = PersistentConfig(
     "LDAP_CIPHERS", "ldap.server.ciphers", os.environ.get("LDAP_CIPHERS", "ALL")
 )
+
+# For LDAP Group Management
+ENABLE_LDAP_GROUP_MANAGEMENT = PersistentConfig(
+    "ENABLE_LDAP_GROUP_MANAGEMENT",
+    "ldap.group.enable_management",
+    os.environ.get("ENABLE_LDAP_GROUP_MANAGEMENT", "False").lower() == "true",
+)
+
+ENABLE_LDAP_GROUP_CREATION = PersistentConfig(
+    "ENABLE_LDAP_GROUP_CREATION",
+    "ldap.group.enable_creation",
+    os.environ.get("ENABLE_LDAP_GROUP_CREATION", "False").lower() == "true",
+)
+
+LDAP_ATTRIBUTE_FOR_GROUPS = PersistentConfig(
+    "LDAP_ATTRIBUTE_FOR_GROUPS",
+    "ldap.server.attribute_for_groups",
+    os.environ.get("LDAP_ATTRIBUTE_FOR_GROUPS", "memberOf"),
+)

+ 1 - 0
backend/open_webui/env.py

@@ -539,6 +539,7 @@ AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
 ####################################
 
 ENABLE_OTEL = os.environ.get("ENABLE_OTEL", "False").lower() == "true"
+ENABLE_OTEL_METRICS = os.environ.get("ENABLE_OTEL_METRICS", "False").lower() == "true"
 OTEL_EXPORTER_OTLP_ENDPOINT = os.environ.get(
     "OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"
 )

+ 50 - 2
backend/open_webui/main.py

@@ -57,6 +57,8 @@ from open_webui.utils.logger import start_logger
 from open_webui.socket.main import (
     app as socket_app,
     periodic_usage_pool_cleanup,
+    get_models_in_use,
+    get_active_user_ids,
 )
 from open_webui.routers import (
     audio,
@@ -157,6 +159,7 @@ from open_webui.config import (
     # Audio
     AUDIO_STT_ENGINE,
     AUDIO_STT_MODEL,
+    AUDIO_STT_SUPPORTED_CONTENT_TYPES,
     AUDIO_STT_OPENAI_API_BASE_URL,
     AUDIO_STT_OPENAI_API_KEY,
     AUDIO_STT_AZURE_API_KEY,
@@ -208,6 +211,8 @@ from open_webui.config import (
     RAG_ALLOWED_FILE_EXTENSIONS,
     RAG_FILE_MAX_COUNT,
     RAG_FILE_MAX_SIZE,
+    FILE_IMAGE_COMPRESSION_WIDTH,
+    FILE_IMAGE_COMPRESSION_HEIGHT,
     RAG_OPENAI_API_BASE_URL,
     RAG_OPENAI_API_KEY,
     RAG_AZURE_OPENAI_BASE_URL,
@@ -349,6 +354,10 @@ from open_webui.config import (
     LDAP_CA_CERT_FILE,
     LDAP_VALIDATE_CERT,
     LDAP_CIPHERS,
+    # LDAP Group Management
+    ENABLE_LDAP_GROUP_MANAGEMENT,
+    ENABLE_LDAP_GROUP_CREATION,
+    LDAP_ATTRIBUTE_FOR_GROUPS,
     # Misc
     ENV,
     CACHE_DIR,
@@ -676,6 +685,11 @@ app.state.config.LDAP_CA_CERT_FILE = LDAP_CA_CERT_FILE
 app.state.config.LDAP_VALIDATE_CERT = LDAP_VALIDATE_CERT
 app.state.config.LDAP_CIPHERS = LDAP_CIPHERS
 
+# For LDAP Group Management
+app.state.config.ENABLE_LDAP_GROUP_MANAGEMENT = ENABLE_LDAP_GROUP_MANAGEMENT
+app.state.config.ENABLE_LDAP_GROUP_CREATION = ENABLE_LDAP_GROUP_CREATION
+app.state.config.LDAP_ATTRIBUTE_FOR_GROUPS = LDAP_ATTRIBUTE_FOR_GROUPS
+
 
 app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 app.state.AUTH_TRUSTED_NAME_HEADER = WEBUI_AUTH_TRUSTED_NAME_HEADER
@@ -701,9 +715,13 @@ app.state.config.TOP_K = RAG_TOP_K
 app.state.config.TOP_K_RERANKER = RAG_TOP_K_RERANKER
 app.state.config.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD
 app.state.config.HYBRID_BM25_WEIGHT = RAG_HYBRID_BM25_WEIGHT
+
+
 app.state.config.ALLOWED_FILE_EXTENSIONS = RAG_ALLOWED_FILE_EXTENSIONS
 app.state.config.FILE_MAX_SIZE = RAG_FILE_MAX_SIZE
 app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT
+app.state.config.FILE_IMAGE_COMPRESSION_WIDTH = FILE_IMAGE_COMPRESSION_WIDTH
+app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT = FILE_IMAGE_COMPRESSION_HEIGHT
 
 
 app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT
@@ -948,10 +966,12 @@ app.state.config.IMAGE_STEPS = IMAGE_STEPS
 #
 ########################################
 
-app.state.config.STT_OPENAI_API_BASE_URL = AUDIO_STT_OPENAI_API_BASE_URL
-app.state.config.STT_OPENAI_API_KEY = AUDIO_STT_OPENAI_API_KEY
 app.state.config.STT_ENGINE = AUDIO_STT_ENGINE
 app.state.config.STT_MODEL = AUDIO_STT_MODEL
+app.state.config.STT_SUPPORTED_CONTENT_TYPES = AUDIO_STT_SUPPORTED_CONTENT_TYPES
+
+app.state.config.STT_OPENAI_API_BASE_URL = AUDIO_STT_OPENAI_API_BASE_URL
+app.state.config.STT_OPENAI_API_KEY = AUDIO_STT_OPENAI_API_KEY
 
 app.state.config.WHISPER_MODEL = WHISPER_MODEL
 app.state.config.WHISPER_VAD_FILTER = WHISPER_VAD_FILTER
@@ -1362,6 +1382,17 @@ async def chat_completion(
             request, response, form_data, user, metadata, model, events, tasks
         )
     except Exception as e:
+        log.debug(f"Error in chat completion: {e}")
+        if metadata.get("chat_id") and metadata.get("message_id"):
+            # Update the chat message with the error
+            Chats.upsert_message_to_chat_by_id_and_message_id(
+                metadata["chat_id"],
+                metadata["message_id"],
+                {
+                    "error": {"content": str(e)},
+                },
+            )
+
         raise HTTPException(
             status_code=status.HTTP_400_BAD_REQUEST,
             detail=str(e),
@@ -1533,6 +1564,10 @@ async def get_app_config(request: Request):
                 "file": {
                     "max_size": app.state.config.FILE_MAX_SIZE,
                     "max_count": app.state.config.FILE_MAX_COUNT,
+                    "image_compression": {
+                        "width": app.state.config.FILE_IMAGE_COMPRESSION_WIDTH,
+                        "height": app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT,
+                    },
                 },
                 "permissions": {**app.state.config.USER_PERMISSIONS},
                 "google_drive": {
@@ -1618,6 +1653,19 @@ async def get_app_changelog():
     return {key: CHANGELOG[key] for idx, key in enumerate(CHANGELOG) if idx < 5}
 
 
+@app.get("/api/usage")
+async def get_current_usage(user=Depends(get_verified_user)):
+    """
+    Get current usage statistics for Open WebUI.
+    This is an experimental endpoint and subject to change.
+    """
+    try:
+        return {"model_ids": get_models_in_use(), "user_ids": get_active_user_ids()}
+    except Exception as e:
+        log.error(f"Error getting usage statistics: {e}")
+        raise HTTPException(status_code=500, detail="Internal Server Error")
+
+
 ############################
 # OAuth Login & Callback
 ############################

+ 32 - 2
backend/open_webui/models/groups.py

@@ -207,9 +207,39 @@ class GroupTable:
             except Exception:
                 return False
 
-    def sync_user_groups_by_group_names(
+    def create_groups_by_group_names(
         self, user_id: str, group_names: list[str]
-    ) -> bool:
+    ) -> list[GroupModel]:
+
+        # check for existing groups
+        existing_groups = self.get_groups()
+        existing_group_names = {group.name for group in existing_groups}
+
+        new_groups = []
+
+        with get_db() as db:
+            for group_name in group_names:
+                if group_name not in existing_group_names:
+                    new_group = GroupModel(
+                        id=str(uuid.uuid4()),
+                        user_id=user_id,
+                        name=group_name,
+                        description="",
+                        created_at=int(time.time()),
+                        updated_at=int(time.time()),
+                    )
+                    try:
+                        result = Group(**new_group.model_dump())
+                        db.add(result)
+                        db.commit()
+                        db.refresh(result)
+                        new_groups.append(GroupModel.model_validate(result))
+                    except Exception as e:
+                        log.exception(e)
+                        continue
+            return new_groups
+
+    def sync_groups_by_group_names(self, user_id: str, group_names: list[str]) -> bool:
         with get_db() as db:
             try:
                 groups = db.query(Group).filter(Group.name.in_(group_names)).all()

+ 38 - 13
backend/open_webui/retrieval/loaders/external_document.py

@@ -1,5 +1,5 @@
 import requests
-import logging
+import logging, os
 from typing import Iterator, List, Union
 
 from langchain_core.document_loaders import BaseLoader
@@ -25,7 +25,7 @@ class ExternalDocumentLoader(BaseLoader):
         self.file_path = file_path
         self.mime_type = mime_type
 
-    def load(self) -> list[Document]:
+    def load(self) -> List[Document]:
         with open(self.file_path, "rb") as f:
             data = f.read()
 
@@ -36,23 +36,48 @@ class ExternalDocumentLoader(BaseLoader):
         if self.api_key is not None:
             headers["Authorization"] = f"Bearer {self.api_key}"
 
+        try:
+            headers["X-Filename"] = os.path.basename(self.file_path)
+        except:
+            pass
+
         url = self.url
         if url.endswith("/"):
             url = url[:-1]
 
-        r = requests.put(f"{url}/process", data=data, headers=headers)
+        try:
+            response = requests.put(f"{url}/process", data=data, headers=headers)
+        except Exception as e:
+            log.error(f"Error connecting to endpoint: {e}")
+            raise Exception(f"Error connecting to endpoint: {e}")
+
+        if response.ok:
 
-        if r.ok:
-            res = r.json()
+            response_data = response.json()
+            if response_data:
+                if isinstance(response_data, dict):
+                    return [
+                        Document(
+                            page_content=response_data.get("page_content"),
+                            metadata=response_data.get("metadata"),
+                        )
+                    ]
+                elif isinstance(response_data, list):
+                    documents = []
+                    for document in response_data:
+                        documents.append(
+                            Document(
+                                page_content=document.get("page_content"),
+                                metadata=document.get("metadata"),
+                            )
+                        )
+                    return documents
+                else:
+                    raise Exception("Error loading document: Unable to parse content")
 
-            if res:
-                return [
-                    Document(
-                        page_content=res.get("page_content"),
-                        metadata=res.get("metadata"),
-                    )
-                ]
             else:
                 raise Exception("Error loading document: No content returned")
         else:
-            raise Exception(f"Error loading document: {r.status_code} {r.text}")
+            raise Exception(
+                f"Error loading document: {response.status_code} {response.text}"
+            )

+ 4 - 4
backend/open_webui/retrieval/loaders/main.py

@@ -162,15 +162,15 @@ class DoclingLoader:
                     if picture_description_mode == "local" and self.params.get(
                         "picture_description_local", {}
                     ):
-                        params["picture_description_local"] = self.params.get(
-                            "picture_description_local", {}
+                        params["picture_description_local"] = json.dumps(
+                            self.params.get("picture_description_local", {})
                         )
 
                     elif picture_description_mode == "api" and self.params.get(
                         "picture_description_api", {}
                     ):
-                        params["picture_description_api"] = self.params.get(
-                            "picture_description_api", {}
+                        params["picture_description_api"] = json.dumps(
+                            self.params.get("picture_description_api", {})
                         )
 
                 if self.params.get("ocr_engine") and self.params.get("ocr_lang"):

+ 19 - 6
backend/open_webui/routers/audio.py

@@ -10,7 +10,7 @@ from pydub.silence import split_on_silence
 from concurrent.futures import ThreadPoolExecutor
 from typing import Optional
 
-
+from fnmatch import fnmatch
 import aiohttp
 import aiofiles
 import requests
@@ -168,6 +168,7 @@ class STTConfigForm(BaseModel):
     OPENAI_API_KEY: str
     ENGINE: str
     MODEL: str
+    SUPPORTED_CONTENT_TYPES: list[str] = []
     WHISPER_MODEL: str
     DEEPGRAM_API_KEY: str
     AZURE_API_KEY: str
@@ -202,6 +203,7 @@ async def get_audio_config(request: Request, user=Depends(get_admin_user)):
             "OPENAI_API_KEY": request.app.state.config.STT_OPENAI_API_KEY,
             "ENGINE": request.app.state.config.STT_ENGINE,
             "MODEL": request.app.state.config.STT_MODEL,
+            "SUPPORTED_CONTENT_TYPES": request.app.state.config.STT_SUPPORTED_CONTENT_TYPES,
             "WHISPER_MODEL": request.app.state.config.WHISPER_MODEL,
             "DEEPGRAM_API_KEY": request.app.state.config.DEEPGRAM_API_KEY,
             "AZURE_API_KEY": request.app.state.config.AUDIO_STT_AZURE_API_KEY,
@@ -236,6 +238,10 @@ async def update_audio_config(
     request.app.state.config.STT_OPENAI_API_KEY = form_data.stt.OPENAI_API_KEY
     request.app.state.config.STT_ENGINE = form_data.stt.ENGINE
     request.app.state.config.STT_MODEL = form_data.stt.MODEL
+    request.app.state.config.STT_SUPPORTED_CONTENT_TYPES = (
+        form_data.stt.SUPPORTED_CONTENT_TYPES
+    )
+
     request.app.state.config.WHISPER_MODEL = form_data.stt.WHISPER_MODEL
     request.app.state.config.DEEPGRAM_API_KEY = form_data.stt.DEEPGRAM_API_KEY
     request.app.state.config.AUDIO_STT_AZURE_API_KEY = form_data.stt.AZURE_API_KEY
@@ -250,6 +256,8 @@ async def update_audio_config(
         request.app.state.faster_whisper_model = set_faster_whisper_model(
             form_data.stt.WHISPER_MODEL, WHISPER_MODEL_AUTO_UPDATE
         )
+    else:
+        request.app.state.faster_whisper_model = None
 
     return {
         "tts": {
@@ -269,6 +277,7 @@ async def update_audio_config(
             "OPENAI_API_KEY": request.app.state.config.STT_OPENAI_API_KEY,
             "ENGINE": request.app.state.config.STT_ENGINE,
             "MODEL": request.app.state.config.STT_MODEL,
+            "SUPPORTED_CONTENT_TYPES": request.app.state.config.STT_SUPPORTED_CONTENT_TYPES,
             "WHISPER_MODEL": request.app.state.config.WHISPER_MODEL,
             "DEEPGRAM_API_KEY": request.app.state.config.DEEPGRAM_API_KEY,
             "AZURE_API_KEY": request.app.state.config.AUDIO_STT_AZURE_API_KEY,
@@ -628,7 +637,7 @@ def transcription_handler(request, file_path, metadata):
 
             # Make request to Deepgram API
             r = requests.post(
-                "https://api.deepgram.com/v1/listen",
+                "https://api.deepgram.com/v1/listen?smart_format=true",
                 headers=headers,
                 params=params,
                 data=file_data,
@@ -910,10 +919,14 @@ def transcription(
 ):
     log.info(f"file.content_type: {file.content_type}")
 
-    SUPPORTED_CONTENT_TYPES = {"video/webm"}  # Extend if you add more video types!
-    if not (
-        file.content_type.startswith("audio/")
-        or file.content_type in SUPPORTED_CONTENT_TYPES
+    supported_content_types = request.app.state.config.STT_SUPPORTED_CONTENT_TYPES or [
+        "audio/*",
+        "video/webm",
+    ]
+
+    if not any(
+        fnmatch(file.content_type, content_type)
+        for content_type in supported_content_types
     ):
         raise HTTPException(
             status_code=status.HTTP_400_BAD_REQUEST,

+ 103 - 9
backend/open_webui/routers/auths.py

@@ -55,9 +55,8 @@ from typing import Optional, List
 
 from ssl import CERT_NONE, CERT_REQUIRED, PROTOCOL_TLS
 
-if ENABLE_LDAP.value:
-    from ldap3 import Server, Connection, NONE, Tls
-    from ldap3.utils.conv import escape_filter_chars
+from ldap3 import Server, Connection, NONE, Tls
+from ldap3.utils.conv import escape_filter_chars
 
 router = APIRouter()
 
@@ -229,14 +228,30 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
         if not connection_app.bind():
             raise HTTPException(400, detail="Application account bind failed")
 
+        ENABLE_LDAP_GROUP_MANAGEMENT = (
+            request.app.state.config.ENABLE_LDAP_GROUP_MANAGEMENT
+        )
+        ENABLE_LDAP_GROUP_CREATION = request.app.state.config.ENABLE_LDAP_GROUP_CREATION
+        LDAP_ATTRIBUTE_FOR_GROUPS = request.app.state.config.LDAP_ATTRIBUTE_FOR_GROUPS
+
+        search_attributes = [
+            f"{LDAP_ATTRIBUTE_FOR_USERNAME}",
+            f"{LDAP_ATTRIBUTE_FOR_MAIL}",
+            "cn",
+        ]
+
+        if ENABLE_LDAP_GROUP_MANAGEMENT:
+            search_attributes.append(f"{LDAP_ATTRIBUTE_FOR_GROUPS}")
+            log.info(
+                f"LDAP Group Management enabled. Adding {LDAP_ATTRIBUTE_FOR_GROUPS} to search attributes"
+            )
+
+        log.info(f"LDAP search attributes: {search_attributes}")
+
         search_success = connection_app.search(
             search_base=LDAP_SEARCH_BASE,
             search_filter=f"(&({LDAP_ATTRIBUTE_FOR_USERNAME}={escape_filter_chars(form_data.user.lower())}){LDAP_SEARCH_FILTERS})",
-            attributes=[
-                f"{LDAP_ATTRIBUTE_FOR_USERNAME}",
-                f"{LDAP_ATTRIBUTE_FOR_MAIL}",
-                "cn",
-            ],
+            attributes=search_attributes,
         )
 
         if not search_success or not connection_app.entries:
@@ -259,6 +274,69 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
         cn = str(entry["cn"])
         user_dn = entry.entry_dn
 
+        user_groups = []
+        if ENABLE_LDAP_GROUP_MANAGEMENT and LDAP_ATTRIBUTE_FOR_GROUPS in entry:
+            group_dns = entry[LDAP_ATTRIBUTE_FOR_GROUPS]
+            log.info(f"LDAP raw group DNs for user {username}: {group_dns}")
+
+            if group_dns:
+                log.info(f"LDAP group_dns original: {group_dns}")
+                log.info(f"LDAP group_dns type: {type(group_dns)}")
+                log.info(f"LDAP group_dns length: {len(group_dns)}")
+
+                if hasattr(group_dns, "value"):
+                    group_dns = group_dns.value
+                    log.info(f"Extracted .value property: {group_dns}")
+                elif hasattr(group_dns, "__iter__") and not isinstance(
+                    group_dns, (str, bytes)
+                ):
+                    group_dns = list(group_dns)
+                    log.info(f"Converted to list: {group_dns}")
+
+                if isinstance(group_dns, list):
+                    group_dns = [str(item) for item in group_dns]
+                else:
+                    group_dns = [str(group_dns)]
+
+                log.info(
+                    f"LDAP group_dns after processing - type: {type(group_dns)}, length: {len(group_dns)}"
+                )
+
+                for group_idx, group_dn in enumerate(group_dns):
+                    group_dn = str(group_dn)
+                    log.info(f"Processing group DN #{group_idx + 1}: {group_dn}")
+
+                    try:
+                        group_cn = None
+
+                        for item in group_dn.split(","):
+                            item = item.strip()
+                            if item.upper().startswith("CN="):
+                                group_cn = item[3:]
+                                break
+
+                        if group_cn:
+                            user_groups.append(group_cn)
+
+                        else:
+                            log.warning(
+                                f"Could not extract CN from group DN: {group_dn}"
+                            )
+                    except Exception as e:
+                        log.warning(
+                            f"Failed to extract group name from DN {group_dn}: {e}"
+                        )
+
+                log.info(
+                    f"LDAP groups for user {username}: {user_groups} (total: {len(user_groups)})"
+                )
+            else:
+                log.info(f"No groups found for user {username}")
+        elif ENABLE_LDAP_GROUP_MANAGEMENT:
+            log.warning(
+                f"LDAP Group Management enabled but {LDAP_ATTRIBUTE_FOR_GROUPS} attribute not found in user entry"
+            )
+
         if username == form_data.user.lower():
             connection_user = Connection(
                 server,
@@ -334,6 +412,22 @@ async def ldap_auth(request: Request, response: Response, form_data: LdapForm):
                     user.id, request.app.state.config.USER_PERMISSIONS
                 )
 
+                if (
+                    user.role != "admin"
+                    and ENABLE_LDAP_GROUP_MANAGEMENT
+                    and user_groups
+                ):
+                    if ENABLE_LDAP_GROUP_CREATION:
+                        Groups.create_groups_by_group_names(user.id, user_groups)
+
+                    try:
+                        Groups.sync_groups_by_group_names(user.id, user_groups)
+                        log.info(
+                            f"Successfully synced groups for user {user.id}: {user_groups}"
+                        )
+                    except Exception as e:
+                        log.error(f"Failed to sync groups for user {user.id}: {e}")
+
                 return {
                     "token": token,
                     "token_type": "Bearer",
@@ -386,7 +480,7 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
             group_names = [name.strip() for name in group_names if name.strip()]
 
             if group_names:
-                Groups.sync_user_groups_by_group_names(user.id, group_names)
+                Groups.sync_groups_by_group_names(user.id, group_names)
 
     elif WEBUI_AUTH == False:
         admin_email = "admin@localhost"

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

@@ -155,9 +155,18 @@ def upload_file(
         if process:
             try:
                 if file.content_type:
-                    if file.content_type.startswith("audio/") or file.content_type in {
-                        "video/webm"
-                    }:
+                    stt_supported_content_types = (
+                        request.app.state.config.STT_SUPPORTED_CONTENT_TYPES
+                        or [
+                            "audio/*",
+                            "video/webm",
+                        ]
+                    )
+
+                    if any(
+                        fnmatch(file.content_type, content_type)
+                        for content_type in stt_supported_content_types
+                    ):
                         file_path = Storage.get_file(file_path)
                         result = transcribe(request, file_path, file_metadata)
 

+ 4 - 4
backend/open_webui/routers/notes.py

@@ -124,9 +124,9 @@ async def get_note_by_id(request: Request, id: str, user=Depends(get_verified_us
             status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
         )
 
-    if user.role != "admin" or (
+    if user.role != "admin" and (
         user.id != note.user_id
-        and not has_access(user.id, type="read", access_control=note.access_control)
+        and (not has_access(user.id, type="read", access_control=note.access_control))
     ):
         raise HTTPException(
             status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()
@@ -158,7 +158,7 @@ async def update_note_by_id(
             status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
         )
 
-    if user.role != "admin" or (
+    if user.role != "admin" and (
         user.id != note.user_id
         and not has_access(user.id, type="write", access_control=note.access_control)
     ):
@@ -197,7 +197,7 @@ async def delete_note_by_id(request: Request, id: str, user=Depends(get_verified
             status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
         )
 
-    if user.role != "admin" or (
+    if user.role != "admin" and (
         user.id != note.user_id
         and not has_access(user.id, type="write", access_control=note.access_control)
     ):

+ 22 - 11
backend/open_webui/routers/ollama.py

@@ -16,6 +16,8 @@ from urllib.parse import urlparse
 import aiohttp
 from aiocache import cached
 import requests
+
+from open_webui.models.chats import Chats
 from open_webui.models.users import UserModel
 
 from open_webui.env import (
@@ -147,8 +149,23 @@ async def send_post_request(
             },
             ssl=AIOHTTP_CLIENT_SESSION_SSL,
         )
-        r.raise_for_status()
 
+        if r.ok is False:
+            try:
+                res = await r.json()
+                await cleanup_response(r, session)
+                if "error" in res:
+                    raise HTTPException(status_code=r.status, detail=res["error"])
+            except HTTPException as e:
+                raise e  # Re-raise HTTPException to be handled by FastAPI
+            except Exception as e:
+                log.error(f"Failed to parse error response: {e}")
+                raise HTTPException(
+                    status_code=r.status,
+                    detail=f"Open WebUI: Server Connection Error",
+                )
+
+        r.raise_for_status()  # Raises an error for bad responses (4xx, 5xx)
         if stream:
             response_headers = dict(r.headers)
 
@@ -168,20 +185,14 @@ async def send_post_request(
             await cleanup_response(r, session)
             return res
 
+    except HTTPException as e:
+        raise e  # Re-raise HTTPException to be handled by FastAPI
     except Exception as e:
-        detail = None
-
-        if r is not None:
-            try:
-                res = await r.json()
-                if "error" in res:
-                    detail = f"Ollama: {res.get('error', 'Unknown error')}"
-            except Exception:
-                detail = f"Ollama: {e}"
+        detail = f"Ollama: {e}"
 
         raise HTTPException(
             status_code=r.status if r else 500,
-            detail=detail if detail else "Open WebUI: Server Connection Error",
+            detail=detail if e else "Open WebUI: Server Connection Error",
         )
 
 

+ 16 - 8
backend/open_webui/routers/retrieval.py

@@ -432,6 +432,8 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
         # File upload settings
         "FILE_MAX_SIZE": request.app.state.config.FILE_MAX_SIZE,
         "FILE_MAX_COUNT": request.app.state.config.FILE_MAX_COUNT,
+        "FILE_IMAGE_COMPRESSION_WIDTH": request.app.state.config.FILE_IMAGE_COMPRESSION_WIDTH,
+        "FILE_IMAGE_COMPRESSION_HEIGHT": request.app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT,
         "ALLOWED_FILE_EXTENSIONS": request.app.state.config.ALLOWED_FILE_EXTENSIONS,
         # Integration settings
         "ENABLE_GOOGLE_DRIVE_INTEGRATION": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
@@ -599,6 +601,8 @@ class ConfigForm(BaseModel):
     # File upload settings
     FILE_MAX_SIZE: Optional[int] = None
     FILE_MAX_COUNT: Optional[int] = None
+    FILE_IMAGE_COMPRESSION_WIDTH: Optional[int] = None
+    FILE_IMAGE_COMPRESSION_HEIGHT: Optional[int] = None
     ALLOWED_FILE_EXTENSIONS: Optional[List[str]] = None
 
     # Integration settings
@@ -847,15 +851,13 @@ async def update_rag_config(
     )
 
     # File upload settings
-    request.app.state.config.FILE_MAX_SIZE = (
-        form_data.FILE_MAX_SIZE
-        if form_data.FILE_MAX_SIZE is not None
-        else request.app.state.config.FILE_MAX_SIZE
+    request.app.state.config.FILE_MAX_SIZE = form_data.FILE_MAX_SIZE
+    request.app.state.config.FILE_MAX_COUNT = form_data.FILE_MAX_COUNT
+    request.app.state.config.FILE_IMAGE_COMPRESSION_WIDTH = (
+        form_data.FILE_IMAGE_COMPRESSION_WIDTH
     )
-    request.app.state.config.FILE_MAX_COUNT = (
-        form_data.FILE_MAX_COUNT
-        if form_data.FILE_MAX_COUNT is not None
-        else request.app.state.config.FILE_MAX_COUNT
+    request.app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT = (
+        form_data.FILE_IMAGE_COMPRESSION_HEIGHT
     )
     request.app.state.config.ALLOWED_FILE_EXTENSIONS = (
         form_data.ALLOWED_FILE_EXTENSIONS
@@ -1025,6 +1027,8 @@ async def update_rag_config(
         # File upload settings
         "FILE_MAX_SIZE": request.app.state.config.FILE_MAX_SIZE,
         "FILE_MAX_COUNT": request.app.state.config.FILE_MAX_COUNT,
+        "FILE_IMAGE_COMPRESSION_WIDTH": request.app.state.config.FILE_IMAGE_COMPRESSION_WIDTH,
+        "FILE_IMAGE_COMPRESSION_HEIGHT": request.app.state.config.FILE_IMAGE_COMPRESSION_HEIGHT,
         "ALLOWED_FILE_EXTENSIONS": request.app.state.config.ALLOWED_FILE_EXTENSIONS,
         # Integration settings
         "ENABLE_GOOGLE_DRIVE_INTEGRATION": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
@@ -1867,6 +1871,10 @@ async def process_web_search(
 
     try:
         if request.app.state.config.BYPASS_WEB_SEARCH_WEB_LOADER:
+            search_results = [
+                item for result in search_results for item in result if result
+            ]
+
             docs = [
                 Document(
                     page_content=result.snippet,

+ 36 - 1
backend/open_webui/routers/users.py

@@ -14,7 +14,11 @@ from open_webui.models.users import (
 )
 
 
-from open_webui.socket.main import get_active_status_by_user_id
+from open_webui.socket.main import (
+    get_active_status_by_user_id,
+    get_active_user_ids,
+    get_user_active_status,
+)
 from open_webui.constants import ERROR_MESSAGES
 from open_webui.env import SRC_LOG_LEVELS
 from fastapi import APIRouter, Depends, HTTPException, Request, status
@@ -29,6 +33,24 @@ log.setLevel(SRC_LOG_LEVELS["MODELS"])
 
 router = APIRouter()
 
+
+############################
+# GetActiveUsers
+############################
+
+
+@router.get("/active")
+async def get_active_users(
+    user=Depends(get_verified_user),
+):
+    """
+    Get a list of active users.
+    """
+    return {
+        "user_ids": get_active_user_ids(),
+    }
+
+
 ############################
 # GetUsers
 ############################
@@ -111,6 +133,7 @@ class SharingPermissions(BaseModel):
 
 class ChatPermissions(BaseModel):
     controls: bool = True
+    system_prompt: bool = True
     file_upload: bool = True
     delete: bool = True
     edit: bool = True
@@ -303,6 +326,18 @@ async def get_user_by_id(user_id: str, user=Depends(get_verified_user)):
         )
 
 
+############################
+# GetUserActiveStatusById
+############################
+
+
+@router.get("/{user_id}/active", response_model=dict)
+async def get_user_active_status_by_id(user_id: str, user=Depends(get_verified_user)):
+    return {
+        "active": get_user_active_status(user_id),
+    }
+
+
 ############################
 # UpdateUserById
 ############################

+ 37 - 51
backend/open_webui/socket/main.py

@@ -135,11 +135,6 @@ async def periodic_usage_pool_cleanup():
                     USAGE_POOL[model_id] = connections
 
                 send_usage = True
-
-            if send_usage:
-                # Emit updated usage information after cleaning
-                await sio.emit("usage", {"models": get_models_in_use()})
-
             await asyncio.sleep(TIMEOUT_DURATION)
     finally:
         release_func()
@@ -157,6 +152,43 @@ def get_models_in_use():
     return models_in_use
 
 
+def get_active_user_ids():
+    """Get the list of active user IDs."""
+    return list(USER_POOL.keys())
+
+
+def get_user_active_status(user_id):
+    """Check if a user is currently active."""
+    return user_id in USER_POOL
+
+
+def get_user_id_from_session_pool(sid):
+    user = SESSION_POOL.get(sid)
+    if user:
+        return user["id"]
+    return None
+
+
+def get_user_ids_from_room(room):
+    active_session_ids = sio.manager.get_participants(
+        namespace="/",
+        room=room,
+    )
+
+    active_user_ids = list(
+        set(
+            [SESSION_POOL.get(session_id[0])["id"] for session_id in active_session_ids]
+        )
+    )
+    return active_user_ids
+
+
+def get_active_status_by_user_id(user_id):
+    if user_id in USER_POOL:
+        return True
+    return False
+
+
 @sio.on("usage")
 async def usage(sid, data):
     if sid in SESSION_POOL:
@@ -170,9 +202,6 @@ async def usage(sid, data):
             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):
@@ -190,10 +219,6 @@ async def connect(sid, environ, auth):
             else:
                 USER_POOL[user.id] = [sid]
 
-            # print(f"user {user.name}({user.id}) connected with session ID {sid}")
-            await sio.emit("user-list", {"user_ids": list(USER_POOL.keys())})
-            await sio.emit("usage", {"models": get_models_in_use()})
-
 
 @sio.on("user-join")
 async def user_join(sid, data):
@@ -221,10 +246,6 @@ async def user_join(sid, data):
     log.debug(f"{channels=}")
     for channel in channels:
         await sio.enter_room(sid, f"channel:{channel.id}")
-
-    # print(f"user {user.name}({user.id}) connected with session ID {sid}")
-
-    await sio.emit("user-list", {"user_ids": list(USER_POOL.keys())})
     return {"id": user.id, "name": user.name}
 
 
@@ -277,12 +298,6 @@ async def channel_events(sid, data):
         )
 
 
-@sio.on("user-list")
-async def user_list(sid):
-    if sid in SESSION_POOL:
-        await sio.emit("user-list", {"user_ids": list(USER_POOL.keys())})
-
-
 @sio.event
 async def disconnect(sid):
     if sid in SESSION_POOL:
@@ -294,8 +309,6 @@ async def disconnect(sid):
 
         if len(USER_POOL[user_id]) == 0:
             del USER_POOL[user_id]
-
-        await sio.emit("user-list", {"user_ids": list(USER_POOL.keys())})
     else:
         pass
         # print(f"Unknown session ID {sid} disconnected")
@@ -388,30 +401,3 @@ def get_event_call(request_info):
 
 
 get_event_caller = get_event_call
-
-
-def get_user_id_from_session_pool(sid):
-    user = SESSION_POOL.get(sid)
-    if user:
-        return user["id"]
-    return None
-
-
-def get_user_ids_from_room(room):
-    active_session_ids = sio.manager.get_participants(
-        namespace="/",
-        room=room,
-    )
-
-    active_user_ids = list(
-        set(
-            [SESSION_POOL.get(session_id[0])["id"] for session_id in active_session_ids]
-        )
-    )
-    return active_user_ids
-
-
-def get_active_status_by_user_id(user_id):
-    if user_id in USER_POOL:
-        return True
-    return False

BIN
backend/open_webui/static/apple-touch-icon.png


+ 0 - 0
backend/open_webui/static/custom.css


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

@@ -60,7 +60,7 @@ def get_permissions(
 
     # Combine permissions from all user groups
     for group in user_groups:
-        group_permissions = group.permissions
+        group_permissions = group.permissions or {}
         permissions = combine_permissions(permissions, group_permissions)
 
     # Ensure all fields from default_permissions are present and filled in

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

@@ -228,7 +228,9 @@ def get_current_user(
             )
         else:
             if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
-                trusted_email = request.headers.get(WEBUI_AUTH_TRUSTED_EMAIL_HEADER)
+                trusted_email = request.headers.get(
+                    WEBUI_AUTH_TRUSTED_EMAIL_HEADER, ""
+                ).lower()
                 if trusted_email and user.email != trusted_email:
                     # Delete the token cookie
                     response.delete_cookie("token")

+ 32 - 14
backend/open_webui/utils/middleware.py

@@ -697,7 +697,7 @@ def apply_params_to_form_data(form_data, model):
         # If custom_params are provided, merge them into params
         params = deep_update(params, custom_params)
 
-    if model.get("ollama"):
+    if model.get("owned_by") == "ollama":
         # Ollama specific parameters
         form_data["options"] = params
     else:
@@ -1078,6 +1078,7 @@ async def process_chat_response(
                             follow_ups = json.loads(follow_ups_string).get(
                                 "follow_ups", []
                             )
+
                             Chats.upsert_message_to_chat_by_id_and_message_id(
                                 metadata["chat_id"],
                                 metadata["message_id"],
@@ -1098,7 +1099,12 @@ async def process_chat_response(
                             pass
 
                 if TASKS.TITLE_GENERATION in tasks:
+                    user_message = get_last_user_message(messages)
+                    if user_message and len(user_message) > 100:
+                        user_message = user_message[:100] + "..."
+
                     if tasks[TASKS.TITLE_GENERATION]:
+
                         res = await generate_title(
                             request,
                             {
@@ -1114,7 +1120,9 @@ async def process_chat_response(
                                 title_string = (
                                     res.get("choices", [])[0]
                                     .get("message", {})
-                                    .get("content", message.get("content", "New Chat"))
+                                    .get(
+                                        "content", message.get("content", user_message)
+                                    )
                                 )
                             else:
                                 title_string = ""
@@ -1125,13 +1133,13 @@ async def process_chat_response(
 
                             try:
                                 title = json.loads(title_string).get(
-                                    "title", "New Chat"
+                                    "title", user_message
                                 )
                             except Exception as e:
                                 title = ""
 
                             if not title:
-                                title = messages[0].get("content", "New Chat")
+                                title = messages[0].get("content", user_message)
 
                             Chats.update_chat_title_by_id(metadata["chat_id"], title)
 
@@ -1142,14 +1150,14 @@ async def process_chat_response(
                                 }
                             )
                     elif len(messages) == 2:
-                        title = messages[0].get("content", "New Chat")
+                        title = messages[0].get("content", user_message)
 
                         Chats.update_chat_title_by_id(metadata["chat_id"], title)
 
                         await event_emitter(
                             {
                                 "type": "chat:title",
-                                "data": message.get("content", "New Chat"),
+                                "data": message.get("content", user_message),
                             }
                         )
 
@@ -2053,28 +2061,38 @@ async def process_chat_response(
                     tools = metadata.get("tools", {})
 
                     results = []
+
                     for tool_call in response_tool_calls:
                         tool_call_id = tool_call.get("id", "")
                         tool_name = tool_call.get("function", {}).get("name", "")
+                        tool_args = tool_call.get("function", {}).get("arguments", "{}")
 
                         tool_function_params = {}
                         try:
                             # json.loads cannot be used because some models do not produce valid JSON
-                            tool_function_params = ast.literal_eval(
-                                tool_call.get("function", {}).get("arguments", "{}")
-                            )
+                            tool_function_params = ast.literal_eval(tool_args)
                         except Exception as e:
                             log.debug(e)
                             # Fallback to JSON parsing
                             try:
-                                tool_function_params = json.loads(
-                                    tool_call.get("function", {}).get("arguments", "{}")
-                                )
+                                tool_function_params = json.loads(tool_args)
                             except Exception as e:
-                                log.debug(
-                                    f"Error parsing tool call arguments: {tool_call.get('function', {}).get('arguments', '{}')}"
+                                log.error(
+                                    f"Error parsing tool call arguments: {tool_args}"
                                 )
 
+                        # Mutate the original tool call response params as they are passed back to the passed
+                        # back to the LLM via the content blocks. If they are in a json block and are invalid json,
+                        # this can cause downstream LLM integrations to fail (e.g. bedrock gateway) where response
+                        # params are not valid json.
+                        # Main case so far is no args = "" = invalid json.
+                        log.debug(
+                            f"Parsed args from {tool_args} to {tool_function_params}"
+                        )
+                        tool_call.setdefault("function", {})["arguments"] = json.dumps(
+                            tool_function_params
+                        )
+
                         tool_result = None
 
                         if tool_name in tools:

+ 2 - 2
backend/open_webui/utils/oauth.py

@@ -537,8 +537,8 @@ class OAuthManager:
             )
         # Redirect back to the frontend with the JWT token
 
-        redirect_base_url = request.app.state.config.WEBUI_URL or request.base_url
-        if isinstance(redirect_base_url, str) and redirect_base_url.endswith("/"):
+        redirect_base_url = str(request.app.state.config.WEBUI_URL or request.base_url)
+        if redirect_base_url.endswith("/"):
             redirect_base_url = redirect_base_url[:-1]
         redirect_url = f"{redirect_base_url}/auth#token={jwt_token}"
 

+ 110 - 0
backend/open_webui/utils/telemetry/metrics.py

@@ -0,0 +1,110 @@
+"""OpenTelemetry metrics bootstrap for Open WebUI.
+
+This module initialises a MeterProvider that sends metrics to an OTLP
+collector. The collector is responsible for exposing a Prometheus
+`/metrics` endpoint – WebUI does **not** expose it directly.
+
+Metrics collected:
+
+* http.server.requests (counter)
+* http.server.duration (histogram, milliseconds)
+
+Attributes used: http.method, http.route, http.status_code
+
+If you wish to add more attributes (e.g. user-agent) you can, but beware of
+high-cardinality label sets.
+"""
+
+from __future__ import annotations
+
+import time
+from typing import Dict, List, Sequence, Any
+
+from fastapi import FastAPI, Request
+from opentelemetry import metrics
+from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
+    OTLPMetricExporter,
+)
+from opentelemetry.sdk.metrics import MeterProvider
+from opentelemetry.sdk.metrics.view import View
+from opentelemetry.sdk.metrics.export import (
+    PeriodicExportingMetricReader,
+)
+from opentelemetry.sdk.resources import SERVICE_NAME, Resource
+
+from open_webui.env import OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT
+
+
+_EXPORT_INTERVAL_MILLIS = 10_000  # 10 seconds
+
+
+def _build_meter_provider() -> MeterProvider:
+    """Return a configured MeterProvider."""
+
+    # Periodic reader pushes metrics over OTLP/gRPC to collector
+    readers: List[PeriodicExportingMetricReader] = [
+        PeriodicExportingMetricReader(
+            OTLPMetricExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT),
+            export_interval_millis=_EXPORT_INTERVAL_MILLIS,
+        )
+    ]
+
+    # Optional view to limit cardinality: drop user-agent etc.
+    views: List[View] = [
+        View(
+            instrument_name="http.server.duration",
+            attribute_keys=["http.method", "http.route", "http.status_code"],
+        ),
+        View(
+            instrument_name="http.server.requests",
+            attribute_keys=["http.method", "http.route", "http.status_code"],
+        ),
+    ]
+
+    provider = MeterProvider(
+        resource=Resource.create({SERVICE_NAME: OTEL_SERVICE_NAME}),
+        metric_readers=list(readers),
+        views=views,
+    )
+    return provider
+
+
+def setup_metrics(app: FastAPI) -> None:
+    """Attach OTel metrics middleware to *app* and initialise provider."""
+
+    metrics.set_meter_provider(_build_meter_provider())
+    meter = metrics.get_meter(__name__)
+
+    # Instruments
+    request_counter = meter.create_counter(
+        name="http.server.requests",
+        description="Total HTTP requests",
+        unit="1",
+    )
+    duration_histogram = meter.create_histogram(
+        name="http.server.duration",
+        description="HTTP request duration",
+        unit="ms",
+    )
+
+    # FastAPI middleware
+    @app.middleware("http")
+    async def _metrics_middleware(request: Request, call_next):
+        start_time = time.perf_counter()
+        response = await call_next(request)
+        elapsed_ms = (time.perf_counter() - start_time) * 1000.0
+
+        # Route template e.g. "/items/{item_id}" instead of real path.
+        route = request.scope.get("route")
+        route_path = getattr(route, "path", request.url.path)
+
+        attrs: Dict[str, str | int] = {
+            "http.method": request.method,
+            "http.route": route_path,
+            "http.status_code": response.status_code,
+        }
+
+        request_counter.add(1, attrs)
+        duration_histogram.record(elapsed_ms, attrs)
+
+        return response

+ 10 - 1
backend/open_webui/utils/telemetry/setup.py

@@ -7,7 +7,12 @@ from sqlalchemy import Engine
 
 from open_webui.utils.telemetry.exporters import LazyBatchSpanProcessor
 from open_webui.utils.telemetry.instrumentors import Instrumentor
-from open_webui.env import OTEL_SERVICE_NAME, OTEL_EXPORTER_OTLP_ENDPOINT
+from open_webui.utils.telemetry.metrics import setup_metrics
+from open_webui.env import (
+    OTEL_SERVICE_NAME,
+    OTEL_EXPORTER_OTLP_ENDPOINT,
+    ENABLE_OTEL_METRICS,
+)
 
 
 def setup(app: FastAPI, db_engine: Engine):
@@ -21,3 +26,7 @@ def setup(app: FastAPI, db_engine: Engine):
     exporter = OTLPSpanExporter(endpoint=OTEL_EXPORTER_OTLP_ENDPOINT)
     trace.get_tracer_provider().add_span_processor(LazyBatchSpanProcessor(exporter))
     Instrumentor(app=app, db_engine=db_engine).instrument()
+
+    # set up metrics only if enabled
+    if ENABLE_OTEL_METRICS:
+        setup_metrics(app)

+ 2 - 2
backend/open_webui/utils/tools.py

@@ -479,7 +479,7 @@ async def get_tool_server_data(token: str, url: str) -> Dict[str, Any]:
         "specs": convert_openapi_to_tool_payload(res),
     }
 
-    log.info("Fetched data:", data)
+    log.info(f"Fetched data: {data}")
     return data
 
 
@@ -644,5 +644,5 @@ async def execute_tool_server(
 
     except Exception as err:
         error = str(err)
-        log.exception("API Request Error:", error)
+        log.exception(f"API Request Error: {error}")
         return {"error": error}

+ 2 - 2
backend/requirements.txt

@@ -66,7 +66,7 @@ pypdf==4.3.1
 fpdf2==2.8.2
 pymdown-extensions==10.14.2
 docx2txt==0.8
-python-pptx==1.0.0
+python-pptx==1.0.2
 unstructured==0.16.17
 nltk==3.9.1
 Markdown==3.7
@@ -95,7 +95,7 @@ authlib==1.4.1
 
 black==25.1.0
 langfuse==2.44.0
-youtube-transcript-api==1.0.3
+youtube-transcript-api==1.1.0
 pytube==15.0.0
 
 extract_msg

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
 	"name": "open-webui",
-	"version": "0.6.14",
+	"version": "0.6.15",
 	"lockfileVersion": 3,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "open-webui",
-			"version": "0.6.14",
+			"version": "0.6.15",
 			"dependencies": {
 				"@azure/msal-browser": "^4.5.0",
 				"@codemirror/lang-javascript": "^6.2.2",

+ 1 - 1
package.json

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

+ 2 - 2
pyproject.toml

@@ -73,7 +73,7 @@ dependencies = [
     "fpdf2==2.8.2",
     "pymdown-extensions==10.14.2",
     "docx2txt==0.8",
-    "python-pptx==1.0.0",
+    "python-pptx==1.0.2",
     "unstructured==0.16.17",
     "nltk==3.9.1",
     "Markdown==3.7",
@@ -102,7 +102,7 @@ dependencies = [
 
     "black==25.1.0",
     "langfuse==2.44.0",
-    "youtube-transcript-api==1.0.3",
+    "youtube-transcript-api==1.1.0",
     "pytube==15.0.0",
 
     "extract_msg",

+ 1 - 0
src/app.html

@@ -24,6 +24,7 @@
 			href="/opensearch.xml"
 		/>
 		<script src="/static/loader.js" defer></script>
+		<link rel="stylesheet" href="/static/custom.css" />
 
 		<script>
 			function resizeIframe(obj) {

+ 27 - 0
src/lib/apis/index.ts

@@ -1271,6 +1271,33 @@ export const updatePipelineValves = async (
 	return res;
 };
 
+export const getUsage = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_BASE_URL}/api/usage`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			...(token && { Authorization: `Bearer ${token}` })
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.error(err);
+			error = err;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
 export const getBackendConfig = async () => {
 	let error = null;
 

+ 27 - 0
src/lib/apis/users/index.ts

@@ -348,6 +348,33 @@ export const getAndUpdateUserLocation = async (token: string) => {
 	}
 };
 
+export const getUserActiveStatusById = async (token: string, userId: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/users/${userId}/active`, {
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+			Authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.error(err);
+			error = err.detail;
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
 export const deleteUserById = async (token: string, userId: string) => {
 	let error = null;
 

+ 71 - 20
src/lib/components/AddConnectionModal.svelte

@@ -3,7 +3,7 @@
 	import { getContext, onMount } from 'svelte';
 	const i18n = getContext('i18n');
 
-	import { models } from '$lib/stores';
+	import { settings } from '$lib/stores';
 	import { verifyOpenAIConnection } from '$lib/apis/openai';
 	import { verifyOllamaConnection } from '$lib/apis/ollama';
 
@@ -194,15 +194,16 @@
 <Modal size="sm" bind:show>
 	<div>
 		<div class=" flex justify-between dark:text-gray-100 px-5 pt-4 pb-1.5">
-			<div class=" text-lg font-medium self-center font-primary">
+			<h1 class="text-lg font-medium self-center font-primary">
 				{#if edit}
 					{$i18n.t('Edit Connection')}
 				{:else}
 					{$i18n.t('Add Connection')}
 				{/if}
-			</div>
+			</h1>
 			<button
 				class="self-center"
+				aria-label={$i18n.t('Close modal')}
 				on:click={() => {
 					show = false;
 				}}
@@ -211,6 +212,7 @@
 					xmlns="http://www.w3.org/2000/svg"
 					viewBox="0 0 20 20"
 					fill="currentColor"
+					aria-hidden="true"
 					class="w-5 h-5"
 				>
 					<path
@@ -256,11 +258,17 @@
 
 						<div class="flex gap-2 mt-1.5">
 							<div class="flex flex-col w-full">
-								<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('URL')}</div>
+								<label
+									for="url-input"
+									class={`mb-0.5 text-xs text-gray-500
+								${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
+									>{$i18n.t('URL')}</label
+								>
 
 								<div class="flex-1">
 									<input
-										class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
+										id="url-input"
+										class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
 										type="text"
 										bind:value={url}
 										placeholder={$i18n.t('API Base URL')}
@@ -277,11 +285,13 @@
 										verifyHandler();
 									}}
 									type="button"
+									aria-label={$i18n.t('Verify Connection')}
 								>
 									<svg
 										xmlns="http://www.w3.org/2000/svg"
 										viewBox="0 0 20 20"
 										fill="currentColor"
+										aria-hidden="true"
 										class="w-4 h-4"
 									>
 										<path
@@ -294,19 +304,27 @@
 							</Tooltip>
 
 							<div class="flex flex-col shrink-0 self-end">
+								<label class="sr-only" for="toggle-connection"
+									>{$i18n.t('Toggle whether current connection is active.')}</label
+								>
 								<Tooltip content={enable ? $i18n.t('Enabled') : $i18n.t('Disabled')}>
-									<Switch bind:state={enable} />
+									<Switch id="toggle-connection" bind:state={enable} />
 								</Tooltip>
 							</div>
 						</div>
 
 						<div class="flex gap-2 mt-2">
 							<div class="flex flex-col w-full">
-								<div class=" mb-0.5 text-xs text-gray-500">{$i18n.t('Key')}</div>
+								<div
+									class={`mb-0.5 text-xs text-gray-500
+								${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
+								>
+									{$i18n.t('Key')}
+								</div>
 
 								<div class="flex-1">
 									<SensitiveInput
-										className="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
+										inputClassName={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
 										bind:value={key}
 										placeholder={$i18n.t('API Key')}
 										required={false}
@@ -315,7 +333,12 @@
 							</div>
 
 							<div class="flex flex-col w-full">
-								<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Prefix ID')}</div>
+								<label
+									for="prefix-id-input"
+									class={`mb-0.5 text-xs text-gray-500
+								${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
+									>{$i18n.t('Prefix ID')}</label
+								>
 
 								<div class="flex-1">
 									<Tooltip
@@ -324,8 +347,9 @@
 										)}
 									>
 										<input
-											class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
+											class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
 											type="text"
+											id="prefix-id-input"
 											bind:value={prefixId}
 											placeholder={$i18n.t('Prefix ID')}
 											autocomplete="off"
@@ -338,11 +362,17 @@
 						{#if azure}
 							<div class="flex gap-2 mt-2">
 								<div class="flex flex-col w-full">
-									<div class=" mb-1 text-xs text-gray-500">{$i18n.t('API Version')}</div>
+									<label
+										for="api-version-input"
+										class={`mb-0.5 text-xs text-gray-500
+								${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
+										>{$i18n.t('API Version')}</label
+									>
 
 									<div class="flex-1">
 										<input
-											class="w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
+											id="api-version-input"
+											class={`w-full text-sm bg-transparent placeholder:text-gray-300 dark:placeholder:text-gray-700 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
 											type="text"
 											bind:value={apiVersion}
 											placeholder={$i18n.t('API Version')}
@@ -356,7 +386,12 @@
 
 						<div class="flex gap-2 mt-2">
 							<div class="flex flex-col w-full">
-								<div class=" mb-1.5 text-xs text-gray-500">{$i18n.t('Tags')}</div>
+								<div
+									class={`mb-0.5 text-xs text-gray-500
+								${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
+								>
+									{$i18n.t('Tags')}
+								</div>
 
 								<div class="flex-1">
 									<Tags
@@ -381,18 +416,26 @@
 
 						<div class="flex flex-col w-full">
 							<div class="mb-1 flex justify-between">
-								<div class="text-xs text-gray-500">{$i18n.t('Model IDs')}</div>
+								<div
+									class={`mb-0.5 text-xs text-gray-500
+								${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
+								>
+									{$i18n.t('Model IDs')}
+								</div>
 							</div>
 
 							{#if modelIds.length > 0}
-								<div class="flex flex-col">
+								<ul class="flex flex-col">
 									{#each modelIds as modelId, modelIdx}
-										<div class=" flex gap-2 w-full justify-between items-center">
+										<li class=" flex gap-2 w-full justify-between items-center">
 											<div class=" text-sm flex-1 py-1 rounded-lg">
 												{modelId}
 											</div>
 											<div class="shrink-0">
 												<button
+													aria-label={$i18n.t(`Remove {{MODELID}} from list.`, {
+														MODELID: modelId
+													})}
 													type="button"
 													on:click={() => {
 														modelIds = modelIds.filter((_, idx) => idx !== modelIdx);
@@ -401,11 +444,14 @@
 													<Minus strokeWidth="2" className="size-3.5" />
 												</button>
 											</div>
-										</div>
+										</li>
 									{/each}
-								</div>
+								</ul>
 							{:else}
-								<div class="text-gray-500 text-xs text-center py-2 px-10">
+								<div
+									class={`text-gray-500 text-xs text-center py-2 px-10
+								${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : ''}`}
+								>
 									{#if ollama}
 										{$i18n.t('Leave empty to include all models from "{{url}}/api/tags" endpoint', {
 											url: url
@@ -427,17 +473,22 @@
 						<hr class=" border-gray-100 dark:border-gray-700/10 my-1.5 w-full" />
 
 						<div class="flex items-center">
+							<label class="sr-only" for="add-model-id-input">{$i18n.t('Add a model ID')}</label>
 							<input
 								class="w-full py-1 text-sm rounded-lg bg-transparent {modelId
 									? ''
-									: 'text-gray-500'} placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden"
+									: 'text-gray-500'} {($settings?.highContrastMode ?? false)
+									? 'dark:placeholder:text-gray-100 placeholder:text-gray-700'
+									: 'placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden'}"
 								bind:value={modelId}
+								id="add-model-id-input"
 								placeholder={$i18n.t('Add a model ID')}
 							/>
 
 							<div>
 								<button
 									type="button"
+									aria-label={$i18n.t('Add')}
 									on:click={() => {
 										addModelHandler();
 									}}

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

@@ -26,7 +26,7 @@
 	<div class="px-5 pt-4 dark:text-gray-300 text-gray-700">
 		<div class="flex justify-between items-start">
 			<div class="text-xl font-semibold">
-				{$i18n.t('What’s New in')}
+				{$i18n.t("What's New in")}
 				{$WEBUI_NAME}
 				<Confetti x={[-1, -0.25]} y={[0, 0.5]} />
 			</div>

+ 80 - 0
src/lib/components/admin/Evaluations/FeedbackModal.svelte

@@ -0,0 +1,80 @@
+<script lang="ts">
+	import Modal from '$lib/components/common/Modal.svelte';
+	import { getContext } from 'svelte';
+	const i18n = getContext('i18n');
+
+	export let show = false;
+	export let selectedFeedback = null;
+
+	export let onClose: () => void = () => {};
+
+	const close = () => {
+		show = false;
+		onClose();
+	};
+</script>
+
+<Modal size="sm" bind:show>
+	{#if selectedFeedback}
+		<div>
+			<div class="flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+				<div class="text-lg font-medium self-center">
+					{$i18n.t('Feedback Details')}
+				</div>
+				<button class="self-center" on:click={close} aria-label="Close">
+					<svg
+						xmlns="http://www.w3.org/2000/svg"
+						viewBox="0 0 20 20"
+						fill="currentColor"
+						class="w-5 h-5"
+					>
+						<path
+							d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+						/>
+					</svg>
+				</button>
+			</div>
+
+			<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200">
+				<div class="flex flex-col w-full">
+					<div class="flex flex-col w-full mb-2">
+						<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Rating')}</div>
+
+						<div class="flex-1">
+							<span>{selectedFeedback?.data?.details?.rating ?? '-'}</span>
+						</div>
+					</div>
+					<div class="flex flex-col w-full mb-2">
+						<div class=" mb-1 text-xs text-gray-500">{$i18n.t('Reason')}</div>
+
+						<div class="flex-1">
+							<span>{selectedFeedback?.data?.reason || '-'}</span>
+						</div>
+					</div>
+
+					<div class="mb-2">
+						{#if selectedFeedback?.data?.tags && selectedFeedback?.data?.tags.length}
+							<div class="flex flex-wrap gap-1 mt-1">
+								{#each selectedFeedback?.data?.tags as tag}
+									<span class="px-2 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-xs">{tag}</span
+									>
+								{/each}
+							</div>
+						{:else}
+							<span>-</span>
+						{/if}
+					</div>
+					<div class="flex justify-end pt-3">
+						<button
+							class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+							type="button"
+							on:click={close}
+						>
+							{$i18n.t('Close')}
+						</button>
+					</div>
+				</div>
+			</div>
+		</div>
+	{/if}
+</Modal>

+ 150 - 10
src/lib/components/admin/Evaluations/Feedbacks.svelte

@@ -18,12 +18,19 @@
 	import CloudArrowUp from '$lib/components/icons/CloudArrowUp.svelte';
 	import Pagination from '$lib/components/common/Pagination.svelte';
 	import FeedbackMenu from './FeedbackMenu.svelte';
+	import FeedbackModal from './FeedbackModal.svelte';
 	import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
 
+	import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
+	import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
+
 	export let feedbacks = [];
 
 	let page = 1;
-	$: paginatedFeedbacks = feedbacks.slice((page - 1) * 10, page * 10);
+	$: paginatedFeedbacks = sortedFeedbacks.slice((page - 1) * 10, page * 10);
+
+	let orderBy: string = 'updated_at';
+	let direction: 'asc' | 'desc' = 'desc';
 
 	type Feedback = {
 		id: string;
@@ -48,6 +55,58 @@
 		lost: number;
 	};
 
+	function setSortKey(key: string) {
+		if (orderBy === key) {
+			direction = direction === 'asc' ? 'desc' : 'asc';
+		} else {
+			orderBy = key;
+			if (key === 'user' || key === 'model_id') {
+				direction = 'asc';
+			} else {
+				direction = 'desc';
+			}
+		}
+		page = 1;
+	}
+
+	$: sortedFeedbacks = [...feedbacks].sort((a, b) => {
+		let aVal, bVal;
+
+		switch (orderBy) {
+			case 'user':
+				aVal = a.user?.name || '';
+				bVal = b.user?.name || '';
+				return direction === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
+			case 'model_id':
+				aVal = a.data.model_id || '';
+				bVal = b.data.model_id || '';
+				return direction === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
+			case 'rating':
+				aVal = a.data.rating;
+				bVal = b.data.rating;
+				return direction === 'asc' ? aVal - bVal : bVal - aVal;
+			case 'updated_at':
+				aVal = a.updated_at;
+				bVal = b.updated_at;
+				return direction === 'asc' ? aVal - bVal : bVal - aVal;
+			default:
+				return 0;
+		}
+	});
+
+	let showFeedbackModal = false;
+	let selectedFeedback = null;
+
+	const openFeedbackModal = (feedback) => {
+		showFeedbackModal = true;
+		selectedFeedback = feedback;
+	};
+
+	const closeFeedbackModal = () => {
+		showFeedbackModal = false;
+		selectedFeedback = null;
+	};
+
 	//////////////////////
 	//
 	// CRUD operations
@@ -106,6 +165,8 @@
 	};
 </script>
 
+<FeedbackModal bind:show={showFeedbackModal} {selectedFeedback} onClose={closeFeedbackModal} />
+
 <div class="mt-0.5 mb-2 gap-1 flex flex-row justify-between">
 	<div class="flex md:self-center text-lg font-medium px-0.5">
 		{$i18n.t('Feedback History')}
@@ -146,20 +207,96 @@
 				class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
 			>
 				<tr class="">
-					<th scope="col" class="px-3 text-right cursor-pointer select-none w-0">
-						{$i18n.t('User')}
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none w-3"
+						on:click={() => setSortKey('user')}
+					>
+						<div class="flex gap-1.5 items-center justify-end">
+							{$i18n.t('User')}
+							{#if orderBy === 'user'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
 
-					<th scope="col" class="px-3 pr-1.5 cursor-pointer select-none">
-						{$i18n.t('Models')}
+					<th
+						scope="col"
+						class="px-3 pr-1.5 cursor-pointer select-none"
+						on:click={() => setSortKey('model_id')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('Models')}
+							{#if orderBy === 'model_id'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
 
-					<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-fit">
-						{$i18n.t('Result')}
+					<th
+						scope="col"
+						class="px-3 py-1.5 text-right cursor-pointer select-none w-fit"
+						on:click={() => setSortKey('rating')}
+					>
+						<div class="flex gap-1.5 items-center justify-end">
+							{$i18n.t('Result')}
+							{#if orderBy === 'rating'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
 
-					<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-0">
-						{$i18n.t('Updated At')}
+					<th
+						scope="col"
+						class="px-3 py-1.5 text-right cursor-pointer select-none w-0"
+						on:click={() => setSortKey('updated_at')}
+					>
+						<div class="flex gap-1.5 items-center justify-end">
+							{$i18n.t('Updated At')}
+							{#if orderBy === 'updated_at'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
 
 					<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-0"> </th>
@@ -167,7 +304,10 @@
 			</thead>
 			<tbody class="">
 				{#each paginatedFeedbacks as feedback (feedback.id)}
-					<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
+					<tr
+						class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition"
+						on:click={() => openFeedbackModal(feedback)}
+					>
 						<td class=" py-0.5 text-right font-semibold">
 							<div class="flex justify-center">
 								<Tooltip content={feedback?.user?.name}>

+ 175 - 12
src/lib/components/admin/Evaluations/Leaderboard.svelte

@@ -7,10 +7,15 @@
 	import { onMount, getContext } from 'svelte';
 	import { models } from '$lib/stores';
 
+	import ModelModal from './LeaderboardModal.svelte';
+
 	import Spinner from '$lib/components/common/Spinner.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import MagnifyingGlass from '$lib/components/icons/MagnifyingGlass.svelte';
 
+	import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
+	import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
+
 	const i18n = getContext('i18n');
 
 	const EMBEDDING_MODEL = 'TaylorAI/bge-micro-v2';
@@ -28,6 +33,9 @@
 	let loadingLeaderboard = true;
 	let debounceTimer;
 
+	let orderBy: string = 'rating'; // default sort column
+	let direction: 'asc' | 'desc' = 'desc'; // default sort order
+
 	type Feedback = {
 		id: string;
 		data: {
@@ -51,6 +59,34 @@
 		lost: number;
 	};
 
+	function setSortKey(key) {
+		if (orderBy === key) {
+			direction = direction === 'asc' ? 'desc' : 'asc';
+		} else {
+			orderBy = key;
+			direction = key === 'name' ? 'asc' : 'desc';
+		}
+	}
+
+	//////////////////////
+	//
+	// Aggregate Level Modal
+	//
+	//////////////////////
+
+	let showLeaderboardModal = false;
+	let selectedModel = null;
+
+	const openFeedbackModal = (model) => {
+		showLeaderboardModal = true;
+		selectedModel = model;
+	};
+
+	const closeLeaderboardModal = () => {
+		showLeaderboardModal = false;
+		selectedModel = null;
+	};
+
 	//////////////////////
 	//
 	// Rank models by Elo rating
@@ -266,8 +302,37 @@
 	onMount(async () => {
 		rankHandler();
 	});
+
+	$: sortedModels = [...rankedModels].sort((a, b) => {
+		let aVal, bVal;
+		if (orderBy === 'name') {
+			aVal = a.name;
+			bVal = b.name;
+			return direction === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
+		} else if (orderBy === 'rating') {
+			aVal = a.rating === '-' ? -Infinity : a.rating;
+			bVal = b.rating === '-' ? -Infinity : b.rating;
+			return direction === 'asc' ? aVal - bVal : bVal - aVal;
+		} else if (orderBy === 'won') {
+			aVal = a.stats.won === '-' ? -Infinity : Number(a.stats.won);
+			bVal = b.stats.won === '-' ? -Infinity : Number(b.stats.won);
+			return direction === 'asc' ? aVal - bVal : bVal - aVal;
+		} else if (orderBy === 'lost') {
+			aVal = a.stats.lost === '-' ? -Infinity : Number(a.stats.lost);
+			bVal = b.stats.lost === '-' ? -Infinity : Number(b.stats.lost);
+			return direction === 'asc' ? aVal - bVal : bVal - aVal;
+		}
+		return 0;
+	});
 </script>
 
+<ModelModal
+	bind:show={showLeaderboardModal}
+	model={selectedModel}
+	{feedbacks}
+	onClose={closeLeaderboardModal}
+/>
+
 <div class="mt-0.5 mb-2 gap-1 flex flex-col md:flex-row justify-between">
 	<div class="flex md:self-center text-lg font-medium px-0.5 shrink-0 items-center">
 		<div class=" gap-1">
@@ -324,26 +389,124 @@
 				class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 -translate-y-0.5"
 			>
 				<tr class="">
-					<th scope="col" class="px-3 py-1.5 cursor-pointer select-none w-3">
-						{$i18n.t('RK')}
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none w-3"
+						on:click={() => setSortKey('rating')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('RK')}
+							{#if orderBy === 'rating'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
-					<th scope="col" class="px-3 py-1.5 cursor-pointer select-none">
-						{$i18n.t('Model')}
+					<th
+						scope="col"
+						class="px-3 py-1.5 cursor-pointer select-none"
+						on:click={() => setSortKey('name')}
+					>
+						<div class="flex gap-1.5 items-center">
+							{$i18n.t('Model')}
+							{#if orderBy === 'name'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
-					<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-fit">
-						{$i18n.t('Rating')}
+					<th
+						scope="col"
+						class="px-3 py-1.5 text-right cursor-pointer select-none w-fit"
+						on:click={() => setSortKey('rating')}
+					>
+						<div class="flex gap-1.5 items-center justify-end">
+							{$i18n.t('Rating')}
+							{#if orderBy === 'rating'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
-					<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-5">
-						{$i18n.t('Won')}
+					<th
+						scope="col"
+						class="px-3 py-1.5 text-right cursor-pointer select-none w-5"
+						on:click={() => setSortKey('won')}
+					>
+						<div class="flex gap-1.5 items-center justify-end">
+							{$i18n.t('Won')}
+							{#if orderBy === 'won'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
-					<th scope="col" class="px-3 py-1.5 text-right cursor-pointer select-none w-5">
-						{$i18n.t('Lost')}
+					<th
+						scope="col"
+						class="px-3 py-1.5 text-right cursor-pointer select-none w-5"
+						on:click={() => setSortKey('lost')}
+					>
+						<div class="flex gap-1.5 items-center justify-end">
+							{$i18n.t('Lost')}
+							{#if orderBy === 'lost'}
+								<span class="font-normal">
+									{#if direction === 'asc'}
+										<ChevronUp className="size-2" />
+									{:else}
+										<ChevronDown className="size-2" />
+									{/if}
+								</span>
+							{:else}
+								<span class="invisible">
+									<ChevronUp className="size-2" />
+								</span>
+							{/if}
+						</div>
 					</th>
 				</tr>
 			</thead>
 			<tbody class="">
-				{#each rankedModels as model, modelIdx (model.id)}
-					<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs group">
+				{#each sortedModels as model, modelIdx (model.id)}
+					<tr
+						class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs group cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 transition"
+						on:click={() => openFeedbackModal(model)}
+					>
 						<td class="px-3 py-1.5 text-left font-medium text-gray-900 dark:text-white w-fit">
 							<div class=" line-clamp-1">
 								{model?.rating !== '-' ? modelIdx + 1 : '-'}

+ 77 - 0
src/lib/components/admin/Evaluations/LeaderboardModal.svelte

@@ -0,0 +1,77 @@
+<script lang="ts">
+	import Modal from '$lib/components/common/Modal.svelte';
+	import { getContext } from 'svelte';
+	export let show = false;
+	export let model = null;
+	export let feedbacks = [];
+	export let onClose: () => void = () => {};
+	const i18n = getContext('i18n');
+
+	const close = () => {
+		show = false;
+		onClose();
+	};
+
+	$: topTags = model ? getTopTagsForModel(model.id, feedbacks) : [];
+
+	const getTopTagsForModel = (modelId: string, feedbacks: any[], topN = 5) => {
+		const tagCounts = new Map();
+		feedbacks
+			.filter((fb) => fb.data.model_id === modelId)
+			.forEach((fb) => {
+				(fb.data.tags || []).forEach((tag) => {
+					tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
+				});
+			});
+		return Array.from(tagCounts.entries())
+			.sort((a, b) => b[1] - a[1])
+			.slice(0, topN)
+			.map(([tag, count]) => ({ tag, count }));
+	};
+</script>
+
+<Modal size="sm" bind:show>
+	{#if model}
+		<div class="flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
+			<div class="text-lg font-medium self-center">
+				{model.name}
+			</div>
+			<button class="self-center" on:click={close} aria-label="Close">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<path
+						d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
+					/>
+				</svg>
+			</button>
+		</div>
+		<div class="px-5 pb-4 dark:text-gray-200">
+			<div class="mb-2">
+				{#if topTags.length}
+					<div class="flex flex-wrap gap-1 mt-1">
+						{#each topTags as tagInfo}
+							<span class="px-2 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-xs">
+								{tagInfo.tag} <span class="text-gray-500">({tagInfo.count})</span>
+							</span>
+						{/each}
+					</div>
+				{:else}
+					<span>-</span>
+				{/if}
+			</div>
+			<div class="flex justify-end pt-3">
+				<button
+					class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
+					type="button"
+					on:click={close}
+				>
+					{$i18n.t('Close')}
+				</button>
+			</div>
+		</div>
+	{/if}
+</Modal>

+ 190 - 172
src/lib/components/admin/Settings/Audio.svelte

@@ -39,6 +39,7 @@
 	let STT_OPENAI_API_KEY = '';
 	let STT_ENGINE = '';
 	let STT_MODEL = '';
+	let STT_SUPPORTED_CONTENT_TYPES = '';
 	let STT_WHISPER_MODEL = '';
 	let STT_AZURE_API_KEY = '';
 	let STT_AZURE_REGION = '';
@@ -114,6 +115,7 @@
 				OPENAI_API_KEY: STT_OPENAI_API_KEY,
 				ENGINE: STT_ENGINE,
 				MODEL: STT_MODEL,
+				SUPPORTED_CONTENT_TYPES: STT_SUPPORTED_CONTENT_TYPES.split(','),
 				WHISPER_MODEL: STT_WHISPER_MODEL,
 				DEEPGRAM_API_KEY: STT_DEEPGRAM_API_KEY,
 				AZURE_API_KEY: STT_AZURE_API_KEY,
@@ -160,6 +162,7 @@
 
 			STT_ENGINE = res.stt.ENGINE;
 			STT_MODEL = res.stt.MODEL;
+			STT_SUPPORTED_CONTENT_TYPES = (res?.stt?.SUPPORTED_CONTENT_TYPES ?? []).join(',');
 			STT_WHISPER_MODEL = res.stt.WHISPER_MODEL;
 			STT_AZURE_API_KEY = res.stt.AZURE_API_KEY;
 			STT_AZURE_REGION = res.stt.AZURE_REGION;
@@ -184,9 +187,26 @@
 	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
 		<div class="flex flex-col gap-3">
 			<div>
-				<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
+				<div class=" mb-2.5 text-base font-medium">{$i18n.t('Speech-to-Text')}</div>
 
-				<div class=" py-0.5 flex w-full justify-between">
+				<hr class=" border-gray-100 dark:border-gray-850 my-2" />
+
+				{#if STT_ENGINE !== 'web'}
+					<div class="mb-2">
+						<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Supported MIME Types')}</div>
+						<div class="flex w-full">
+							<div class="flex-1">
+								<input
+									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+									bind:value={STT_SUPPORTED_CONTENT_TYPES}
+									placeholder={$i18n.t('e.g., audio/wav,audio/mpeg (leave blank for defaults)')}
+								/>
+							</div>
+						</div>
+					</div>
+				{/if}
+
+				<div class="mb-2 py-0.5 flex w-full justify-between">
 					<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
 					<div class="flex items-center relative">
 						<select
@@ -220,7 +240,7 @@
 					<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 					<div>
-						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
+						<div class=" mb-1.5 text-xs font-medium">{$i18n.t('STT Model')}</div>
 						<div class="flex w-full">
 							<div class="flex-1">
 								<input
@@ -246,7 +266,7 @@
 					<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 					<div>
-						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
+						<div class=" mb-1.5 text-xs font-medium">{$i18n.t('STT Model')}</div>
 						<div class="flex w-full">
 							<div class="flex-1">
 								<input
@@ -280,7 +300,7 @@
 						<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 						<div>
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Azure Region')}</div>
+							<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Azure Region')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
@@ -293,7 +313,7 @@
 						</div>
 
 						<div>
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Language Locales')}</div>
+							<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Language Locales')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
@@ -306,7 +326,7 @@
 						</div>
 
 						<div>
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Endpoint URL')}</div>
+							<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Endpoint URL')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
@@ -319,7 +339,7 @@
 						</div>
 
 						<div>
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Max Speakers')}</div>
+							<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Max Speakers')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
@@ -333,7 +353,7 @@
 					</div>
 				{:else if STT_ENGINE === ''}
 					<div>
-						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('STT Model')}</div>
+						<div class=" mb-1.5 text-xs font-medium">{$i18n.t('STT Model')}</div>
 
 						<div class="flex w-full">
 							<div class="flex-1 mr-2">
@@ -416,12 +436,12 @@
 				{/if}
 			</div>
 
-			<hr class="border-gray-100 dark:border-gray-850" />
-
 			<div>
-				<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
+				<div class=" mb-2.5 text-base font-medium">{$i18n.t('Text-to-Speech')}</div>
+
+				<hr class=" border-gray-100 dark:border-gray-850 my-2" />
 
-				<div class=" py-0.5 flex w-full justify-between">
+				<div class="mb-2 py-0.5 flex w-full justify-between">
 					<div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
 					<div class="flex items-center relative">
 						<select
@@ -484,7 +504,7 @@
 						<hr class="border-gray-100 dark:border-gray-850 my-2" />
 
 						<div>
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Azure Region')}</div>
+							<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Azure Region')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
@@ -497,7 +517,7 @@
 						</div>
 
 						<div>
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Endpoint URL')}</div>
+							<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Endpoint URL')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
@@ -511,198 +531,196 @@
 					</div>
 				{/if}
 
-				<hr class="border-gray-100 dark:border-gray-850 my-2" />
-
-				{#if TTS_ENGINE === ''}
-					<div>
-						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
-						<div class="flex w-full">
-							<div class="flex-1">
-								<select
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
-									bind:value={TTS_VOICE}
-								>
-									<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
-									{#each voices as voice}
-										<option
-											value={voice.voiceURI}
-											class="bg-gray-100 dark:bg-gray-700"
-											selected={TTS_VOICE === voice.voiceURI}>{voice.name}</option
-										>
-									{/each}
-								</select>
-							</div>
-						</div>
-					</div>
-				{:else if TTS_ENGINE === 'transformers'}
-					<div>
-						<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Model')}</div>
-						<div class="flex w-full">
-							<div class="flex-1">
-								<input
-									list="model-list"
-									class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
-									bind:value={TTS_MODEL}
-									placeholder="CMU ARCTIC speaker embedding name"
-								/>
-
-								<datalist id="model-list">
-									<option value="tts-1" />
-								</datalist>
-							</div>
-						</div>
-						<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
-							{$i18n.t(`Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.`)}
-
-							To learn more about SpeechT5,
-
-							<a
-								class=" hover:underline dark:text-gray-200 text-gray-800"
-								href="https://github.com/microsoft/SpeechT5"
-								target="_blank"
-							>
-								{$i18n.t(`click here`, {
-									name: 'SpeechT5'
-								})}.
-							</a>
-							To see the available CMU Arctic speaker embeddings,
-							<a
-								class=" hover:underline dark:text-gray-200 text-gray-800"
-								href="https://huggingface.co/datasets/Matthijs/cmu-arctic-xvectors"
-								target="_blank"
-							>
-								{$i18n.t(`click here`)}.
-							</a>
-						</div>
-					</div>
-				{:else if TTS_ENGINE === 'openai'}
-					<div class=" flex gap-2">
-						<div class="w-full">
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
+				<div class="mb-2">
+					{#if TTS_ENGINE === ''}
+						<div>
+							<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Voice')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
-									<input
-										list="voice-list"
+									<select
 										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										bind:value={TTS_VOICE}
-										placeholder="Select a voice"
-									/>
-
-									<datalist id="voice-list">
+									>
+										<option value="" selected={TTS_VOICE !== ''}>{$i18n.t('Default')}</option>
 										{#each voices as voice}
-											<option value={voice.id}>{voice.name}</option>
+											<option
+												value={voice.voiceURI}
+												class="bg-gray-100 dark:bg-gray-700"
+												selected={TTS_VOICE === voice.voiceURI}>{voice.name}</option
+											>
 										{/each}
-									</datalist>
+									</select>
 								</div>
 							</div>
 						</div>
-						<div class="w-full">
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Model')}</div>
+					{:else if TTS_ENGINE === 'transformers'}
+						<div>
+							<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Model')}</div>
 							<div class="flex w-full">
 								<div class="flex-1">
 									<input
-										list="tts-model-list"
+										list="model-list"
 										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
 										bind:value={TTS_MODEL}
-										placeholder="Select a model"
+										placeholder="CMU ARCTIC speaker embedding name"
 									/>
 
-									<datalist id="tts-model-list">
-										{#each models as model}
-											<option value={model.id} class="bg-gray-50 dark:bg-gray-700" />
-										{/each}
+									<datalist id="model-list">
+										<option value="tts-1" />
 									</datalist>
 								</div>
 							</div>
+							<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
+								{$i18n.t(`Open WebUI uses SpeechT5 and CMU Arctic speaker embeddings.`)}
+
+								To learn more about SpeechT5,
+
+								<a
+									class=" hover:underline dark:text-gray-200 text-gray-800"
+									href="https://github.com/microsoft/SpeechT5"
+									target="_blank"
+								>
+									{$i18n.t(`click here`, {
+										name: 'SpeechT5'
+									})}.
+								</a>
+								To see the available CMU Arctic speaker embeddings,
+								<a
+									class=" hover:underline dark:text-gray-200 text-gray-800"
+									href="https://huggingface.co/datasets/Matthijs/cmu-arctic-xvectors"
+									target="_blank"
+								>
+									{$i18n.t(`click here`)}.
+								</a>
+							</div>
 						</div>
-					</div>
-				{:else if TTS_ENGINE === 'elevenlabs'}
-					<div class=" flex gap-2">
-						<div class="w-full">
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
-							<div class="flex w-full">
-								<div class="flex-1">
-									<input
-										list="voice-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
-										bind:value={TTS_VOICE}
-										placeholder="Select a voice"
-									/>
+					{:else if TTS_ENGINE === 'openai'}
+						<div class=" flex gap-2">
+							<div class="w-full">
+								<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Voice')}</div>
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											list="voice-list"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+											bind:value={TTS_VOICE}
+											placeholder="Select a voice"
+										/>
 
-									<datalist id="voice-list">
-										{#each voices as voice}
-											<option value={voice.id}>{voice.name}</option>
-										{/each}
-									</datalist>
+										<datalist id="voice-list">
+											{#each voices as voice}
+												<option value={voice.id}>{voice.name}</option>
+											{/each}
+										</datalist>
+									</div>
 								</div>
 							</div>
-						</div>
-						<div class="w-full">
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Model')}</div>
-							<div class="flex w-full">
-								<div class="flex-1">
-									<input
-										list="tts-model-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
-										bind:value={TTS_MODEL}
-										placeholder="Select a model"
-									/>
+							<div class="w-full">
+								<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Model')}</div>
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											list="tts-model-list"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+											bind:value={TTS_MODEL}
+											placeholder="Select a model"
+										/>
 
-									<datalist id="tts-model-list">
-										{#each models as model}
-											<option value={model.id} class="bg-gray-50 dark:bg-gray-700" />
-										{/each}
-									</datalist>
+										<datalist id="tts-model-list">
+											{#each models as model}
+												<option value={model.id} class="bg-gray-50 dark:bg-gray-700" />
+											{/each}
+										</datalist>
+									</div>
 								</div>
 							</div>
 						</div>
-					</div>
-				{:else if TTS_ENGINE === 'azure'}
-					<div class=" flex gap-2">
-						<div class="w-full">
-							<div class=" mb-1.5 text-sm font-medium">{$i18n.t('TTS Voice')}</div>
-							<div class="flex w-full">
-								<div class="flex-1">
-									<input
-										list="voice-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
-										bind:value={TTS_VOICE}
-										placeholder="Select a voice"
-									/>
+					{:else if TTS_ENGINE === 'elevenlabs'}
+						<div class=" flex gap-2">
+							<div class="w-full">
+								<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Voice')}</div>
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											list="voice-list"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+											bind:value={TTS_VOICE}
+											placeholder="Select a voice"
+										/>
 
-									<datalist id="voice-list">
-										{#each voices as voice}
-											<option value={voice.id}>{voice.name}</option>
-										{/each}
-									</datalist>
+										<datalist id="voice-list">
+											{#each voices as voice}
+												<option value={voice.id}>{voice.name}</option>
+											{/each}
+										</datalist>
+									</div>
+								</div>
+							</div>
+							<div class="w-full">
+								<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Model')}</div>
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											list="tts-model-list"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+											bind:value={TTS_MODEL}
+											placeholder="Select a model"
+										/>
+
+										<datalist id="tts-model-list">
+											{#each models as model}
+												<option value={model.id} class="bg-gray-50 dark:bg-gray-700" />
+											{/each}
+										</datalist>
+									</div>
 								</div>
 							</div>
 						</div>
-						<div class="w-full">
-							<div class=" mb-1.5 text-sm font-medium">
-								{$i18n.t('Output format')}
-								<a
-									href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming#audio-outputs"
-									target="_blank"
-								>
-									<small>{$i18n.t('Available list')}</small>
-								</a>
+					{:else if TTS_ENGINE === 'azure'}
+						<div class=" flex gap-2">
+							<div class="w-full">
+								<div class=" mb-1.5 text-xs font-medium">{$i18n.t('TTS Voice')}</div>
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											list="voice-list"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+											bind:value={TTS_VOICE}
+											placeholder="Select a voice"
+										/>
+
+										<datalist id="voice-list">
+											{#each voices as voice}
+												<option value={voice.id}>{voice.name}</option>
+											{/each}
+										</datalist>
+									</div>
+								</div>
 							</div>
-							<div class="flex w-full">
-								<div class="flex-1">
-									<input
-										list="tts-model-list"
-										class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
-										bind:value={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
-										placeholder="Select a output format"
-									/>
+							<div class="w-full">
+								<div class=" mb-1.5 text-xs font-medium">
+									{$i18n.t('Output format')}
+									<a
+										href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming#audio-outputs"
+										target="_blank"
+									>
+										<small>{$i18n.t('Available list')}</small>
+									</a>
+								</div>
+								<div class="flex w-full">
+									<div class="flex-1">
+										<input
+											list="tts-model-list"
+											class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
+											bind:value={TTS_AZURE_SPEECH_OUTPUT_FORMAT}
+											placeholder="Select a output format"
+										/>
+									</div>
 								</div>
 							</div>
 						</div>
-					</div>
-				{/if}
-
-				<hr class="border-gray-100 dark:border-gray-850 my-2" />
+					{/if}
+				</div>
 
 				<div class="pt-0.5 flex w-full justify-between">
 					<div class="self-center text-xs font-medium">{$i18n.t('Response splitting')}</div>

+ 44 - 0
src/lib/components/admin/Settings/Documents.svelte

@@ -1144,6 +1144,50 @@
 							</Tooltip>
 						</div>
 					</div>
+
+					<div class="  mb-2.5 flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">{$i18n.t('Image Compression Width')}</div>
+						<div class="flex items-center relative">
+							<Tooltip
+								content={$i18n.t(
+									'The width in pixels to compress images to. Leave empty for no compression.'
+								)}
+								placement="top-start"
+							>
+								<input
+									class="flex-1 w-full text-sm bg-transparent outline-hidden"
+									type="number"
+									placeholder={$i18n.t('Leave empty for no compression')}
+									bind:value={RAGConfig.FILE_IMAGE_COMPRESSION_WIDTH}
+									autocomplete="off"
+									min="0"
+								/>
+							</Tooltip>
+						</div>
+					</div>
+
+					<div class="  mb-2.5 flex w-full justify-between">
+						<div class=" self-center text-xs font-medium">
+							{$i18n.t('Image Compression Height')}
+						</div>
+						<div class="flex items-center relative">
+							<Tooltip
+								content={$i18n.t(
+									'The height in pixels to compress images to. Leave empty for no compression.'
+								)}
+								placement="top-start"
+							>
+								<input
+									class="flex-1 w-full text-sm bg-transparent outline-hidden"
+									type="number"
+									placeholder={$i18n.t('Leave empty for no compression')}
+									bind:value={RAGConfig.FILE_IMAGE_COMPRESSION_HEIGHT}
+									autocomplete="off"
+									min="0"
+								/>
+							</Tooltip>
+						</div>
+					</div>
 				</div>
 
 				<div class="mb-3">

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

@@ -65,6 +65,7 @@
 		},
 		chat: {
 			controls: true,
+			system_prompt: true,
 			file_upload: true,
 			delete: true,
 			edit: true,

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

@@ -263,6 +263,14 @@
 			<Switch bind:state={permissions.chat.controls} />
 		</div>
 
+		<div class="  flex w-full justify-between my-2 pr-2">
+			<div class=" self-center text-xs font-medium">
+				{$i18n.t('Allow Chat System Prompt')}
+			</div>
+
+			<Switch bind:state={permissions.chat.system_prompt} />
+		</div>
+
 		<div class="  flex w-full justify-between my-2 pr-2">
 			<div class=" self-center text-xs font-medium">
 				{$i18n.t('Allow Chat Delete')}

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

@@ -101,7 +101,7 @@
 
 								<div class="flex-1">
 									<select
-										class="w-full dark:bg-gray-900 rounded-sm text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
+										class="w-full dark:bg-gray-900 text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
 										bind:value={_user.role}
 										disabled={_user.id == sessionUser.id}
 										required
@@ -118,13 +118,12 @@
 
 								<div class="flex-1">
 									<input
-										class="w-full rounded-sm text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
+										class="w-full text-sm bg-transparent disabled:text-gray-500 dark:disabled:text-gray-500 outline-hidden"
 										type="email"
 										bind:value={_user.email}
 										placeholder={$i18n.t('Enter Your Email')}
 										autocomplete="off"
 										required
-										disabled={_user.id == sessionUser.id}
 									/>
 								</div>
 							</div>
@@ -134,7 +133,7 @@
 
 								<div class="flex-1">
 									<input
-										class="w-full rounded-sm text-sm bg-transparent outline-hidden"
+										class="w-full text-sm bg-transparent outline-hidden"
 										type="text"
 										bind:value={_user.name}
 										placeholder={$i18n.t('Enter Your Name')}
@@ -149,7 +148,7 @@
 
 								<div class="flex-1">
 									<input
-										class="w-full rounded-sm text-sm bg-transparent outline-hidden"
+										class="w-full text-sm bg-transparent outline-hidden"
 										type="password"
 										placeholder={$i18n.t('Enter New Password')}
 										bind:value={_user.password}

+ 24 - 3
src/lib/components/channel/MessageInput.svelte

@@ -110,9 +110,30 @@
 				reader.onload = async (event) => {
 					let imageUrl = event.target.result;
 
-					if ($settings?.imageCompression ?? false) {
-						const width = $settings?.imageCompressionSize?.width ?? null;
-						const height = $settings?.imageCompressionSize?.height ?? null;
+					if (
+						($settings?.imageCompression ?? false) ||
+						($config?.file?.image_compression?.width ?? null) ||
+						($config?.file?.image_compression?.height ?? null)
+					) {
+						let width = null;
+						let height = null;
+
+						if ($settings?.imageCompression ?? false) {
+							width = $settings?.imageCompressionSize?.width ?? null;
+							height = $settings?.imageCompressionSize?.height ?? null;
+						}
+
+						if (
+							($config?.file?.image_compression?.width ?? null) ||
+							($config?.file?.image_compression?.height ?? null)
+						) {
+							if (width > ($config?.file?.image_compression?.width ?? null)) {
+								width = $config?.file?.image_compression?.width ?? null;
+							}
+							if (height > ($config?.file?.image_compression?.height ?? null)) {
+								height = $config?.file?.image_compression?.height ?? null;
+							}
+						}
 
 						if (width || height) {
 							imageUrl = await compressImage(imageUrl, width, height);

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

@@ -1,10 +1,9 @@
 <script lang="ts">
 	import { DropdownMenu } from 'bits-ui';
-	import { createEventDispatcher } from 'svelte';
 
 	import { flyAndScale } from '$lib/utils/transitions';
 	import { WEBUI_BASE_URL } from '$lib/constants';
-	import { activeUserIds } from '$lib/stores';
+	import { getUserActiveStatusById } from '$lib/apis/users';
 
 	export let side = 'right';
 	export let align = 'top';
@@ -12,15 +11,29 @@
 	export let user = null;
 	let show = false;
 
-	const dispatch = createEventDispatcher();
+	let active = false;
+
+	const getActiveStatus = async () => {
+		const res = await getUserActiveStatusById(localStorage.token, user.id).catch((error) => {
+			console.error('Error fetching user active status:', error);
+		});
+
+		if (res) {
+			active = res.active;
+		} else {
+			active = false;
+		}
+	};
+
+	$: if (show) {
+		getActiveStatus();
+	}
 </script>
 
 <DropdownMenu.Root
 	bind:open={show}
 	closeFocus={false}
-	onOpenChange={(state) => {
-		dispatch('change', state);
-	}}
+	onOpenChange={(state) => {}}
 	typeahead={false}
 >
 	<DropdownMenu.Trigger>
@@ -52,7 +65,7 @@
 						</div>
 
 						<div class=" flex items-center gap-2">
-							{#if $activeUserIds.includes(user.id)}
+							{#if active}
 								<div>
 									<span class="relative flex size-2">
 										<span

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

@@ -335,7 +335,7 @@
 								title="Content"
 								srcdoc={contents[selectedContentIdx].content}
 								class="w-full border-0 h-full rounded-none"
-								sandbox="allow-scripts{($settings?.iframeSandboxAllowForms ?? false)
+								sandbox="allow-scripts allow-downloads{($settings?.iframeSandboxAllowForms ?? false)
 									? ' allow-forms'
 									: ''}{($settings?.iframeSandboxAllowSameOrigin ?? false)
 									? ' allow-same-origin'

+ 36 - 23
src/lib/components/chat/Chat.svelte

@@ -432,31 +432,20 @@
 		}
 	};
 
+	let pageSubscribe = null;
 	onMount(async () => {
 		loading = true;
 		console.log('mounted');
 		window.addEventListener('message', onMessageHandler);
 		$socket?.on('chat-events', chatEventHandler);
 
-		page.subscribe((page) => {
-			if (page.url.pathname === '/') {
+		pageSubscribe = page.subscribe(async (p) => {
+			if (p.url.pathname === '/') {
+				await tick();
 				initNewChat();
 			}
 		});
 
-		if (!$chatId) {
-			chatIdUnsubscriber = chatId.subscribe(async (value) => {
-				if (!value) {
-					await tick(); // Wait for DOM updates
-					await initNewChat();
-				}
-			});
-		} else {
-			if ($temporaryChatEnabled) {
-				await goto('/');
-			}
-		}
-
 		if (localStorage.getItem(`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`)) {
 			prompt = '';
 			files = [];
@@ -515,6 +504,7 @@
 	});
 
 	onDestroy(() => {
+		pageSubscribe();
 		chatIdUnsubscriber?.();
 		window.removeEventListener('message', onMessageHandler);
 		$socket?.off('chat-events', chatEventHandler);
@@ -805,6 +795,11 @@
 				`https://www.youtube.com/watch?v=${$page.url.searchParams.get('youtube')}`
 			);
 		}
+
+		if ($page.url.searchParams.get('load-url')) {
+			await uploadWeb($page.url.searchParams.get('load-url'));
+		}
+
 		if ($page.url.searchParams.get('web-search') === 'true') {
 			webSearchEnabled = true;
 		}
@@ -813,6 +808,10 @@
 			imageGenerationEnabled = true;
 		}
 
+		if ($page.url.searchParams.get('code-interpreter') === 'true') {
+			codeInterpreterEnabled = true;
+		}
+
 		if ($page.url.searchParams.get('tools')) {
 			selectedToolIds = $page.url.searchParams
 				.get('tools')
@@ -859,6 +858,11 @@
 
 	const loadChat = async () => {
 		chatId.set(chatIdProp);
+
+		if ($temporaryChatEnabled) {
+			temporaryChatEnabled.set(false);
+		}
+
 		chat = await getChatById(localStorage.token, $chatId).catch(async (error) => {
 			await goto('/');
 			return null;
@@ -878,6 +882,11 @@
 					(chatContent?.models ?? undefined) !== undefined
 						? chatContent.models
 						: [chatContent.models ?? ''];
+
+				if (!($user?.role === 'admin' || ($user?.permissions?.chat?.multiple_models ?? true))) {
+					selectedModels = selectedModels.length > 0 ? [selectedModels[0]] : [''];
+				}
+
 				oldSelectedModelIds = selectedModels;
 
 				history =
@@ -1725,6 +1734,7 @@
 
 			history.messages[responseMessageId] = responseMessage;
 			history.currentId = responseMessageId;
+
 			return null;
 		});
 
@@ -1821,7 +1831,8 @@
 			childrenIds: [],
 			role: 'user',
 			content: userPrompt,
-			models: selectedModels
+			models: selectedModels,
+			timestamp: Math.floor(Date.now() / 1000) // Unix epoch
 		};
 
 		if (parentId !== null) {
@@ -2107,13 +2118,15 @@
 									{stopResponse}
 									{createMessagePair}
 									onChange={(input) => {
-										if (input.prompt !== null) {
-											localStorage.setItem(
-												`chat-input${$chatId ? `-${$chatId}` : ''}`,
-												JSON.stringify(input)
-											);
-										} else {
-											localStorage.removeItem(`chat-input${$chatId ? `-${$chatId}` : ''}`);
+										if (!$temporaryChatEnabled) {
+											if (input.prompt !== null) {
+												localStorage.setItem(
+													`chat-input${$chatId ? `-${$chatId}` : ''}`,
+													JSON.stringify(input)
+												);
+											} else {
+												localStorage.removeItem(`chat-input${$chatId ? `-${$chatId}` : ''}`);
+											}
 										}
 									}}
 									on:upload={async (e) => {

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

@@ -67,7 +67,7 @@
 			</div>
 		</Collapsible>
 
-		{#if $user?.role === 'admin' || $user?.permissions.chat?.controls}
+		{#if $user?.role === 'admin' || ($user?.permissions.chat?.system_prompt ?? true)}
 			<hr class="my-2 border-gray-50 dark:border-gray-700/10" />
 
 			<Collapsible title={$i18n.t('System Prompt')} open={true} buttonClassName="w-full">
@@ -80,7 +80,9 @@
 					/>
 				</div>
 			</Collapsible>
+		{/if}
 
+		{#if $user?.role === 'admin' || ($user?.permissions.chat?.controls ?? true)}
 			<hr class="my-2 border-gray-50 dark:border-gray-700/10" />
 
 			<Collapsible title={$i18n.t('Advanced Params')} open={true} buttonClassName="w-full">

+ 46 - 4
src/lib/components/chat/MessageInput.svelte

@@ -91,7 +91,15 @@
 
 	$: onChange({
 		prompt,
-		files: files.filter((file) => file.type !== 'image'),
+		files: files
+			.filter((file) => file.type !== 'image')
+			.map((file) => {
+				return {
+					...file,
+					user: undefined,
+					access_control: undefined
+				};
+			}),
 		selectedToolIds,
 		selectedFilterIds,
 		imageGenerationEnabled,
@@ -299,6 +307,19 @@
 
 	const inputFilesHandler = async (inputFiles) => {
 		console.log('Input files handler called with:', inputFiles);
+
+		if (
+			($config?.file?.max_count ?? null) !== null &&
+			files.length + inputFiles.length > $config?.file?.max_count
+		) {
+			toast.error(
+				$i18n.t(`You can only chat with a maximum of {{maxCount}} file(s) at a time.`, {
+					maxCount: $config?.file?.max_count
+				})
+			);
+			return;
+		}
+
 		inputFiles.forEach((file) => {
 			console.log('Processing file:', {
 				name: file.name,
@@ -334,9 +355,30 @@
 				reader.onload = async (event) => {
 					let imageUrl = event.target.result;
 
-					if ($settings?.imageCompression ?? false) {
-						const width = $settings?.imageCompressionSize?.width ?? null;
-						const height = $settings?.imageCompressionSize?.height ?? null;
+					if (
+						($settings?.imageCompression ?? false) ||
+						($config?.file?.image_compression?.width ?? null) ||
+						($config?.file?.image_compression?.height ?? null)
+					) {
+						let width = null;
+						let height = null;
+
+						if ($settings?.imageCompression ?? false) {
+							width = $settings?.imageCompressionSize?.width ?? null;
+							height = $settings?.imageCompressionSize?.height ?? null;
+						}
+
+						if (
+							($config?.file?.image_compression?.width ?? null) ||
+							($config?.file?.image_compression?.height ?? null)
+						) {
+							if (width > ($config?.file?.image_compression?.width ?? null)) {
+								width = $config?.file?.image_compression?.width ?? null;
+							}
+							if (height > ($config?.file?.image_compression?.height ?? null)) {
+								height = $config?.file?.image_compression?.height ?? null;
+							}
+						}
 
 						if (width || height) {
 							imageUrl = await compressImage(imageUrl, width, height);

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

@@ -256,6 +256,10 @@
 	};
 
 	const editMessage = async (messageId, { content, files }, submit = true) => {
+		if ((selectedModels ?? []).filter((id) => id).length === 0) {
+			toast.error($i18n.t('Model not selected'));
+			return;
+		}
 		if (history.messages[messageId].role === 'user') {
 			if (submit) {
 				// New user message

+ 2 - 3
src/lib/components/chat/Messages/Citations.svelte

@@ -188,9 +188,8 @@
 				</div>
 				<div slot="content">
 					<div class="flex text-xs font-medium flex-wrap">
-						{#each citations as citation, idx}
+						{#each citations.slice(2) as citation, idx}
 							<button
-								id={`source-${id}-${idx + 1}`}
 								class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
 								on:click={() => {
 									showCitationModal = true;
@@ -199,7 +198,7 @@
 							>
 								{#if citations.every((c) => c.distances !== undefined)}
 									<div class="bg-gray-50 dark:bg-gray-800 rounded-full size-4">
-										{idx + 1}
+										{idx + 3}
 									</div>
 								{/if}
 								<div class="flex-1 mx-1 truncate">

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

@@ -109,7 +109,7 @@
 									>
 										{decodeString(document?.metadata?.name ?? document.source.name)}
 									</a>
-									{#if document?.metadata?.page}
+									{#if Number.isInteger(document?.metadata?.page)}
 										<span class="text-xs text-gray-500 dark:text-gray-400">
 											({$i18n.t('page')}
 											{document.metadata.page + 1})

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

@@ -69,6 +69,21 @@
 			>
 			</iframe>
 		{/if}
+	{:else if token.text && token.text.includes('<iframe')}
+		{@const match = token.text.match(/<iframe\s+[^>]*src="([^"]+)"[^>]*><\/iframe>/)}
+		{@const iframeSrc = match && match[1]}
+		{#if iframeSrc}
+			<iframe
+				class="w-full my-2"
+				src={iframeSrc}
+				title="Embedded content"
+				frameborder="0"
+				sandbox
+				onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';"
+			></iframe>
+		{:else}
+			{token.text}
+		{/if}
 	{:else if token.text.includes(`<file type="html"`)}
 		{@const match = token.text.match(/<file type="html" id="([^"]+)"/)}
 		{@const fileId = match && match[1]}
@@ -78,7 +93,7 @@
 				src={`${WEBUI_BASE_URL}/api/v1/files/${fileId}/content/html`}
 				title="Content"
 				frameborder="0"
-				sandbox="allow-scripts{($settings?.iframeSandboxAllowForms ?? false)
+				sandbox="allow-scripts allow-downloads{($settings?.iframeSandboxAllowForms ?? false)
 					? ' allow-forms'
 					: ''}{($settings?.iframeSandboxAllowSameOrigin ?? false) ? ' allow-same-origin' : ''}"
 				referrerpolicy="strict-origin-when-cross-origin"

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

@@ -225,7 +225,7 @@
 					<div
 						class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
 							?.modelIdx == modelIdx
-							? `border-gray-100 dark:border-gray-850 border-[1.5px] ${
+							? `bg-gray-50 dark:bg-gray-850 border-gray-100 dark:border-gray-800 border-2 ${
 									$mobile ? 'min-w-full' : 'min-w-80'
 								}`
 							: `border-gray-100 dark:border-gray-850 border-dashed ${

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

@@ -605,7 +605,7 @@
 			<ProfileImage
 				src={model?.info?.meta?.profile_image_url ??
 					($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
-				className={'size-8'}
+				className={'size-8 assistant-message-profile-image'}
 			/>
 		</div>
 

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

@@ -15,8 +15,10 @@
 
 	<div class="flex flex-col text-left gap-1 mt-1.5">
 		{#each followUps as followUp, idx (idx)}
-			<button
-				class=" mr-2 py-1.5 bg-transparent text-left text-sm flex items-center gap-2 px-1.5 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white transition"
+			<!-- svelte-ignore a11y-no-static-element-interactions -->
+			<!-- svelte-ignore a11y-click-events-have-key-events -->
+			<div
+				class=" mr-2 py-1.5 bg-transparent text-left text-sm flex items-center gap-2 px-1.5 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white transition cursor-pointer"
 				on:click={() => onClick(followUp)}
 				title={followUp}
 				aria-label={followUp}
@@ -26,7 +28,7 @@
 				<div class="line-clamp-1">
 					{followUp}
 				</div>
-			</button>
+			</div>
 
 			{#if idx < followUps.length - 1}
 				<hr class="border-gray-100 dark:border-gray-850" />

+ 16 - 2
src/lib/components/chat/Messages/UserMessage.svelte

@@ -107,7 +107,11 @@
 	}}
 />
 
-<div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
+<div
+	class=" flex w-full user-message group"
+	dir={$settings.chatDirection}
+	id="message-{message.id}"
+>
 	{#if !($settings?.chatBubble ?? true)}
 		<div class={`shrink-0 ltr:mr-3 rtl:ml-3`}>
 			<ProfileImage
@@ -115,7 +119,7 @@
 					? ($models.find((m) => m.id === message.user)?.info?.meta?.profile_image_url ??
 						'/user.png')
 					: (user?.profile_image_url ?? '/user.png')}
-				className={'size-8'}
+				className={'size-8 user-message-profile-image'}
 			/>
 		</div>
 	{/if}
@@ -143,6 +147,16 @@
 					{/if}
 				</Name>
 			</div>
+		{:else if message.timestamp}
+			<div class="flex justify-end pb-1 pr-2">
+				<div
+					class="text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize translate-y-[1px]"
+				>
+					<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
+						<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
+					</Tooltip>
+				</div>
+			</div>
 		{/if}
 
 		<div class="chat-{message.role} w-full min-w-full markdown-prose">

+ 5 - 2
src/lib/components/chat/ModelSelector/Selector.svelte

@@ -425,7 +425,7 @@
 							class="flex gap-1 w-fit text-center text-sm font-medium rounded-full bg-transparent px-1.5 pb-0.5"
 							bind:this={tagsContainerElement}
 						>
-							{#if (items.find((item) => item.model?.connection_type === 'local') && items.find((item) => item.model?.connection_type === 'external')) || items.find((item) => item.model?.direct) || tags.length > 0}
+							{#if items.find((item) => item.model?.connection_type === 'local') || items.find((item) => item.model?.connection_type === 'external') || items.find((item) => item.model?.direct) || tags.length > 0}
 								<button
 									class="min-w-fit outline-none p-1.5 {selectedTag === '' &&
 									selectedConnectionType === ''
@@ -440,7 +440,7 @@
 								</button>
 							{/if}
 
-							{#if items.find((item) => item.model?.connection_type === 'local') && items.find((item) => item.model?.connection_type === 'external')}
+							{#if items.find((item) => item.model?.connection_type === 'local')}
 								<button
 									class="min-w-fit outline-none p-1.5 {selectedConnectionType === 'local'
 										? ''
@@ -452,6 +452,9 @@
 								>
 									{$i18n.t('Local')}
 								</button>
+							{/if}
+
+							{#if items.find((item) => item.model?.connection_type === 'external')}
 								<button
 									class="min-w-fit outline-none p-1.5 {selectedConnectionType === 'external'
 										? ''

+ 33 - 17
src/lib/components/chat/Navbar.svelte

@@ -47,6 +47,15 @@
 
 <ShareChatModal bind:show={showShareChatModal} chatId={$chatId} />
 
+<button
+	id="new-chat-button"
+	class="hidden"
+	on:click={() => {
+		initNewChat();
+	}}
+	aria-label="New Chat"
+/>
+
 <nav class="sticky top-0 z-30 w-full py-1 -mb-8 flex flex-col items-center drag-region">
 	<div class="flex items-center w-full pl-1.5 pr-1">
 		<div
@@ -72,6 +81,24 @@
 							<MenuLines />
 						</div>
 					</button>
+
+					{#if !$mobile}
+						<Tooltip content={$i18n.t('New Chat')}>
+							<button
+								class=" flex {$showSidebar
+									? 'md:hidden'
+									: ''} cursor-pointer px-2 py-2 rounded-xl text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-850 transition"
+								on:click={() => {
+									initNewChat();
+								}}
+								aria-label="New Chat"
+							>
+								<div class=" m-auto self-center">
+									<PencilSquare className=" size-5" strokeWidth="2" />
+								</div>
+							</button>
+						</Tooltip>
+					{/if}
 				</div>
 
 				<div
@@ -135,23 +162,6 @@
 						</button>
 					</Tooltip>
 
-					<Tooltip content={$i18n.t('New Chat')}>
-						<button
-							id="new-chat-button"
-							class=" flex {$showSidebar
-								? 'md:hidden'
-								: ''} cursor-pointer px-2 py-2 rounded-xl text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-850 transition"
-							on:click={() => {
-								initNewChat();
-							}}
-							aria-label="New Chat"
-						>
-							<div class=" m-auto self-center">
-								<PencilSquare className=" size-5" strokeWidth="2" />
-							</div>
-						</button>
-					</Tooltip>
-
 					{#if $user !== undefined && $user !== null}
 						<UserMenu
 							className="max-w-[240px]"
@@ -183,6 +193,12 @@
 		</div>
 	</div>
 
+	{#if $temporaryChatEnabled && $chatId === 'local'}
+		<div class=" w-full z-30 text-center">
+			<div class="text-xs text-gray-500">{$i18n.t('Temporary Chat')}</div>
+		</div>
+	{/if}
+
 	{#if !history.currentId && !$chatId && ($banners.length > 0 || ($config?.license_metadata?.type ?? null) === 'trial' || (($config?.license_metadata?.seats ?? null) !== null && $config?.user_count > $config?.license_metadata?.seats))}
 		<div class=" w-full z-30 mt-5">
 			<div class=" flex flex-col gap-1 w-full">

+ 21 - 8
src/lib/components/chat/Placeholder.svelte

@@ -93,7 +93,7 @@
 <div class="m-auto w-full max-w-6xl px-2 @2xl:px-20 translate-y-6 py-24 text-center">
 	{#if $temporaryChatEnabled}
 		<Tooltip
-			content={$i18n.t('This chat won’t appear in history and your messages will not be saved.')}
+			content={$i18n.t("This chat won't appear in history and your messages will not be saved.")}
 			className="w-full flex justify-center mb-0.5"
 			placement="top"
 		>
@@ -107,7 +107,7 @@
 		class="w-full text-3xl text-gray-800 dark:text-gray-100 text-center flex items-center gap-4 font-primary"
 	>
 		<div class="w-full flex flex-col justify-center items-center">
-			<div class="flex flex-row justify-center gap-3 @sm:gap-3.5 w-fit px-5">
+			<div class="flex flex-row justify-center gap-3 @sm:gap-3.5 w-fit px-5 max-w-xl">
 				<div class="flex shrink-0 justify-center">
 					<div class="flex -space-x-4 mb-0.5" in:fade={{ duration: 100 }}>
 						{#each models as model, modelIdx}
@@ -138,9 +138,20 @@
 					</div>
 				</div>
 
-				<div class=" text-3xl @sm:text-3xl line-clamp-1" in:fade={{ duration: 100 }}>
+				<div
+					class=" text-3xl @sm:text-3xl line-clamp-1 flex items-center"
+					in:fade={{ duration: 100 }}
+				>
 					{#if models[selectedModelIdx]?.name}
-						{models[selectedModelIdx]?.name}
+						<Tooltip
+							content={models[selectedModelIdx]?.name}
+							placement="top"
+							className=" flex items-center "
+						>
+							<span class="line-clamp-1">
+								{models[selectedModelIdx]?.name}
+							</span>
+						</Tooltip>
 					{:else}
 						{$i18n.t('Hello, {{name}}', { name: $user?.name })}
 					{/if}
@@ -205,10 +216,12 @@
 					{createMessagePair}
 					placeholder={$i18n.t('How can I help you today?')}
 					onChange={(input) => {
-						if (input.prompt !== null) {
-							localStorage.setItem(`chat-input`, JSON.stringify(input));
-						} else {
-							localStorage.removeItem(`chat-input`);
+						if (!$temporaryChatEnabled) {
+							if (input.prompt !== null) {
+								localStorage.setItem(`chat-input`, JSON.stringify(input));
+							} else {
+								localStorage.removeItem(`chat-input`);
+							}
 						}
 					}}
 					on:upload={(e) => {

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

@@ -42,7 +42,7 @@
 	});
 </script>
 
-<div class="flex flex-col h-full justify-between space-y-3 text-sm mb-6">
+<div id="tab-about" class="flex flex-col h-full justify-between space-y-3 text-sm mb-6">
 	<div class=" space-y-3 overflow-y-scroll max-h-[28rem] lg:max-h-full">
 		<div>
 			<div class=" mb-2.5 text-sm font-medium flex space-x-2 items-center">

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

@@ -86,7 +86,7 @@
 	});
 </script>
 
-<div class="flex flex-col h-full justify-between text-sm">
+<div id="tab-account" class="flex flex-col h-full justify-between text-sm">
 	<div class=" overflow-y-scroll max-h-[28rem] lg:max-h-full">
 		<input
 			id="profile-image-input"

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

@@ -91,7 +91,7 @@
 	<div>
 		<Tooltip
 			content={$i18n.t(
-				'Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model’s built-in tool-calling capabilities, but requires the model to inherently support this feature.'
+				"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature."
 			)}
 			placement="top-start"
 			className="inline-tooltip"

+ 1 - 0
src/lib/components/chat/Settings/Audio.svelte

@@ -154,6 +154,7 @@
 </script>
 
 <form
+	id="tab-audio"
 	class="flex flex-col h-full justify-between space-y-3 text-sm"
 	on:submit|preventDefault={async () => {
 		saveSettings({

+ 1 - 1
src/lib/components/chat/Settings/Chats.svelte

@@ -107,7 +107,7 @@
 
 <ArchivedChatsModal bind:show={showArchivedChatsModal} onUpdate={handleArchivedChatsChange} />
 
-<div class="flex flex-col h-full justify-between space-y-3 text-sm">
+<div id="tab-chats" class="flex flex-col h-full justify-between space-y-3 text-sm">
 	<div class=" space-y-2 overflow-y-scroll max-h-[28rem] lg:max-h-full">
 		<div class="flex flex-col">
 			<input

+ 6 - 1
src/lib/components/chat/Settings/Connections.svelte

@@ -70,6 +70,7 @@
 <AddConnectionModal direct bind:show={showConnectionModal} onSubmit={addConnectionHandler} />
 
 <form
+	id="tab-connections"
 	class="flex flex-col h-full justify-between text-sm"
 	on:submit|preventDefault={() => {
 		updateHandler();
@@ -126,7 +127,11 @@
 					</div>
 
 					<div class="my-1.5">
-						<div class="text-xs text-gray-500">
+						<div
+							class="text-xs {($settings?.highContrastMode ?? false)
+								? 'text-gray-800 dark:text-gray-100'
+								: 'text-gray-500'}"
+						>
 							{$i18n.t('Connect to your own OpenAI compatible API endpoints.')}
 							<br />
 							{$i18n.t(

+ 4 - 2
src/lib/components/chat/Settings/Connections/Connection.svelte

@@ -2,6 +2,7 @@
 	import { getContext, tick } from 'svelte';
 	const i18n = getContext('i18n');
 
+	import { settings } from '$lib/stores';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
 	import Cog6 from '$lib/components/icons/Cog6.svelte';
@@ -65,7 +66,7 @@
 		<div class="flex w-full gap-2">
 			<div class="flex-1 relative">
 				<input
-					class=" outline-hidden w-full bg-transparent {pipeline ? 'pr-8' : ''}"
+					class={`w-full bg-transparent ${($settings?.highContrastMode ?? false) ? '' : 'outline-hidden'} ${pipeline ? 'pr-8' : ''}`}
 					placeholder={$i18n.t('API Base URL')}
 					bind:value={url}
 					autocomplete="off"
@@ -73,7 +74,7 @@
 			</div>
 
 			<SensitiveInput
-				inputClassName=" outline-hidden bg-transparent w-full"
+				inputClassName="bg-transparent w-full"
 				placeholder={$i18n.t('API Key')}
 				bind:value={key}
 			/>
@@ -83,6 +84,7 @@
 	<div class="flex gap-1">
 		<Tooltip content={$i18n.t('Configure')} className="self-start">
 			<button
+				aria-label={$i18n.t('Open modal to configure connection')}
 				class="self-center p-1 bg-transparent hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 rounded-lg transition"
 				on:click={() => {
 					showConfigModal = true;

+ 10 - 4
src/lib/components/chat/Settings/General.svelte

@@ -187,7 +187,7 @@
 	};
 </script>
 
-<div class="flex flex-col h-full justify-between text-sm">
+<div class="flex flex-col h-full justify-between text-sm" id="tab-general">
 	<div class="  overflow-y-scroll max-h-[28rem] lg:max-h-full">
 		<div class="">
 			<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
@@ -196,7 +196,9 @@
 				<div class=" self-center text-xs font-medium">{$i18n.t('Theme')}</div>
 				<div class="flex items-center relative">
 					<select
-						class=" dark:bg-gray-900 w-fit pr-8 rounded-sm py-2 px-2 text-xs bg-transparent outline-hidden text-right"
+						class="dark:bg-gray-900 w-fit pr-8 rounded-sm py-2 px-2 text-xs bg-transparent text-right {$settings.highContrastMode
+							? ''
+							: 'outline-hidden'}"
 						bind:value={selectedTheme}
 						placeholder="Select a theme"
 						on:change={() => themeChangeHandler(selectedTheme)}
@@ -216,7 +218,9 @@
 				<div class=" self-center text-xs font-medium">{$i18n.t('Language')}</div>
 				<div class="flex items-center relative">
 					<select
-						class=" dark:bg-gray-900 w-fit pr-8 rounded-sm py-2 px-2 text-xs bg-transparent outline-hidden text-right"
+						class="dark:bg-gray-900 w-fit pr-8 rounded-sm py-2 px-2 text-xs bg-transparent text-right {$settings.highContrastMode
+							? ''
+							: 'outline-hidden'}"
 						bind:value={lang}
 						placeholder="Select a language"
 						on:change={(e) => {
@@ -263,7 +267,7 @@
 			</div>
 		</div>
 
-		{#if $user?.role === 'admin' || $user?.permissions.chat?.controls}
+		{#if $user?.role === 'admin' || ($user?.permissions.chat?.system_prompt ?? true)}
 			<hr class="border-gray-50 dark:border-gray-850 my-3" />
 
 			<div>
@@ -275,7 +279,9 @@
 					placeholder={$i18n.t('Enter system prompt here')}
 				/>
 			</div>
+		{/if}
 
+		{#if $user?.role === 'admin' || ($user?.permissions.chat?.controls ?? true)}
 			<div class="mt-2 space-y-3 pr-1.5">
 				<div class="flex justify-between items-center text-sm">
 					<div class="  font-medium">{$i18n.t('Advanced Parameters')}</div>

+ 104 - 35
src/lib/components/chat/Settings/Interface.svelte

@@ -348,6 +348,7 @@
 </script>
 
 <form
+	id="tab-interface"
 	class="flex flex-col h-full justify-between space-y-3 text-sm"
 	on:submit|preventDefault={() => {
 		updateInterfaceHandler();
@@ -384,15 +385,16 @@
 
 	<div class=" space-y-3 overflow-y-scroll max-h-[28rem] lg:max-h-full">
 		<div>
-			<div class=" mb-1.5 text-sm font-medium">{$i18n.t('UI')}</div>
+			<h1 class=" mb-1.5 text-sm font-medium">{$i18n.t('UI')}</h1>
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="high-contrast-mode-label" class=" self-center text-xs">
 						{$i18n.t('High Contrast Mode')} ({$i18n.t('Beta')})
 					</div>
 
 					<button
+						aria-labelledby="high-contrast-mode-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleHighContrastMode();
@@ -410,9 +412,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Landing Page Mode')}</div>
+					<div id="landing-page-mode-label" class=" self-center text-xs">
+						{$i18n.t('Landing Page Mode')}
+					</div>
 
 					<button
+						aria-labelledby="landing-page-mode-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleLandingPageMode();
@@ -430,9 +435,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Chat Bubble UI')}</div>
+					<div id="chat-bubble-ui-label" class=" self-center text-xs">
+						{$i18n.t('Chat Bubble UI')}
+					</div>
 
 					<button
+						aria-labelledby="chat-bubble-ui-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleChatBubble();
@@ -451,11 +459,12 @@
 			{#if !$settings.chatBubble}
 				<div>
 					<div class=" py-0.5 flex w-full justify-between">
-						<div class=" self-center text-xs">
+						<div id="chat-bubble-username-label" class=" self-center text-xs">
 							{$i18n.t('Display the username instead of You in the Chat')}
 						</div>
 
 						<button
+							aria-labelledby="chat-bubble-username-label"
 							class="p-1 px-3 text-xs flex rounded-sm transition"
 							on:click={() => {
 								toggleShowUsername();
@@ -474,13 +483,16 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Widescreen Mode')}</div>
+					<div id="widescreen-mode-label" class=" self-center text-xs">
+						{$i18n.t('Widescreen Mode')}
+					</div>
 
 					<button
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleWidescreenMode();
 						}}
+						aria-labelledby="widescreen-mode-label"
 						type="button"
 					>
 						{#if widescreenMode === true}
@@ -494,9 +506,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Chat direction')}</div>
+					<div id="chat-direction-label" class=" self-center text-xs">
+						{$i18n.t('Chat direction')}
+					</div>
 
 					<button
+						aria-labelledby="chat-direction-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={toggleChangeChatDirection}
 						type="button"
@@ -513,12 +528,13 @@
 			</div>
 
 			<div>
-				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+				<div class="py-0.5 flex w-full justify-between">
+					<div id="notification-sound-label" class=" self-center text-xs">
 						{$i18n.t('Notification Sound')}
 					</div>
 
 					<button
+						aria-labelledby="notification-sound-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleNotificationSound();
@@ -537,11 +553,12 @@
 			{#if notificationSound}
 				<div>
 					<div class=" py-0.5 flex w-full justify-between">
-						<div class=" self-center text-xs">
+						<div id="play-notification-sound-label" class=" self-center text-xs">
 							{$i18n.t('Always Play Notification Sound')}
 						</div>
 
 						<button
+							aria-labelledby="play-notification-sound-label"
 							class="p-1 px-3 text-xs flex rounded-sm transition"
 							on:click={() => {
 								toggleNotificationSoundAlways();
@@ -561,11 +578,12 @@
 			{#if $user?.role === 'admin'}
 				<div>
 					<div class=" py-0.5 flex w-full justify-between">
-						<div class=" self-center text-xs">
+						<div id="toast-notifications-label" class=" self-center text-xs">
 							{$i18n.t('Toast notifications for new updates')}
 						</div>
 
 						<button
+							aria-labelledby="toast-notifications-label"
 							class="p-1 px-3 text-xs flex rounded-sm transition"
 							on:click={() => {
 								toggleShowUpdateToast();
@@ -583,11 +601,12 @@
 
 				<div>
 					<div class=" py-0.5 flex w-full justify-between">
-						<div class=" self-center text-xs">
+						<div id="whats-new-label" class=" self-center text-xs">
 							{$i18n.t(`Show "What's New" modal on login`)}
 						</div>
 
 						<button
+							aria-labelledby="whats-new-label"
 							class="p-1 px-3 text-xs flex rounded-sm transition"
 							on:click={() => {
 								toggleShowChangelog();
@@ -608,9 +627,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Title Auto-Generation')}</div>
+					<div id="auto-generation-label" class=" self-center text-xs">
+						{$i18n.t('Title Auto-Generation')}
+					</div>
 
 					<button
+						aria-labelledby="auto-generation-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleTitleAutoGenerate();
@@ -631,6 +653,7 @@
 					<div class=" self-center text-xs">{$i18n.t('Follow-Up Auto-Generation')}</div>
 
 					<button
+						aria-labelledby="auto-generation-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleAutoFollowUps();
@@ -648,9 +671,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Chat Tags Auto-Generation')}</div>
+					<div id="chat-tags-label" class=" self-center text-xs">
+						{$i18n.t('Chat Tags Auto-Generation')}
+					</div>
 
 					<button
+						aria-labelledby="chat-tags-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleAutoTags();
@@ -668,11 +694,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="detect-artifacts-label" class=" self-center text-xs">
 						{$i18n.t('Detect Artifacts Automatically')}
 					</div>
 
 					<button
+						aria-labelledby="detect-artifacts-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleDetectArtifacts();
@@ -690,11 +717,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="auto-copy-label" class=" self-center text-xs">
 						{$i18n.t('Auto-Copy Response to Clipboard')}
 					</div>
 
 					<button
+						aria-labelledby="auto-copy-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleResponseAutoCopy();
@@ -712,11 +740,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="rich-input-label" class=" self-center text-xs">
 						{$i18n.t('Rich Text Input for Chat')}
 					</div>
 
 					<button
+						aria-labelledby="rich-input-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleRichTextInput();
@@ -735,11 +764,12 @@
 			{#if $config?.features?.enable_autocomplete_generation && richTextInput}
 				<div>
 					<div class=" py-0.5 flex w-full justify-between">
-						<div class=" self-center text-xs">
+						<div id="prompt-autocompletion-label" class=" self-center text-xs">
 							{$i18n.t('Prompt Autocompletion')}
 						</div>
 
 						<button
+							aria-labelledby="prompt-autocompletion-label"
 							class="p-1 px-3 text-xs flex rounded-sm transition"
 							on:click={() => {
 								togglePromptAutocomplete();
@@ -758,11 +788,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="paste-large-label" class=" self-center text-xs">
 						{$i18n.t('Paste Large Text as File')}
 					</div>
 
 					<button
+						aria-labelledby="paste-large-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleLargeTextAsFile();
@@ -780,11 +811,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="copy-formatted-label" class=" self-center text-xs">
 						{$i18n.t('Copy Formatted Text')}
 					</div>
 
 					<button
+						aria-labelledby="copy-formatted-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleCopyFormatted();
@@ -802,9 +834,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Always Collapse Code Blocks')}</div>
+					<div id="always-collapse-label" class=" self-center text-xs">
+						{$i18n.t('Always Collapse Code Blocks')}
+					</div>
 
 					<button
+						aria-labelledby="always-collapse-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleCollapseCodeBlocks();
@@ -822,9 +857,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Always Expand Details')}</div>
+					<div id="always-expand-label" class=" self-center text-xs">
+						{$i18n.t('Always Expand Details')}
+					</div>
 
 					<button
+						aria-labelledby="always-expand-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleExpandDetails();
@@ -842,11 +880,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="chat-background-label" class=" self-center text-xs">
 						{$i18n.t('Chat Background Image')}
 					</div>
 
 					<button
+						aria-labelledby="chat-background-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							if (backgroundImageUrl !== null) {
@@ -868,10 +907,11 @@
 			</div>
 
 			<div>
-				<div class=" py-0.5 flex w-full justify-between">
+				<div id="allow-user-location-label" class=" py-0.5 flex w-full justify-between">
 					<div class=" self-center text-xs">{$i18n.t('Allow User Location')}</div>
 
 					<button
+						aria-labelledby="allow-user-location-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleUserLocation();
@@ -889,11 +929,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="haptic-feedback-label" class=" self-center text-xs">
 						{$i18n.t('Haptic Feedback')} ({$i18n.t('Android')})
 					</div>
 
 					<button
+						aria-labelledby="haptic-feedback-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleHapticFeedback();
@@ -911,11 +952,12 @@
 
 			<!-- <div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="fluidly-stream-label" class=" self-center text-xs">
 						{$i18n.t('Fluidly stream large external response chunks')}
 					</div>
 
 					<button
+					aria-labelledby="fluidly-stream-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleSplitLargeChunks();
@@ -933,11 +975,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="enter-key-behavior-label" class=" self-center text-xs">
 						{$i18n.t('Enter Key Behavior')}
 					</div>
 
 					<button
+						aria-labelledby="enter-key-behavior-label"
 						class="p-1 px-3 text-xs flex rounded transition"
 						on:click={() => {
 							togglectrlEnterToSend();
@@ -955,11 +998,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="scroll-on-branch-change-label" class=" self-center text-xs">
 						{$i18n.t('Scroll On Branch Change')}
 					</div>
 
 					<button
+						aria-labelledby="scroll-on-branch-change-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							togglesScrollOnBranchChange();
@@ -977,9 +1021,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Web Search in Chat')}</div>
+					<div id="web-search-in-chat-label" class=" self-center text-xs">
+						{$i18n.t('Web Search in Chat')}
+					</div>
 
 					<button
+						aria-labelledby="web-search-in-chat-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleWebSearch();
@@ -997,9 +1044,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('iframe Sandbox Allow Same Origin')}</div>
+					<div id="iframe-sandbox-allow-same-origin-label" class=" self-center text-xs">
+						{$i18n.t('iframe Sandbox Allow Same Origin')}
+					</div>
 
 					<button
+						aria-labelledby="iframe-sandbox-allow-same-origin-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleIframeSandboxAllowSameOrigin();
@@ -1017,9 +1067,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('iframe Sandbox Allow Forms')}</div>
+					<div id="iframe-sandbox-allow-forms-label" class=" self-center text-xs">
+						{$i18n.t('iframe Sandbox Allow Forms')}
+					</div>
 
 					<button
+						aria-labelledby="iframe-sandbox-allow-forms-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleIframeSandboxAllowForms();
@@ -1037,11 +1090,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">
+					<div id="stylized-pdf-export-label" class=" self-center text-xs">
 						{$i18n.t('Stylized PDF Export')}
 					</div>
 
 					<button
+						aria-labelledby="stylized-pdf-export-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleStylizedPdfExport();
@@ -1064,6 +1118,7 @@
 					<div class=" self-center text-xs">{$i18n.t('Allow Voice Interruption in Call')}</div>
 
 					<button
+						aria-labelledby="allow-voice-interruption-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleVoiceInterruption();
@@ -1081,9 +1136,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Display Emoji in Call')}</div>
+					<div id="display-emoji-label" class=" self-center text-xs">
+						{$i18n.t('Display Emoji in Call')}
+					</div>
 
 					<button
+						aria-labelledby="display-emoji-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleEmojiInCall();
@@ -1103,9 +1161,12 @@
 
 			<div>
 				<div class=" py-0.5 flex w-full justify-between">
-					<div class=" self-center text-xs">{$i18n.t('Image Compression')}</div>
+					<div id="image-compression-label" class=" self-center text-xs">
+						{$i18n.t('Image Compression')}
+					</div>
 
 					<button
+						aria-labelledby="image-compression-label"
 						class="p-1 px-3 text-xs flex rounded-sm transition"
 						on:click={() => {
 							toggleImageCompression();
@@ -1124,9 +1185,14 @@
 			{#if imageCompression}
 				<div>
 					<div class=" py-0.5 flex w-full justify-between text-xs">
-						<div class=" self-center text-xs">{$i18n.t('Image Max Compression Size')}</div>
+						<div id="image-compression-size-label" class=" self-center text-xs">
+							{$i18n.t('Image Max Compression Size')}
+						</div>
 
 						<div>
+							<label class="sr-only" for="image-comp-width"
+								>{$i18n.t('Image Max Compression Size width')}</label
+							>
 							<input
 								bind:value={imageCompressionSize.width}
 								type="number"
@@ -1134,6 +1200,9 @@
 								min="0"
 								placeholder="Width"
 							/>x
+							<label class="sr-only" for="image-comp-height"
+								>{$i18n.t('Image Max Compression Size height')}</label
+							>
 							<input
 								bind:value={imageCompressionSize.height}
 								type="number"

+ 1 - 0
src/lib/components/chat/Settings/Personalization.svelte

@@ -24,6 +24,7 @@
 <ManageModal bind:show={showManageModal} />
 
 <form
+	id="tab-personalization"
 	class="flex flex-col h-full justify-between space-y-3 text-sm"
 	on:submit|preventDefault={() => {
 		dispatch('save');

+ 6 - 1
src/lib/components/chat/Settings/Tools.svelte

@@ -42,6 +42,7 @@
 <AddServerModal bind:show={showConnectionModal} onSubmit={addConnectionHandler} direct />
 
 <form
+	id="tab-tools"
 	class="flex flex-col h-full justify-between text-sm"
 	on:submit|preventDefault={() => {
 		updateHandler();
@@ -60,6 +61,7 @@
 
 							<Tooltip content={$i18n.t(`Add Connection`)}>
 								<button
+									aria-label={$i18n.t(`Add Connection`)}
 									class="px-1"
 									on:click={() => {
 										showConnectionModal = true;
@@ -89,7 +91,10 @@
 					</div>
 
 					<div class="my-1.5">
-						<div class="text-xs text-gray-500">
+						<div
+							class={`text-xs 
+								${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
+						>
 							{$i18n.t('Connect to your own OpenAPI compatible external tool servers.')}
 							<br />
 							{$i18n.t(

+ 41 - 5
src/lib/components/chat/SettingsModal.svelte

@@ -211,7 +211,9 @@
 			: []),
 
 		...($user?.role === 'admin' ||
-		($user?.role === 'user' && $user?.permissions?.features?.direct_tool_servers)
+		($user?.role === 'user' &&
+			$user?.permissions?.features?.direct_tool_servers &&
+			$config?.features?.direct_tool_servers)
 			? [
 					{
 						id: 'tools',
@@ -554,6 +556,7 @@
 
 		<div class="flex flex-col md:flex-row w-full px-4 pt-1 pb-4 md:space-x-4">
 			<div
+				role="tablist"
 				id="settings-tabs-container"
 				class="tabs flex flex-row overflow-x-auto gap-2.5 md:gap-1 md:flex-col flex-1 md:flex-none md:w-40 md:min-h-[32rem] md:max-h-[32rem] dark:text-gray-200 text-sm font-medium text-left mb-1 md:mb-0 -translate-y-1"
 			>
@@ -574,11 +577,13 @@
 						placeholder={$i18n.t('Search')}
 					/>
 				</div>
-
 				{#if visibleTabs.length > 0}
 					{#each visibleTabs as tabId (tabId)}
 						{#if tabId === 'general'}
 							<button
+								role="tab"
+								aria-controls="tab-general"
+								aria-selected={selectedTab === 'general'}
 								class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'general'
@@ -596,6 +601,7 @@
 								<div class=" self-center mr-2">
 									<svg
 										xmlns="http://www.w3.org/2000/svg"
+										aria-hidden="true"
 										viewBox="0 0 20 20"
 										fill="currentColor"
 										class="w-4 h-4"
@@ -611,6 +617,9 @@
 							</button>
 						{:else if tabId === 'interface'}
 							<button
+								role="tab"
+								aria-controls="tab-interface"
+								aria-selected={selectedTab === 'interface'}
 								class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'interface'
@@ -628,6 +637,7 @@
 								<div class=" self-center mr-2">
 									<svg
 										xmlns="http://www.w3.org/2000/svg"
+										aria-hidden="true"
 										viewBox="0 0 16 16"
 										fill="currentColor"
 										class="w-4 h-4"
@@ -644,6 +654,9 @@
 						{:else if tabId === 'connections'}
 							{#if $user?.role === 'admin' || ($user?.role === 'user' && $config?.features?.enable_direct_connections)}
 								<button
+									role="tab"
+									aria-controls="tab-connections"
+									aria-selected={selectedTab === 'connections'}
 									class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'connections'
@@ -661,6 +674,7 @@
 									<div class=" self-center mr-2">
 										<svg
 											xmlns="http://www.w3.org/2000/svg"
+											aria-hidden="true"
 											viewBox="0 0 16 16"
 											fill="currentColor"
 											class="w-4 h-4"
@@ -674,8 +688,11 @@
 								</button>
 							{/if}
 						{:else if tabId === 'tools'}
-							{#if $user?.role === 'admin' || ($user?.role === 'user' && $user?.permissions?.features?.direct_tool_servers)}
+							{#if $user?.role === 'admin' || ($user?.role === 'user' && $user?.permissions?.features?.direct_tool_servers && $config?.features?.direct_tool_servers)}
 								<button
+									role="tab"
+									aria-controls="tab-tools"
+									aria-selected={selectedTab === 'tools'}
 									class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'tools'
@@ -693,6 +710,7 @@
 									<div class=" self-center mr-2">
 										<svg
 											xmlns="http://www.w3.org/2000/svg"
+											aria-hidden="true"
 											viewBox="0 0 24 24"
 											fill="currentColor"
 											class="size-4"
@@ -709,6 +727,9 @@
 							{/if}
 						{:else if tabId === 'personalization'}
 							<button
+								role="tab"
+								aria-controls="tab-personalization"
+								aria-selected={selectedTab === 'personalization'}
 								class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'personalization'
@@ -730,6 +751,9 @@
 							</button>
 						{:else if tabId === 'audio'}
 							<button
+								role="tab"
+								aria-controls="tab-audio"
+								aria-selected={selectedTab === 'audio'}
 								class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'audio'
@@ -747,6 +771,7 @@
 								<div class=" self-center mr-2">
 									<svg
 										xmlns="http://www.w3.org/2000/svg"
+										aria-hidden="true"
 										viewBox="0 0 16 16"
 										fill="currentColor"
 										class="w-4 h-4"
@@ -763,6 +788,9 @@
 							</button>
 						{:else if tabId === 'chats'}
 							<button
+								role="tab"
+								aria-controls="tab-chats"
+								aria-selected={selectedTab === 'chats'}
 								class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'chats'
@@ -780,6 +808,7 @@
 								<div class=" self-center mr-2">
 									<svg
 										xmlns="http://www.w3.org/2000/svg"
+										aria-hidden="true"
 										viewBox="0 0 16 16"
 										fill="currentColor"
 										class="w-4 h-4"
@@ -795,6 +824,9 @@
 							</button>
 						{:else if tabId === 'account'}
 							<button
+								role="tab"
+								aria-controls="tab-account"
+								aria-selected={selectedTab === 'account'}
 								class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'account'
@@ -812,6 +844,7 @@
 								<div class=" self-center mr-2">
 									<svg
 										xmlns="http://www.w3.org/2000/svg"
+										aria-hidden="true"
 										viewBox="0 0 16 16"
 										fill="currentColor"
 										class="w-4 h-4"
@@ -827,6 +860,9 @@
 							</button>
 						{:else if tabId === 'about'}
 							<button
+								role="tab"
+								aria-controls="tab-about"
+								aria-selected={selectedTab === 'about'}
 								class={`px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition
 								${
 									selectedTab === 'about'
@@ -844,6 +880,7 @@
 								<div class=" self-center mr-2">
 									<svg
 										xmlns="http://www.w3.org/2000/svg"
+										aria-hidden="true"
 										viewBox="0 0 20 20"
 										fill="currentColor"
 										class="w-4 h-4"
@@ -864,7 +901,6 @@
 						{$i18n.t('No results found')}
 					</div>
 				{/if}
-
 				{#if $user?.role === 'admin'}
 					<a
 						href="/admin/settings"
@@ -880,9 +916,9 @@
 						<div class=" self-center mr-2">
 							<svg
 								xmlns="http://www.w3.org/2000/svg"
+								aria-hidden="true"
 								viewBox="0 0 24 24"
 								fill="currentColor"
-								aria-hidden="true"
 								class="size-4"
 							>
 								<path

+ 60 - 2
src/lib/components/chat/ShortcutsModal.svelte

@@ -2,6 +2,7 @@
 	import { getContext } from 'svelte';
 	import Modal from '../common/Modal.svelte';
 
+	import Tooltip from '../common/Tooltip.svelte';
 	const i18n = getContext('i18n');
 
 	export let show = false;
@@ -75,6 +76,26 @@
 						</div>
 					</div>
 
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">
+							<Tooltip
+								content={$i18n.t(
+									'Only active when the chat input is in focus and an LLM is generating a response.'
+								)}
+							>
+								{$i18n.t('Stop Generating')}<span class="text-xs"> *</span>
+							</Tooltip>
+						</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Esc
+							</div>
+						</div>
+					</div>
+
 					<div class="w-full flex justify-between items-center">
 						<div class=" text-sm">{$i18n.t('Copy last code block')}</div>
 
@@ -124,7 +145,15 @@
 					</div>
 
 					<div class="w-full flex justify-between items-center">
-						<div class=" text-sm">{$i18n.t('Generate prompt pair')}</div>
+						<div class=" text-sm">
+							<Tooltip
+								content={$i18n.t(
+									'Only active when "Paste Large Text as File" setting is toggled on.'
+								)}
+							>
+								{$i18n.t('Prevent file creation')}<span class="text-s"> *</span>
+							</Tooltip>
+						</div>
 
 						<div class="flex space-x-1 text-xs">
 							<div
@@ -142,13 +171,37 @@
 							<div
 								class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
 							>
-								Enter
+								V
 							</div>
 						</div>
 					</div>
 				</div>
 
 				<div class="flex flex-col space-y-3 w-full self-start">
+					<div class="w-full flex justify-between items-center">
+						<div class=" text-sm">{$i18n.t('Generate prompt pair')}</div>
+
+						<div class="flex space-x-1 text-xs">
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Ctrl/⌘
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Shift
+							</div>
+
+							<div
+								class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
+							>
+								Enter
+							</div>
+						</div>
+					</div>
+
 					<div class="w-full flex justify-between items-center">
 						<div class=" text-sm">{$i18n.t('Toggle search')}</div>
 
@@ -251,6 +304,11 @@
 			</div>
 		</div>
 
+		<div class="px-5 pb-4 text-xs text-gray-500 dark:text-gray-400">
+			{$i18n.t(
+				'Shortcuts with an asterisk (*) are situational and only active under specific conditions.'
+			)}
+		</div>
 		<div class=" flex justify-between dark:text-gray-300 px-5">
 			<div class=" text-lg font-medium self-center">{$i18n.t('Input commands')}</div>
 		</div>

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

@@ -83,7 +83,7 @@
 					{/if}
 				</div>
 
-				<div class="flex-1 text-xs text-gray-700 dark:text-white">
+				<div class="flex-1 text-xs text-gray-700 dark:text-white max-h-20 overflow-y-auto">
 					{@html marked.parse(DOMPurify.sanitize(banner.content))}
 				</div>
 			</div>

+ 10 - 1
src/lib/components/common/SensitiveInput.svelte

@@ -1,4 +1,7 @@
 <script lang="ts">
+	const i18n = getContext('i18n');
+	import { getContext } from 'svelte';
+	import { settings } from '$lib/stores';
 	export let value: string = '';
 	export let placeholder = '';
 	export let required = true;
@@ -12,9 +15,11 @@
 </script>
 
 <div class={outerClassName}>
+	<label class="sr-only" for="password-input">{placeholder || $i18n.t('Password')}</label>
 	<input
-		class={`${inputClassName} ${show ? '' : 'password'}`}
+		class={`${inputClassName} ${show ? '' : 'password'} ${($settings?.highContrastMode ?? false) ? '' : ' outline-hidden'}`}
 		{placeholder}
+		id="password-input"
 		bind:value
 		required={required && !readOnly}
 		disabled={readOnly}
@@ -24,6 +29,8 @@
 	<button
 		class={showButtonClassName}
 		type="button"
+		aria-pressed={show}
+		aria-label={$i18n.t('Make password visible in the user interface')}
 		on:click={(e) => {
 			e.preventDefault();
 			show = !show;
@@ -34,6 +41,7 @@
 				xmlns="http://www.w3.org/2000/svg"
 				viewBox="0 0 16 16"
 				fill="currentColor"
+				aria-hidden="true"
 				class="size-4"
 			>
 				<path
@@ -51,6 +59,7 @@
 				viewBox="0 0 16 16"
 				fill="currentColor"
 				class="size-4"
+				aria-hidden="true"
 			>
 				<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
 				<path

+ 2 - 0
src/lib/components/common/Switch.svelte

@@ -2,6 +2,7 @@
 	import { createEventDispatcher, tick } from 'svelte';
 	import { Switch } from 'bits-ui';
 	export let state = true;
+	export let id = '';
 
 	const dispatch = createEventDispatcher();
 
@@ -10,6 +11,7 @@
 
 <Switch.Root
 	bind:checked={state}
+	{id}
 	class="flex h-5 min-h-5 w-9 shrink-0 cursor-pointer items-center rounded-full px-[3px] mx-[1px] transition  {state
 		? ' bg-emerald-600'
 		: 'bg-gray-200 dark:bg-transparent'} outline outline-1 outline-gray-100 dark:outline-gray-800"

+ 2 - 2
src/lib/components/common/Tags.svelte

@@ -9,7 +9,7 @@
 	export let tags = [];
 </script>
 
-<div class="flex flex-row flex-wrap gap-1 line-clamp-1">
+<ul class="flex flex-row flex-wrap gap-1 line-clamp-1">
 	<TagList
 		{tags}
 		on:delete={(e) => {
@@ -23,4 +23,4 @@
 			dispatch('add', e.detail);
 		}}
 	/>
-</div>
+</ul>

+ 3 - 0
src/lib/components/common/Tags/TagInput.svelte

@@ -29,6 +29,7 @@
 				bind:value={tagName}
 				class=" px-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-hidden line-clamp-1 w-[6.5rem]"
 				placeholder={$i18n.t('Add a tag')}
+				aria-label={$i18n.t('Add a tag')}
 				list="tagOptions"
 				on:keydown={(event) => {
 					if (event.key === 'Enter') {
@@ -48,6 +49,7 @@
 					viewBox="0 0 16 16"
 					fill="currentColor"
 					stroke-width="2"
+					aria-hidden="true"
 					class="w-3 h-3"
 				>
 					<path
@@ -72,6 +74,7 @@
 			<svg
 				xmlns="http://www.w3.org/2000/svg"
 				viewBox="0 0 16 16"
+				aria-hidden="true"
 				fill="currentColor"
 				class="w-3 h-3 {showTagInput ? 'rotate-45' : ''} transition-all transform"
 			>

+ 5 - 2
src/lib/components/common/Tags/TagList.svelte

@@ -1,5 +1,7 @@
 <script lang="ts">
 	import { createEventDispatcher } from 'svelte';
+	import { getContext } from 'svelte';
+	const i18n = getContext('i18n');
 	import Tooltip from '../Tooltip.svelte';
 	import XMark from '$lib/components/icons/XMark.svelte';
 	import Badge from '../Badge.svelte';
@@ -10,7 +12,7 @@
 
 {#each tags as tag}
 	<Tooltip content={tag.name}>
-		<div
+		<li
 			class="relative group/tags px-1.5 py-[0.2px] gap-0.5 flex justify-between h-fit max-h-fit w-fit items-center rounded-full bg-gray-500/20 text-gray-700 dark:text-gray-200 transition cursor-pointer"
 		>
 			<div class=" text-[0.7rem] font-medium self-center line-clamp-1 w-fit">
@@ -23,10 +25,11 @@
 						dispatch('delete', tag.name);
 					}}
 					type="button"
+					aria-label={$i18n.t('Remove this tag from list')}
 				>
 					<XMark className="size-3" strokeWidth="2.5" />
 				</button>
 			</div>
-		</div>
+		</li>
 	</Tooltip>
 {/each}

+ 19 - 0
src/lib/components/icons/Code.svelte

@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className: string = 'w-5 h-5';
+	export let strokeWidth: string = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M14.25 9.75 16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0 0 20.25 18V6A2.25 2.25 0 0 0 18 3.75H6A2.25 2.25 0 0 0 3.75 6v12A2.25 2.25 0 0 0 6 20.25Z"
+	/>
+</svg>

+ 1 - 0
src/lib/components/icons/Cog6.svelte

@@ -8,6 +8,7 @@
 	fill="none"
 	viewBox="0 0 24 24"
 	stroke-width={strokeWidth}
+	aria-hidden="true"
 	stroke="currentColor"
 	class={className}
 >

+ 1 - 0
src/lib/components/icons/Minus.svelte

@@ -6,6 +6,7 @@
 <svg
 	xmlns="http://www.w3.org/2000/svg"
 	fill="none"
+	aria-hidden="true"
 	viewBox="0 0 24 24"
 	stroke-width={strokeWidth}
 	stroke="currentColor"

+ 1 - 0
src/lib/components/icons/Plus.svelte

@@ -10,6 +10,7 @@
 	stroke-width={strokeWidth}
 	stroke="currentColor"
 	class={className}
+	aria-hidden="true"
 >
 	<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
 </svg>

+ 20 - 0
src/lib/components/icons/Settings.svelte

@@ -0,0 +1,20 @@
+<script lang="ts">
+	export let className: string = 'w-5 h-5';
+	export let strokeWidth: string = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
+	/>
+	<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
+</svg>

+ 16 - 0
src/lib/components/icons/SignOut.svelte

@@ -0,0 +1,16 @@
+<script lang="ts">
+	export let className: string = 'w-5 h-5';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M3 4.25A2.25 2.25 0 015.25 2h5.5A2.25 2.25 0 0113 4.25v2a.75.75 0 01-1.5 0v-2a.75.75 0 00-.75-.75h-5.5a.75.75 0 00-.75.75v11.5c0 .414.336.75.75.75h5.5a.75.75 0 00.75-.75v-2a.75.75 0 011.5 0v2A2.25 2.25 0 0110.75 18h-5.5A2.25 2.25 0 013 15.75V4.25z"
+		clip-rule="evenodd"
+	/>
+	<path
+		fill-rule="evenodd"
+		d="M6 10a.75.75 0 01.75-.75h9.546l-1.048-.943a.75.75 0 111.004-1.114l2.5 2.25a.75.75 0 010 1.114l-2.5 2.25a.75.75 0 11-1.004-1.114l1.048-.943H6.75A.75.75 0 016 10z"
+		clip-rule="evenodd"
+	/>
+</svg>

+ 19 - 0
src/lib/components/icons/UserGroup.svelte

@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className: string = 'w-5 h-5';
+	export let strokeWidth: string = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
+	/>
+</svg>

+ 9 - 9
src/lib/components/layout/Navbar/Menu.svelte

@@ -250,7 +250,7 @@
 
 			{#if $mobile}
 				<DropdownMenu.Item
-					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 					id="chat-controls-button"
 					on:click={async () => {
 						await showControls.set(true);
@@ -265,7 +265,7 @@
 
 			{#if !$temporaryChatEnabled && ($user?.role === 'admin' || ($user.permissions?.chat?.share ?? true))}
 				<DropdownMenu.Item
-					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 					id="chat-share-button"
 					on:click={() => {
 						shareHandler();
@@ -288,7 +288,7 @@
 			{/if}
 
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 				id="chat-overview-button"
 				on:click={async () => {
 					await showControls.set(true);
@@ -301,7 +301,7 @@
 			</DropdownMenu.Item>
 
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 				id="chat-overview-button"
 				on:click={async () => {
 					await showControls.set(true);
@@ -315,7 +315,7 @@
 
 			<DropdownMenu.Sub>
 				<DropdownMenu.SubTrigger
-					class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 				>
 					<svg
 						xmlns="http://www.w3.org/2000/svg"
@@ -341,7 +341,7 @@
 				>
 					{#if $user?.role === 'admin' || ($user.permissions?.chat?.export ?? true)}
 						<DropdownMenu.Item
-							class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+							class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 							on:click={() => {
 								downloadJSONExport();
 							}}
@@ -350,7 +350,7 @@
 						</DropdownMenu.Item>
 					{/if}
 					<DropdownMenu.Item
-						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+						class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 						on:click={() => {
 							downloadTxt();
 						}}
@@ -359,7 +359,7 @@
 					</DropdownMenu.Item>
 
 					<DropdownMenu.Item
-						class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+						class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 						on:click={() => {
 							downloadPdf();
 						}}
@@ -370,7 +370,7 @@
 			</DropdownMenu.Sub>
 
 			<DropdownMenu.Item
-				class="flex gap-2 items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+				class="flex gap-2 items-center px-3 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md select-none w-full"
 				id="chat-copy-button"
 				on:click={async () => {
 					const res = await copyToClipboard(await getChatAsText()).catch((e) => {

+ 4 - 13
src/lib/components/layout/Sidebar.svelte

@@ -490,10 +490,9 @@
 				draggable="false"
 				on:click={async () => {
 					selectedChatId = null;
-					await goto('/');
-					const newChatButton = document.getElementById('new-chat-button');
+
+					await temporaryChatEnabled.set(false);
 					setTimeout(() => {
-						newChatButton?.click();
 						if ($mobile) {
 							showSidebar.set(false);
 						}
@@ -505,7 +504,7 @@
 						<img
 							crossorigin="anonymous"
 							src="{WEBUI_BASE_URL}/static/favicon.png"
-							class=" size-5 -translate-x-1.5 rounded-full"
+							class="sidebar-new-chat-icon size-5 -translate-x-1.5 rounded-full"
 							alt="logo"
 						/>
 					</div>
@@ -645,11 +644,7 @@
 			</div>
 		{/if}
 
-		<div
-			class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden {$temporaryChatEnabled
-				? 'opacity-20'
-				: ''}"
-		>
+		<div class="relative flex flex-col flex-1 overflow-y-auto overflow-x-hidden">
 			{#if ($models ?? []).length > 0 && ($settings?.pinnedModels ?? []).length > 0}
 				<div class="mt-0.5">
 					{#each $settings.pinnedModels as modelId (modelId)}
@@ -773,10 +768,6 @@
 					}
 				}}
 			>
-				{#if $temporaryChatEnabled}
-					<div class="absolute z-40 w-full h-full flex justify-center"></div>
-				{/if}
-
 				{#if $pinnedChats.length > 0}
 					<div class="flex flex-col space-y-1 rounded-xl">
 						<Folder

+ 77 - 102
src/lib/components/layout/Sidebar/UserMenu.svelte

@@ -4,15 +4,23 @@
 
 	import { flyAndScale } from '$lib/utils/transitions';
 	import { goto } from '$app/navigation';
-	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
-	import { showSettings, activeUserIds, USAGE_POOL, mobile, showSidebar, user } from '$lib/stores';
 	import { fade, slide } from 'svelte/transition';
-	import Tooltip from '$lib/components/common/Tooltip.svelte';
+
+	import { getUsage } from '$lib/apis';
 	import { userSignOut } from '$lib/apis/auths';
+
+	import { showSettings, mobile, showSidebar, user } from '$lib/stores';
+
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
 	import QuestionMarkCircle from '$lib/components/icons/QuestionMarkCircle.svelte';
 	import Map from '$lib/components/icons/Map.svelte';
 	import Keyboard from '$lib/components/icons/Keyboard.svelte';
 	import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
+	import Settings from '$lib/components/icons/Settings.svelte';
+	import Code from '$lib/components/icons/Code.svelte';
+	import UserGroup from '$lib/components/icons/UserGroup.svelte';
+	import SignOut from '$lib/components/icons/SignOut.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -24,10 +32,28 @@
 	let showShortcuts = false;
 
 	const dispatch = createEventDispatcher();
+
+	let usage = null;
+	const getUsageInfo = async () => {
+		const res = await getUsage(localStorage.token).catch((error) => {
+			console.error('Error fetching usage info:', error);
+		});
+
+		if (res) {
+			usage = res;
+		} else {
+			usage = null;
+		}
+	};
+
+	$: if (show) {
+		getUsageInfo();
+	}
 </script>
 
 <ShortcutsModal bind:show={showShortcuts} />
 
+<!-- svelte-ignore a11y-no-static-element-interactions -->
 <DropdownMenu.Root
 	bind:open={show}
 	onOpenChange={(state) => {
@@ -58,25 +84,7 @@
 				}}
 			>
 				<div class=" self-center mr-3">
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						fill="none"
-						viewBox="0 0 24 24"
-						stroke-width="1.5"
-						stroke="currentColor"
-						class="w-5 h-5"
-					>
-						<path
-							stroke-linecap="round"
-							stroke-linejoin="round"
-							d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
-						/>
-						<path
-							stroke-linecap="round"
-							stroke-linejoin="round"
-							d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
-						/>
-					</svg>
+					<Settings className="w-5 h-5" strokeWidth="1.5" />
 				</div>
 				<div class=" self-center truncate">{$i18n.t('Settings')}</div>
 			</button>
@@ -99,10 +107,10 @@
 			</button>
 
 			{#if role === 'admin'}
-				<a
+				<button
 					class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
-					href="/playground"
 					on:click={() => {
+						goto('/playground');
 						show = false;
 
 						if ($mobile) {
@@ -111,28 +119,15 @@
 					}}
 				>
 					<div class=" self-center mr-3">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							fill="none"
-							viewBox="0 0 24 24"
-							stroke-width="1.5"
-							stroke="currentColor"
-							class="size-5"
-						>
-							<path
-								stroke-linecap="round"
-								stroke-linejoin="round"
-								d="M14.25 9.75 16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0 0 20.25 18V6A2.25 2.25 0 0 0 18 3.75H6A2.25 2.25 0 0 0 3.75 6v12A2.25 2.25 0 0 0 6 20.25Z"
-							/>
-						</svg>
+						<Code className="size-5" strokeWidth="1.5" />
 					</div>
 					<div class=" self-center truncate">{$i18n.t('Playground')}</div>
-				</a>
+				</button>
 
-				<a
+				<button
 					class="flex rounded-md py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition"
-					href="/admin"
 					on:click={() => {
+						goto('/admin');
 						show = false;
 
 						if ($mobile) {
@@ -141,23 +136,10 @@
 					}}
 				>
 					<div class=" self-center mr-3">
-						<svg
-							xmlns="http://www.w3.org/2000/svg"
-							fill="none"
-							viewBox="0 0 24 24"
-							stroke-width="1.5"
-							stroke="currentColor"
-							class="w-5 h-5"
-						>
-							<path
-								stroke-linecap="round"
-								stroke-linejoin="round"
-								d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z"
-							/>
-						</svg>
+						<UserGroup className="w-5 h-5" strokeWidth="1.5" />
 					</div>
 					<div class=" self-center truncate">{$i18n.t('Admin Panel')}</div>
-				</a>
+				</button>
 			{/if}
 
 			{#if help}
@@ -165,10 +147,11 @@
 
 				<!-- {$i18n.t('Help')} -->
 				<DropdownMenu.Item
-					class="flex gap-2 items-center py-1.5 px-3 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					class="flex gap-2 items-center py-1.5 px-3 text-sm select-none w-full cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md transition"
 					id="chat-share-button"
 					on:click={() => {
 						window.open('https://docs.openwebui.com', '_blank');
+						show = false;
 					}}
 				>
 					<QuestionMarkCircle className="size-5" />
@@ -177,10 +160,11 @@
 
 				<!-- Releases -->
 				<DropdownMenu.Item
-					class="flex gap-2 items-center py-1.5 px-3 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					class="flex gap-2 items-center py-1.5 px-3 text-sm select-none w-full cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md transition"
 					id="menu-item-releases"
 					on:click={() => {
 						window.open('https://github.com/open-webui/open-webui/releases', '_blank');
+						show = false;
 					}}
 				>
 					<Map className="size-5" />
@@ -188,7 +172,7 @@
 				</DropdownMenu.Item>
 
 				<DropdownMenu.Item
-					class="flex gap-2 items-center py-1.5 px-3 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					class="flex gap-2 items-center py-1.5 px-3 text-sm select-none w-full cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md transition"
 					id="chat-share-button"
 					on:click={() => {
 						showShortcuts = !showShortcuts;
@@ -214,55 +198,46 @@
 				}}
 			>
 				<div class=" self-center mr-3">
-					<svg
-						xmlns="http://www.w3.org/2000/svg"
-						viewBox="0 0 20 20"
-						fill="currentColor"
-						class="w-5 h-5"
-					>
-						<path
-							fill-rule="evenodd"
-							d="M3 4.25A2.25 2.25 0 015.25 2h5.5A2.25 2.25 0 0113 4.25v2a.75.75 0 01-1.5 0v-2a.75.75 0 00-.75-.75h-5.5a.75.75 0 00-.75.75v11.5c0 .414.336.75.75.75h5.5a.75.75 0 00.75-.75v-2a.75.75 0 011.5 0v2A2.25 2.25 0 0110.75 18h-5.5A2.25 2.25 0 013 15.75V4.25z"
-							clip-rule="evenodd"
-						/>
-						<path
-							fill-rule="evenodd"
-							d="M6 10a.75.75 0 01.75-.75h9.546l-1.048-.943a.75.75 0 111.004-1.114l2.5 2.25a.75.75 0 010 1.114l-2.5 2.25a.75.75 0 11-1.004-1.114l1.048-.943H6.75A.75.75 0 016 10z"
-							clip-rule="evenodd"
-						/>
-					</svg>
+					<SignOut className="w-5 h-5" strokeWidth="1.5" />
 				</div>
 				<div class=" self-center truncate">{$i18n.t('Sign Out')}</div>
 			</button>
 
-			{#if $activeUserIds?.length > 0}
-				<hr class=" border-gray-100 dark:border-gray-800 my-1 p-0" />
-
-				<Tooltip
-					content={$USAGE_POOL && $USAGE_POOL.length > 0
-						? `${$i18n.t('Running')}: ${$USAGE_POOL.join(', ')} ✨`
-						: ''}
-				>
-					<div class="flex rounded-md py-1 px-3 text-xs gap-2.5 items-center">
-						<div class=" flex items-center">
-							<span class="relative flex size-2">
-								<span
-									class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
-								/>
-								<span class="relative inline-flex rounded-full size-2 bg-green-500" />
-							</span>
-						</div>
+			{#if usage}
+				{#if usage?.user_ids?.length > 0}
+					<hr class=" border-gray-100 dark:border-gray-800 my-1 p-0" />
 
-						<div class=" ">
-							<span class="">
-								{$i18n.t('Active Users')}:
-							</span>
-							<span class=" font-semibold">
-								{$activeUserIds?.length}
-							</span>
+					<Tooltip
+						content={usage?.model_ids && usage?.model_ids.length > 0
+							? `${$i18n.t('Running')}: ${usage.model_ids.join(', ')} ✨`
+							: ''}
+					>
+						<div
+							class="flex rounded-md py-1 px-3 text-xs gap-2.5 items-center"
+							on:mouseenter={() => {
+								getUsageInfo();
+							}}
+						>
+							<div class=" flex items-center">
+								<span class="relative flex size-2">
+									<span
+										class="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"
+									/>
+									<span class="relative inline-flex rounded-full size-2 bg-green-500" />
+								</span>
+							</div>
+
+							<div class=" ">
+								<span class="">
+									{$i18n.t('Active Users')}:
+								</span>
+								<span class=" font-semibold">
+									{usage?.user_ids?.length}
+								</span>
+							</div>
 						</div>
-					</div>
-				</Tooltip>
+					</Tooltip>
+				{/if}
 			{/if}
 
 			<!-- <DropdownMenu.Item class="flex items-center py-1.5 px-3 text-sm ">

+ 1 - 1
src/lib/components/notes/Notes/NoteMenu.svelte

@@ -5,7 +5,7 @@
 	import { flyAndScale } from '$lib/utils/transitions';
 	import { fade, slide } from 'svelte/transition';
 
-	import { showSettings, activeUserIds, USAGE_POOL, mobile, showSidebar, user } from '$lib/stores';
+	import { showSettings, mobile, showSidebar, user } from '$lib/stores';
 
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';

+ 1 - 1
src/lib/components/notes/RecordMenu.svelte

@@ -2,7 +2,7 @@
 	import { DropdownMenu } from 'bits-ui';
 	import { createEventDispatcher, getContext, onMount } from 'svelte';
 
-	import { showSettings, activeUserIds, USAGE_POOL, mobile, showSidebar, user } from '$lib/stores';
+	import { showSettings, mobile, showSidebar, user } from '$lib/stores';
 	import { fade, slide } from 'svelte/transition';
 
 	import Mic from '../icons/Mic.svelte';

+ 28 - 2
src/lib/i18n/locales/ar-BH/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "",
 	"Allow Chat Export": "",
 	"Allow Chat Share": "",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "",
 	"Allow Multiple Models in Chat": "",
 	"Allow non-local voices": "",
@@ -209,6 +210,7 @@
 	"Clone Chat": "",
 	"Clone of {{TITLE}}": "",
 	"Close": "أغلق",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "",
 	"Code Execution": "",
@@ -297,7 +299,7 @@
 	"Default": "الإفتراضي",
 	"Default (Open AI)": "",
 	"Default (SentenceTransformers)": "(SentenceTransformers) الإفتراضي",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
 	"Default Model": "النموذج الافتراضي",
 	"Default model updated": "الإفتراضي تحديث الموديل",
 	"Default Models": "",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "",
 	"e.g., 3, 4, 5 (leave blank for default)": "",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
 	"e.g., westus (leave blank for eastus)": "",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "",
 	"Features Permissions": "",
 	"February": "فبراير",
+	"Feedback Details": "",
 	"Feedback History": "",
 	"Feedbacks": "",
 	"Feel free to add specific details": "لا تتردد في إضافة تفاصيل محددة",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "",
 	"Image": "",
 	"Image Compression": "",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "",
 	"Image Generation (Experimental)": "توليد الصور (تجريبي)",
 	"Image Generation Engine": "محرك توليد الصور",
 	"Image Max Compression Size": "",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "",
 	"Image Prompt Generation Prompt": "",
 	"Image Settings": "إعدادات الصورة",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "",
 	"Leaderboard": "",
 	"Learn more about OpenAPI tool servers.": "",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
@@ -777,6 +786,7 @@
 	"Lost": "",
 	"LTR": "من جهة اليسار إلى اليمين",
 	"Made by Open WebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "تأكد من إرفاقها",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
 	"Manage": "",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Ollama الاصدار",
 	"On": "تشغيل",
 	"OneDrive": "",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "يُسمح فقط بالأحرف الأبجدية الرقمية والواصلات في سلسلة الأمر.",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
@@ -909,6 +921,7 @@
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "خطاء! أنت تستخدم طريقة غير مدعومة (الواجهة الأمامية فقط). يرجى تقديم واجهة WebUI من الواجهة الخلفية.",
 	"Open file": "",
 	"Open in full screen": "",
+	"Open modal to configure connection": "",
 	"Open new chat": "فتح محادثة جديده",
 	"Open WebUI can use tools provided by any OpenAPI server.": "",
 	"Open WebUI uses faster-whisper internally.": "",
@@ -977,6 +990,7 @@
 	"Positive attitude": "موقف ايجابي",
 	"Prefix ID": "",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "أخر 30 يوم",
 	"Previous 7 days": "أخر 7 أيام",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "",
 	"Read": "",
 	"Read Aloud": "أقراء لي",
+	"Reason": "",
 	"Reasoning Effort": "",
 	"Record": "",
 	"Record voice": "سجل صوت",
@@ -1018,7 +1033,9 @@
 	"Relevance": "",
 	"Relevance Threshold": "",
 	"Remove": "إزالة",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "حذف الموديل",
+	"Remove this tag from list": "",
 	"Rename": "إعادة تسمية",
 	"Reorder Models": "",
 	"Reply in Thread": "",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "مشاركة الدردشة",
 	"Share to Open WebUI Community": "OpenWebUI شارك في مجتمع",
 	"Sharing Permissions": "",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "عرض",
 	"Show \"What's New\" modal on login": "",
 	"Show Admin Details in Account Pending Overlay": "",
@@ -1153,8 +1171,10 @@
 	"Source": "المصدر",
 	"Speech Playback Speed": "",
 	"Speech recognition error: {{error}}": "{{error}} خطأ في التعرف على الكلام",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "محرك تحويل الكلام إلى نص",
 	"Stop": "",
+	"Stop Generating": "",
 	"Stop Sequence": "وقف التسلسل",
 	"Stream Chat Response": "",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "مقترحات",
 	"Support": "",
 	"Support this plugin:": "",
+	"Supported MIME Types": "",
 	"Sync directory": "",
 	"System": "النظام",
 	"System Instructions": "",
@@ -1186,6 +1207,7 @@
 	"Temperature": "درجة حرارة",
 	"Temporary Chat": "",
 	"Text Splitter": "",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "محرك تحويل النص إلى كلام",
 	"Thanks for your feedback!": "شكرا لملاحظاتك!",
 	"The Application Account DN you bind with for search": "",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "",
 	"The LDAP attribute that maps to the username that users use to sign in.": "",
@@ -1203,10 +1226,12 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "يجب أن تكون النتيجة قيمة تتراوح بين 0.0 (0%) و1.0 (100%).",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "الثيم",
 	"Thinking...": "",
 	"This action cannot be undone. Do you wish to continue?": "",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
+	"This chat won't appear in history and your messages will not be saved.": "",
 	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "وهذا يضمن حفظ محادثاتك القيمة بشكل آمن في قاعدة بياناتك الخلفية. شكرًا لك!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "فتح وأغلاق الاعدادات",
 	"Toggle sidebar": "فتح وأغلاق الشريط الجانبي",
+	"Toggle whether current connection is active.": "",
 	"Token": "",
 	"Too verbose": "",
 	"Tool created successfully": "",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "",
 	"What are you working on?": "",
-	"Whats New in": "ما هو الجديد",
+	"What's New in": "ما هو الجديد",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
 	"wherever you are": "",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 28 - 2
src/lib/i18n/locales/ar/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "السماح بتعديل المحادثة",
 	"Allow Chat Export": "",
 	"Allow Chat Share": "",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "السماح بتحميل الملفات",
 	"Allow Multiple Models in Chat": "",
 	"Allow non-local voices": "السماح بالأصوات غير المحلية",
@@ -209,6 +210,7 @@
 	"Clone Chat": "استنساخ المحادثة",
 	"Clone of {{TITLE}}": "استنساخ لـ {{TITLE}}",
 	"Close": "إغلاق",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "تنفيذ الشيفرة",
 	"Code Execution": "تنفيذ الشيفرة",
@@ -297,7 +299,7 @@
 	"Default": "افتراضي",
 	"Default (Open AI)": "افتراضي (Open AI)",
 	"Default (SentenceTransformers)": "افتراضي (SentenceTransformers)",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "الوضع الافتراضي يعمل مع مجموعة أوسع من النماذج من خلال استدعاء الأدوات مرة واحدة قبل التنفيذ. أما الوضع الأصلي فيستخدم قدرات استدعاء الأدوات المدمجة في النموذج، لكنه يتطلب دعمًا داخليًا لهذه الميزة.",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "الوضع الافتراضي يعمل مع مجموعة أوسع من النماذج من خلال استدعاء الأدوات مرة واحدة قبل التنفيذ. أما الوضع الأصلي فيستخدم قدرات استدعاء الأدوات المدمجة في النموذج، لكنه يتطلب دعمًا داخليًا لهذه الميزة.",
 	"Default Model": "النموذج الافتراضي",
 	"Default model updated": "الإفتراضي تحديث الموديل",
 	"Default Models": "النماذج الافتراضية",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "مثال: أدوات لتنفيذ عمليات متنوعة",
 	"e.g., 3, 4, 5 (leave blank for default)": "",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
 	"e.g., westus (leave blank for eastus)": "",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "الميزات",
 	"Features Permissions": "أذونات الميزات",
 	"February": "فبراير",
+	"Feedback Details": "",
 	"Feedback History": "سجل الملاحظات",
 	"Feedbacks": "الملاحظات",
 	"Feel free to add specific details": "لا تتردد في إضافة تفاصيل محددة",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "أشعل الفضول",
 	"Image": "صورة",
 	"Image Compression": "ضغط الصور",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "توليد الصور",
 	"Image Generation (Experimental)": "توليد الصور (تجريبي)",
 	"Image Generation Engine": "محرك توليد الصور",
 	"Image Max Compression Size": "الحد الأقصى لضغط الصورة",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "توليد التوجيه للصورة",
 	"Image Prompt Generation Prompt": "نص توجيه توليد الصورة",
 	"Image Settings": "إعدادات الصورة",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "تم تحديث خادم LDAP",
 	"Leaderboard": "لوحة المتصدرين",
 	"Learn more about OpenAPI tool servers.": "",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "اتركه فارغًا لعدم وجود حد",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
@@ -777,6 +786,7 @@
 	"Lost": "ضائع",
 	"LTR": "من جهة اليسار إلى اليمين",
 	"Made by Open WebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "تأكد من إرفاقها",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "تأكد من تصدير ملف workflow.json بصيغة API من ComfyUI.",
 	"Manage": "إدارة",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Ollama الاصدار",
 	"On": "تشغيل",
 	"OneDrive": "OneDrive",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "يُسمح فقط بالحروف والأرقام والواصلات",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "يُسمح فقط بالأحرف الأبجدية الرقمية والواصلات في سلسلة الأمر.",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "يمكن تعديل المجموعات فقط، أنشئ قاعدة معرفة جديدة لتعديل أو إضافة مستندات.",
@@ -909,6 +921,7 @@
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "خطاء! أنت تستخدم طريقة غير مدعومة (الواجهة الأمامية فقط). يرجى تقديم واجهة WebUI من الواجهة الخلفية.",
 	"Open file": "فتح الملف",
 	"Open in full screen": "فتح في وضع ملء الشاشة",
+	"Open modal to configure connection": "",
 	"Open new chat": "فتح محادثة جديده",
 	"Open WebUI can use tools provided by any OpenAPI server.": "",
 	"Open WebUI uses faster-whisper internally.": "تستخدم واجهة WebUI أداة faster-whisper داخليًا.",
@@ -977,6 +990,7 @@
 	"Positive attitude": "موقف ايجابي",
 	"Prefix ID": "معرف البادئة",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "يُستخدم معرف البادئة لتفادي التعارض مع الاتصالات الأخرى من خلال إضافة بادئة إلى معرفات النماذج – اتركه فارغًا لتعطيله",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "أخر 30 يوم",
 	"Previous 7 days": "أخر 7 أيام",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "إعادة ترتيب النماذج حسب تشابه الموضوع",
 	"Read": "قراءة",
 	"Read Aloud": "أقراء لي",
+	"Reason": "",
 	"Reasoning Effort": "جهد الاستدلال",
 	"Record": "",
 	"Record voice": "سجل صوت",
@@ -1018,7 +1033,9 @@
 	"Relevance": "الصلة",
 	"Relevance Threshold": "",
 	"Remove": "إزالة",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "حذف الموديل",
+	"Remove this tag from list": "",
 	"Rename": "إعادة تسمية",
 	"Reorder Models": "إعادة ترتيب النماذج",
 	"Reply in Thread": "الرد داخل سلسلة الرسائل",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "مشاركة الدردشة",
 	"Share to Open WebUI Community": "OpenWebUI شارك في مجتمع",
 	"Sharing Permissions": "",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "عرض",
 	"Show \"What's New\" modal on login": "عرض نافذة \"ما الجديد\" عند تسجيل الدخول",
 	"Show Admin Details in Account Pending Overlay": "عرض تفاصيل المشرف في نافذة \"الحساب قيد الانتظار\"",
@@ -1153,8 +1171,10 @@
 	"Source": "المصدر",
 	"Speech Playback Speed": "سرعة تشغيل الصوت",
 	"Speech recognition error: {{error}}": "{{error}} خطأ في التعرف على الكلام",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "محرك تحويل الكلام إلى نص",
 	"Stop": "إيقاف",
+	"Stop Generating": "",
 	"Stop Sequence": "وقف التسلسل",
 	"Stream Chat Response": "بث استجابة الدردشة",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "مقترحات",
 	"Support": "الدعم",
 	"Support this plugin:": "دعم هذا المكون الإضافي:",
+	"Supported MIME Types": "",
 	"Sync directory": "مزامنة المجلد",
 	"System": "النظام",
 	"System Instructions": "تعليمات النظام",
@@ -1186,6 +1207,7 @@
 	"Temperature": "درجة حرارة",
 	"Temporary Chat": "محادثة مؤقتة",
 	"Text Splitter": "تقسيم النص",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "محرك تحويل النص إلى كلام",
 	"Thanks for your feedback!": "شكرا لملاحظاتك!",
 	"The Application Account DN you bind with for search": "DN لحساب التطبيق الذي تستخدمه للبحث",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "المطورون خلف هذا المكون الإضافي هم متطوعون شغوفون من المجتمع. إذا وجدت هذا المكون مفيدًا، فكر في المساهمة في تطويره.",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "قائمة التقييم تعتمد على نظام Elo ويتم تحديثها في الوقت الفعلي.",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "السمة LDAP التي تتوافق مع البريد الإلكتروني الذي يستخدمه المستخدمون لتسجيل الدخول.",
 	"The LDAP attribute that maps to the username that users use to sign in.": "السمة LDAP التي تتوافق مع اسم المستخدم الذي يستخدمه المستخدمون لتسجيل الدخول.",
@@ -1203,10 +1226,12 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "يجب أن تكون النتيجة قيمة تتراوح بين 0.0 (0%) و1.0 (100%).",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "درجة حرارة النموذج. زيادتها تجعل الإجابات أكثر إبداعًا.",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "الثيم",
 	"Thinking...": "جارٍ التفكير...",
 	"This action cannot be undone. Do you wish to continue?": "لا يمكن التراجع عن هذا الإجراء. هل ترغب في المتابعة؟",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
+	"This chat won't appear in history and your messages will not be saved.": "",
 	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "وهذا يضمن حفظ محادثاتك القيمة بشكل آمن في قاعدة بياناتك الخلفية. شكرًا لك!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "هذه ميزة تجريبية، وقد لا تعمل كما هو متوقع وقد تتغير في أي وقت.",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "فتح وأغلاق الاعدادات",
 	"Toggle sidebar": "فتح وأغلاق الشريط الجانبي",
+	"Toggle whether current connection is active.": "",
 	"Token": "رمز",
 	"Too verbose": "مفرط في التفاصيل",
 	"Tool created successfully": "تم إنشاء الأداة بنجاح",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "ما الذي تحاول تحقيقه؟",
 	"What are you working on?": "على ماذا تعمل؟",
-	"Whats New in": "ما هو الجديد",
+	"What's New in": "ما هو الجديد",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "عند التفعيل، سيستجيب النموذج لكل رسالة في المحادثة بشكل فوري، مولدًا الرد بمجرد إرسال المستخدم لرسالته. هذا الوضع مفيد لتطبيقات الدردشة الحية، لكنه قد يؤثر على الأداء في الأجهزة الأبطأ.",
 	"wherever you are": "أينما كنت",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 28 - 2
src/lib/i18n/locales/bg-BG/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "Разреши редактиране на чат",
 	"Allow Chat Export": "",
 	"Allow Chat Share": "",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "Разреши качване на файлове",
 	"Allow Multiple Models in Chat": "",
 	"Allow non-local voices": "Разреши нелокални гласове",
@@ -209,6 +210,7 @@
 	"Clone Chat": "Клониране на чат",
 	"Clone of {{TITLE}}": "Клонинг на {{TITLE}}",
 	"Close": "Затвори",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "Изпълнение на код",
 	"Code Execution": "Изпълнение на код",
@@ -297,7 +299,7 @@
 	"Default": "По подразбиране",
 	"Default (Open AI)": "По подразбиране (Open AI)",
 	"Default (SentenceTransformers)": "По подразбиране (SentenceTransformers)",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "Режимът по подразбиране работи с по-широк набор от модели, като извиква инструменти веднъж преди изпълнение. Нативният режим използва вградените възможности за извикване на инструменти на модела, но изисква моделът да поддържа тази функция по същество.",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "Режимът по подразбиране работи с по-широк набор от модели, като извиква инструменти веднъж преди изпълнение. Нативният режим използва вградените възможности за извикване на инструменти на модела, но изисква моделът да поддържа тази функция по същество.",
 	"Default Model": "Модел по подразбиране",
 	"Default model updated": "Моделът по подразбиране е обновен",
 	"Default Models": "Модели по подразбиране",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "напр. Инструменти за извършване на различни операции",
 	"e.g., 3, 4, 5 (leave blank for default)": "",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
 	"e.g., westus (leave blank for eastus)": "",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "Функции",
 	"Features Permissions": "Разрешения за функции",
 	"February": "Февруари",
+	"Feedback Details": "",
 	"Feedback History": "История на обратната връзка",
 	"Feedbacks": "Обратни връзки",
 	"Feel free to add specific details": "Не се колебайте да добавите конкретни детайли",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "Запалете любопитството",
 	"Image": "Изображение",
 	"Image Compression": "Компресия на изображенията",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "Генериране на изображения",
 	"Image Generation (Experimental)": "Генерация на изображения (Експериментално)",
 	"Image Generation Engine": "Двигател за генериране на изображения",
 	"Image Max Compression Size": "Максимален размер на компресия на изображения",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "Генериране на промпт за изображения",
 	"Image Prompt Generation Prompt": "Промпт за генериране на промпт за изображения",
 	"Image Settings": "Настройки на изображения",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "LDAP сървърът е актуализиран",
 	"Leaderboard": "Класация",
 	"Learn more about OpenAPI tool servers.": "",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "Оставете празно за неограничено",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
@@ -777,6 +786,7 @@
 	"Lost": "Изгубено",
 	"LTR": "LTR",
 	"Made by Open WebUI Community": "Направено от OpenWebUI общността",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "Уверете се, че сте заключени с",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "Уверете се, че експортирате файл workflow.json като API формат от ComfyUI.",
 	"Manage": "Управление",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Ollama Версия",
 	"On": "Вкл.",
 	"OneDrive": "OneDrive",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "Разрешени са само буквено-цифрови знаци и тирета",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "Само алфанумерични знаци и тире са разрешени в командния низ.",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Само колекциите могат да бъдат редактирани, създайте нова база от знания, за да редактирате/добавяте документи.",
@@ -909,6 +921,7 @@
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Упс! Използвате неподдържан метод (само фронтенд). Моля, сервирайте WebUI от бекенда.",
 	"Open file": "Отвори файл",
 	"Open in full screen": "Отвори на цял екран",
+	"Open modal to configure connection": "",
 	"Open new chat": "Отвори нов чат",
 	"Open WebUI can use tools provided by any OpenAPI server.": "",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI използва вътрешно по-бързо-whisper.",
@@ -977,6 +990,7 @@
 	"Positive attitude": "Позитивно отношение",
 	"Prefix ID": "Префикс ID",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "Префикс ID се използва за избягване на конфликти с други връзки чрез добавяне на префикс към ID-тата на моделите - оставете празно, за да деактивирате",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "Предишните 30 дни",
 	"Previous 7 days": "Предишните 7 дни",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "Преоценка на моделите по сходство на темата",
 	"Read": "Четене",
 	"Read Aloud": "Прочети на глас",
+	"Reason": "",
 	"Reasoning Effort": "Усилие за разсъждение",
 	"Record": "Запиши",
 	"Record voice": "Записване на глас",
@@ -1018,7 +1033,9 @@
 	"Relevance": "Релевантност",
 	"Relevance Threshold": "",
 	"Remove": "Изтриване",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "Изтриване на модела",
+	"Remove this tag from list": "",
 	"Rename": "Преименуване",
 	"Reorder Models": "Преорганизиране на моделите",
 	"Reply in Thread": "Отговори в тред",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "Подели Чат",
 	"Share to Open WebUI Community": "Споделете с OpenWebUI Общността",
 	"Sharing Permissions": "Права за споделяне",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "Покажи",
 	"Show \"What's New\" modal on login": "Покажи модалния прозорец \"Какво е ново\" при вписване",
 	"Show Admin Details in Account Pending Overlay": "Покажи детайлите на администратора в наслагването на изчакващ акаунт",
@@ -1153,8 +1171,10 @@
 	"Source": "Източник",
 	"Speech Playback Speed": "Скорост на възпроизвеждане на речта",
 	"Speech recognition error: {{error}}": "Грешка при разпознаване на речта: {{error}}",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "Двигател за преобразуване на реч в текста",
 	"Stop": "Спри",
+	"Stop Generating": "",
 	"Stop Sequence": "Стоп последователност",
 	"Stream Chat Response": "Поточен чат отговор",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "Препоръчано",
 	"Support": "Поддръжка",
 	"Support this plugin:": "Подкрепете този плъгин:",
+	"Supported MIME Types": "",
 	"Sync directory": "Синхронизирай директория",
 	"System": "Система",
 	"System Instructions": "Системни инструкции",
@@ -1186,6 +1207,7 @@
 	"Temperature": "Температура",
 	"Temporary Chat": "Временен чат",
 	"Text Splitter": "Разделител на текст",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "Двигател за преобразуване на текст в реч",
 	"Thanks for your feedback!": "Благодарим ви за вашия отзив!",
 	"The Application Account DN you bind with for search": "DN на акаунта на приложението, с който се свързвате за търсене",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Разработчиците зад този плъгин са страстни доброволци от общността. Ако намирате този плъгин полезен, моля, обмислете да допринесете за неговото развитие.",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "Класацията за оценка се базира на рейтинговата система Elo и се обновява в реално време.",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "LDAP атрибутът, който съответства на имейла, който потребителите използват за вписване.",
 	"The LDAP attribute that maps to the username that users use to sign in.": "LDAP атрибутът, който съответства на потребителското име, което потребителите използват за вписване.",
@@ -1203,10 +1226,12 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Резултатът трябва да бъде стойност между 0,0 (0%) и 1,0 (100%).",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "Тема",
 	"Thinking...": "Мисля...",
 	"This action cannot be undone. Do you wish to continue?": "Това действие не може да бъде отменено. Желаете ли да продължите?",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
+	"This chat won't appear in history and your messages will not be saved.": "",
 	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Това гарантира, че ценните ви разговори се запазват сигурно във вашата бекенд база данни. Благодарим ви!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Това е експериментална функция, може да не работи според очакванията и подлежи на промяна по всяко време.",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "Превключване на настройките",
 	"Toggle sidebar": "Превключване на страничната лента",
+	"Toggle whether current connection is active.": "",
 	"Token": "Токен",
 	"Too verbose": "Прекалено многословно",
 	"Tool created successfully": "Инструментът е създаден успешно",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "Какво се опитвате да постигнете?",
 	"What are you working on?": "Върху какво работите?",
-	"Whats New in": "Какво е ново в",
+	"What's New in": "Какво е ново в",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Когато е активирано, моделът ще отговаря на всяко съобщение в чата в реално време, генерирайки отговор веднага щом потребителят изпрати съобщение. Този режим е полезен за приложения за чат на живо, но може да повлияе на производителността на по-бавен хардуер.",
 	"wherever you are": "където и да сте",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 28 - 2
src/lib/i18n/locales/bn-BD/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "",
 	"Allow Chat Export": "",
 	"Allow Chat Share": "",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "",
 	"Allow Multiple Models in Chat": "",
 	"Allow non-local voices": "",
@@ -209,6 +210,7 @@
 	"Clone Chat": "",
 	"Clone of {{TITLE}}": "",
 	"Close": "বন্ধ",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "",
 	"Code Execution": "",
@@ -297,7 +299,7 @@
 	"Default": "ডিফল্ট",
 	"Default (Open AI)": "",
 	"Default (SentenceTransformers)": "ডিফল্ট (SentenceTransformers)",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
 	"Default Model": "ডিফল্ট মডেল",
 	"Default model updated": "ডিফল্ট মডেল আপডেট হয়েছে",
 	"Default Models": "",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "",
 	"e.g., 3, 4, 5 (leave blank for default)": "",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
 	"e.g., westus (leave blank for eastus)": "",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "",
 	"Features Permissions": "",
 	"February": "ফেব্রুয়ারি",
+	"Feedback Details": "",
 	"Feedback History": "",
 	"Feedbacks": "",
 	"Feel free to add specific details": "নির্দিষ্ট বিবরণ যোগ করতে বিনা দ্বিধায়",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "",
 	"Image": "",
 	"Image Compression": "",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "",
 	"Image Generation (Experimental)": "ইমেজ জেনারেশন (পরিক্ষামূলক)",
 	"Image Generation Engine": "ইমেজ জেনারেশন ইঞ্জিন",
 	"Image Max Compression Size": "",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "",
 	"Image Prompt Generation Prompt": "",
 	"Image Settings": "ছবির সেটিংসমূহ",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "",
 	"Leaderboard": "",
 	"Learn more about OpenAPI tool servers.": "",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
@@ -777,6 +786,7 @@
 	"Lost": "",
 	"LTR": "LTR",
 	"Made by Open WebUI Community": "OpenWebUI কমিউনিটিকর্তৃক নির্মিত",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "এটা দিয়ে বন্ধনী দিতে ভুলবেন না",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
 	"Manage": "",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Ollama ভার্সন",
 	"On": "চালু",
 	"OneDrive": "",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "কমান্ড স্ট্রিং-এ শুধুমাত্র ইংরেজি অক্ষর, সংখ্যা এবং হাইফেন ব্যবহার করা যাবে।",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
@@ -909,6 +921,7 @@
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "আপনি একটা আনসাপোর্টেড পদ্ধতি (শুধু ফ্রন্টএন্ড) ব্যবহার করছেন। দয়া করে WebUI ব্যাকএন্ড থেকে চালনা করুন।",
 	"Open file": "",
 	"Open in full screen": "",
+	"Open modal to configure connection": "",
 	"Open new chat": "নতুন চ্যাট খুলুন",
 	"Open WebUI can use tools provided by any OpenAPI server.": "",
 	"Open WebUI uses faster-whisper internally.": "",
@@ -977,6 +990,7 @@
 	"Positive attitude": "পজিটিভ আক্রমণ",
 	"Prefix ID": "",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "পূর্ব ৩০ দিন",
 	"Previous 7 days": "পূর্ব ৭ দিন",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "",
 	"Read": "",
 	"Read Aloud": "পড়াশোনা করুন",
+	"Reason": "",
 	"Reasoning Effort": "",
 	"Record": "",
 	"Record voice": "ভয়েস রেকর্ড করুন",
@@ -1018,7 +1033,9 @@
 	"Relevance": "",
 	"Relevance Threshold": "",
 	"Remove": "রিমুভ করুন",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "মডেল রিমুভ করুন",
+	"Remove this tag from list": "",
 	"Rename": "রেনেম",
 	"Reorder Models": "",
 	"Reply in Thread": "",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "চ্যাট শেয়ার করুন",
 	"Share to Open WebUI Community": "OpenWebUI কমিউনিটিতে শেয়ার করুন",
 	"Sharing Permissions": "",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "দেখান",
 	"Show \"What's New\" modal on login": "",
 	"Show Admin Details in Account Pending Overlay": "",
@@ -1153,8 +1171,10 @@
 	"Source": "উৎস",
 	"Speech Playback Speed": "",
 	"Speech recognition error: {{error}}": "স্পিচ রিকগনিশনে সমস্যা: {{error}}",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "স্পিচ-টু-টেক্সট ইঞ্জিন",
 	"Stop": "",
+	"Stop Generating": "",
 	"Stop Sequence": "সিকোয়েন্স থামান",
 	"Stream Chat Response": "",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "প্রস্তাবিত",
 	"Support": "",
 	"Support this plugin:": "",
+	"Supported MIME Types": "",
 	"Sync directory": "",
 	"System": "সিস্টেম",
 	"System Instructions": "",
@@ -1186,6 +1207,7 @@
 	"Temperature": "তাপমাত্রা",
 	"Temporary Chat": "",
 	"Text Splitter": "",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "টেক্সট-টু-স্পিচ ইঞ্জিন",
 	"Thanks for your feedback!": "আপনার মতামত ধন্যবাদ!",
 	"The Application Account DN you bind with for search": "",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "",
 	"The LDAP attribute that maps to the username that users use to sign in.": "",
@@ -1203,10 +1226,12 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "স্কোর একটি 0.0 (0%) এবং 1.0 (100%) এর মধ্যে একটি মান হওয়া উচিত।",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "থিম",
 	"Thinking...": "",
 	"This action cannot be undone. Do you wish to continue?": "",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
+	"This chat won't appear in history and your messages will not be saved.": "",
 	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "এটা নিশ্চিত করে যে, আপনার গুরুত্বপূর্ণ আলোচনা নিরাপদে আপনার ব্যাকএন্ড ডেটাবেজে সংরক্ষিত আছে। ধন্যবাদ!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "সেটিংস টোগল",
 	"Toggle sidebar": "সাইডবার টোগল",
+	"Toggle whether current connection is active.": "",
 	"Token": "",
 	"Too verbose": "",
 	"Tool created successfully": "",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "",
 	"What are you working on?": "",
-	"Whats New in": "এতে নতুন কী",
+	"What's New in": "এতে নতুন কী",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
 	"wherever you are": "",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 28 - 2
src/lib/i18n/locales/bo-TB/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "ཁ་བརྡ་ཞུ་དག་ལ་གནང་བ་སྤྲོད་པ།",
 	"Allow Chat Export": "",
 	"Allow Chat Share": "",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "ཡིག་ཆ་སྤར་བར་གནང་བ་སྤྲོད་པ།",
 	"Allow Multiple Models in Chat": "",
 	"Allow non-local voices": "ས་གནས་མིན་པའི་སྐད་གདངས་ལ་གནང་བ་སྤྲོད་པ།",
@@ -209,6 +210,7 @@
 	"Clone Chat": "ཁ་བརྡ་འདྲ་བཟོ།",
 	"Clone of {{TITLE}}": "{{TITLE}} ཡི་འདྲ་བཟོ།",
 	"Close": "ཁ་རྒྱག་པ།",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "ཀོཌ་ལག་བསྟར།",
 	"Code Execution": "ཀོཌ་ལག་བསྟར།",
@@ -297,7 +299,7 @@
 	"Default": "སྔོན་སྒྲིག",
 	"Default (Open AI)": "སྔོན་སྒྲིག (Open AI)",
 	"Default (SentenceTransformers)": "སྔོན་སྒྲིག (SentenceTransformers)",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "སྔོན་སྒྲིག་མ་དཔེ་ནི་ལག་བསྟར་མ་བྱས་སྔོན་དུ་ལག་ཆ་ཐེངས་གཅིག་འབོད་ནས་དཔེ་དབྱིབས་རྒྱ་ཆེ་བའི་ཁྱབ་ཁོངས་དང་མཉམ་ལས་བྱེད་ཐུབ། ས་སྐྱེས་མ་དཔེ་ཡིས་དཔེ་དབྱིབས་ཀྱི་ནང་འདྲེས་ལག་ཆ་འབོད་པའི་ནུས་པ་སྤྱོད་ཀྱི་ཡོད་མོད། འོན་ཀྱང་དཔེ་དབྱིབས་དེས་ཁྱད་ཆོས་འདི་ལ་ངོ་བོའི་ཐོག་ནས་རྒྱབ་སྐྱོར་བྱེད་དགོས།",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "སྔོན་སྒྲིག་མ་དཔེ་ནི་ལག་བསྟར་མ་བྱས་སྔོན་དུ་ལག་ཆ་ཐེངས་གཅིག་འབོད་ནས་དཔེ་དབྱིབས་རྒྱ་ཆེ་བའི་ཁྱབ་ཁོངས་དང་མཉམ་ལས་བྱེད་ཐུབ། ས་སྐྱེས་མ་དཔེ་ཡིས་དཔེ་དབྱིབས་ཀྱི་ནང་འདྲེས་ལག་ཆ་འབོད་པའི་ནུས་པ་སྤྱོད་ཀྱི་ཡོད་མོད། འོན་ཀྱང་དཔེ་དབྱིབས་དེས་ཁྱད་ཆོས་འདི་ལ་ངོ་བོའི་ཐོག་ནས་རྒྱབ་སྐྱོར་བྱེད་དགོས།",
 	"Default Model": "སྔོན་སྒྲིག་དཔེ་དབྱིབས།",
 	"Default model updated": "སྔོན་སྒྲིག་དཔེ་དབྱིབས་གསར་སྒྱུར་བྱས།",
 	"Default Models": "སྔོན་སྒྲིག་དཔེ་དབྱིབས།",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "དཔེར་ན། ལས་ཀ་སྣ་ཚོགས་སྒྲུབ་བྱེད་ཀྱི་ལག་ཆ།",
 	"e.g., 3, 4, 5 (leave blank for default)": "",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
 	"e.g., westus (leave blank for eastus)": "",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "ཁྱད་ཆོས།",
 	"Features Permissions": "ཁྱད་ཆོས་ཀྱི་དབང་ཚད།",
 	"February": "ཟླ་བ་གཉིས་པ།",
+	"Feedback Details": "",
 	"Feedback History": "བསམ་འཆར་གྱི་ལོ་རྒྱུས།",
 	"Feedbacks": "བསམ་འཆར།",
 	"Feel free to add specific details": "ཞིབ་ཕྲ་ངེས་ཅན་སྣོན་པར་སེམས་ཁྲལ་མེད།",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "ཤེས་འདོད་སློང་བ།",
 	"Image": "པར།",
 	"Image Compression": "པར་བསྡུ་སྐུམ།",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "པར་བཟོ།",
 	"Image Generation (Experimental)": "པར་བཟོ། (ཚོད་ལྟའི་རང་བཞིན།)",
 	"Image Generation Engine": "པར་བཟོ་འཕྲུལ་འཁོར།",
 	"Image Max Compression Size": "པར་གྱི་བསྡུ་སྐུམ་ཆེ་ཤོས།",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "པར་འགུལ་སློང་བཟོ་སྐྲུན།",
 	"Image Prompt Generation Prompt": "པར་འགུལ་སློང་བཟོ་སྐྲུན་གྱི་འགུལ་སློང་།",
 	"Image Settings": "པར་གྱི་སྒྲིག་འགོད།",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "LDAP སར་བར་གསར་སྒྱུར་བྱས།",
 	"Leaderboard": "འགྲན་རེས་རེའུ་མིག",
 	"Learn more about OpenAPI tool servers.": "",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "ཚད་མེད་པའི་ཆེད་དུ་སྟོང་པ་བཞག་པ།",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "\"{{url}}/api/tags\" མཇུག་མཐུད་ནས་དཔེ་དབྱིབས་ཡོངས་རྫོགས་ཚུད་པར་སྟོང་པ་བཞག་པ།",
@@ -777,6 +786,7 @@
 	"Lost": "བརླགས།",
 	"LTR": "LTR",
 	"Made by Open WebUI Community": "Open WebUI སྤྱི་ཚོགས་ཀྱིས་བཟོས།",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "དེ་དག་འདིས་བསྐོར་བ་ཁག་ཐེག་བྱེད་པ།",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "ComfyUI ནས་ workflow.json ཡིག་ཆ་ API བཀོད་པའི་ཐོག་ཕྱིར་གཏོང་བྱེད་པ་ཁག་ཐེག་བྱེད་པ།",
 	"Manage": "དོ་དམ།",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Ollama པར་གཞི།",
 	"On": "ཁ་ཕྱེ་བ།",
 	"OneDrive": "OneDrive",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "ཨང་ཀི་དང་དབྱིན་ཡིག་གི་ཡིག་འབྲུ་དང་སྦྲེལ་རྟགས་ཁོ་ན་ཆོག་པ།",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "བཀའ་བརྡའི་ཡིག་ཕྲེང་ནང་ཨང་ཀི་དང་དབྱིན་ཡིག་གི་ཡིག་འབྲུ་དང་སྦྲེལ་རྟགས་ཁོ་ན་ཆོག་པ།",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "བསྡུ་གསོག་ཁོ་ན་ཞུ་དག་བྱེད་ཐུབ། ཡིག་ཆ་ཞུ་དག་/སྣོན་པར་ཤེས་བྱའི་རྟེན་གཞི་གསར་པ་ཞིག་བཟོ་བ།",
@@ -909,6 +921,7 @@
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "ཨོའོ། ཁྱེད་ཀྱིས་རྒྱབ་སྐྱོར་མེད་པའི་ཐབས་ལམ་ཞིག་ (མདུན་ངོས་ཁོ་ན།) བེད་སྤྱོད་གཏོང་བཞིན་འདུག རྒྱབ་སྣེ་ནས་ WebUI མཁོ་སྤྲོད་བྱེད་རོགས།",
 	"Open file": "ཡིག་ཆ་ཁ་ཕྱེ་བ།",
 	"Open in full screen": "ཡོངས་གནས་ངོས་སུ་ཁ་ཕྱེ་བ།",
+	"Open modal to configure connection": "",
 	"Open new chat": "ཁ་བརྡ་གསར་པ་ཁ་ཕྱེ་བ།",
 	"Open WebUI can use tools provided by any OpenAPI server.": "",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI ཡིས་ནང་ཁུལ་དུ་ faster-whisper བེད་སྤྱོད་བྱེད།",
@@ -977,6 +990,7 @@
 	"Positive attitude": "ལྟ་སྟངས་དགེ་མཚན།",
 	"Prefix ID": "སྔོན་སྦྱོར་ ID",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "སྔོན་སྦྱོར་ ID ནི་དཔེ་དབྱིབས་ཀྱི་ IDs ལ་སྔོན་སྦྱོར་ཞིག་སྣོན་ནས་སྦྲེལ་མཐུད་གཞན་དང་གདོང་ཐུག་ལས་གཡོལ་བར་བེད་སྤྱོད་བྱེད། - ནུས་མེད་བཏང་བར་སྟོང་པ་བཞག་པ།",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "ཉིན་ ༣༠ སྔོན་མ།",
 	"Previous 7 days": "ཉིན་ ༧ སྔོན་མ།",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "བརྗོད་གཞི་འདྲ་མཚུངས་ལྟར་དཔེ་དབྱིབས་བསྐྱར་སྒྲིག་བྱེད་པ།",
 	"Read": "ཀློག་པ།",
 	"Read Aloud": "སྐད་གསལ་པོས་ཀློག་པ།",
+	"Reason": "",
 	"Reasoning Effort": "རྒྱུ་མཚན་འདྲེན་པའི་འབད་བརྩོན།",
 	"Record": "",
 	"Record voice": "སྐད་སྒྲ་ཕབ་པ།",
@@ -1018,7 +1033,9 @@
 	"Relevance": "འབྲེལ་ཡོད་རང་བཞིན།",
 	"Relevance Threshold": "",
 	"Remove": "འདོར་བ།",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "དཔེ་དབྱིབས་འདོར་བ།",
+	"Remove this tag from list": "",
 	"Rename": "མིང་བསྐྱར་འདོགས།",
 	"Reorder Models": "དཔེ་དབྱིབས་བསྐྱར་སྒྲིག",
 	"Reply in Thread": "བརྗོད་གཞིའི་ནང་ལན་འདེབས།",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "ཁ་བརྡ་མཉམ་སྤྱོད།",
 	"Share to Open WebUI Community": "Open WebUI སྤྱི་ཚོགས་ལ་མཉམ་སྤྱོད།",
 	"Sharing Permissions": "མཉམ་སྤྱོད་དབང་ཚད།",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "སྟོན་པ།",
 	"Show \"What's New\" modal on login": "ནང་འཛུལ་སྐབས་ \"གསར་པ་ཅི་ཡོད\" modal སྟོན་པ།",
 	"Show Admin Details in Account Pending Overlay": "རྩིས་ཁྲ་སྒུག་བཞིན་པའི་གཏོགས་ངོས་སུ་དོ་དམ་པའི་ཞིབ་ཕྲ་སྟོན་པ།",
@@ -1153,8 +1171,10 @@
 	"Source": "འབྱུང་ཁུངས།",
 	"Speech Playback Speed": "གཏམ་བཤད་ཕྱིར་གཏོང་གི་མྱུར་ཚད།",
 	"Speech recognition error: {{error}}": "གཏམ་བཤད་ངོས་འཛིན་ནོར་འཁྲུལ།: {{error}}",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "གཏམ་བཤད་ནས་ཡིག་རྐྱང་གི་འཕྲུལ་འཁོར།",
 	"Stop": "མཚམས་འཇོག",
+	"Stop Generating": "",
 	"Stop Sequence": "མཚམས་འཇོག་རིམ་པ།",
 	"Stream Chat Response": "ཁ་བརྡའི་ལན་རྒྱུག་པ།",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "གྲོས་གཞི།",
 	"Support": "རྒྱབ་སྐྱོར།",
 	"Support this plugin:": "plugin འདི་ལ་རྒྱབ་སྐྱོར་བྱེད་པ།:",
+	"Supported MIME Types": "",
 	"Sync directory": "ཐོ་འཚོལ་མཉམ་སྡེབ།",
 	"System": "མ་ལག",
 	"System Instructions": "མ་ལག་གི་ལམ་སྟོན།",
@@ -1186,6 +1207,7 @@
 	"Temperature": "དྲོད་ཚད།",
 	"Temporary Chat": "གནས་སྐབས་ཁ་བརྡ།",
 	"Text Splitter": "ཡིག་རྐྱང་བགོ་བྱེད།",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "ཡིག་རྐྱང་ནས་གཏམ་བཤད་ཀྱི་འཕྲུལ་འཁོར།",
 	"Thanks for your feedback!": "ཁྱེད་ཀྱི་བསམ་འཆར་ལ་ཐུགས་རྗེ་ཆེ།",
 	"The Application Account DN you bind with for search": "ཁྱེད་ཀྱིས་འཚོལ་བཤེར་གྱི་ཆེད་དུ་སྦྲེལ་བའི་ Application Account DN",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "plugin འདིའི་རྒྱབ་ཀྱི་གསར་སྤེལ་བ་དག་ནི་སྤྱི་ཚོགས་ནས་ཡིན་པའི་སེམས་ཤུགས་ཅན་གྱི་དང་བླངས་པ་ཡིན། གལ་ཏེ་ཁྱེད་ཀྱིས་ plugin འདི་ཕན་ཐོགས་ཡོད་པ་མཐོང་ན། དེའི་གསར་སྤེལ་ལ་ཞལ་འདེབས་གནང་བར་བསམ་ཞིབ་གནང་རོགས།",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "གདེང་འཇོག་འགྲན་རེས་རེའུ་མིག་དེ་ Elo སྐར་མ་སྤྲོད་པའི་མ་ལག་ལ་གཞི་བཅོལ་ཡོད། དེ་མིན་དུས་ཐོག་ཏུ་གསར་སྒྱུར་བྱེད་ཀྱི་ཡོད།",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "བེད་སྤྱོད་མཁན་ཚོས་ནང་འཛུལ་བྱེད་སྐབས་བེད་སྤྱོད་གཏོང་བའི་ཡིག་ཟམ་ལ་སྦྲེལ་བའི་ LDAP ཁྱད་ཆོས།",
 	"The LDAP attribute that maps to the username that users use to sign in.": "བེད་སྤྱོད་མཁན་ཚོས་ནང་འཛུལ་བྱེད་སྐབས་བེད་སྤྱོད་གཏོང་བའི་བེད་སྤྱོད་མིང་ལ་སྦྲེལ་བའི་ LDAP ཁྱད་ཆོས།",
@@ -1203,10 +1226,12 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "སྐར་མ་དེ་ 0.0 (0%) ནས་ 1.0 (100%) བར་གྱི་རིན་ཐང་ཞིག་ཡིན་དགོས།",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "དཔེ་དབྱིབས་ཀྱི་དྲོད་ཚད། དྲོད་ཚད་མཐོ་རུ་བཏང་ན་དཔེ་དབྱིབས་ཀྱིས་ལན་གསར་གཏོད་ཆེ་བ་སྤྲོད་ངེས།",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "བརྗོད་གཞི།",
 	"Thinking...": "བསམ་བཞིན་པ།...",
 	"This action cannot be undone. Do you wish to continue?": "བྱ་སྤྱོད་འདི་ཕྱིར་ལྡོག་བྱེད་མི་ཐུབ། ཁྱེད་མུ་མཐུད་འདོད་ཡོད་དམ།",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "བགྲོ་གླེང་འདི་ {{createdAt}} ལ་བཟོས་པ། འདི་ནི་ {{channelName}} བགྲོ་གླེང་གི་ཐོག་མ་རང་ཡིན།",
+	"This chat won't appear in history and your messages will not be saved.": "",
 	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "འདིས་ཁྱེད་ཀྱི་རྩ་ཆེའི་ཁ་བརྡ་དག་བདེ་འཇགས་ངང་ཁྱེད་ཀྱི་རྒྱབ་སྣེ་གནས་ཚུལ་མཛོད་དུ་ཉར་ཚགས་བྱེད་པ་ཁག་ཐེག་བྱེད། ཐུགས་རྗེ་ཆེ།",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "འདི་ནི་ཚོད་ལྟའི་རང་བཞིན་གྱི་ཁྱད་ཆོས་ཤིག་ཡིན། དེ་རེ་སྒུག་ལྟར་ལས་ཀ་བྱེད་མི་སྲིད། དེ་མིན་དུས་ཚོད་གང་རུང་ལ་འགྱུར་བ་འགྲོ་སྲིད།",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "སྒྲིག་འགོད་བརྗེ་བ།",
 	"Toggle sidebar": "ཟུར་ངོས་བརྗེ་བ།",
+	"Toggle whether current connection is active.": "",
 	"Token": "ཊོཀ་ཀེན།",
 	"Too verbose": "རིང་དྲགས།",
 	"Tool created successfully": "ལག་ཆ་ལེགས་པར་བཟོས་ཟིན།",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "ཁྱེད་ཀྱིས་ཅི་ཞིག་འགྲུབ་ཐབས་བྱེད་བཞིན་ཡོད།",
 	"What are you working on?": "ཁྱེད་ཀྱིས་ཅི་ཞིག་ལས་ཀ་བྱེད་བཞིན་ཡོད།",
-	"Whats New in": "གསར་པ་ཅི་ཡོད།",
+	"What's New in": "གསར་པ་ཅི་ཡོད།",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "སྒུལ་བསྐྱོད་བྱས་ཚེ། དཔེ་དབྱིབས་ཀྱིས་ཁ་བརྡའི་འཕྲིན་རེ་རེར་དུས་ཐོག་ཏུ་ལན་འདེབས་བྱེད་ངེས། བེད་སྤྱོད་མཁན་གྱིས་འཕྲིན་བཏང་མ་ཐག་ལན་ཞིག་བཟོ་ངེས། མ་དཔེ་འདི་ཐད་གཏོང་ཁ་བརྡའི་བཀོལ་ཆས་ལ་ཕན་ཐོགས་ཡོད། འོན་ཀྱང་དེས་མཁྲེགས་ཆས་དལ་བའི་སྟེང་ལས་ཆོད་ལ་ཤུགས་རྐྱེན་ཐེབས་སྲིད།",
 	"wherever you are": "ཁྱེད་གང་དུ་ཡོད་ཀྱང་།",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 29 - 3
src/lib/i18n/locales/ca-ES/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "Permetre editar el xat",
 	"Allow Chat Export": "Permetre exportar el xat",
 	"Allow Chat Share": "Permetre compartir el xat",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "Permetre la pujada d'arxius",
 	"Allow Multiple Models in Chat": "Permetre múltiple models al xat",
 	"Allow non-local voices": "Permetre veus no locals",
@@ -209,6 +210,7 @@
 	"Clone Chat": "Clonar el xat",
 	"Clone of {{TITLE}}": "Clon de {{TITLE}}",
 	"Close": "Tancar",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "Execució de codi",
 	"Code Execution": "Excució de Codi",
@@ -297,7 +299,7 @@
 	"Default": "Per defecte",
 	"Default (Open AI)": "Per defecte (Open AI)",
 	"Default (SentenceTransformers)": "Per defecte (SentenceTransformers)",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "El mode predeterminat funciona amb una gamma més àmplia de models cridant a les eines una vegada abans de l'execució. El mode natiu aprofita les capacitats de crida d'eines integrades del model, però requereix que el model admeti aquesta funció de manera inherent.",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "El mode predeterminat funciona amb una gamma més àmplia de models cridant a les eines una vegada abans de l'execució. El mode natiu aprofita les capacitats de crida d'eines integrades del model, però requereix que el model admeti aquesta funció de manera inherent.",
 	"Default Model": "Model per defecte",
 	"Default model updated": "Model per defecte actualitzat",
 	"Default Models": "Models per defecte",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "p. ex. Eines per dur a terme operacions",
 	"e.g., 3, 4, 5 (leave blank for default)": "p. ex. 3, 4, 5 (deixa-ho en blanc per utilitzar el per defecte)",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "p. ex. en-US, ja-JP, ca-ES (deixa-ho en blanc per detecció automàtica)",
 	"e.g., westus (leave blank for eastus)": "p. ex. westus (deixa-ho en blanc per a eastus)",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "Característiques",
 	"Features Permissions": "Permisos de les característiques",
 	"February": "Febrer",
+	"Feedback Details": "",
 	"Feedback History": "Històric de comentaris",
 	"Feedbacks": "Comentaris",
 	"Feel free to add specific details": "Sent-te lliure d'afegir detalls específics",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "Despertar la curiositat",
 	"Image": "Imatge",
 	"Image Compression": "Compressió d'imatges",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "Generació d'imatges",
 	"Image Generation (Experimental)": "Generació d'imatges (Experimental)",
 	"Image Generation Engine": "Motor de generació d'imatges",
 	"Image Max Compression Size": "Mida màxima de la compressió d'imatges",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "Generació d'indicacions d'imatge",
 	"Image Prompt Generation Prompt": "Indicació per a la generació d'indicacions d'imatge",
 	"Image Settings": "Preferències d'imatges",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "Servidor LDAP actualitzat",
 	"Leaderboard": "Tauler de classificació",
 	"Learn more about OpenAPI tool servers.": "Aprèn més sobre els servidors d'eines OpenAPI",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "Deixar-ho buit per il·limitat",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "Deixar-ho buit per incloure tots els models del punt de connexió \"{{url}}/api/tags\"",
@@ -777,6 +786,7 @@
 	"Lost": "Perdut",
 	"LTR": "LTR",
 	"Made by Open WebUI Community": "Creat per la Comunitat OpenWebUI",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "Assegura't d'envoltar-los amb",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "Assegura't d'exportar un fitxer workflow.json com a format API des de ComfyUI.",
 	"Manage": "Gestionar",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Versió d'Ollama",
 	"On": "Activat",
 	"OneDrive": "OneDrive",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "Només es permeten caràcters alfanumèrics i guions",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "Només es permeten caràcters alfanumèrics i guions en la comanda.",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Només es poden editar col·leccions, crea una nova base de coneixement per editar/afegir documents.",
@@ -909,6 +921,7 @@
 	"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": "Obrir arxiu",
 	"Open in full screen": "Obrir en pantalla complerta",
+	"Open modal to configure connection": "",
 	"Open new chat": "Obre un xat nou",
 	"Open WebUI can use tools provided by any OpenAPI server.": "Open WebUI pot utilitzar eines de servidors OpenAPI.",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI utilitza faster-whisper internament.",
@@ -977,6 +990,7 @@
 	"Positive attitude": "Actitud positiva",
 	"Prefix ID": "Identificador del prefix",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "L'identificador de prefix s'utilitza per evitar conflictes amb altres connexions afegint un prefix als ID de model; deixa'l en blanc per desactivar-lo.",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "30 dies anteriors",
 	"Previous 7 days": "7 dies anteriors",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "Reclassificar els models per similitud de temes",
 	"Read": "Llegit",
 	"Read Aloud": "Llegir en veu alta",
+	"Reason": "",
 	"Reasoning Effort": "Esforç de raonament",
 	"Record": "Enregistrar",
 	"Record voice": "Enregistrar la veu",
@@ -1018,7 +1033,9 @@
 	"Relevance": "Rellevància",
 	"Relevance Threshold": "Límit de rellevància",
 	"Remove": "Eliminar",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "Eliminar el model",
+	"Remove this tag from list": "",
 	"Rename": "Canviar el nom",
 	"Reorder Models": "Reordenar els models",
 	"Reply in Thread": "Respondre al fil",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "Compartir el xat",
 	"Share to Open WebUI Community": "Compartir amb la comunitat OpenWebUI",
 	"Sharing Permissions": "Compartir els permisos",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "Mostrar",
 	"Show \"What's New\" modal on login": "Veure 'Què hi ha de nou' a l'entrada",
 	"Show Admin Details in Account Pending Overlay": "Mostrar els detalls de l'administrador a la superposició del compte pendent",
@@ -1153,8 +1171,10 @@
 	"Source": "Font",
 	"Speech Playback Speed": "Velocitat de la parla",
 	"Speech recognition error: {{error}}": "Error de reconeixement de veu: {{error}}",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "Motor de veu a text",
 	"Stop": "Atura",
+	"Stop Generating": "",
 	"Stop Sequence": "Atura la seqüència",
 	"Stream Chat Response": "Fer streaming de la resposta del xat",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "Suggerit",
 	"Support": "Dona suport",
 	"Support this plugin:": "Dona suport a aquest complement:",
+	"Supported MIME Types": "",
 	"Sync directory": "Sincronitzar directori",
 	"System": "Sistema",
 	"System Instructions": "Instruccions de sistema",
@@ -1186,6 +1207,7 @@
 	"Temperature": "Temperatura",
 	"Temporary Chat": "Xat temporal",
 	"Text Splitter": "Separador de text",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "Motor de text a veu",
 	"Thanks for your feedback!": "Gràcies pel teu comentari!",
 	"The Application Account DN you bind with for search": "El DN del compte d'aplicació per realitzar la cerca",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Els desenvolupadors d'aquest complement són voluntaris apassionats de la comunitat. Si trobeu útil aquest complement, considereu contribuir al seu desenvolupament.",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "La classificació d'avaluació es basa en el sistema de qualificació Elo i s'actualitza en temps real.",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "L'atribut LDAP que s'associa al correu que els usuaris utilitzen per iniciar la sessió.",
 	"The LDAP attribute that maps to the username that users use to sign in.": "L'atribut LDAP que mapeja el nom d'usuari amb l'usuari que vol iniciar sessió",
@@ -1203,11 +1226,13 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "El valor de puntuació hauria de ser entre 0.0 (0%) i 1.0 (100%).",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "La temperatura del model. Augmentar la temperatura farà que el model respongui de manera més creativa.",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "Tema",
 	"Thinking...": "Pensant...",
 	"This action cannot be undone. Do you wish to continue?": "Aquesta acció no es pot desfer. Vols continuar?",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Aquest canal es va crear el dia {{createdAt}}. Aquest és el començament del canal {{channelName}}.",
-	"This chat won’t appear in history and your messages will not be saved.": "Aquest xat no apareixerà a l'historial i els teus missatges no es desaran.",
+	"This chat won't appear in history and your messages will not be saved.": "Aquest xat no apareixerà a l'historial i els teus missatges no es desaran.",
+	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Això assegura que les teves converses valuoses queden desades de manera segura a la teva base de dades. Gràcies!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Aquesta és una funció experimental, és possible que no funcioni com s'espera i està subjecta a canvis en qualsevol moment.",
 	"This model is not publicly available. Please select another model.": "Aquest model no està disponible públicament. Seleccioneu-ne un altre.",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "Alterna preferències",
 	"Toggle sidebar": "Alterna la barra lateral",
+	"Toggle whether current connection is active.": "",
 	"Token": "Token",
 	"Too verbose": "Massa explicit",
 	"Tool created successfully": "Eina creada correctament",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "Què intentes aconseguir?",
 	"What are you working on?": "En què estàs treballant?",
-	"Whats New in": "Què hi ha de nou a",
+	"What's New in": "Què hi ha de nou a",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Quan està activat, el model respondrà a cada missatge de xat en temps real, generant una resposta tan bon punt l'usuari envia un missatge. Aquest mode és útil per a aplicacions de xat en directe, però pot afectar el rendiment en maquinari més lent.",
 	"wherever you are": "allà on estiguis",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 28 - 2
src/lib/i18n/locales/ceb-PH/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "",
 	"Allow Chat Export": "",
 	"Allow Chat Share": "",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "",
 	"Allow Multiple Models in Chat": "",
 	"Allow non-local voices": "",
@@ -209,6 +210,7 @@
 	"Clone Chat": "",
 	"Clone of {{TITLE}}": "",
 	"Close": "Suod nga",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "",
 	"Code Execution": "",
@@ -297,7 +299,7 @@
 	"Default": "Pinaagi sa default",
 	"Default (Open AI)": "",
 	"Default (SentenceTransformers)": "",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
 	"Default Model": "",
 	"Default model updated": "Gi-update nga default template",
 	"Default Models": "",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "",
 	"e.g., 3, 4, 5 (leave blank for default)": "",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
 	"e.g., westus (leave blank for eastus)": "",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "",
 	"Features Permissions": "",
 	"February": "",
+	"Feedback Details": "",
 	"Feedback History": "",
 	"Feedbacks": "",
 	"Feel free to add specific details": "",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "",
 	"Image": "",
 	"Image Compression": "",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "",
 	"Image Generation (Experimental)": "Pagmugna og hulagway (Eksperimento)",
 	"Image Generation Engine": "Makina sa paghimo og imahe",
 	"Image Max Compression Size": "",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "",
 	"Image Prompt Generation Prompt": "",
 	"Image Settings": "Mga Setting sa Imahen",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "",
 	"Leaderboard": "",
 	"Learn more about OpenAPI tool servers.": "",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
@@ -777,6 +786,7 @@
 	"Lost": "",
 	"LTR": "",
 	"Made by Open WebUI Community": "Gihimo sa komunidad sa OpenWebUI",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "Siguruha nga palibutan sila",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "",
 	"Manage": "",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Ollama nga bersyon",
 	"On": "Gipaandar",
 	"OneDrive": "",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "Ang alphanumeric nga mga karakter ug hyphen lang ang gitugotan sa command string.",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "",
@@ -909,6 +921,7 @@
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Oops! ",
 	"Open file": "",
 	"Open in full screen": "",
+	"Open modal to configure connection": "",
 	"Open new chat": "Ablihi ang bag-ong diskusyon",
 	"Open WebUI can use tools provided by any OpenAPI server.": "",
 	"Open WebUI uses faster-whisper internally.": "",
@@ -977,6 +990,7 @@
 	"Positive attitude": "",
 	"Prefix ID": "",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "",
 	"Previous 7 days": "",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "",
 	"Read": "",
 	"Read Aloud": "",
+	"Reason": "",
 	"Reasoning Effort": "",
 	"Record": "",
 	"Record voice": "Irekord ang tingog",
@@ -1018,7 +1033,9 @@
 	"Relevance": "",
 	"Relevance Threshold": "",
 	"Remove": "",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "",
+	"Remove this tag from list": "",
 	"Rename": "",
 	"Reorder Models": "",
 	"Reply in Thread": "",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "",
 	"Share to Open WebUI Community": "Ipakigbahin sa komunidad sa OpenWebUI",
 	"Sharing Permissions": "",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "Pagpakita",
 	"Show \"What's New\" modal on login": "",
 	"Show Admin Details in Account Pending Overlay": "",
@@ -1153,8 +1171,10 @@
 	"Source": "Tinubdan",
 	"Speech Playback Speed": "",
 	"Speech recognition error: {{error}}": "Sayop sa pag-ila sa tingog: {{error}}",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "Engine sa pag-ila sa tingog",
 	"Stop": "",
+	"Stop Generating": "",
 	"Stop Sequence": "Pagkasunod-sunod sa pagsira",
 	"Stream Chat Response": "",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "",
 	"Support": "",
 	"Support this plugin:": "",
+	"Supported MIME Types": "",
 	"Sync directory": "",
 	"System": "Sistema",
 	"System Instructions": "",
@@ -1186,6 +1207,7 @@
 	"Temperature": "Temperatura",
 	"Temporary Chat": "",
 	"Text Splitter": "",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "Text-to-speech nga makina",
 	"Thanks for your feedback!": "",
 	"The Application Account DN you bind with for search": "",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "",
 	"The LDAP attribute that maps to the username that users use to sign in.": "",
@@ -1203,10 +1226,12 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "Tema",
 	"Thinking...": "",
 	"This action cannot be undone. Do you wish to continue?": "",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
+	"This chat won't appear in history and your messages will not be saved.": "",
 	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Kini nagsiguro nga ang imong bililhon nga mga panag-istoryahanay luwas nga natipig sa imong backend database. ",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "I-toggle ang mga setting",
 	"Toggle sidebar": "I-toggle ang sidebar",
+	"Toggle whether current connection is active.": "",
 	"Token": "",
 	"Too verbose": "",
 	"Tool created successfully": "",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "",
 	"What are you working on?": "",
-	"Whats New in": "Unsay bag-o sa",
+	"What's New in": "Unsay bag-o sa",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
 	"wherever you are": "",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 28 - 2
src/lib/i18n/locales/cs-CZ/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "Povolit úpravu chatu",
 	"Allow Chat Export": "",
 	"Allow Chat Share": "",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "Povolit nahrávat soubory",
 	"Allow Multiple Models in Chat": "",
 	"Allow non-local voices": "Povolit ne-místní hlasy",
@@ -209,6 +210,7 @@
 	"Clone Chat": "",
 	"Clone of {{TITLE}}": "",
 	"Close": "Zavřít",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "Provádění kódu",
 	"Code Execution": "",
@@ -297,7 +299,7 @@
 	"Default": "Výchozí hodnoty nebo nastavení.",
 	"Default (Open AI)": "Výchozí (Open AI)",
 	"Default (SentenceTransformers)": "Výchozí (SentenceTransformers)",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
 	"Default Model": "Výchozí model",
 	"Default model updated": "Výchozí model aktualizován.",
 	"Default Models": "Výchozí modely",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "",
 	"e.g., 3, 4, 5 (leave blank for default)": "",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
 	"e.g., westus (leave blank for eastus)": "",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "",
 	"Features Permissions": "",
 	"February": "Únor",
+	"Feedback Details": "",
 	"Feedback History": "Historie zpětné vazby",
 	"Feedbacks": "",
 	"Feel free to add specific details": "Neváhejte přidat konkrétní detaily.",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "",
 	"Image": "",
 	"Image Compression": "",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "",
 	"Image Generation (Experimental)": "Generování obrázků (experimentální)",
 	"Image Generation Engine": "Engine pro generování obrázků",
 	"Image Max Compression Size": "",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "",
 	"Image Prompt Generation Prompt": "",
 	"Image Settings": "Nastavení obrázku",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "",
 	"Leaderboard": "Žebříček",
 	"Learn more about OpenAPI tool servers.": "",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "Nechte prázdné pro neomezeně",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
@@ -777,6 +786,7 @@
 	"Lost": "Ztracený",
 	"LTR": "LTR",
 	"Made by Open WebUI Community": "Vytvořeno komunitou OpenWebUI",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "Ujistěte se, že jsou uzavřeny pomocí",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "Ujistěte se, že exportujete soubor workflow.json ve formátu API z ComfyUI.",
 	"Manage": "Spravovat",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Verze Ollama",
 	"On": "Na",
 	"OneDrive": "",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "Příkazový řetězec smí obsahovat pouze alfanumerické znaky a pomlčky.",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Pouze kolekce mohou být upravovány, pro úpravu/přidání dokumentů vytvořte novou znalostní bázi.",
@@ -909,6 +921,7 @@
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Jejda! Používáte nepodporovanou metodu (pouze frontend). Prosím, spusťte WebUI ze serverové části (backendu).",
 	"Open file": "Otevřít soubor",
 	"Open in full screen": "Otevřít na celou obrazovku",
+	"Open modal to configure connection": "",
 	"Open new chat": "Otevřít nový chat",
 	"Open WebUI can use tools provided by any OpenAPI server.": "",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI interně používá faster-whisper.",
@@ -977,6 +990,7 @@
 	"Positive attitude": "Pozitivní přístup",
 	"Prefix ID": "",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "Předchozích 30 dnů",
 	"Previous 7 days": "Předchozích 7 dní",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "Znovu seřaďte modely podle podobnosti témat.",
 	"Read": "",
 	"Read Aloud": "Číst nahlas",
+	"Reason": "",
 	"Reasoning Effort": "",
 	"Record": "",
 	"Record voice": "Nahrát hlas",
@@ -1018,7 +1033,9 @@
 	"Relevance": "Relevance",
 	"Relevance Threshold": "",
 	"Remove": "Odebrat",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "Odebrat model",
+	"Remove this tag from list": "",
 	"Rename": "Přejmenovat",
 	"Reorder Models": "",
 	"Reply in Thread": "",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "Sdílet chat",
 	"Share to Open WebUI Community": "Sdílet s komunitou OpenWebUI",
 	"Sharing Permissions": "",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "Zobrazit",
 	"Show \"What's New\" modal on login": "",
 	"Show Admin Details in Account Pending Overlay": "Zobrazit podrobnosti administrátora v překryvném okně s čekajícím účtem",
@@ -1153,8 +1171,10 @@
 	"Source": "Zdroj",
 	"Speech Playback Speed": "Rychlost přehrávání řeči",
 	"Speech recognition error: {{error}}": "Chyba rozpoznávání řeči: {{error}}",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "Motor převodu řeči na text",
 	"Stop": "Zastavit",
+	"Stop Generating": "",
 	"Stop Sequence": "Sekvence Zastavení",
 	"Stream Chat Response": "Odezva chatu Stream",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "Navrhované",
 	"Support": "Podpora",
 	"Support this plugin:": "Podpořte tento plugin:",
+	"Supported MIME Types": "",
 	"Sync directory": "Synchronizovat adresář",
 	"System": "System",
 	"System Instructions": "",
@@ -1186,6 +1207,7 @@
 	"Temperature": "",
 	"Temporary Chat": "Dočasný chat",
 	"Text Splitter": "Rozdělovač textu",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "Stroj pro převod textu na řeč",
 	"Thanks for your feedback!": "Děkujeme za vaši zpětnou vazbu!",
 	"The Application Account DN you bind with for search": "",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Vývojáři stojící za tímto pluginem jsou zapálení dobrovolníci z komunity. Pokud považujete tento plugin za užitečný, zvažte příspěvek k jeho vývoji.",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "Hodnotící žebříček je založen na systému hodnocení Elo a je aktualizován v reálném čase.",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "",
 	"The LDAP attribute that maps to the username that users use to sign in.": "",
@@ -1203,10 +1226,12 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Skóre by mělo být hodnotou mezi 0,0 (0%) a 1,0 (100%).",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "Téma",
 	"Thinking...": "Přemýšlím...",
 	"This action cannot be undone. Do you wish to continue?": "Tuto akci nelze vrátit zpět. Přejete si pokračovat?",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "",
+	"This chat won't appear in history and your messages will not be saved.": "",
 	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "To zajišťuje, že vaše cenné konverzace jsou bezpečně uloženy ve vaší backendové databázi. Děkujeme!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Jedná se o experimentální funkci, nemusí fungovat podle očekávání a může být kdykoliv změněna.",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "Přepnout nastavení",
 	"Toggle sidebar": "Přepnout postranní panel",
+	"Toggle whether current connection is active.": "",
 	"Token": "Token",
 	"Too verbose": "Příliš upovídané",
 	"Tool created successfully": "Nástroj byl úspěšně vytvořen.",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "",
 	"What are you working on?": "",
-	"Whats New in": "Co je nového v",
+	"What's New in": "Co je nového v",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
 	"wherever you are": "kdekoliv jste",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 29 - 3
src/lib/i18n/locales/da-DK/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "Tillad redigering af chats",
 	"Allow Chat Export": "Tillad eksport af chats",
 	"Allow Chat Share": "Tillad deling af chats",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "Tillad upload af fil",
 	"Allow Multiple Models in Chat": "Tillad flere modeller i chats",
 	"Allow non-local voices": "Tillad ikke-lokale stemmer",
@@ -209,6 +210,7 @@
 	"Clone Chat": "Klon chat",
 	"Clone of {{TITLE}}": "Klon af {{TITLE}}",
 	"Close": "Luk",
+	"Close modal": "",
 	"Close settings modal": "Luk dialogboks med indstillinger",
 	"Code execution": "Kode kørsel",
 	"Code Execution": "Kode kørsel",
@@ -297,7 +299,7 @@
 	"Default": "Standard",
 	"Default (Open AI)": "Standard (Open AI)",
 	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "",
 	"Default Model": "Standard model",
 	"Default model updated": "Standard model opdateret",
 	"Default Models": "Standard modeller",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "",
 	"e.g. Tools for performing various operations": "",
 	"e.g., 3, 4, 5 (leave blank for default)": "",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "",
 	"e.g., westus (leave blank for eastus)": "",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "Features",
 	"Features Permissions": "",
 	"February": "Februar",
+	"Feedback Details": "",
 	"Feedback History": "",
 	"Feedbacks": "Feedback",
 	"Feel free to add specific details": "Du er velkommen til at tilføje specifikke detaljer",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "",
 	"Image": "Billede",
 	"Image Compression": "Billedkomprimering",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "Billedgenerering",
 	"Image Generation (Experimental)": "Billedgenerering (eksperimentel)",
 	"Image Generation Engine": "Billedgenereringsengine",
 	"Image Max Compression Size": "",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "Billedpromptgenerering",
 	"Image Prompt Generation Prompt": "Billedpromptgenerering prompt",
 	"Image Settings": "Billedindstillinger",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "",
 	"Leaderboard": "",
 	"Learn more about OpenAPI tool servers.": "",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "Lad stå tomt for ubegrænset",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "",
@@ -777,6 +786,7 @@
 	"Lost": "Tabt",
 	"LTR": "LTR",
 	"Made by Open WebUI Community": "Lavet af OpenWebUI Community",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "Sørg for at omslutte dem med",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "Sørg for at eksportere en workflow.json-fil som API-format fra ComfyUI.",
 	"Manage": "Administrer",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Ollama-version",
 	"On": "Til",
 	"OneDrive": "OneDrive",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "Kun alfanumeriske tegn og bindestreger er tilladt",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "Kun alfanumeriske tegn og bindestreger er tilladt i kommandostrengen.",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Kun samlinger kan redigeres, opret en ny vidensbase for at redigere/tilføje dokumenter.",
@@ -909,6 +921,7 @@
 	"Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Du bruger en metode, der ikke understøttes (kun frontend). Kør WebUI fra backend.",
 	"Open file": "Åbn fil",
 	"Open in full screen": "Åbn i fuld skærm",
+	"Open modal to configure connection": "",
 	"Open new chat": "Åbn ny chat",
 	"Open WebUI can use tools provided by any OpenAPI server.": "",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI bruger faster-whisper internt.",
@@ -977,6 +990,7 @@
 	"Positive attitude": "Positiv holdning",
 	"Prefix ID": "Prefix ID",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "Prefix ID bruges til at undgå konflikter med andre forbindelser ved at tilføje et prefix til model-ID'erne - lad være tom for at deaktivere",
+	"Prevent file creation": "",
 	"Preview": "",
 	"Previous 30 days": "Seneste 30 dage",
 	"Previous 7 days": "Seneste 7 dage",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "",
 	"Read": "Læs",
 	"Read Aloud": "Læs højt",
+	"Reason": "",
 	"Reasoning Effort": "",
 	"Record": "Optag",
 	"Record voice": "Optag stemme",
@@ -1018,7 +1033,9 @@
 	"Relevance": "",
 	"Relevance Threshold": "",
 	"Remove": "Fjern",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "Fjern model",
+	"Remove this tag from list": "",
 	"Rename": "Omdøb",
 	"Reorder Models": "Omarranger modeller",
 	"Reply in Thread": "Svar i tråd",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "Del chat",
 	"Share to Open WebUI Community": "Del til OpenWebUI Community",
 	"Sharing Permissions": "Delingstilladelser",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "Vis",
 	"Show \"What's New\" modal on login": "",
 	"Show Admin Details in Account Pending Overlay": "Vis administratordetaljer i overlay for ventende konto",
@@ -1153,8 +1171,10 @@
 	"Source": "Kilde",
 	"Speech Playback Speed": "Talehastighed",
 	"Speech recognition error: {{error}}": "Talegenkendelsesfejl: {{error}}",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "Tale-til-tekst-engine",
 	"Stop": "Stop",
+	"Stop Generating": "",
 	"Stop Sequence": "Stopsekvens",
 	"Stream Chat Response": "Stream chatsvar",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "Foreslået",
 	"Support": "Support",
 	"Support this plugin:": "Støt dette plugin:",
+	"Supported MIME Types": "",
 	"Sync directory": "Synkroniser mappe",
 	"System": "System",
 	"System Instructions": "",
@@ -1186,6 +1207,7 @@
 	"Temperature": "Temperatur",
 	"Temporary Chat": "Midlertidig chat",
 	"Text Splitter": "",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "Tekst-til-tale-engine",
 	"Thanks for your feedback!": "Tak for din feedback!",
 	"The Application Account DN you bind with for search": "",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Udviklerne bag dette plugin er passionerede frivillige fra fællesskabet. Hvis du finder dette plugin nyttigt, kan du overveje at bidrage til dets udvikling.",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "",
 	"The LDAP attribute that maps to the username that users use to sign in.": "",
@@ -1203,11 +1226,13 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Scoren skal være en værdi mellem 0,0 (0%) og 1,0 (100%).",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "Tema",
 	"Thinking...": "Tænker...",
 	"This action cannot be undone. Do you wish to continue?": "Denne handling kan ikke fortrydes. Vil du fortsætte?",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Denne kanal blev oprettet den {{createdAt}}. Dette er det helt første i kanalen {{channelName}}.",
-	"This chat won’t appear in history and your messages will not be saved.": "Denne chat vil ikke vises i historikken, og dine beskeder vil ikke blive gemt.",
+	"This chat won't appear in history and your messages will not be saved.": "Denne chat vil ikke vises i historikken, og dine beskeder vil ikke blive gemt.",
+	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dette sikrer, at dine værdifulde samtaler gemmes sikkert i din backend-database. Tak!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dette er en eksperimentel funktion, den fungerer muligvis ikke som forventet og kan ændres når som helst.",
 	"This model is not publicly available. Please select another model.": "",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "",
 	"Toggle settings": "Skift indstillinger",
 	"Toggle sidebar": "Skift sidebjælke",
+	"Toggle whether current connection is active.": "",
 	"Token": "",
 	"Too verbose": "For ordrigt",
 	"Tool created successfully": "Værktøj oprettet.",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "Hvad prøver du at opnå?",
 	"What are you working on?": "Hvad arbejder du på?",
-	"Whats New in": "Nyheder i",
+	"What's New in": "Nyheder i",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "",
 	"wherever you are": "hvad end du er",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

+ 32 - 6
src/lib/i18n/locales/de-DE/translation.json

@@ -65,6 +65,7 @@
 	"Allow Chat Edit": "Bearbeiten von Chats erlauben",
 	"Allow Chat Export": "Erlaube Chat Export",
 	"Allow Chat Share": "Erlaube Chat teilen",
+	"Allow Chat System Prompt": "",
 	"Allow File Upload": "Hochladen von Dateien erlauben",
 	"Allow Multiple Models in Chat": "Multiple Modelle in Chat erlauben",
 	"Allow non-local voices": "Nicht-lokale Stimmen erlauben",
@@ -209,6 +210,7 @@
 	"Clone Chat": "Konversation klonen",
 	"Clone of {{TITLE}}": "Klon von {{TITLE}}",
 	"Close": "Schließen",
+	"Close modal": "",
 	"Close settings modal": "",
 	"Code execution": "Codeausführung",
 	"Code Execution": "Codeausführung",
@@ -297,7 +299,7 @@
 	"Default": "Standard",
 	"Default (Open AI)": "Standard (Open AI)",
 	"Default (SentenceTransformers)": "Standard (SentenceTransformers)",
-	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the models built-in tool-calling capabilities, but requires the model to inherently support this feature.": "Der Standardmodus funktioniert mit einer breiteren Auswahl von Modellen, indem er Werkzeuge einmal vor der Ausführung aufruft. Der native Modus nutzt die integrierten Tool-Aufrufmöglichkeiten des Modells, erfordert jedoch, dass das Modell diese Funktion von Natur aus unterstützt.",
+	"Default mode works with a wider range of models by calling tools once before execution. Native mode leverages the model's built-in tool-calling capabilities, but requires the model to inherently support this feature.": "Der Standardmodus funktioniert mit einer breiteren Auswahl von Modellen, indem er Werkzeuge einmal vor der Ausführung aufruft. Der native Modus nutzt die integrierten Tool-Aufrufmöglichkeiten des Modells, erfordert jedoch, dass das Modell diese Funktion von Natur aus unterstützt.",
 	"Default Model": "Standardmodell",
 	"Default model updated": "Standardmodell aktualisiert",
 	"Default Models": "Standardmodelle",
@@ -393,6 +395,7 @@
 	"e.g. pdf, docx, txt": "z. B. pdf, docx, txt",
 	"e.g. Tools for performing various operations": "z. B. Werkzeuge für verschiedene Operationen",
 	"e.g., 3, 4, 5 (leave blank for default)": "z. B. 3, 4, 5 (leer lassen für Standard)",
+	"e.g., audio/wav,audio/mpeg (leave blank for defaults)": "",
 	"e.g., en-US,ja-JP (leave blank for auto-detect)": "z. B. en-US,de-DE (freilassen für automatische Erkennung)",
 	"e.g., westus (leave blank for eastus)": "z. B. westus (leer lassen für eastus)",
 	"e.g.) en,fr,de": "",
@@ -590,6 +593,7 @@
 	"Features": "Funktionalitäten",
 	"Features Permissions": "Funktionen-Berechtigungen",
 	"February": "Februar",
+	"Feedback Details": "",
 	"Feedback History": "Feedback-Verlauf",
 	"Feedbacks": "Feedbacks",
 	"Feel free to add specific details": "Fühlen Sie sich frei, spezifische Details hinzuzufügen",
@@ -670,7 +674,7 @@
 	"Hex Color": "Hex-Farbe",
 	"Hex Color - Leave empty for default color": "Hex-Farbe - Leer lassen für Standardfarbe",
 	"Hide": "Verbergen",
-	"Hide from Sidebar": "",
+	"Hide from Sidebar": "Von Seitenleiste entfernen",
 	"Hide Model": "Modell verstecken",
 	"High Contrast Mode": "Modus für hohen Kontrast",
 	"Home": "Startseite",
@@ -686,10 +690,14 @@
 	"Ignite curiosity": "Neugier entfachen",
 	"Image": "Bild",
 	"Image Compression": "Bildkomprimierung",
+	"Image Compression Height": "",
+	"Image Compression Width": "",
 	"Image Generation": "Bildgenerierung",
 	"Image Generation (Experimental)": "Bildgenerierung (experimentell)",
 	"Image Generation Engine": "Bildgenerierungs-Engine",
 	"Image Max Compression Size": "Maximale Bildkomprimierungsgröße",
+	"Image Max Compression Size height": "",
+	"Image Max Compression Size width": "",
 	"Image Prompt Generation": "Bild-Prompt-Generierung",
 	"Image Prompt Generation Prompt": "Prompt für die Bild-Prompt-Generierung",
 	"Image Settings": "Bildeinstellungen",
@@ -733,7 +741,7 @@
 	"JWT Expiration": "JWT-Ablauf",
 	"JWT Token": "JWT-Token",
 	"Kagi Search API Key": "Kagi Search API-Schlüssel",
-	"Keep in Sidebar": "",
+	"Keep in Sidebar": "In Seitenleiste anzeigen",
 	"Key": "Schlüssel",
 	"Keyboard shortcuts": "Tastenkombinationen",
 	"Knowledge": "Wissen",
@@ -757,6 +765,7 @@
 	"LDAP server updated": "LDAP-Server aktualisiert",
 	"Leaderboard": "Bestenliste",
 	"Learn more about OpenAPI tool servers.": "Erfahren Sie mehr über OpenAPI-Toolserver.",
+	"Leave empty for no compression": "",
 	"Leave empty for unlimited": "Leer lassen für unbegrenzt",
 	"Leave empty to include all models from \"{{url}}\" endpoint": "Leer lassen, um alle Modelle vom Endpunkt \"{{url}}\" einzuschließen",
 	"Leave empty to include all models from \"{{url}}/api/tags\" endpoint": "Leer lassen, um alle Modelle vom Endpunkt \"{{url}}/api/tags\" einzuschließen",
@@ -777,6 +786,7 @@
 	"Lost": "Verloren",
 	"LTR": "LTR",
 	"Made by Open WebUI Community": "Von der OpenWebUI-Community",
+	"Make password visible in the user interface": "",
 	"Make sure to enclose them with": "Umschließen Sie Variablen mit",
 	"Make sure to export a workflow.json file as API format from ComfyUI.": "Stellen Sie sicher, dass sie eine workflow.json-Datei im API-Format von ComfyUI exportieren.",
 	"Manage": "Verwalten",
@@ -853,7 +863,7 @@
 	"New Password": "Neues Passwort",
 	"New Tool": "Neues Werkzeug",
 	"new-channel": "neuer-kanal",
-	"Next message": "",
+	"Next message": "Nächste Nachricht",
 	"No chats found for this user.": "Keine Chats für diesen Nutzer gefunden.",
 	"No chats found.": "Keine Chats gefunden.",
 	"No content": "Kein Inhalt",
@@ -898,6 +908,8 @@
 	"Ollama Version": "Ollama-Version",
 	"On": "Ein",
 	"OneDrive": "",
+	"Only active when \"Paste Large Text as File\" setting is toggled on.": "",
+	"Only active when the chat input is in focus and an LLM is generating a response.": "",
 	"Only alphanumeric characters and hyphens are allowed": "Nur alphanumerische Zeichen und Bindestriche sind erlaubt",
 	"Only alphanumeric characters and hyphens are allowed in the command string.": "In der Befehlszeichenfolge sind nur alphanumerische Zeichen und Bindestriche erlaubt.",
 	"Only collections can be edited, create a new knowledge base to edit/add documents.": "Nur Sammlungen können bearbeitet werden. Erstellen Sie eine neue Wissensbasis, um Dokumente zu bearbeiten/hinzuzufügen.",
@@ -909,6 +921,7 @@
 	"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 in full screen": "Im Vollbildmodus öffnen",
+	"Open modal to configure connection": "",
 	"Open new chat": "Neuen Chat öffnen",
 	"Open WebUI can use tools provided by any OpenAPI server.": "Open WebUI kann Werkzeuge verwenden, die von irgendeinem OpenAPI-Server bereitgestellt werden.",
 	"Open WebUI uses faster-whisper internally.": "Open WebUI verwendet intern faster-whisper.",
@@ -977,6 +990,7 @@
 	"Positive attitude": "Positive Einstellung",
 	"Prefix ID": "Präfix-ID",
 	"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "Prefix-ID wird verwendet, um Konflikte mit anderen Verbindungen zu vermeiden, indem ein Präfix zu den Modell-IDs hinzugefügt wird - leer lassen, um zu deaktivieren",
+	"Prevent file creation": "",
 	"Preview": "Vorschau",
 	"Previous 30 days": "Vorherige 30 Tage",
 	"Previous 7 days": "Vorherige 7 Tage",
@@ -1002,6 +1016,7 @@
 	"Re-rank models by topic similarity": "Modelle nach thematischer Ähnlichkeit neu ordnen",
 	"Read": "Lesen",
 	"Read Aloud": "Vorlesen",
+	"Reason": "",
 	"Reasoning Effort": "Schlussfolgerungsaufwand",
 	"Record": "Aufzeichnen",
 	"Record voice": "Stimme aufnehmen",
@@ -1018,7 +1033,9 @@
 	"Relevance": "Relevanz",
 	"Relevance Threshold": "Relevanzschwelle",
 	"Remove": "Entfernen",
+	"Remove {{MODELID}} from list.": "",
 	"Remove Model": "Modell entfernen",
+	"Remove this tag from list": "",
 	"Rename": "Umbenennen",
 	"Reorder Models": "Modelle neu anordnen",
 	"Reply in Thread": "Im Thread antworten",
@@ -1128,6 +1145,7 @@
 	"Share Chat": "Chat teilen",
 	"Share to Open WebUI Community": "Mit OpenWebUI Community teilen",
 	"Sharing Permissions": "Berechtigungen teilen",
+	"Shortcuts with an asterisk (*) are situational and only active under specific conditions.": "",
 	"Show": "Anzeigen",
 	"Show \"What's New\" modal on login": "\"Was gibt's Neues\"-Modal beim Anmelden anzeigen",
 	"Show Admin Details in Account Pending Overlay": "Admin-Details im Account-Pending-Overlay anzeigen",
@@ -1153,8 +1171,10 @@
 	"Source": "Quelle",
 	"Speech Playback Speed": "Sprachwiedergabegeschwindigkeit",
 	"Speech recognition error: {{error}}": "Spracherkennungsfehler: {{error}}",
+	"Speech-to-Text": "",
 	"Speech-to-Text Engine": "Sprache-zu-Text-Engine",
 	"Stop": "Stop",
+	"Stop Generating": "",
 	"Stop Sequence": "Stop-Sequenz",
 	"Stream Chat Response": "Chat-Antwort streamen",
 	"Strip Existing OCR": "",
@@ -1168,6 +1188,7 @@
 	"Suggested": "Vorgeschlagen",
 	"Support": "Unterstützung",
 	"Support this plugin:": "Unterstützen Sie dieses Plugin:",
+	"Supported MIME Types": "",
 	"Sync directory": "Verzeichnis synchronisieren",
 	"System": "System",
 	"System Instructions": "Systemanweisungen",
@@ -1186,6 +1207,7 @@
 	"Temperature": "Temperatur",
 	"Temporary Chat": "Temporäre Unterhaltung",
 	"Text Splitter": "Text-Splitter",
+	"Text-to-Speech": "",
 	"Text-to-Speech Engine": "Text-zu-Sprache-Engine",
 	"Thanks for your feedback!": "Danke für Ihr Feedback!",
 	"The Application Account DN you bind with for search": "Der Anwendungs-Konto-DN, mit dem Sie für die Suche binden",
@@ -1194,6 +1216,7 @@
 	"The developers behind this plugin are passionate volunteers from the community. If you find this plugin helpful, please consider contributing to its development.": "Die Entwickler hinter diesem Plugin sind leidenschaftliche Freiwillige aus der Community. Wenn Sie dieses Plugin hilfreich finden, erwägen Sie bitte, zu seiner Entwicklung beizutragen.",
 	"The evaluation leaderboard is based on the Elo rating system and is updated in real-time.": "Die Bewertungs-Bestenliste basiert auf dem Elo-Bewertungssystem und wird in Echtzeit aktualisiert.",
 	"The format to return a response in. Format can be json or a JSON schema.": "",
+	"The height in pixels to compress images to. Leave empty for no compression.": "",
 	"The language of the input audio. Supplying the input language in ISO-639-1 (e.g. en) format will improve accuracy and latency. Leave blank to automatically detect the language.": "",
 	"The LDAP attribute that maps to the mail that users use to sign in.": "Das LDAP-Attribut, das der Mail zugeordnet ist, die Benutzer zum Anmelden verwenden.",
 	"The LDAP attribute that maps to the username that users use to sign in.": "Das LDAP-Attribut, das dem Benutzernamen zugeordnet ist, den Benutzer zum Anmelden verwenden.",
@@ -1203,11 +1226,13 @@
 	"The output format for the text. Can be 'json', 'markdown', or 'html'. Defaults to 'markdown'.": "",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Die Punktzahl sollte ein Wert zwischen 0,0 (0 %) und 1,0 (100 %) sein.",
 	"The temperature of the model. Increasing the temperature will make the model answer more creatively.": "Die Temperatur des Modells. Eine Erhöhung der Temperatur führt zu kreativeren Antworten des Modells.",
+	"The width in pixels to compress images to. Leave empty for no compression.": "",
 	"Theme": "Design",
 	"Thinking...": "Denke nach...",
 	"This action cannot be undone. Do you wish to continue?": "Diese Aktion kann nicht rückgängig gemacht werden. Möchten Sie fortfahren?",
 	"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Dieser Kanal wurde am {{createdAt}} erstellt. Dies ist der Beginn des {{channelName}} Kanals.",
-	"This chat won’t appear in history and your messages will not be saved.": "Diese Unterhaltung erscheint nicht in Ihrem Chat-Verlauf. Alle Nachrichten sind privat und werden nicht gespeichert.",
+	"This chat won't appear in history and your messages will not be saved.": "Diese Unterhaltung erscheint nicht in Ihrem Chat-Verlauf. Alle Nachrichten sind privat und werden nicht gespeichert.",
+	"This chat won’t appear in history and your messages will not be saved.": "",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Dies stellt sicher, dass Ihre wertvollen Chats sicher in Ihrer Backend-Datenbank gespeichert werden. Vielen Dank!",
 	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Dies ist eine experimentelle Funktion, sie funktioniert möglicherweise nicht wie erwartet und kann jederzeit geändert werden.",
 	"This model is not publicly available. Please select another model.": "Dieses Modell ist nicht öffentlich verfügbar. Bitte wählen Sie ein anderes Modell aus.",
@@ -1250,6 +1275,7 @@
 	"Toggle search": "Suche umschalten",
 	"Toggle settings": "Einstellungen umschalten",
 	"Toggle sidebar": "Seitenleiste umschalten",
+	"Toggle whether current connection is active.": "",
 	"Token": "Token",
 	"Too verbose": "Zu ausführlich",
 	"Tool created successfully": "Werkzeug erfolgreich erstellt",
@@ -1359,7 +1385,7 @@
 	"Weight of BM25 Retrieval": "",
 	"What are you trying to achieve?": "Was versuchen Sie zu erreichen?",
 	"What are you working on?": "Woran arbeiten Sie?",
-	"Whats New in": "Neuigkeiten von",
+	"What's New in": "Neuigkeiten von",
 	"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "Wenn aktiviert, antwortet das Modell in Echtzeit auf jede Chat-Nachricht und generiert eine Antwort, sobald der Benutzer eine Nachricht sendet. Dieser Modus ist nützlich für Live-Chat-Anwendungen, kann jedoch die Leistung auf langsamerer Hardware beeinträchtigen.",
 	"wherever you are": "wo immer Sie sind",
 	"Whether to paginate the output. Each page will be separated by a horizontal rule and page number. Defaults to False.": "",

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