浏览代码

Merge pull request #3013 from open-webui/dev

0.3.3
Timothy Jaeryang Baek 11 月之前
父节点
当前提交
c41b33c9c0
共有 93 个文件被更改,包括 3147 次插入398 次删除
  1. 14 0
      CHANGELOG.md
  2. 4 0
      README.md
  3. 43 25
      backend/apps/rag/main.py
  4. 46 0
      backend/apps/rag/search/duckduckgo.py
  5. 10 10
      backend/apps/rag/search/serply.py
  6. 2 12
      backend/apps/rag/utils.py
  7. 0 12
      backend/apps/socket/main.py
  8. 61 0
      backend/apps/webui/internal/migrations/012_add_tools.py
  9. 5 2
      backend/apps/webui/main.py
  10. 132 0
      backend/apps/webui/models/tools.py
  11. 1 1
      backend/apps/webui/routers/chats.py
  12. 183 0
      backend/apps/webui/routers/tools.py
  13. 17 0
      backend/apps/webui/routers/utils.py
  14. 23 0
      backend/apps/webui/utils.py
  15. 23 3
      backend/config.py
  16. 1 0
      backend/constants.py
  17. 221 19
      backend/main.py
  18. 2 1
      backend/requirements.txt
  19. 2 1
      backend/start_windows.bat
  20. 5 0
      backend/utils/task.py
  21. 73 0
      backend/utils/tools.py
  22. 0 21
      cypress/e2e/settings.cy.ts
  23. 196 9
      package-lock.json
  24. 6 2
      package.json
  25. 4 2
      pyproject.toml
  26. 40 48
      requirements-dev.lock
  27. 40 48
      requirements.lock
  28. 21 0
      src/app.css
  29. 97 2
      src/app.html
  30. 193 0
      src/lib/apis/tools/index.ts
  31. 33 0
      src/lib/apis/utils/index.ts
  32. 9 1
      src/lib/components/admin/Settings/WebSearch.svelte
  33. 16 1
      src/lib/components/chat/Chat.svelte
  34. 16 1
      src/lib/components/chat/MessageInput.svelte
  35. 45 8
      src/lib/components/chat/MessageInput/InputMenu.svelte
  36. 2 2
      src/lib/components/chat/Messages/Placeholder.svelte
  37. 1 0
      src/lib/components/chat/ModelSelector/Selector.svelte
  38. 1 0
      src/lib/components/chat/Settings/General.svelte
  39. 135 0
      src/lib/components/common/CodeEditor.svelte
  40. 106 0
      src/lib/components/common/ConfirmDialog.svelte
  41. 11 0
      src/lib/components/icons/WrenchSolid.svelte
  42. 1 1
      src/lib/components/workspace/Documents.svelte
  43. 57 0
      src/lib/components/workspace/Models/ToolsSelector.svelte
  44. 358 0
      src/lib/components/workspace/Tools.svelte
  45. 127 0
      src/lib/components/workspace/Tools/CodeEditor.svelte
  46. 179 0
      src/lib/components/workspace/Tools/ToolkitEditor.svelte
  47. 6 0
      src/lib/i18n/locales/ar-BH/translation.json
  48. 6 0
      src/lib/i18n/locales/bg-BG/translation.json
  49. 6 0
      src/lib/i18n/locales/bn-BD/translation.json
  50. 6 0
      src/lib/i18n/locales/ca-ES/translation.json
  51. 6 0
      src/lib/i18n/locales/ceb-PH/translation.json
  52. 6 0
      src/lib/i18n/locales/de-DE/translation.json
  53. 6 0
      src/lib/i18n/locales/dg-DG/translation.json
  54. 6 0
      src/lib/i18n/locales/en-GB/translation.json
  55. 6 0
      src/lib/i18n/locales/en-US/translation.json
  56. 6 0
      src/lib/i18n/locales/es-ES/translation.json
  57. 6 0
      src/lib/i18n/locales/fa-IR/translation.json
  58. 6 0
      src/lib/i18n/locales/fi-FI/translation.json
  59. 7 1
      src/lib/i18n/locales/fr-CA/translation.json
  60. 7 1
      src/lib/i18n/locales/fr-FR/translation.json
  61. 6 0
      src/lib/i18n/locales/he-IL/translation.json
  62. 6 0
      src/lib/i18n/locales/hi-IN/translation.json
  63. 83 77
      src/lib/i18n/locales/hr-HR/translation.json
  64. 6 0
      src/lib/i18n/locales/it-IT/translation.json
  65. 6 0
      src/lib/i18n/locales/ja-JP/translation.json
  66. 6 0
      src/lib/i18n/locales/ka-GE/translation.json
  67. 6 0
      src/lib/i18n/locales/ko-KR/translation.json
  68. 6 0
      src/lib/i18n/locales/lt-LT/translation.json
  69. 6 0
      src/lib/i18n/locales/nb-NO/translation.json
  70. 6 0
      src/lib/i18n/locales/nl-NL/translation.json
  71. 6 0
      src/lib/i18n/locales/pa-IN/translation.json
  72. 6 0
      src/lib/i18n/locales/pl-PL/translation.json
  73. 6 0
      src/lib/i18n/locales/pt-BR/translation.json
  74. 6 0
      src/lib/i18n/locales/pt-PT/translation.json
  75. 6 0
      src/lib/i18n/locales/ru-RU/translation.json
  76. 6 0
      src/lib/i18n/locales/sr-RS/translation.json
  77. 6 0
      src/lib/i18n/locales/sv-SE/translation.json
  78. 6 0
      src/lib/i18n/locales/tk-TW/translation.json
  79. 6 0
      src/lib/i18n/locales/tr-TR/translation.json
  80. 6 0
      src/lib/i18n/locales/uk-UA/translation.json
  81. 48 42
      src/lib/i18n/locales/vi-VN/translation.json
  82. 8 2
      src/lib/i18n/locales/zh-CN/translation.json
  83. 6 0
      src/lib/i18n/locales/zh-TW/translation.json
  84. 10 16
      src/lib/stores/index.ts
  85. 11 17
      src/routes/(app)/+layout.svelte
  86. 14 1
      src/routes/(app)/workspace/+layout.svelte
  87. 24 6
      src/routes/(app)/workspace/models/create/+page.svelte
  88. 19 1
      src/routes/(app)/workspace/models/edit/+page.svelte
  89. 5 0
      src/routes/(app)/workspace/tools/+page.svelte
  90. 57 0
      src/routes/(app)/workspace/tools/create/+page.svelte
  91. 66 0
      src/routes/(app)/workspace/tools/edit/+page.svelte
  92. 34 2
      src/routes/+layout.svelte
  93. 二进制
      static/audio/greeting.mp3

+ 14 - 0
CHANGELOG.md

@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.3.3] - 2024-06-12
+
+### Added
+
+- **🛠️ Native Python Function Calling**: Introducing native Python function calling within Open WebUI. We’ve also included a built-in code editor to seamlessly develop and integrate function code within the 'Tools' workspace. With this, you can significantly enhance your LLM’s capabilities by creating custom RAG pipelines, web search tools, and even agent-like features such as sending Discord messages.
+- **🌐 DuckDuckGo Integration**: Added DuckDuckGo as a web search provider, giving you more search options.
+- **🌏 Enhanced Translations**: Improved translations for Vietnamese and Chinese languages, making the interface more accessible.
+
+### Fixed
+
+- **🔗 Web Search URL Error Handling**: Fixed the issue where a single URL error would disrupt the data loading process in Web Search mode. Now, such errors will be handled gracefully to ensure uninterrupted data loading.
+- **🖥️ Frontend Responsiveness**: Resolved the problem where the frontend would stop responding if the backend encounters an error while downloading a model. Improved error handling to maintain frontend stability.
+- **🔧 Dependency Issues in pip**: Fixed issues related to pip installations, ensuring all dependencies are correctly managed to prevent installation errors.
+
 ## [0.3.2] - 2024-06-10
 
 ### Added

+ 4 - 0
README.md

@@ -29,8 +29,12 @@ Open WebUI is an [extensible](https://github.com/open-webui/pipelines), feature-
 
 - ✒️🔢 **Full Markdown and LaTeX Support**: Elevate your LLM experience with comprehensive Markdown and LaTeX capabilities for enriched interaction.
 
+- 🎤📹 **Hands-Free Voice/Video Call**: Experience seamless communication with integrated hands-free voice and video call features, allowing for a more dynamic and interactive chat environment.
+
 - 🛠️ **Model Builder**: Easily create Ollama models via the Web UI. Create and add custom characters/agents, customize chat elements, and import models effortlessly through [Open WebUI Community](https://openwebui.com/) integration.
 
+- 🐍 **Native Python Function Calling Tool**: Enhance your LLMs with built-in code editor support in the tools workspace. Bring Your Own Function (BYOF) by simply adding your pure Python functions, enabling seamless integration with LLMs.
+
 - 📚 **Local RAG Integration**: Dive into the future of chat interactions with groundbreaking Retrieval Augmented Generation (RAG) support. This feature seamlessly integrates document interactions into your chat experience. You can load documents directly into the chat or add files to your document library, effortlessly accessing them using the `#` command before a query.
 
 - 🔍 **Web Search for RAG**: Perform web searches using providers like `SearXNG`, `Google PSE`, `Brave Search`, `serpstack`, `serper`, and `Serply` and inject the results directly into your chat experience.

+ 43 - 25
backend/apps/rag/main.py

@@ -8,13 +8,15 @@ from fastapi import (
     Form,
 )
 from fastapi.middleware.cors import CORSMiddleware
+import requests
 import os, shutil, logging, re
 from datetime import datetime
 
 from pathlib import Path
-from typing import List, Union, Sequence
+from typing import List, Union, Sequence, Iterator, Any
 
 from chromadb.utils.batch_utils import create_batches
+from langchain_core.documents import Document
 
 from langchain_community.document_loaders import (
     WebBaseLoader,
@@ -70,6 +72,7 @@ from apps.rag.search.searxng import search_searxng
 from apps.rag.search.serper import search_serper
 from apps.rag.search.serpstack import search_serpstack
 from apps.rag.search.serply import search_serply
+from apps.rag.search.duckduckgo import search_duckduckgo
 
 from utils.misc import (
     calculate_sha256,
@@ -701,7 +704,7 @@ def get_web_loader(url: Union[str, Sequence[str]], verify_ssl: bool = True):
     # Check if the URL is valid
     if not validate_url(url):
         raise ValueError(ERROR_MESSAGES.INVALID_URL)
-    return WebBaseLoader(
+    return SafeWebBaseLoader(
         url,
         verify_ssl=verify_ssl,
         requests_per_second=RAG_WEB_SEARCH_CONCURRENT_REQUESTS,
@@ -714,18 +717,13 @@ def validate_url(url: Union[str, Sequence[str]]):
         if isinstance(validators.url(url), validators.ValidationError):
             raise ValueError(ERROR_MESSAGES.INVALID_URL)
         if not ENABLE_RAG_LOCAL_WEB_FETCH:
-            # Local web fetch is disabled, filter out any URLs that resolve to private IP addresses
-            parsed_url = urllib.parse.urlparse(url)
-            # Get IPv4 and IPv6 addresses
-            ipv4_addresses, ipv6_addresses = resolve_hostname(parsed_url.hostname)
-            # Check if any of the resolved addresses are private
-            # This is technically still vulnerable to DNS rebinding attacks, as we don't control WebBaseLoader
-            for ip in ipv4_addresses:
-                if validators.ipv4(ip, private=True):
-                    raise ValueError(ERROR_MESSAGES.INVALID_URL)
-            for ip in ipv6_addresses:
-                if validators.ipv6(ip, private=True):
+            # Check if the URL exists by making a HEAD request
+            try:
+                response = requests.head(url, allow_redirects=True)
+                if response.status_code != 200:
                     raise ValueError(ERROR_MESSAGES.INVALID_URL)
+            except requests.exceptions.RequestException:
+                raise ValueError(ERROR_MESSAGES.INVALID_URL)
         return True
     elif isinstance(url, Sequence):
         return all(validate_url(u) for u in url)
@@ -733,17 +731,6 @@ def validate_url(url: Union[str, Sequence[str]]):
         return False
 
 
-def resolve_hostname(hostname):
-    # Get address information
-    addr_info = socket.getaddrinfo(hostname, None)
-
-    # Extract IP addresses from address information
-    ipv4_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET]
-    ipv6_addresses = [info[4][0] for info in addr_info if info[0] == socket.AF_INET6]
-
-    return ipv4_addresses, ipv6_addresses
-
-
 def search_web(engine: str, query: str) -> list[SearchResult]:
     """Search the web using a search engine and return the results as a list of SearchResult objects.
     Will look for a search engine API key in environment variables in the following order:
@@ -820,6 +807,8 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
             )
         else:
             raise Exception("No SERPLY_API_KEY found in environment variables")
+    elif engine == "duckduckgo":
+        return search_duckduckgo(query, app.state.config.RAG_WEB_SEARCH_RESULT_COUNT)
     else:
         raise Exception("No search engine API key found in environment variables")
 
@@ -827,7 +816,9 @@ def search_web(engine: str, query: str) -> list[SearchResult]:
 @app.post("/web/search")
 def store_web_search(form_data: SearchForm, user=Depends(get_current_user)):
     try:
-        logging.info(f"trying to web search with {app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query}")
+        logging.info(
+            f"trying to web search with {app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query}"
+        )
         web_results = search_web(
             app.state.config.RAG_WEB_SEARCH_ENGINE, form_data.query
         )
@@ -1238,6 +1229,33 @@ def reset(user=Depends(get_admin_user)) -> bool:
     return True
 
 
+class SafeWebBaseLoader(WebBaseLoader):
+    """WebBaseLoader with enhanced error handling for URLs."""
+
+    def lazy_load(self) -> Iterator[Document]:
+        """Lazy load text from the url(s) in web_path with error handling."""
+        for path in self.web_paths:
+            try:
+                soup = self._scrape(path, bs_kwargs=self.bs_kwargs)
+                text = soup.get_text(**self.bs_get_text_kwargs)
+
+                # Build metadata
+                metadata = {"source": path}
+                if title := soup.find("title"):
+                    metadata["title"] = title.get_text()
+                if description := soup.find("meta", attrs={"name": "description"}):
+                    metadata["description"] = description.get(
+                        "content", "No description found."
+                    )
+                if html := soup.find("html"):
+                    metadata["language"] = html.get("lang", "No language found.")
+
+                yield Document(page_content=text, metadata=metadata)
+            except Exception as e:
+                # Log the error and continue with the next URL
+                log.error(f"Error loading {path}: {e}")
+
+
 if ENV == "dev":
 
     @app.get("/ef")

+ 46 - 0
backend/apps/rag/search/duckduckgo.py

@@ -0,0 +1,46 @@
+import logging
+
+from apps.rag.search.main import SearchResult
+from duckduckgo_search import DDGS
+from config import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["RAG"])
+
+
+def search_duckduckgo(query: str, count: int) -> list[SearchResult]:
+    """
+    Search using DuckDuckGo's Search API and return the results as a list of SearchResult objects.
+    Args:
+        query (str): The query to search for
+        count (int): The number of results to return
+
+    Returns:
+        List[SearchResult]: A list of search results
+    """
+    # Use the DDGS context manager to create a DDGS object
+    with DDGS() as ddgs:
+        # Use the ddgs.text() method to perform the search
+        ddgs_gen = ddgs.text(
+            query, safesearch="moderate", max_results=count, backend="api"
+        )
+        # Check if there are search results
+        if ddgs_gen:
+            # Convert the search results into a list
+            search_results = [r for r in ddgs_gen]
+
+    # Create an empty list to store the SearchResult objects
+    results = []
+    # Iterate over each search result
+    for result in search_results:
+        # Create a SearchResult object and append it to the results list
+        results.append(
+            SearchResult(
+                link=result["href"],
+                title=result.get("title"),
+                snippet=result.get("body"),
+            )
+        )
+    print(results)
+    # Return the list of search results
+    return results

+ 10 - 10
backend/apps/rag/search/serply.py

@@ -12,14 +12,14 @@ log.setLevel(SRC_LOG_LEVELS["RAG"])
 
 
 def search_serply(
-        api_key: str,
-        query: str,
-        count: int,
-        hl: str = "us",
-        limit: int = 10,
-        device_type: str = "desktop",
-        proxy_location: str = "US"
-    ) -> list[SearchResult]:
+    api_key: str,
+    query: str,
+    count: int,
+    hl: str = "us",
+    limit: int = 10,
+    device_type: str = "desktop",
+    proxy_location: str = "US",
+) -> list[SearchResult]:
     """Search using serper.dev's API and return the results as a list of SearchResult objects.
 
     Args:
@@ -37,7 +37,7 @@ def search_serply(
         "language": "en",
         "num": limit,
         "gl": proxy_location.upper(),
-        "hl": hl.lower()
+        "hl": hl.lower(),
     }
 
     url = f"{url}{urlencode(query_payload)}"
@@ -45,7 +45,7 @@ def search_serply(
         "X-API-KEY": api_key,
         "X-User-Agent": device_type,
         "User-Agent": "open-webui",
-        "X-Proxy-Location": proxy_location
+        "X-Proxy-Location": proxy_location,
     }
 
     response = requests.request("GET", url, headers=headers)

+ 2 - 12
backend/apps/rag/utils.py

@@ -236,10 +236,9 @@ def get_embedding_function(
         return lambda query: generate_multiple(query, func)
 
 
-def rag_messages(
+def get_rag_context(
     docs,
     messages,
-    template,
     embedding_function,
     k,
     reranking_function,
@@ -318,16 +317,7 @@ def rag_messages(
 
     context_string = context_string.strip()
 
-    ra_content = rag_template(
-        template=template,
-        context=context_string,
-        query=query,
-    )
-
-    log.debug(f"ra_content: {ra_content}")
-    messages = add_or_update_system_message(ra_content, messages)
-
-    return messages, citations
+    return context_string, citations
 
 
 def get_model_path(model: str, update_model: bool = False):

+ 0 - 12
backend/apps/socket/main.py

@@ -19,8 +19,6 @@ TIMEOUT_DURATION = 3
 
 @sio.event
 async def connect(sid, environ, auth):
-    print("connect ", sid)
-
     user = None
     if auth and "token" in auth:
         data = decode_token(auth["token"])
@@ -37,7 +35,6 @@ async def connect(sid, environ, auth):
 
             print(f"user {user.name}({user.id}) connected with session ID {sid}")
 
-            print(len(set(USER_POOL)))
             await sio.emit("user-count", {"count": len(set(USER_POOL))})
             await sio.emit("usage", {"models": get_models_in_use()})
 
@@ -64,13 +61,11 @@ async def user_join(sid, data):
 
             print(f"user {user.name}({user.id}) connected with session ID {sid}")
 
-            print(len(set(USER_POOL)))
             await sio.emit("user-count", {"count": len(set(USER_POOL))})
 
 
 @sio.on("user-count")
 async def user_count(sid):
-    print("user-count", sid)
     await sio.emit("user-count", {"count": len(set(USER_POOL))})
 
 
@@ -79,14 +74,12 @@ def get_models_in_use():
     models_in_use = []
     for model_id, data in USAGE_POOL.items():
         models_in_use.append(model_id)
-    print(f"Models in use: {models_in_use}")
 
     return models_in_use
 
 
 @sio.on("usage")
 async def usage(sid, data):
-    print(f'Received "usage" event from {sid}: {data}')
 
     model_id = data["model"]
 
@@ -114,7 +107,6 @@ async def usage(sid, data):
 
 async def remove_after_timeout(sid, model_id):
     try:
-        print("remove_after_timeout", sid, model_id)
         await asyncio.sleep(TIMEOUT_DURATION)
         if model_id in USAGE_POOL:
             print(USAGE_POOL[model_id]["sids"])
@@ -124,7 +116,6 @@ async def remove_after_timeout(sid, model_id):
             if len(USAGE_POOL[model_id]["sids"]) == 0:
                 del USAGE_POOL[model_id]
 
-            print(f"Removed usage data for {model_id} due to timeout")
             # Broadcast the usage data to all clients
             await sio.emit("usage", {"models": get_models_in_use()})
     except asyncio.CancelledError:
@@ -143,9 +134,6 @@ async def disconnect(sid):
         if len(USER_POOL[user_id]) == 0:
             del USER_POOL[user_id]
 
-        print(f"user {user_id} disconnected with session ID {sid}")
-        print(USER_POOL)
-
         await sio.emit("user-count", {"count": len(USER_POOL)})
     else:
         print(f"Unknown session ID {sid} disconnected")

+ 61 - 0
backend/apps/webui/internal/migrations/012_add_tools.py

@@ -0,0 +1,61 @@
+"""Peewee migrations -- 009_add_models.py.
+
+Some examples (model - class or model name)::
+
+    > Model = migrator.orm['table_name']            # Return model in current state by name
+    > Model = migrator.ModelClass                   # Return model in current state by name
+
+    > migrator.sql(sql)                             # Run custom SQL
+    > migrator.run(func, *args, **kwargs)           # Run python function with the given args
+    > migrator.create_model(Model)                  # Create a model (could be used as decorator)
+    > migrator.remove_model(model, cascade=True)    # Remove a model
+    > migrator.add_fields(model, **fields)          # Add fields to a model
+    > migrator.change_fields(model, **fields)       # Change fields
+    > migrator.remove_fields(model, *field_names, cascade=True)
+    > migrator.rename_field(model, old_field_name, new_field_name)
+    > migrator.rename_table(model, new_table_name)
+    > migrator.add_index(model, *col_names, unique=False)
+    > migrator.add_not_null(model, *field_names)
+    > migrator.add_default(model, field_name, default)
+    > migrator.add_constraint(model, name, sql)
+    > migrator.drop_index(model, *col_names)
+    > migrator.drop_not_null(model, *field_names)
+    > migrator.drop_constraints(model, *constraints)
+
+"""
+
+from contextlib import suppress
+
+import peewee as pw
+from peewee_migrate import Migrator
+
+
+with suppress(ImportError):
+    import playhouse.postgres_ext as pw_pext
+
+
+def migrate(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your migrations here."""
+
+    @migrator.create_model
+    class Tool(pw.Model):
+        id = pw.TextField(unique=True)
+        user_id = pw.TextField()
+
+        name = pw.TextField()
+        content = pw.TextField()
+        specs = pw.TextField()
+
+        meta = pw.TextField()
+
+        created_at = pw.BigIntegerField(null=False)
+        updated_at = pw.BigIntegerField(null=False)
+
+        class Meta:
+            table_name = "tool"
+
+
+def rollback(migrator: Migrator, database: pw.Database, *, fake=False):
+    """Write your rollback migrations here."""
+
+    migrator.remove_model("tool")

+ 5 - 2
backend/apps/webui/main.py

@@ -6,6 +6,7 @@ from apps.webui.routers import (
     users,
     chats,
     documents,
+    tools,
     models,
     prompts,
     configs,
@@ -26,8 +27,8 @@ from config import (
     WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
     JWT_EXPIRES_IN,
     WEBUI_BANNERS,
-    AppConfig,
     ENABLE_COMMUNITY_SHARING,
+    AppConfig,
 )
 
 app = FastAPI()
@@ -38,6 +39,7 @@ app.state.config = AppConfig()
 
 app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP
 app.state.config.JWT_EXPIRES_IN = JWT_EXPIRES_IN
+app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
 
 
 app.state.config.SHOW_ADMIN_DETAILS = SHOW_ADMIN_DETAILS
@@ -54,7 +56,7 @@ app.state.config.BANNERS = WEBUI_BANNERS
 app.state.config.ENABLE_COMMUNITY_SHARING = ENABLE_COMMUNITY_SHARING
 
 app.state.MODELS = {}
-app.state.AUTH_TRUSTED_EMAIL_HEADER = WEBUI_AUTH_TRUSTED_EMAIL_HEADER
+app.state.TOOLS = {}
 
 
 app.add_middleware(
@@ -70,6 +72,7 @@ app.include_router(users.router, prefix="/users", tags=["users"])
 app.include_router(chats.router, prefix="/chats", tags=["chats"])
 
 app.include_router(documents.router, prefix="/documents", tags=["documents"])
+app.include_router(tools.router, prefix="/tools", tags=["tools"])
 app.include_router(models.router, prefix="/models", tags=["models"])
 app.include_router(prompts.router, prefix="/prompts", tags=["prompts"])
 app.include_router(memories.router, prefix="/memories", tags=["memories"])

+ 132 - 0
backend/apps/webui/models/tools.py

@@ -0,0 +1,132 @@
+from pydantic import BaseModel
+from peewee import *
+from playhouse.shortcuts import model_to_dict
+from typing import List, Union, Optional
+import time
+import logging
+from apps.webui.internal.db import DB, JSONField
+
+import json
+
+from config import SRC_LOG_LEVELS
+
+log = logging.getLogger(__name__)
+log.setLevel(SRC_LOG_LEVELS["MODELS"])
+
+####################
+# Tools DB Schema
+####################
+
+
+class Tool(Model):
+    id = CharField(unique=True)
+    user_id = CharField()
+    name = TextField()
+    content = TextField()
+    specs = JSONField()
+    meta = JSONField()
+    updated_at = BigIntegerField()
+    created_at = BigIntegerField()
+
+    class Meta:
+        database = DB
+
+
+class ToolMeta(BaseModel):
+    description: Optional[str] = None
+
+
+class ToolModel(BaseModel):
+    id: str
+    user_id: str
+    name: str
+    content: str
+    specs: List[dict]
+    meta: ToolMeta
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+
+####################
+# Forms
+####################
+
+
+class ToolResponse(BaseModel):
+    id: str
+    user_id: str
+    name: str
+    meta: ToolMeta
+    updated_at: int  # timestamp in epoch
+    created_at: int  # timestamp in epoch
+
+
+class ToolForm(BaseModel):
+    id: str
+    name: str
+    content: str
+    meta: ToolMeta
+
+
+class ToolsTable:
+    def __init__(self, db):
+        self.db = db
+        self.db.create_tables([Tool])
+
+    def insert_new_tool(
+        self, user_id: str, form_data: ToolForm, specs: List[dict]
+    ) -> Optional[ToolModel]:
+        tool = ToolModel(
+            **{
+                **form_data.model_dump(),
+                "specs": specs,
+                "user_id": user_id,
+                "updated_at": int(time.time()),
+                "created_at": int(time.time()),
+            }
+        )
+
+        try:
+            result = Tool.create(**tool.model_dump())
+            if result:
+                return tool
+            else:
+                return None
+        except Exception as e:
+            print(f"Error creating tool: {e}")
+            return None
+
+    def get_tool_by_id(self, id: str) -> Optional[ToolModel]:
+        try:
+            tool = Tool.get(Tool.id == id)
+            return ToolModel(**model_to_dict(tool))
+        except:
+            return None
+
+    def get_tools(self) -> List[ToolModel]:
+        return [ToolModel(**model_to_dict(tool)) for tool in Tool.select()]
+
+    def update_tool_by_id(self, id: str, updated: dict) -> Optional[ToolModel]:
+        try:
+            query = Tool.update(
+                **updated,
+                updated_at=int(time.time()),
+            ).where(Tool.id == id)
+            query.execute()
+
+            tool = Tool.get(Tool.id == id)
+            return ToolModel(**model_to_dict(tool))
+        except:
+            return None
+
+    def delete_tool_by_id(self, id: str) -> bool:
+        try:
+            query = Tool.delete().where((Tool.id == id))
+            query.execute()  # Remove the rows, return number of rows removed.
+
+            return True
+        except:
+            return False
+
+
+Tools = ToolsTable(DB)

+ 1 - 1
backend/apps/webui/routers/chats.py

@@ -161,7 +161,7 @@ async def get_archived_session_user_chat_list(
 ############################
 
 
-@router.post("/archive/all", response_model=List[ChatTitleIdResponse])
+@router.post("/archive/all", response_model=bool)
 async def archive_all_chats(user=Depends(get_current_user)):
     return Chats.archive_all_chats_by_user_id(user.id)
 

+ 183 - 0
backend/apps/webui/routers/tools.py

@@ -0,0 +1,183 @@
+from fastapi import Depends, FastAPI, HTTPException, status, Request
+from datetime import datetime, timedelta
+from typing import List, Union, Optional
+
+from fastapi import APIRouter
+from pydantic import BaseModel
+import json
+
+from apps.webui.models.tools import Tools, ToolForm, ToolModel, ToolResponse
+from apps.webui.utils import load_toolkit_module_by_id
+
+from utils.utils import get_current_user, get_admin_user
+from utils.tools import get_tools_specs
+from constants import ERROR_MESSAGES
+
+from importlib import util
+import os
+
+from config import DATA_DIR
+
+
+TOOLS_DIR = f"{DATA_DIR}/tools"
+os.makedirs(TOOLS_DIR, exist_ok=True)
+
+
+router = APIRouter()
+
+############################
+# GetToolkits
+############################
+
+
+@router.get("/", response_model=List[ToolResponse])
+async def get_toolkits(user=Depends(get_current_user)):
+    toolkits = [toolkit for toolkit in Tools.get_tools()]
+    return toolkits
+
+
+############################
+# ExportToolKits
+############################
+
+
+@router.get("/export", response_model=List[ToolModel])
+async def get_toolkits(user=Depends(get_admin_user)):
+    toolkits = [toolkit for toolkit in Tools.get_tools()]
+    return toolkits
+
+
+############################
+# CreateNewToolKit
+############################
+
+
+@router.post("/create", response_model=Optional[ToolResponse])
+async def create_new_toolkit(
+    request: Request, form_data: ToolForm, user=Depends(get_admin_user)
+):
+    if not form_data.id.isidentifier():
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail="Only alphanumeric characters and underscores are allowed in the id",
+        )
+
+    form_data.id = form_data.id.lower()
+
+    toolkit = Tools.get_tool_by_id(form_data.id)
+    if toolkit == None:
+        toolkit_path = os.path.join(TOOLS_DIR, f"{form_data.id}.py")
+        try:
+            with open(toolkit_path, "w") as tool_file:
+                tool_file.write(form_data.content)
+
+            toolkit_module = load_toolkit_module_by_id(form_data.id)
+
+            TOOLS = request.app.state.TOOLS
+            TOOLS[form_data.id] = toolkit_module
+
+            specs = get_tools_specs(TOOLS[form_data.id])
+            toolkit = Tools.insert_new_tool(user.id, form_data, specs)
+
+            if toolkit:
+                return toolkit
+            else:
+                raise HTTPException(
+                    status_code=status.HTTP_400_BAD_REQUEST,
+                    detail=ERROR_MESSAGES.DEFAULT("Error creating toolkit"),
+                )
+        except Exception as e:
+            print(e)
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT(e),
+            )
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.ID_TAKEN,
+        )
+
+
+############################
+# GetToolkitById
+############################
+
+
+@router.get("/id/{id}", response_model=Optional[ToolModel])
+async def get_toolkit_by_id(id: str, user=Depends(get_admin_user)):
+    toolkit = Tools.get_tool_by_id(id)
+
+    if toolkit:
+        return toolkit
+    else:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail=ERROR_MESSAGES.NOT_FOUND,
+        )
+
+
+############################
+# UpdateToolkitById
+############################
+
+
+@router.post("/id/{id}/update", response_model=Optional[ToolModel])
+async def update_toolkit_by_id(
+    request: Request, id: str, form_data: ToolForm, user=Depends(get_admin_user)
+):
+    toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
+
+    try:
+        with open(toolkit_path, "w") as tool_file:
+            tool_file.write(form_data.content)
+
+        toolkit_module = load_toolkit_module_by_id(id)
+
+        TOOLS = request.app.state.TOOLS
+        TOOLS[id] = toolkit_module
+
+        specs = get_tools_specs(TOOLS[id])
+
+        updated = {
+            **form_data.model_dump(exclude={"id"}),
+            "specs": specs,
+        }
+
+        print(updated)
+        toolkit = Tools.update_tool_by_id(id, updated)
+
+        if toolkit:
+            return toolkit
+        else:
+            raise HTTPException(
+                status_code=status.HTTP_400_BAD_REQUEST,
+                detail=ERROR_MESSAGES.DEFAULT("Error updating toolkit"),
+            )
+
+    except Exception as e:
+        raise HTTPException(
+            status_code=status.HTTP_400_BAD_REQUEST,
+            detail=ERROR_MESSAGES.DEFAULT(e),
+        )
+
+
+############################
+# DeleteToolkitById
+############################
+
+
+@router.delete("/id/{id}/delete", response_model=bool)
+async def delete_toolkit_by_id(request: Request, id: str, user=Depends(get_admin_user)):
+    result = Tools.delete_tool_by_id(id)
+
+    if result:
+        TOOLS = request.app.state.TOOLS
+        if id in TOOLS:
+            del TOOLS[id]
+
+        # delete the toolkit file
+        toolkit_path = os.path.join(TOOLS_DIR, f"{id}.py")
+        os.remove(toolkit_path)
+
+    return result

+ 17 - 0
backend/apps/webui/routers/utils.py

@@ -7,6 +7,8 @@ from pydantic import BaseModel
 
 from fpdf import FPDF
 import markdown
+import black
+
 
 from apps.webui.internal.db import DB
 from utils.utils import get_admin_user
@@ -26,6 +28,21 @@ async def get_gravatar(
     return get_gravatar_url(email)
 
 
+class CodeFormatRequest(BaseModel):
+    code: str
+
+
+@router.post("/code/format")
+async def format_code(request: CodeFormatRequest):
+    try:
+        formatted_code = black.format_str(request.code, mode=black.Mode())
+        return {"code": formatted_code}
+    except black.NothingChanged:
+        return {"code": request.code}
+    except Exception as e:
+        raise HTTPException(status_code=400, detail=str(e))
+
+
 class MarkdownForm(BaseModel):
     md: str
 

+ 23 - 0
backend/apps/webui/utils.py

@@ -0,0 +1,23 @@
+from importlib import util
+import os
+
+from config import TOOLS_DIR
+
+
+def load_toolkit_module_by_id(toolkit_id):
+    toolkit_path = os.path.join(TOOLS_DIR, f"{toolkit_id}.py")
+    spec = util.spec_from_file_location(toolkit_id, toolkit_path)
+    module = util.module_from_spec(spec)
+
+    try:
+        spec.loader.exec_module(module)
+        print(f"Loaded module: {module.__name__}")
+        if hasattr(module, "Tools"):
+            return module.Tools()
+        else:
+            raise Exception("No Tools class found")
+    except Exception as e:
+        print(f"Error loading module: {toolkit_id}")
+        # Move the file to the error folder
+        os.rename(toolkit_path, f"{toolkit_path}.error")
+        raise e

+ 23 - 3
backend/config.py

@@ -368,6 +368,14 @@ DOCS_DIR = os.getenv("DOCS_DIR", f"{DATA_DIR}/docs")
 Path(DOCS_DIR).mkdir(parents=True, exist_ok=True)
 
 
+####################################
+# Tools DIR
+####################################
+
+TOOLS_DIR = os.getenv("TOOLS_DIR", f"{DATA_DIR}/tools")
+Path(TOOLS_DIR).mkdir(parents=True, exist_ok=True)
+
+
 ####################################
 # LITELLM_CONFIG
 ####################################
@@ -669,16 +677,28 @@ Question:
     ),
 )
 
-
 SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD = PersistentConfig(
     "SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD",
     "task.search.prompt_length_threshold",
+    int(
+        os.environ.get(
+            "SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD",
+            100,
+        )
+    ),
+)
+
+TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = PersistentConfig(
+    "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE",
+    "task.tools.prompt_template",
     os.environ.get(
-        "SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD",
-        100,
+        "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE",
+        """Tools: {{TOOLS}}
+If a function tool doesn't match the query, return an empty string. Else, pick a function tool, fill in the parameters from the function tool's schema, and return it in the format { "name": \"functionName\", "parameters": { "key": "value" } }. Only pick a function if the user asks.  Only return the object. Do not return any other text.""",
     ),
 )
 
+
 ####################################
 # WEBUI_SECRET_KEY
 ####################################

+ 1 - 0
backend/constants.py

@@ -32,6 +32,7 @@ class ERROR_MESSAGES(str, Enum):
     COMMAND_TAKEN = "Uh-oh! This command is already registered. Please choose another command string."
     FILE_EXISTS = "Uh-oh! This file is already registered. Please choose another file."
 
+    ID_TAKEN = "Uh-oh! This id is already registered. Please choose another id string."
     MODEL_ID_TAKEN = "Uh-oh! This model id is already registered. Please choose another model id string."
 
     NAME_TAG_TAKEN = "Uh-oh! This name tag is already registered. Please choose another name tag string."

+ 221 - 19
backend/main.py

@@ -11,6 +11,7 @@ import requests
 import mimetypes
 import shutil
 import os
+import inspect
 import asyncio
 
 from fastapi import FastAPI, Request, Depends, status, UploadFile, File, Form
@@ -47,15 +48,24 @@ from pydantic import BaseModel
 from typing import List, Optional
 
 from apps.webui.models.models import Models, ModelModel
+from apps.webui.models.tools import Tools
+from apps.webui.utils import load_toolkit_module_by_id
+
+
 from utils.utils import (
     get_admin_user,
     get_verified_user,
     get_current_user,
     get_http_authorization_cred,
 )
-from utils.task import title_generation_template, search_query_generation_template
+from utils.task import (
+    title_generation_template,
+    search_query_generation_template,
+    tools_function_calling_generation_template,
+)
+from utils.misc import get_last_user_message, add_or_update_system_message
 
-from apps.rag.utils import rag_messages
+from apps.rag.utils import get_rag_context, rag_template
 
 from config import (
     CONFIG_DATA,
@@ -82,6 +92,7 @@ from config import (
     TITLE_GENERATION_PROMPT_TEMPLATE,
     SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
     SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD,
+    TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
     AppConfig,
 )
 from constants import ERROR_MESSAGES
@@ -148,24 +159,117 @@ app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE = (
 app.state.config.SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD = (
     SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD
 )
+app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = (
+    TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
+)
 
 app.state.MODELS = {}
 
 origins = ["*"]
 
-# Custom middleware to add security headers
-# class SecurityHeadersMiddleware(BaseHTTPMiddleware):
-#     async def dispatch(self, request: Request, call_next):
-#         response: Response = await call_next(request)
-#         response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
-#         response.headers["Cross-Origin-Embedder-Policy"] = "require-corp"
-#         return response
 
+async def get_function_call_response(messages, tool_id, template, task_model_id, user):
+    tool = Tools.get_tool_by_id(tool_id)
+    tools_specs = json.dumps(tool.specs, indent=2)
+    content = tools_function_calling_generation_template(template, tools_specs)
+
+    user_message = get_last_user_message(messages)
+    prompt = (
+        "History:\n"
+        + "\n".join(
+            [
+                f"{message['role'].upper()}: \"\"\"{message['content']}\"\"\""
+                for message in messages[::-1][:4]
+            ]
+        )
+        + f"\nQuery: {user_message}"
+    )
+
+    print(prompt)
+
+    payload = {
+        "model": task_model_id,
+        "messages": [
+            {"role": "system", "content": content},
+            {"role": "user", "content": f"Query: {prompt}"},
+        ],
+        "stream": False,
+    }
+
+    payload = filter_pipeline(payload, user)
+    model = app.state.MODELS[task_model_id]
+
+    response = None
+    try:
+        if model["owned_by"] == "ollama":
+            response = await generate_ollama_chat_completion(
+                OpenAIChatCompletionForm(**payload), user=user
+            )
+        else:
+            response = await generate_openai_chat_completion(payload, user=user)
+
+        content = None
+
+        if hasattr(response, "body_iterator"):
+            async for chunk in response.body_iterator:
+                data = json.loads(chunk.decode("utf-8"))
+                content = data["choices"][0]["message"]["content"]
+
+            # Cleanup any remaining background tasks if necessary
+            if response.background is not None:
+                await response.background()
+        else:
+            content = response["choices"][0]["message"]["content"]
+
+        # Parse the function response
+        if content is not None:
+            print(f"content: {content}")
+            result = json.loads(content)
+            print(result)
+
+            # Call the function
+            if "name" in result:
+                if tool_id in webui_app.state.TOOLS:
+                    toolkit_module = webui_app.state.TOOLS[tool_id]
+                else:
+                    toolkit_module = load_toolkit_module_by_id(tool_id)
+                    webui_app.state.TOOLS[tool_id] = toolkit_module
+
+                function = getattr(toolkit_module, result["name"])
+                function_result = None
+                try:
+                    # Get the signature of the function
+                    sig = inspect.signature(function)
+                    # Check if '__user__' is a parameter of the function
+                    if "__user__" in sig.parameters:
+                        # Call the function with the '__user__' parameter included
+                        function_result = function(
+                            **{
+                                **result["parameters"],
+                                "__user__": {
+                                    "id": user.id,
+                                    "email": user.email,
+                                    "name": user.name,
+                                    "role": user.role,
+                                },
+                            }
+                        )
+                    else:
+                        # Call the function without modifying the parameters
+                        function_result = function(**result["parameters"])
+                except Exception as e:
+                    print(e)
+
+                # Add the function result to the system prompt
+                if function_result:
+                    return function_result
+    except Exception as e:
+        print(f"Error: {e}")
 
-# app.add_middleware(SecurityHeadersMiddleware)
+    return None
 
 
-class RAGMiddleware(BaseHTTPMiddleware):
+class ChatCompletionMiddleware(BaseHTTPMiddleware):
     async def dispatch(self, request: Request, call_next):
         return_citations = False
 
@@ -182,35 +286,95 @@ class RAGMiddleware(BaseHTTPMiddleware):
             # Parse string to JSON
             data = json.loads(body_str) if body_str else {}
 
+            user = get_current_user(
+                get_http_authorization_cred(request.headers.get("Authorization"))
+            )
+
+            # Remove the citations from the body
             return_citations = data.get("citations", False)
             if "citations" in data:
                 del data["citations"]
 
-            # Example: Add a new key-value pair or modify existing ones
-            # data["modified"] = True  # Example modification
+            # Set the task model
+            task_model_id = data["model"]
+            if task_model_id not in app.state.MODELS:
+                raise HTTPException(
+                    status_code=status.HTTP_404_NOT_FOUND,
+                    detail="Model not found",
+                )
+
+            # Check if the user has a custom task model
+            # If the user has a custom task model, use that model
+            if app.state.MODELS[task_model_id]["owned_by"] == "ollama":
+                if (
+                    app.state.config.TASK_MODEL
+                    and app.state.config.TASK_MODEL in app.state.MODELS
+                ):
+                    task_model_id = app.state.config.TASK_MODEL
+            else:
+                if (
+                    app.state.config.TASK_MODEL_EXTERNAL
+                    and app.state.config.TASK_MODEL_EXTERNAL in app.state.MODELS
+                ):
+                    task_model_id = app.state.config.TASK_MODEL_EXTERNAL
+
+            prompt = get_last_user_message(data["messages"])
+            context = ""
+
+            # If tool_ids field is present, call the functions
+            if "tool_ids" in data:
+                print(data["tool_ids"])
+                for tool_id in data["tool_ids"]:
+                    print(tool_id)
+                    response = await get_function_call_response(
+                        messages=data["messages"],
+                        tool_id=tool_id,
+                        template=app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
+                        task_model_id=task_model_id,
+                        user=user,
+                    )
+
+                    if response:
+                        context += ("\n" if context != "" else "") + response
+                del data["tool_ids"]
+
+                print(f"tool_context: {context}")
+
+            # If docs field is present, generate RAG completions
             if "docs" in data:
                 data = {**data}
-                data["messages"], citations = rag_messages(
+                rag_context, citations = get_rag_context(
                     docs=data["docs"],
                     messages=data["messages"],
-                    template=rag_app.state.config.RAG_TEMPLATE,
                     embedding_function=rag_app.state.EMBEDDING_FUNCTION,
                     k=rag_app.state.config.TOP_K,
                     reranking_function=rag_app.state.sentence_transformer_rf,
                     r=rag_app.state.config.RELEVANCE_THRESHOLD,
                     hybrid_search=rag_app.state.config.ENABLE_RAG_HYBRID_SEARCH,
                 )
+
+                if rag_context:
+                    context += ("\n" if context != "" else "") + rag_context
+
                 del data["docs"]
 
-                log.debug(
-                    f"data['messages']: {data['messages']}, citations: {citations}"
+                log.debug(f"rag_context: {rag_context}, citations: {citations}")
+
+            if context != "":
+                system_prompt = rag_template(
+                    rag_app.state.config.RAG_TEMPLATE, context, prompt
+                )
+
+                print(system_prompt)
+
+                data["messages"] = add_or_update_system_message(
+                    f"\n{system_prompt}", data["messages"]
                 )
 
             modified_body_bytes = json.dumps(data).encode("utf-8")
 
             # Replace the request body with the modified one
             request._body = modified_body_bytes
-
             # Set custom header to ensure content-length matches new body length
             request.headers.__dict__["_list"] = [
                 (b"content-length", str(len(modified_body_bytes)).encode("utf-8")),
@@ -253,7 +417,7 @@ class RAGMiddleware(BaseHTTPMiddleware):
             yield data
 
 
-app.add_middleware(RAGMiddleware)
+app.add_middleware(ChatCompletionMiddleware)
 
 
 def filter_pipeline(payload, user):
@@ -515,6 +679,7 @@ async def get_task_config(user=Depends(get_verified_user)):
         "TITLE_GENERATION_PROMPT_TEMPLATE": app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE,
         "SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE": app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
         "SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD": app.state.config.SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD,
+        "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE": app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
     }
 
 
@@ -524,6 +689,7 @@ class TaskConfigForm(BaseModel):
     TITLE_GENERATION_PROMPT_TEMPLATE: str
     SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE: str
     SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD: int
+    TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE: str
 
 
 @app.post("/api/task/config/update")
@@ -539,6 +705,9 @@ async def update_task_config(form_data: TaskConfigForm, user=Depends(get_admin_u
     app.state.config.SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD = (
         form_data.SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD
     )
+    app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE = (
+        form_data.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
+    )
 
     return {
         "TASK_MODEL": app.state.config.TASK_MODEL,
@@ -546,6 +715,7 @@ async def update_task_config(form_data: TaskConfigForm, user=Depends(get_admin_u
         "TITLE_GENERATION_PROMPT_TEMPLATE": app.state.config.TITLE_GENERATION_PROMPT_TEMPLATE,
         "SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE": app.state.config.SEARCH_QUERY_GENERATION_PROMPT_TEMPLATE,
         "SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD": app.state.config.SEARCH_QUERY_PROMPT_LENGTH_THRESHOLD,
+        "TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE": app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE,
     }
 
 
@@ -659,6 +829,38 @@ async def generate_search_query(form_data: dict, user=Depends(get_verified_user)
         return await generate_openai_chat_completion(payload, user=user)
 
 
+@app.post("/api/task/tools/completions")
+async def get_tools_function_calling(form_data: dict, user=Depends(get_verified_user)):
+    print("get_tools_function_calling")
+
+    model_id = form_data["model"]
+    if model_id not in app.state.MODELS:
+        raise HTTPException(
+            status_code=status.HTTP_404_NOT_FOUND,
+            detail="Model not found",
+        )
+
+    # Check if the user has a custom task model
+    # If the user has a custom task model, use that model
+    if app.state.MODELS[model_id]["owned_by"] == "ollama":
+        if app.state.config.TASK_MODEL:
+            task_model_id = app.state.config.TASK_MODEL
+            if task_model_id in app.state.MODELS:
+                model_id = task_model_id
+    else:
+        if app.state.config.TASK_MODEL_EXTERNAL:
+            task_model_id = app.state.config.TASK_MODEL_EXTERNAL
+            if task_model_id in app.state.MODELS:
+                model_id = task_model_id
+
+    print(model_id)
+    template = app.state.config.TOOLS_FUNCTION_CALLING_PROMPT_TEMPLATE
+
+    return await get_function_call_response(
+        form_data["messages"], form_data["tool_id"], template, model_id, user
+    )
+
+
 @app.post("/api/chat/completions")
 async def generate_chat_completions(form_data: dict, user=Depends(get_verified_user)):
     model_id = form_data["model"]

+ 2 - 1
backend/requirements.txt

@@ -59,4 +59,5 @@ youtube-transcript-api==0.6.2
 pytube==15.0.0
 
 extract_msg
-pydub
+pydub
+duckduckgo-search~=6.1.5

+ 2 - 1
backend/start_windows.bat

@@ -8,6 +8,7 @@ cd /d "%SCRIPT_DIR%" || exit /b
 
 SET "KEY_FILE=.webui_secret_key"
 IF "%PORT%"=="" SET PORT=8080
+IF "%HOST%"=="" SET HOST=0.0.0.0
 SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%"
 SET "WEBUI_JWT_SECRET_KEY=%WEBUI_JWT_SECRET_KEY%"
 
@@ -29,4 +30,4 @@ IF "%WEBUI_SECRET_KEY%%WEBUI_JWT_SECRET_KEY%" == " " (
 
 :: Execute uvicorn
 SET "WEBUI_SECRET_KEY=%WEBUI_SECRET_KEY%"
-uvicorn main:app --host 0.0.0.0 --port "%PORT%" --forwarded-allow-ips '*'
+uvicorn main:app --host "%HOST%" --port "%PORT%" --forwarded-allow-ips '*'

+ 5 - 0
backend/utils/task.py

@@ -110,3 +110,8 @@ def search_query_generation_template(
         ),
     )
     return template
+
+
+def tools_function_calling_generation_template(template: str, tools_specs: str) -> str:
+    template = template.replace("{{TOOLS}}", tools_specs)
+    return template

+ 73 - 0
backend/utils/tools.py

@@ -0,0 +1,73 @@
+import inspect
+from typing import get_type_hints, List, Dict, Any
+
+
+def doc_to_dict(docstring):
+    lines = docstring.split("\n")
+    description = lines[1].strip()
+    param_dict = {}
+
+    for line in lines:
+        if ":param" in line:
+            line = line.replace(":param", "").strip()
+            param, desc = line.split(":", 1)
+            param_dict[param.strip()] = desc.strip()
+    ret_dict = {"description": description, "params": param_dict}
+    return ret_dict
+
+
+def get_tools_specs(tools) -> List[dict]:
+    function_list = [
+        {"name": func, "function": getattr(tools, func)}
+        for func in dir(tools)
+        if callable(getattr(tools, func)) and not func.startswith("__")
+    ]
+
+    specs = []
+    for function_item in function_list:
+        function_name = function_item["name"]
+        function = function_item["function"]
+
+        function_doc = doc_to_dict(function.__doc__ or function_name)
+        specs.append(
+            {
+                "name": function_name,
+                # TODO: multi-line desc?
+                "description": function_doc.get("description", function_name),
+                "parameters": {
+                    "type": "object",
+                    "properties": {
+                        param_name: {
+                            "type": param_annotation.__name__.lower(),
+                            **(
+                                {
+                                    "enum": (
+                                        str(param_annotation.__args__)
+                                        if hasattr(param_annotation, "__args__")
+                                        else None
+                                    )
+                                }
+                                if hasattr(param_annotation, "__args__")
+                                else {}
+                            ),
+                            "description": function_doc.get("params", {}).get(
+                                param_name, param_name
+                            ),
+                        }
+                        for param_name, param_annotation in get_type_hints(
+                            function
+                        ).items()
+                        if param_name != "return" and param_name != "__user__"
+                    },
+                    "required": [
+                        name
+                        for name, param in inspect.signature(
+                            function
+                        ).parameters.items()
+                        if param.default is param.empty
+                    ],
+                },
+            }
+        )
+
+    return specs

+ 0 - 21
cypress/e2e/settings.cy.ts

@@ -28,19 +28,6 @@ describe('Settings', () => {
 		});
 	});
 
-	context('Connections', () => {
-		it('user can open the Connections modal and hit save', () => {
-			cy.get('button').contains('Connections').click();
-			cy.get('button').contains('Save').click();
-		});
-	});
-
-	context('Models', () => {
-		it('user can open the Models modal', () => {
-			cy.get('button').contains('Models').click();
-		});
-	});
-
 	context('Interface', () => {
 		it('user can open the Interface modal and hit save', () => {
 			cy.get('button').contains('Interface').click();
@@ -55,14 +42,6 @@ describe('Settings', () => {
 		});
 	});
 
-	context('Images', () => {
-		it('user can open the Images modal and hit save', () => {
-			cy.get('button').contains('Images').click();
-			// Currently fails because the backend requires a valid URL
-			// cy.get('button').contains('Save').click();
-		});
-	});
-
 	context('Chats', () => {
 		it('user can open the Chats modal', () => {
 			cy.get('button').contains('Chats').click();

+ 196 - 9
package-lock.json

@@ -1,17 +1,21 @@
 {
 	"name": "open-webui",
-	"version": "0.3.2",
+	"version": "0.3.3",
 	"lockfileVersion": 3,
 	"requires": true,
 	"packages": {
 		"": {
 			"name": "open-webui",
-			"version": "0.3.2",
+			"version": "0.3.3",
 			"dependencies": {
+				"@codemirror/lang-javascript": "^6.2.2",
+				"@codemirror/lang-python": "^6.1.6",
+				"@codemirror/theme-one-dark": "^6.1.2",
 				"@pyscript/core": "^0.4.32",
 				"@sveltejs/adapter-node": "^1.3.1",
 				"async": "^3.2.5",
 				"bits-ui": "^0.19.7",
+				"codemirror": "^6.0.1",
 				"dayjs": "^1.11.10",
 				"eventsource-parser": "^1.1.2",
 				"file-saver": "^2.0.5",
@@ -108,6 +112,119 @@
 			"resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz",
 			"integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A=="
 		},
+		"node_modules/@codemirror/autocomplete": {
+			"version": "6.16.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.2.tgz",
+			"integrity": "sha512-MjfDrHy0gHKlPWsvSsikhO1+BOh+eBHNgfH1OXs1+DAf30IonQldgMM3kxLDTG9ktE7kDLaA1j/l7KMPA4KNfw==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.17.0",
+				"@lezer/common": "^1.0.0"
+			},
+			"peerDependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"@lezer/common": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/commands": {
+			"version": "6.6.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.6.0.tgz",
+			"integrity": "sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.4.0",
+				"@codemirror/view": "^6.27.0",
+				"@lezer/common": "^1.1.0"
+			}
+		},
+		"node_modules/@codemirror/lang-javascript": {
+			"version": "6.2.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
+			"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/language": "^6.6.0",
+				"@codemirror/lint": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.17.0",
+				"@lezer/common": "^1.0.0",
+				"@lezer/javascript": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/lang-python": {
+			"version": "6.1.6",
+			"resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.1.6.tgz",
+			"integrity": "sha512-ai+01WfZhWqM92UqjnvorkxosZ2aq2u28kHvr+N3gu012XqY2CThD67JPMHnGceRfXPDBmn1HnyqowdpF57bNg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.3.2",
+				"@codemirror/language": "^6.8.0",
+				"@codemirror/state": "^6.0.0",
+				"@lezer/common": "^1.2.1",
+				"@lezer/python": "^1.1.4"
+			}
+		},
+		"node_modules/@codemirror/language": {
+			"version": "6.10.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.2.tgz",
+			"integrity": "sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==",
+			"dependencies": {
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.23.0",
+				"@lezer/common": "^1.1.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0",
+				"style-mod": "^4.0.0"
+			}
+		},
+		"node_modules/@codemirror/lint": {
+			"version": "6.8.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.0.tgz",
+			"integrity": "sha512-lsFofvaw0lnPRJlQylNsC4IRt/1lI4OD/yYslrSGVndOJfStc58v+8p9dgGiD90ktOfL7OhBWns1ZETYgz0EJA==",
+			"dependencies": {
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"crelt": "^1.0.5"
+			}
+		},
+		"node_modules/@codemirror/search": {
+			"version": "6.5.6",
+			"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
+			"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
+			"dependencies": {
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"crelt": "^1.0.5"
+			}
+		},
+		"node_modules/@codemirror/state": {
+			"version": "6.4.1",
+			"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
+			"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
+		},
+		"node_modules/@codemirror/theme-one-dark": {
+			"version": "6.1.2",
+			"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz",
+			"integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==",
+			"dependencies": {
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0",
+				"@lezer/highlight": "^1.0.0"
+			}
+		},
+		"node_modules/@codemirror/view": {
+			"version": "6.28.0",
+			"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.28.0.tgz",
+			"integrity": "sha512-fo7CelaUDKWIyemw4b+J57cWuRkOu4SWCCPfNDkPvfWkGjM9D5racHQXr4EQeYCD6zEBIBxGCeaKkQo+ysl0gA==",
+			"dependencies": {
+				"@codemirror/state": "^6.4.0",
+				"style-mod": "^4.1.0",
+				"w3c-keyname": "^2.2.4"
+			}
+		},
 		"node_modules/@colors/colors": {
 			"version": "1.5.0",
 			"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@@ -825,6 +942,47 @@
 				"@jridgewell/sourcemap-codec": "^1.4.14"
 			}
 		},
+		"node_modules/@lezer/common": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
+			"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
+		},
+		"node_modules/@lezer/highlight": {
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
+			"integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
+			"dependencies": {
+				"@lezer/common": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/javascript": {
+			"version": "1.4.16",
+			"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.16.tgz",
+			"integrity": "sha512-84UXR3N7s11MPQHWgMnjb9571fr19MmXnr5zTv2XX0gHXXUvW3uPJ8GCjKrfTXmSdfktjRK0ayKklw+A13rk4g==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.1.3",
+				"@lezer/lr": "^1.3.0"
+			}
+		},
+		"node_modules/@lezer/lr": {
+			"version": "1.4.1",
+			"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.1.tgz",
+			"integrity": "sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==",
+			"dependencies": {
+				"@lezer/common": "^1.0.0"
+			}
+		},
+		"node_modules/@lezer/python": {
+			"version": "1.1.14",
+			"resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.14.tgz",
+			"integrity": "sha512-ykDOb2Ti24n76PJsSa4ZoDF0zH12BSw1LGfQXCYJhJyOGiFTfGaX0Du66Ze72R+u/P35U+O6I9m8TFXov1JzsA==",
+			"dependencies": {
+				"@lezer/common": "^1.2.0",
+				"@lezer/highlight": "^1.0.0",
+				"@lezer/lr": "^1.0.0"
+			}
+		},
 		"node_modules/@melt-ui/svelte": {
 			"version": "0.76.0",
 			"resolved": "https://registry.npmjs.org/@melt-ui/svelte/-/svelte-0.76.0.tgz",
@@ -2224,12 +2382,12 @@
 			}
 		},
 		"node_modules/braces": {
-			"version": "3.0.2",
-			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-			"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+			"version": "3.0.3",
+			"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+			"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
 			"dev": true,
 			"dependencies": {
-				"fill-range": "^7.0.1"
+				"fill-range": "^7.1.1"
 			},
 			"engines": {
 				"node": ">=8"
@@ -2769,6 +2927,20 @@
 				"plain-tag": "^0.1.3"
 			}
 		},
+		"node_modules/codemirror": {
+			"version": "6.0.1",
+			"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+			"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+			"dependencies": {
+				"@codemirror/autocomplete": "^6.0.0",
+				"@codemirror/commands": "^6.0.0",
+				"@codemirror/language": "^6.0.0",
+				"@codemirror/lint": "^6.0.0",
+				"@codemirror/search": "^6.0.0",
+				"@codemirror/state": "^6.0.0",
+				"@codemirror/view": "^6.0.0"
+			}
+		},
 		"node_modules/coincident": {
 			"version": "1.2.3",
 			"resolved": "https://registry.npmjs.org/coincident/-/coincident-1.2.3.tgz",
@@ -2891,6 +3063,11 @@
 				"layout-base": "^1.0.0"
 			}
 		},
+		"node_modules/crelt": {
+			"version": "1.0.6",
+			"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+			"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
+		},
 		"node_modules/cross-spawn": {
 			"version": "7.0.3",
 			"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -4429,9 +4606,9 @@
 			"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
 		},
 		"node_modules/fill-range": {
-			"version": "7.0.1",
-			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-			"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+			"version": "7.1.1",
+			"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+			"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
 			"dev": true,
 			"dependencies": {
 				"to-regex-range": "^5.0.1"
@@ -8278,6 +8455,11 @@
 				"url": "https://github.com/sponsors/antfu"
 			}
 		},
+		"node_modules/style-mod": {
+			"version": "4.1.2",
+			"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+			"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
+		},
 		"node_modules/stylis": {
 			"version": "4.3.2",
 			"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
@@ -10022,6 +10204,11 @@
 				"he": "^1.2.0"
 			}
 		},
+		"node_modules/w3c-keyname": {
+			"version": "2.2.8",
+			"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+			"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
+		},
 		"node_modules/walk-sync": {
 			"version": "2.2.0",
 			"resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-2.2.0.tgz",

+ 6 - 2
package.json

@@ -1,6 +1,6 @@
 {
 	"name": "open-webui",
-	"version": "0.3.2",
+	"version": "0.3.3",
 	"private": true,
 	"scripts": {
 		"dev": "npm run pyodide:fetch && vite dev --host",
@@ -16,7 +16,7 @@
 		"format:backend": "black . --exclude \".venv/|/venv/\"",
 		"i18n:parse": "i18next --config i18next-parser.config.ts && prettier --write \"src/lib/i18n/**/*.{js,json}\"",
 		"cy:open": "cypress open",
-		"test:frontend": "vitest",
+		"test:frontend": "vitest --passWithNoTests",
 		"pyodide:fetch": "node scripts/prepare-pyodide.js"
 	},
 	"devDependencies": {
@@ -48,10 +48,14 @@
 	},
 	"type": "module",
 	"dependencies": {
+		"@codemirror/lang-javascript": "^6.2.2",
+		"@codemirror/lang-python": "^6.1.6",
+		"@codemirror/theme-one-dark": "^6.1.2",
 		"@pyscript/core": "^0.4.32",
 		"@sveltejs/adapter-node": "^1.3.1",
 		"async": "^3.2.5",
 		"bits-ui": "^0.19.7",
+		"codemirror": "^6.0.1",
 		"dayjs": "^1.11.10",
 		"eventsource-parser": "^1.1.2",
 		"file-saver": "^2.0.5",

+ 4 - 2
pyproject.toml

@@ -26,8 +26,6 @@ dependencies = [
     "PyMySQL==1.1.0",
     "bcrypt==4.1.3",
 
-    "litellm[proxy]==1.37.20",
-
     "boto3==1.34.110",
 
     "argon2-cffi==23.1.0",
@@ -66,6 +64,10 @@ dependencies = [
     "langfuse==2.33.0",
     "youtube-transcript-api==0.6.2",
     "pytube==15.0.0",
+    "extract_msg",
+    "pydub",
+    "duckduckgo-search~=6.1.5"
+
 ]
 readme = "README.md"
 requires-python = ">= 3.11, < 3.12.0a1"

+ 40 - 48
requirements-dev.lock

@@ -12,7 +12,6 @@
 aiohttp==3.9.5
     # via langchain
     # via langchain-community
-    # via litellm
     # via open-webui
 aiosignal==1.3.1
     # via aiohttp
@@ -20,11 +19,9 @@ annotated-types==0.6.0
     # via pydantic
 anyio==4.3.0
     # via httpx
-    # via openai
     # via starlette
     # via watchfiles
 apscheduler==3.10.4
-    # via litellm
     # via open-webui
 argon2-cffi==23.1.0
     # via open-webui
@@ -38,7 +35,6 @@ av==11.0.0
     # via faster-whisper
 backoff==2.2.1
     # via langfuse
-    # via litellm
     # via posthog
     # via unstructured
 bcrypt==4.1.3
@@ -46,6 +42,7 @@ bcrypt==4.1.3
     # via open-webui
     # via passlib
 beautifulsoup4==4.12.3
+    # via extract-msg
     # via unstructured
 bidict==0.23.1
     # via python-socketio
@@ -83,17 +80,20 @@ chromadb==0.5.0
     # via open-webui
 click==8.1.7
     # via black
+    # via duckduckgo-search
     # via flask
-    # via litellm
     # via nltk
     # via peewee-migrate
-    # via rq
     # via typer
     # via uvicorn
+colorclass==2.2.2
+    # via oletools
 coloredlogs==15.0.1
     # via onnxruntime
+compressed-rtf==1.0.6
+    # via extract-msg
 cryptography==42.0.7
-    # via litellm
+    # via msoffcrypto-tool
     # via pyjwt
 ctranslate2==4.2.1
     # via faster-whisper
@@ -109,33 +109,34 @@ defusedxml==0.7.1
 deprecated==1.2.14
     # via opentelemetry-api
     # via opentelemetry-exporter-otlp-proto-grpc
-distro==1.9.0
-    # via openai
 dnspython==2.6.1
     # via email-validator
 docx2txt==0.8
     # via open-webui
+duckduckgo-search==6.1.5
+    # via open-webui
+easygui==0.98.3
+    # via oletools
+ebcdic==1.1.1
+    # via extract-msg
 ecdsa==0.19.0
     # via python-jose
 email-validator==2.1.1
     # via fastapi
-    # via pydantic
 emoji==2.11.1
     # via unstructured
 et-xmlfile==1.1.0
     # via openpyxl
+extract-msg==0.48.5
+    # via open-webui
 fake-useragent==1.5.1
     # via open-webui
 fastapi==0.111.0
     # via chromadb
-    # via fastapi-sso
     # via langchain-chroma
-    # via litellm
     # via open-webui
 fastapi-cli==0.0.4
     # via fastapi
-fastapi-sso==0.10.0
-    # via litellm
 faster-whisper==1.0.2
     # via open-webui
 filelock==3.14.0
@@ -191,8 +192,6 @@ grpcio==1.63.0
     # via opentelemetry-exporter-otlp-proto-grpc
 grpcio-status==1.62.2
     # via google-api-core
-gunicorn==22.0.0
-    # via litellm
 h11==0.14.0
     # via httpcore
     # via uvicorn
@@ -206,9 +205,7 @@ httptools==0.6.1
     # via uvicorn
 httpx==0.27.0
     # via fastapi
-    # via fastapi-sso
     # via langfuse
-    # via openai
 huggingface-hub==0.23.0
     # via faster-whisper
     # via sentence-transformers
@@ -225,7 +222,6 @@ idna==3.7
     # via unstructured-client
     # via yarl
 importlib-metadata==7.0.0
-    # via litellm
     # via opentelemetry-api
 importlib-resources==6.4.0
     # via chromadb
@@ -234,7 +230,6 @@ itsdangerous==2.2.0
 jinja2==3.1.4
     # via fastapi
     # via flask
-    # via litellm
     # via torch
 jmespath==1.0.1
     # via boto3
@@ -272,8 +267,8 @@ langsmith==0.1.57
     # via langchain
     # via langchain-community
     # via langchain-core
-litellm==1.37.20
-    # via open-webui
+lark==1.1.8
+    # via rtfde
 lxml==5.2.2
     # via unstructured
 markdown==3.6
@@ -294,6 +289,8 @@ monotonic==1.6
     # via posthog
 mpmath==1.3.0
     # via sympy
+msoffcrypto-tool==5.4.1
+    # via oletools
 multidict==6.0.5
     # via aiohttp
     # via yarl
@@ -325,15 +322,19 @@ numpy==1.26.4
     # via transformers
     # via unstructured
 oauthlib==3.2.2
-    # via fastapi-sso
     # via kubernetes
     # via requests-oauthlib
+olefile==0.47
+    # via extract-msg
+    # via msoffcrypto-tool
+    # via oletools
+oletools==0.60.1
+    # via pcodedmp
+    # via rtfde
 onnxruntime==1.17.3
     # via chromadb
     # via faster-whisper
     # via rapidocr-onnxruntime
-openai==1.28.1
-    # via litellm
 opencv-python==4.9.0.80
     # via rapidocr-onnxruntime
 opencv-python-headless==4.9.0.80
@@ -375,15 +376,14 @@ ordered-set==4.1.0
     # via deepdiff
 orjson==3.10.3
     # via chromadb
+    # via duckduckgo-search
     # via fastapi
     # via langsmith
-    # via litellm
 overrides==7.7.0
     # via chromadb
 packaging==23.2
     # via black
     # via build
-    # via gunicorn
     # via huggingface-hub
     # via langchain-core
     # via langfuse
@@ -397,6 +397,8 @@ passlib==1.7.4
     # via open-webui
 pathspec==0.12.1
     # via black
+pcodedmp==1.2.6
+    # via oletools
 peewee==3.17.5
     # via open-webui
     # via peewee-migrate
@@ -437,27 +439,27 @@ pycparser==2.22
 pydantic==2.7.1
     # via chromadb
     # via fastapi
-    # via fastapi-sso
     # via google-generativeai
     # via langchain
     # via langchain-core
     # via langfuse
     # via langsmith
     # via open-webui
-    # via openai
 pydantic-core==2.18.2
     # via pydantic
+pydub==0.25.1
+    # via open-webui
 pygments==2.18.0
     # via rich
 pyjwt==2.8.0
-    # via litellm
     # via open-webui
 pymysql==1.1.0
     # via open-webui
 pypandoc==1.13
     # via open-webui
-pyparsing==3.1.2
+pyparsing==2.4.7
     # via httplib2
+    # via oletools
 pypdf==4.2.0
     # via open-webui
     # via unstructured-client
@@ -465,6 +467,8 @@ pypika==0.48.9
     # via chromadb
 pyproject-hooks==1.1.0
     # via build
+pyreqwest-impersonate==0.4.7
+    # via duckduckgo-search
 python-dateutil==2.9.0.post0
     # via botocore
     # via kubernetes
@@ -472,7 +476,6 @@ python-dateutil==2.9.0.post0
     # via posthog
     # via unstructured-client
 python-dotenv==1.0.1
-    # via litellm
     # via uvicorn
 python-engineio==4.9.0
     # via python-socketio
@@ -484,7 +487,6 @@ python-magic==0.4.27
     # via unstructured
 python-multipart==0.0.9
     # via fastapi
-    # via litellm
     # via open-webui
 python-socketio==5.11.2
     # via open-webui
@@ -503,7 +505,6 @@ pyyaml==6.0.1
     # via langchain
     # via langchain-community
     # via langchain-core
-    # via litellm
     # via rapidocr-onnxruntime
     # via transformers
     # via uvicorn
@@ -513,11 +514,10 @@ rapidfuzz==3.9.0
     # via unstructured
 rapidocr-onnxruntime==1.3.22
     # via open-webui
-redis==5.0.4
-    # via rq
+red-black-tree-mod==1.20
+    # via extract-msg
 regex==2024.5.10
     # via nltk
-    # via tiktoken
     # via transformers
 requests==2.32.2
     # via chromadb
@@ -527,11 +527,9 @@ requests==2.32.2
     # via langchain
     # via langchain-community
     # via langsmith
-    # via litellm
     # via open-webui
     # via posthog
     # via requests-oauthlib
-    # via tiktoken
     # via transformers
     # via unstructured
     # via unstructured-client
@@ -540,11 +538,11 @@ requests-oauthlib==2.0.0
     # via kubernetes
 rich==13.7.1
     # via typer
-rq==1.16.2
-    # via litellm
 rsa==4.9
     # via google-auth
     # via python-jose
+rtfde==0.1.1
+    # via extract-msg
 s3transfer==0.10.1
     # via boto3
 safetensors==0.4.3
@@ -577,7 +575,6 @@ six==1.16.0
 sniffio==1.3.1
     # via anyio
     # via httpx
-    # via openai
 soupsieve==2.5
     # via beautifulsoup4
 sqlalchemy==2.0.30
@@ -597,12 +594,9 @@ tenacity==8.3.0
     # via langchain-core
 threadpoolctl==3.5.0
     # via scikit-learn
-tiktoken==0.6.0
-    # via litellm
 tokenizers==0.15.2
     # via chromadb
     # via faster-whisper
-    # via litellm
     # via transformers
 torch==2.3.0
     # via sentence-transformers
@@ -611,7 +605,6 @@ tqdm==4.66.4
     # via google-generativeai
     # via huggingface-hub
     # via nltk
-    # via openai
     # via sentence-transformers
     # via transformers
 transformers==4.39.3
@@ -624,7 +617,6 @@ typing-extensions==4.11.0
     # via fastapi
     # via google-generativeai
     # via huggingface-hub
-    # via openai
     # via opentelemetry-sdk
     # via pydantic
     # via pydantic-core
@@ -641,6 +633,7 @@ tzdata==2024.1
     # via pandas
 tzlocal==5.2
     # via apscheduler
+    # via extract-msg
 ujson==5.10.0
     # via fastapi
 unstructured==0.14.0
@@ -657,7 +650,6 @@ urllib3==2.2.1
 uvicorn==0.22.0
     # via chromadb
     # via fastapi
-    # via litellm
     # via open-webui
 uvloop==0.19.0
     # via uvicorn

+ 40 - 48
requirements.lock

@@ -12,7 +12,6 @@
 aiohttp==3.9.5
     # via langchain
     # via langchain-community
-    # via litellm
     # via open-webui
 aiosignal==1.3.1
     # via aiohttp
@@ -20,11 +19,9 @@ annotated-types==0.6.0
     # via pydantic
 anyio==4.3.0
     # via httpx
-    # via openai
     # via starlette
     # via watchfiles
 apscheduler==3.10.4
-    # via litellm
     # via open-webui
 argon2-cffi==23.1.0
     # via open-webui
@@ -38,7 +35,6 @@ av==11.0.0
     # via faster-whisper
 backoff==2.2.1
     # via langfuse
-    # via litellm
     # via posthog
     # via unstructured
 bcrypt==4.1.3
@@ -46,6 +42,7 @@ bcrypt==4.1.3
     # via open-webui
     # via passlib
 beautifulsoup4==4.12.3
+    # via extract-msg
     # via unstructured
 bidict==0.23.1
     # via python-socketio
@@ -83,17 +80,20 @@ chromadb==0.5.0
     # via open-webui
 click==8.1.7
     # via black
+    # via duckduckgo-search
     # via flask
-    # via litellm
     # via nltk
     # via peewee-migrate
-    # via rq
     # via typer
     # via uvicorn
+colorclass==2.2.2
+    # via oletools
 coloredlogs==15.0.1
     # via onnxruntime
+compressed-rtf==1.0.6
+    # via extract-msg
 cryptography==42.0.7
-    # via litellm
+    # via msoffcrypto-tool
     # via pyjwt
 ctranslate2==4.2.1
     # via faster-whisper
@@ -109,33 +109,34 @@ defusedxml==0.7.1
 deprecated==1.2.14
     # via opentelemetry-api
     # via opentelemetry-exporter-otlp-proto-grpc
-distro==1.9.0
-    # via openai
 dnspython==2.6.1
     # via email-validator
 docx2txt==0.8
     # via open-webui
+duckduckgo-search==6.1.5
+    # via open-webui
+easygui==0.98.3
+    # via oletools
+ebcdic==1.1.1
+    # via extract-msg
 ecdsa==0.19.0
     # via python-jose
 email-validator==2.1.1
     # via fastapi
-    # via pydantic
 emoji==2.11.1
     # via unstructured
 et-xmlfile==1.1.0
     # via openpyxl
+extract-msg==0.48.5
+    # via open-webui
 fake-useragent==1.5.1
     # via open-webui
 fastapi==0.111.0
     # via chromadb
-    # via fastapi-sso
     # via langchain-chroma
-    # via litellm
     # via open-webui
 fastapi-cli==0.0.4
     # via fastapi
-fastapi-sso==0.10.0
-    # via litellm
 faster-whisper==1.0.2
     # via open-webui
 filelock==3.14.0
@@ -191,8 +192,6 @@ grpcio==1.63.0
     # via opentelemetry-exporter-otlp-proto-grpc
 grpcio-status==1.62.2
     # via google-api-core
-gunicorn==22.0.0
-    # via litellm
 h11==0.14.0
     # via httpcore
     # via uvicorn
@@ -206,9 +205,7 @@ httptools==0.6.1
     # via uvicorn
 httpx==0.27.0
     # via fastapi
-    # via fastapi-sso
     # via langfuse
-    # via openai
 huggingface-hub==0.23.0
     # via faster-whisper
     # via sentence-transformers
@@ -225,7 +222,6 @@ idna==3.7
     # via unstructured-client
     # via yarl
 importlib-metadata==7.0.0
-    # via litellm
     # via opentelemetry-api
 importlib-resources==6.4.0
     # via chromadb
@@ -234,7 +230,6 @@ itsdangerous==2.2.0
 jinja2==3.1.4
     # via fastapi
     # via flask
-    # via litellm
     # via torch
 jmespath==1.0.1
     # via boto3
@@ -272,8 +267,8 @@ langsmith==0.1.57
     # via langchain
     # via langchain-community
     # via langchain-core
-litellm==1.37.20
-    # via open-webui
+lark==1.1.8
+    # via rtfde
 lxml==5.2.2
     # via unstructured
 markdown==3.6
@@ -294,6 +289,8 @@ monotonic==1.6
     # via posthog
 mpmath==1.3.0
     # via sympy
+msoffcrypto-tool==5.4.1
+    # via oletools
 multidict==6.0.5
     # via aiohttp
     # via yarl
@@ -325,15 +322,19 @@ numpy==1.26.4
     # via transformers
     # via unstructured
 oauthlib==3.2.2
-    # via fastapi-sso
     # via kubernetes
     # via requests-oauthlib
+olefile==0.47
+    # via extract-msg
+    # via msoffcrypto-tool
+    # via oletools
+oletools==0.60.1
+    # via pcodedmp
+    # via rtfde
 onnxruntime==1.17.3
     # via chromadb
     # via faster-whisper
     # via rapidocr-onnxruntime
-openai==1.28.1
-    # via litellm
 opencv-python==4.9.0.80
     # via rapidocr-onnxruntime
 opencv-python-headless==4.9.0.80
@@ -375,15 +376,14 @@ ordered-set==4.1.0
     # via deepdiff
 orjson==3.10.3
     # via chromadb
+    # via duckduckgo-search
     # via fastapi
     # via langsmith
-    # via litellm
 overrides==7.7.0
     # via chromadb
 packaging==23.2
     # via black
     # via build
-    # via gunicorn
     # via huggingface-hub
     # via langchain-core
     # via langfuse
@@ -397,6 +397,8 @@ passlib==1.7.4
     # via open-webui
 pathspec==0.12.1
     # via black
+pcodedmp==1.2.6
+    # via oletools
 peewee==3.17.5
     # via open-webui
     # via peewee-migrate
@@ -437,27 +439,27 @@ pycparser==2.22
 pydantic==2.7.1
     # via chromadb
     # via fastapi
-    # via fastapi-sso
     # via google-generativeai
     # via langchain
     # via langchain-core
     # via langfuse
     # via langsmith
     # via open-webui
-    # via openai
 pydantic-core==2.18.2
     # via pydantic
+pydub==0.25.1
+    # via open-webui
 pygments==2.18.0
     # via rich
 pyjwt==2.8.0
-    # via litellm
     # via open-webui
 pymysql==1.1.0
     # via open-webui
 pypandoc==1.13
     # via open-webui
-pyparsing==3.1.2
+pyparsing==2.4.7
     # via httplib2
+    # via oletools
 pypdf==4.2.0
     # via open-webui
     # via unstructured-client
@@ -465,6 +467,8 @@ pypika==0.48.9
     # via chromadb
 pyproject-hooks==1.1.0
     # via build
+pyreqwest-impersonate==0.4.7
+    # via duckduckgo-search
 python-dateutil==2.9.0.post0
     # via botocore
     # via kubernetes
@@ -472,7 +476,6 @@ python-dateutil==2.9.0.post0
     # via posthog
     # via unstructured-client
 python-dotenv==1.0.1
-    # via litellm
     # via uvicorn
 python-engineio==4.9.0
     # via python-socketio
@@ -484,7 +487,6 @@ python-magic==0.4.27
     # via unstructured
 python-multipart==0.0.9
     # via fastapi
-    # via litellm
     # via open-webui
 python-socketio==5.11.2
     # via open-webui
@@ -503,7 +505,6 @@ pyyaml==6.0.1
     # via langchain
     # via langchain-community
     # via langchain-core
-    # via litellm
     # via rapidocr-onnxruntime
     # via transformers
     # via uvicorn
@@ -513,11 +514,10 @@ rapidfuzz==3.9.0
     # via unstructured
 rapidocr-onnxruntime==1.3.22
     # via open-webui
-redis==5.0.4
-    # via rq
+red-black-tree-mod==1.20
+    # via extract-msg
 regex==2024.5.10
     # via nltk
-    # via tiktoken
     # via transformers
 requests==2.32.2
     # via chromadb
@@ -527,11 +527,9 @@ requests==2.32.2
     # via langchain
     # via langchain-community
     # via langsmith
-    # via litellm
     # via open-webui
     # via posthog
     # via requests-oauthlib
-    # via tiktoken
     # via transformers
     # via unstructured
     # via unstructured-client
@@ -540,11 +538,11 @@ requests-oauthlib==2.0.0
     # via kubernetes
 rich==13.7.1
     # via typer
-rq==1.16.2
-    # via litellm
 rsa==4.9
     # via google-auth
     # via python-jose
+rtfde==0.1.1
+    # via extract-msg
 s3transfer==0.10.1
     # via boto3
 safetensors==0.4.3
@@ -577,7 +575,6 @@ six==1.16.0
 sniffio==1.3.1
     # via anyio
     # via httpx
-    # via openai
 soupsieve==2.5
     # via beautifulsoup4
 sqlalchemy==2.0.30
@@ -597,12 +594,9 @@ tenacity==8.3.0
     # via langchain-core
 threadpoolctl==3.5.0
     # via scikit-learn
-tiktoken==0.6.0
-    # via litellm
 tokenizers==0.15.2
     # via chromadb
     # via faster-whisper
-    # via litellm
     # via transformers
 torch==2.3.0
     # via sentence-transformers
@@ -611,7 +605,6 @@ tqdm==4.66.4
     # via google-generativeai
     # via huggingface-hub
     # via nltk
-    # via openai
     # via sentence-transformers
     # via transformers
 transformers==4.39.3
@@ -624,7 +617,6 @@ typing-extensions==4.11.0
     # via fastapi
     # via google-generativeai
     # via huggingface-hub
-    # via openai
     # via opentelemetry-sdk
     # via pydantic
     # via pydantic-core
@@ -641,6 +633,7 @@ tzdata==2024.1
     # via pandas
 tzlocal==5.2
     # via apscheduler
+    # via extract-msg
 ujson==5.10.0
     # via fastapi
 unstructured==0.14.0
@@ -657,7 +650,6 @@ urllib3==2.2.1
 uvicorn==0.22.0
     # via chromadb
     # via fastapi
-    # via litellm
     # via open-webui
 uvloop==0.19.0
     # via uvicorn

+ 21 - 0
src/app.css

@@ -92,10 +92,18 @@ select {
 	visibility: hidden;
 }
 
+.scrollbar-hidden::-webkit-scrollbar-corner {
+	display: none;
+}
+
 .scrollbar-none::-webkit-scrollbar {
 	display: none; /* for Chrome, Safari and Opera */
 }
 
+.scrollbar-none::-webkit-scrollbar-corner {
+	display: none;
+}
+
 .scrollbar-none {
 	-ms-overflow-style: none; /* IE and Edge */
 	scrollbar-width: none; /* Firefox */
@@ -111,3 +119,16 @@ input::-webkit-inner-spin-button {
 input[type='number'] {
 	-moz-appearance: textfield; /* Firefox */
 }
+
+.cm-editor {
+	height: 100%;
+	width: 100%;
+}
+
+.cm-scroller {
+	@apply scrollbar-hidden;
+}
+
+.cm-editor.cm-focused {
+	outline: none;
+}

+ 97 - 2
src/app.html

@@ -32,6 +32,9 @@
 				} else if (localStorage.theme && localStorage.theme === 'system') {
 					systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
 					document.documentElement.classList.add(systemTheme ? 'dark' : 'light');
+				} else if (localStorage.theme && localStorage.theme === 'her') {
+					document.documentElement.classList.add('dark');
+					document.documentElement.classList.add('her');
 				} else {
 					document.documentElement.classList.add('dark');
 				}
@@ -68,17 +71,67 @@
 			</style>
 
 			<img
+				id="logo"
 				style="
 					position: absolute;
 					width: 6rem;
 					height: 6rem;
-					top: 46%;
+					top: 41%;
 					left: 50%;
-					margin: -40px 0 0 -40px;
+					margin-left: -3rem;
 				"
 				src="/logo.svg"
 			/>
 
+			<img
+				id="logo-her"
+				style="
+					position: absolute;
+					width: 13rem;
+					height: 13rem;
+					top: 33%;
+					left: 50%;
+					margin-left: -6.5rem;
+				"
+				src="/logo.svg"
+				class="animate-pulse-fast"
+			/>
+
+			<div
+				id="progress-background"
+				style="
+					position: absolute;
+					top: 58%;
+					left: 50%;
+
+					margin-left: -12rem;
+
+					width: 24rem;
+					height: 0.75rem;
+					border-radius: 9999px;
+					background-color: #fafafa9a;
+				"
+				class="bg-white"
+			></div>
+
+			<div
+				id="progress-bar"
+				style="
+					position: absolute;
+					top: 58%;
+					left: 50%;
+
+					margin-left: -12rem;
+
+					height: 0.75rem;
+					border-radius: 9999px;
+					background-color: #fff;
+
+					width: 0rem;
+				"
+				class="bg-white"
+			></div>
+
 			<!-- <span style="position: absolute; bottom: 32px; left: 50%; margin: -36px 0 0 -36px">
 				Footer content
 			</span> -->
@@ -101,4 +154,46 @@
 	html.dark #splash-screen img {
 		filter: invert(1);
 	}
+
+	html.her #splash-screen {
+		background: #983724;
+	}
+
+	#logo-her {
+		display: none;
+	}
+
+	#progress-background {
+		display: none;
+	}
+
+	#progress-bar {
+		display: none;
+	}
+
+	html.her #logo {
+		display: none;
+	}
+
+	html.her #logo-her {
+		display: block;
+		filter: invert(1);
+	}
+
+	html.her #progress-background {
+		display: block;
+	}
+
+	html.her #progress-bar {
+		display: block;
+	}
+
+	@keyframes pulse {
+		50% {
+			opacity: 0.65;
+		}
+	}
+	.animate-pulse-fast {
+		animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+	}
 </style>

+ 193 - 0
src/lib/apis/tools/index.ts

@@ -0,0 +1,193 @@
+import { WEBUI_API_BASE_URL } from '$lib/constants';
+
+export const createNewTool = async (token: string, tool: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/create`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...tool
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getTools = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const exportTools = async (token: string = '') => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/export`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const getToolById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}`, {
+		method: 'GET',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const updateToolById = async (token: string, id: string, tool: object) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/update`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		},
+		body: JSON.stringify({
+			...tool
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
+export const deleteToolById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/tools/id/${id}/delete`, {
+		method: 'DELETE',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};

+ 33 - 0
src/lib/apis/utils/index.ts

@@ -22,6 +22,39 @@ export const getGravatarUrl = async (email: string) => {
 	return res;
 };
 
+export const formatPythonCode = async (code: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/utils/code/format`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json'
+		},
+		body: JSON.stringify({
+			code: code
+		})
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.catch((err) => {
+			console.log(err);
+
+			error = err;
+			if (err.detail) {
+				error = err.detail;
+			}
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
 export const downloadChatAsPDF = async (chat: object) => {
 	let error = null;
 

+ 9 - 1
src/lib/components/admin/Settings/WebSearch.svelte

@@ -11,7 +11,15 @@
 	export let saveHandler: Function;
 
 	let webConfig = null;
-	let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper', 'serply'];
+	let webSearchEngines = [
+		'searxng',
+		'google_pse',
+		'brave',
+		'serpstack',
+		'serper',
+		'serply',
+		'duckduckgo'
+	];
 
 	let youtubeLanguage = 'en';
 	let youtubeTranslation = null;

+ 16 - 1
src/lib/components/chat/Chat.svelte

@@ -24,7 +24,8 @@
 		banners,
 		user,
 		socket,
-		showCallOverlay
+		showCallOverlay,
+		tools
 	} from '$lib/stores';
 	import {
 		convertMessagesToHistory,
@@ -73,6 +74,10 @@
 	let selectedModels = [''];
 	let atSelectedModel: Model | undefined;
 
+	let selectedModelIds = [];
+	$: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels;
+
+	let selectedToolIds = [];
 	let webSearchEnabled = false;
 
 	let chat = null;
@@ -687,6 +692,7 @@
 			},
 			format: $settings.requestFormat ?? undefined,
 			keep_alive: $settings.keepAlive ?? undefined,
+			tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
 			docs: docs.length > 0 ? docs : undefined,
 			citations: docs.length > 0,
 			chat_id: $chatId
@@ -948,6 +954,7 @@
 					top_p: $settings?.params?.top_p ?? undefined,
 					frequency_penalty: $settings?.params?.frequency_penalty ?? undefined,
 					max_tokens: $settings?.params?.max_tokens ?? undefined,
+					tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
 					docs: docs.length > 0 ? docs : undefined,
 					citations: docs.length > 0,
 					chat_id: $chatId
@@ -1274,8 +1281,16 @@
 				bind:files
 				bind:prompt
 				bind:autoScroll
+				bind:selectedToolIds
 				bind:webSearchEnabled
 				bind:atSelectedModel
+				availableToolIds={selectedModelIds.reduce((a, e, i, arr) => {
+					const model = $models.find((m) => m.id === e);
+					if (model?.info?.meta?.toolIds ?? false) {
+						return [...new Set([...a, ...model.info.meta.toolIds])];
+					}
+					return a;
+				}, [])}
 				{selectedModels}
 				{messages}
 				{submitPrompt}

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

@@ -8,7 +8,9 @@
 		showSidebar,
 		models,
 		config,
-		showCallOverlay
+		showCallOverlay,
+		tools,
+		user as _user
 	} from '$lib/stores';
 	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
 
@@ -58,6 +60,8 @@
 
 	export let files = [];
 
+	export let availableToolIds = [];
+	export let selectedToolIds = [];
 	export let webSearchEnabled = false;
 
 	export let prompt = '';
@@ -653,6 +657,17 @@
 								<div class=" ml-0.5 self-end mb-1.5 flex space-x-1">
 									<InputMenu
 										bind:webSearchEnabled
+										bind:selectedToolIds
+										tools={$tools.reduce((a, e, i, arr) => {
+											if (availableToolIds.includes(e.id) || ($_user?.role ?? 'user') === 'admin') {
+												a[e.id] = {
+													name: e.name,
+													description: e.meta.description,
+													enabled: false
+												};
+											}
+											return a;
+										}, {})}
 										uploadFilesHandler={() => {
 											filesInputElement.click();
 										}}

+ 45 - 8
src/lib/components/chat/MessageInput/InputMenu.svelte

@@ -4,24 +4,33 @@
 	import { getContext } from 'svelte';
 
 	import Dropdown from '$lib/components/common/Dropdown.svelte';
-	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
-	import Pencil from '$lib/components/icons/Pencil.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
-	import Tags from '$lib/components/chat/Tags.svelte';
-	import Share from '$lib/components/icons/Share.svelte';
-	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
 	import DocumentArrowUpSolid from '$lib/components/icons/DocumentArrowUpSolid.svelte';
 	import Switch from '$lib/components/common/Switch.svelte';
 	import GlobeAltSolid from '$lib/components/icons/GlobeAltSolid.svelte';
 	import { config } from '$lib/stores';
+	import WrenchSolid from '$lib/components/icons/WrenchSolid.svelte';
 
 	const i18n = getContext('i18n');
 
 	export let uploadFilesHandler: Function;
+
+	export let selectedToolIds: string[] = [];
 	export let webSearchEnabled: boolean;
 
+	export let tools = {};
 	export let onClose: Function;
 
+	$: tools = Object.fromEntries(
+		Object.keys(tools).map((toolId) => [
+			toolId,
+			{
+				...tools[toolId],
+				enabled: selectedToolIds.includes(toolId)
+			}
+		])
+	);
+
 	let show = false;
 </script>
 
@@ -39,20 +48,48 @@
 
 	<div slot="content">
 		<DropdownMenu.Content
-			class="w-full max-w-[190px] rounded-xl px-1 py-1  border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			class="w-full max-w-[200px] rounded-xl px-1 py-1  border-gray-300/30 dark:border-gray-700/50 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
 			sideOffset={15}
 			alignOffset={-8}
 			side="top"
 			align="start"
 			transition={flyAndScale}
 		>
+			{#if Object.keys(tools).length > 0}
+				<div class="  max-h-28 overflow-y-auto scrollbar-hidden">
+					{#each Object.keys(tools) as toolId}
+						<div
+							class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
+						>
+							<div class="flex-1 flex items-center gap-2">
+								<WrenchSolid />
+								<Tooltip content={tools[toolId]?.description ?? ''} className="flex-1">
+									<div class=" line-clamp-1">{tools[toolId].name}</div>
+								</Tooltip>
+							</div>
+
+							<Switch
+								bind:state={tools[toolId].enabled}
+								on:change={(e) => {
+									selectedToolIds = e.detail
+										? [...selectedToolIds, toolId]
+										: selectedToolIds.filter((id) => id !== toolId);
+								}}
+							/>
+						</div>
+					{/each}
+				</div>
+
+				<hr class="border-gray-100 dark:border-gray-800 my-1" />
+			{/if}
+
 			{#if $config?.features?.enable_web_search}
 				<div
 					class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer rounded-xl"
 				>
 					<div class="flex-1 flex items-center gap-2">
 						<GlobeAltSolid />
-						<div class="flex items-center">{$i18n.t('Web Search')}</div>
+						<div class=" line-clamp-1">{$i18n.t('Web Search')}</div>
 					</div>
 
 					<Switch bind:state={webSearchEnabled} />
@@ -68,7 +105,7 @@
 				}}
 			>
 				<DocumentArrowUpSolid />
-				<div class="flex items-center">{$i18n.t('Upload Files')}</div>
+				<div class=" line-clamp-1">{$i18n.t('Upload Files')}</div>
 			</DropdownMenu.Item>
 		</DropdownMenu.Content>
 	</div>

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

@@ -29,7 +29,7 @@
 </script>
 
 {#key mounted}
-	<div class="m-auto w-full max-w-6xl px-8 lg:px-24 pb-16">
+	<div class="m-auto w-full max-w-6xl px-8 lg:px-24 pb-10">
 		<div class="flex justify-start">
 			<div class="flex -space-x-4 mb-1" in:fade={{ duration: 200 }}>
 				{#each models as model, modelIdx}
@@ -85,7 +85,7 @@
 							</div>
 						{/if}
 					{:else}
-						<div class=" font-medium text-gray-400 dark:text-gray-500">
+						<div class=" font-medium text-gray-400 dark:text-gray-500 line-clamp-1">
 							{$i18n.t('How can I help you today?')}
 						</div>
 					{/if}

+ 1 - 0
src/lib/components/chat/ModelSelector/Selector.svelte

@@ -152,6 +152,7 @@
 
 					toast.error(error);
 					// opts.callback({ success: false, error, modelName: opts.modelName });
+					break;
 				}
 			}
 

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

@@ -140,6 +140,7 @@
 						<option value="dark">🌑 {$i18n.t('Dark')}</option>
 						<option value="oled-dark">🌃 {$i18n.t('OLED Dark')}</option>
 						<option value="light">☀️ {$i18n.t('Light')}</option>
+						<option value="her">🌷 Her</option>
 						<!-- <option value="rose-pine dark">🪻 {$i18n.t('Rosé Pine')}</option>
 						<option value="rose-pine-dawn light">🌷 {$i18n.t('Rosé Pine Dawn')}</option> -->
 					</select>

+ 135 - 0
src/lib/components/common/CodeEditor.svelte

@@ -0,0 +1,135 @@
+<script lang="ts">
+	import { basicSetup, EditorView } from 'codemirror';
+	import { keymap, placeholder } from '@codemirror/view';
+	import { Compartment, EditorState } from '@codemirror/state';
+
+	import { acceptCompletion } from '@codemirror/autocomplete';
+	import { indentWithTab } from '@codemirror/commands';
+
+	import { indentUnit } from '@codemirror/language';
+	import { python } from '@codemirror/lang-python';
+	import { oneDark } from '@codemirror/theme-one-dark';
+
+	import { onMount, createEventDispatcher } from 'svelte';
+	import { formatPythonCode } from '$lib/apis/utils';
+	import { toast } from 'svelte-sonner';
+
+	const dispatch = createEventDispatcher();
+
+	export let boilerplate = '';
+	export let value = '';
+
+	let codeEditor;
+
+	let isDarkMode = false;
+	let editorTheme = new Compartment();
+
+	export const formatPythonCodeHandler = async () => {
+		if (codeEditor) {
+			const res = await formatPythonCode(value).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+
+			if (res && res.code) {
+				const formattedCode = res.code;
+				codeEditor.dispatch({
+					changes: [{ from: 0, to: codeEditor.state.doc.length, insert: formattedCode }]
+				});
+
+				toast.success('Code formatted successfully');
+				return true;
+			}
+			return false;
+		}
+		return false;
+	};
+
+	let extensions = [
+		basicSetup,
+		keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
+		python(),
+		indentUnit.of('    '),
+		placeholder('Enter your code here...'),
+		EditorView.updateListener.of((e) => {
+			if (e.docChanged) {
+				value = e.state.doc.toString();
+			}
+		}),
+		editorTheme.of([])
+	];
+
+	onMount(() => {
+		console.log(value);
+		if (value === '') {
+			value = boilerplate;
+		}
+
+		// Check if html class has dark mode
+		isDarkMode = document.documentElement.classList.contains('dark');
+
+		// python code editor, highlight python code
+		codeEditor = new EditorView({
+			state: EditorState.create({
+				doc: value,
+				extensions: extensions
+			}),
+			parent: document.getElementById('code-textarea')
+		});
+
+		if (isDarkMode) {
+			codeEditor.dispatch({
+				effects: editorTheme.reconfigure(oneDark)
+			});
+		}
+
+		// listen to html class changes this should fire only when dark mode is toggled
+		const observer = new MutationObserver((mutations) => {
+			mutations.forEach((mutation) => {
+				if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
+					const _isDarkMode = document.documentElement.classList.contains('dark');
+
+					if (_isDarkMode !== isDarkMode) {
+						isDarkMode = _isDarkMode;
+						if (_isDarkMode) {
+							codeEditor.dispatch({
+								effects: editorTheme.reconfigure(oneDark)
+							});
+						} else {
+							codeEditor.dispatch({
+								effects: editorTheme.reconfigure()
+							});
+						}
+					}
+				}
+			});
+		});
+
+		observer.observe(document.documentElement, {
+			attributes: true,
+			attributeFilter: ['class']
+		});
+
+		const keydownHandler = async (e) => {
+			if ((e.ctrlKey || e.metaKey) && e.key === 's') {
+				e.preventDefault();
+				dispatch('save');
+			}
+
+			// Format code when Ctrl + Shift + F is pressed
+			if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'f') {
+				e.preventDefault();
+				await formatPythonCodeHandler();
+			}
+		};
+
+		document.addEventListener('keydown', keydownHandler);
+
+		return () => {
+			observer.disconnect();
+			document.removeEventListener('keydown', keydownHandler);
+		};
+	});
+</script>
+
+<div id="code-textarea" class="h-full w-full" />

+ 106 - 0
src/lib/components/common/ConfirmDialog.svelte

@@ -0,0 +1,106 @@
+<script lang="ts">
+	import { onMount, createEventDispatcher } from 'svelte';
+	import { fade } from 'svelte/transition';
+
+	import { flyAndScale } from '$lib/utils/transitions';
+
+	const dispatch = createEventDispatcher();
+
+	export let title = 'Confirm your action';
+	export let message = 'This action cannot be undone. Do you wish to continue?';
+
+	export let show = false;
+	let modalElement = null;
+	let mounted = false;
+
+	const handleKeyDown = (event: KeyboardEvent) => {
+		if (event.key === 'Escape') {
+			console.log('Escape');
+			show = false;
+		}
+	};
+
+	onMount(() => {
+		mounted = true;
+	});
+
+	$: if (mounted) {
+		if (show) {
+			window.addEventListener('keydown', handleKeyDown);
+			document.body.style.overflow = 'hidden';
+		} else {
+			window.removeEventListener('keydown', handleKeyDown);
+			document.body.style.overflow = 'unset';
+		}
+	}
+</script>
+
+{#if show}
+	<!-- svelte-ignore a11y-click-events-have-key-events -->
+	<!-- svelte-ignore a11y-no-static-element-interactions -->
+	<div
+		bind:this={modalElement}
+		class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
+		in:fade={{ duration: 10 }}
+		on:mousedown={() => {
+			show = false;
+		}}
+	>
+		<div
+			class=" m-auto rounded-2xl max-w-full w-[32rem] mx-2 bg-gray-50 dark:bg-gray-950 shadow-3xl border border-gray-850"
+			in:flyAndScale
+			on:mousedown={(e) => {
+				e.stopPropagation();
+			}}
+		>
+			<div class="px-[1.75rem] py-6">
+				<div class=" text-lg font-semibold dark:text-gray-200 mb-2.5">{title}</div>
+
+				<slot>
+					<div class=" text-sm text-gray-500">
+						{message}
+					</div>
+				</slot>
+
+				<div class="mt-6 flex justify-between gap-1.5">
+					<button
+						class="bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white font-medium w-full py-2.5 rounded-lg transition"
+						on:click={() => {
+							show = false;
+						}}
+						type="button"
+					>
+						Cancel
+					</button>
+					<button
+						class="bg-gray-900 hover:bg-gray-850 text-gray-100 dark:bg-gray-100 dark:hover:bg-white dark:text-gray-800 font-medium w-full py-2.5 rounded-lg transition"
+						on:click={() => {
+							show = false;
+							dispatch('confirm');
+						}}
+						type="button"
+					>
+						Confirm
+					</button>
+				</div>
+			</div>
+		</div>
+	</div>
+{/if}
+
+<style>
+	.modal-content {
+		animation: scaleUp 0.1s ease-out forwards;
+	}
+
+	@keyframes scaleUp {
+		from {
+			transform: scale(0.985);
+			opacity: 0;
+		}
+		to {
+			transform: scale(1);
+			opacity: 1;
+		}
+	}
+</style>

+ 11 - 0
src/lib/components/icons/WrenchSolid.svelte

@@ -0,0 +1,11 @@
+<script lang="ts">
+	export let className = 'size-4';
+</script>
+
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
+	<path
+		fill-rule="evenodd"
+		d="M12 6.75a5.25 5.25 0 0 1 6.775-5.025.75.75 0 0 1 .313 1.248l-3.32 3.319c.063.475.276.934.641 1.299.365.365.824.578 1.3.64l3.318-3.319a.75.75 0 0 1 1.248.313 5.25 5.25 0 0 1-5.472 6.756c-1.018-.086-1.87.1-2.309.634L7.344 21.3A3.298 3.298 0 1 1 2.7 16.657l8.684-7.151c.533-.44.72-1.291.634-2.309A5.342 5.342 0 0 1 12 6.75ZM4.117 19.125a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75h-.008a.75.75 0 0 1-.75-.75v-.008Z"
+		clip-rule="evenodd"
+	/>
+</svg>

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

@@ -491,7 +491,7 @@
 	{/each}
 </div>
 
-<div class=" text-gray-500 text-xs mt-1">
+<div class=" text-gray-500 text-xs mt-1 mb-2">
 	ⓘ {$i18n.t("Use '#' in the prompt input to load and select your documents.")}
 </div>
 

+ 57 - 0
src/lib/components/workspace/Models/ToolsSelector.svelte

@@ -0,0 +1,57 @@
+<script lang="ts">
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+	import { getContext, onMount } from 'svelte';
+
+	export let tools = [];
+
+	let _tools = {};
+
+	export let selectedToolIds = [];
+
+	const i18n = getContext('i18n');
+
+	onMount(() => {
+		_tools = tools.reduce((acc, tool) => {
+			acc[tool.id] = {
+				...tool,
+				selected: selectedToolIds.includes(tool.id)
+			};
+
+			return acc;
+		}, {});
+	});
+</script>
+
+<div>
+	<div class="flex w-full justify-between mb-1">
+		<div class=" self-center text-sm font-semibold">{$i18n.t('Tools')}</div>
+	</div>
+
+	<div class=" text-xs dark:text-gray-500">
+		{$i18n.t('To select toolkits here, add them to the "Tools" workspace first.')}
+	</div>
+
+	<div class="flex flex-col">
+		{#if tools.length > 0}
+			<div class=" flex items-center gap-2 mt-2">
+				{#each Object.keys(_tools) as tool, toolIdx}
+					<div class=" flex items-center gap-2">
+						<div class="self-center flex items-center">
+							<Checkbox
+								state={_tools[tool].selected ? 'checked' : 'unchecked'}
+								on:change={(e) => {
+									_tools[tool].selected = e.detail === 'checked';
+									selectedToolIds = Object.keys(_tools).filter((t) => _tools[t].selected);
+								}}
+							/>
+						</div>
+
+						<div class=" py-0.5 text-sm w-full capitalize font-medium">
+							{_tools[tool].name}
+						</div>
+					</div>
+				{/each}
+			</div>
+		{/if}
+	</div>
+</div>

+ 358 - 0
src/lib/components/workspace/Tools.svelte

@@ -0,0 +1,358 @@
+<script lang="ts">
+	import { toast } from 'svelte-sonner';
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
+	import { onMount, getContext } from 'svelte';
+	import { WEBUI_NAME, prompts, tools } from '$lib/stores';
+	import { createNewPrompt, deletePromptByCommand, getPrompts } from '$lib/apis/prompts';
+
+	import { goto } from '$app/navigation';
+	import {
+		createNewTool,
+		deleteToolById,
+		exportTools,
+		getToolById,
+		getTools
+	} from '$lib/apis/tools';
+	import ArrowDownTray from '../icons/ArrowDownTray.svelte';
+	import Tooltip from '../common/Tooltip.svelte';
+	import ConfirmDialog from '../common/ConfirmDialog.svelte';
+
+	const i18n = getContext('i18n');
+
+	let toolsImportInputElement: HTMLInputElement;
+	let importFiles;
+
+	let showConfirm = false;
+	let query = '';
+</script>
+
+<svelte:head>
+	<title>
+		{$i18n.t('Tools')} | {$WEBUI_NAME}
+	</title>
+</svelte:head>
+
+<div class="mb-3 flex justify-between items-center">
+	<div class=" text-lg font-semibold self-center">{$i18n.t('Tools')}</div>
+</div>
+
+<div class=" flex w-full space-x-2">
+	<div class="flex flex-1">
+		<div class=" self-center ml-1 mr-3">
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 20 20"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					fill-rule="evenodd"
+					d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+					clip-rule="evenodd"
+				/>
+			</svg>
+		</div>
+		<input
+			class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+			bind:value={query}
+			placeholder={$i18n.t('Search Tools')}
+		/>
+	</div>
+
+	<div>
+		<a
+			class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
+			href="/workspace/tools/create"
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</a>
+	</div>
+</div>
+<hr class=" dark:border-gray-850 my-2.5" />
+
+<div class="my-3 mb-5">
+	{#each $tools.filter((t) => query === '' || t.name
+				.toLowerCase()
+				.includes(query.toLowerCase()) || t.id.toLowerCase().includes(query.toLowerCase())) as tool}
+		<button
+			class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+			type="button"
+			on:click={() => {
+				goto(`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`);
+			}}
+		>
+			<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
+				<a
+					href={`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`}
+					class="flex items-center text-left"
+				>
+					<div class=" flex-1 self-center pl-5">
+						<div class=" font-semibold flex items-center gap-1.5">
+							<div>
+								{tool.name}
+							</div>
+							<div class=" text-gray-500 text-xs font-medium">{tool.id}</div>
+						</div>
+						<div class=" text-xs overflow-hidden text-ellipsis line-clamp-1">
+							{tool.meta.description}
+						</div>
+					</div>
+				</a>
+			</div>
+			<div class="flex flex-row space-x-1 self-center">
+				<Tooltip content="Edit">
+					<a
+						class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+						type="button"
+						href={`/workspace/tools/edit?id=${encodeURIComponent(tool.id)}`}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125"
+							/>
+						</svg>
+					</a>
+				</Tooltip>
+
+				<Tooltip content="Clone">
+					<button
+						class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+						type="button"
+						on:click={async (e) => {
+							e.stopPropagation();
+
+							const _tool = await getToolById(localStorage.token, tool.id).catch((error) => {
+								toast.error(error);
+								return null;
+							});
+
+							if (_tool) {
+								sessionStorage.tool = JSON.stringify({
+									..._tool,
+									id: `${_tool.id}_clone`,
+									name: `${_tool.name} (Clone)`
+								});
+								goto('/workspace/tools/create');
+							}
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
+							/>
+						</svg>
+					</button>
+				</Tooltip>
+
+				<Tooltip content="Export">
+					<button
+						class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+						type="button"
+						on:click={async (e) => {
+							e.stopPropagation();
+
+							const _tool = await getToolById(localStorage.token, tool.id).catch((error) => {
+								toast.error(error);
+								return null;
+							});
+
+							if (_tool) {
+								let blob = new Blob([JSON.stringify([_tool])], {
+									type: 'application/json'
+								});
+								saveAs(blob, `tool-${_tool.id}-export-${Date.now()}.json`);
+							}
+						}}
+					>
+						<ArrowDownTray />
+					</button>
+				</Tooltip>
+
+				<Tooltip content="Delete">
+					<button
+						class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+						type="button"
+						on:click={async (e) => {
+							e.stopPropagation();
+
+							const res = await deleteToolById(localStorage.token, tool.id).catch((error) => {
+								toast.error(error);
+								return null;
+							});
+
+							if (res) {
+								toast.success('Tool deleted successfully');
+								tools.set(await getTools(localStorage.token));
+							}
+						}}
+					>
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							fill="none"
+							viewBox="0 0 24 24"
+							stroke-width="1.5"
+							stroke="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								stroke-linecap="round"
+								stroke-linejoin="round"
+								d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
+							/>
+						</svg>
+					</button>
+				</Tooltip>
+			</div>
+		</button>
+	{/each}
+</div>
+
+<div class=" text-gray-500 text-xs mt-1 mb-2">
+	ⓘ {$i18n.t(
+		'Admins have access to all tools at all times; users need tools assigned per model in the workspace.'
+	)}
+</div>
+
+<div class=" flex justify-end w-full mb-2">
+	<div class="flex space-x-2">
+		<input
+			id="documents-import-input"
+			bind:this={toolsImportInputElement}
+			bind:files={importFiles}
+			type="file"
+			accept=".json"
+			hidden
+			on:change={() => {
+				console.log(importFiles);
+				showConfirm = true;
+			}}
+		/>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={() => {
+				toolsImportInputElement.click();
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Import Tools')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 9.5a.75.75 0 0 1-.75-.75V8.06l-.72.72a.75.75 0 0 1-1.06-1.06l2-2a.75.75 0 0 1 1.06 0l2 2a.75.75 0 1 1-1.06 1.06l-.72-.72v2.69a.75.75 0 0 1-.75.75Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+
+		<button
+			class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
+			on:click={async () => {
+				const _tools = await exportTools(localStorage.token).catch((error) => {
+					toast.error(error);
+					return null;
+				});
+
+				if (_tools) {
+					let blob = new Blob([JSON.stringify(_tools)], {
+						type: 'application/json'
+					});
+					saveAs(blob, `tools-export-${Date.now()}.json`);
+				}
+			}}
+		>
+			<div class=" self-center mr-2 font-medium">{$i18n.t('Export Tools')}</div>
+
+			<div class=" self-center">
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 16 16"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<path
+						fill-rule="evenodd"
+						d="M4 2a1.5 1.5 0 0 0-1.5 1.5v9A1.5 1.5 0 0 0 4 14h8a1.5 1.5 0 0 0 1.5-1.5V6.621a1.5 1.5 0 0 0-.44-1.06L9.94 2.439A1.5 1.5 0 0 0 8.878 2H4Zm4 3.5a.75.75 0 0 1 .75.75v2.69l.72-.72a.75.75 0 1 1 1.06 1.06l-2 2a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 0 1 1.06-1.06l.72.72V6.25A.75.75 0 0 1 8 5.5Z"
+						clip-rule="evenodd"
+					/>
+				</svg>
+			</div>
+		</button>
+	</div>
+</div>
+
+<ConfirmDialog
+	bind:show={showConfirm}
+	on:confirm={() => {
+		const reader = new FileReader();
+		reader.onload = async (event) => {
+			const _tools = JSON.parse(event.target.result);
+			console.log(_tools);
+
+			for (const tool of _tools) {
+				const res = await createNewTool(localStorage.token, tool).catch((error) => {
+					toast.error(error);
+					return null;
+				});
+			}
+
+			toast.success('Tool imported successfully');
+			tools.set(await getTools(localStorage.token));
+		};
+
+		reader.readAsText(importFiles[0]);
+	}}
+>
+	<div class="text-sm text-gray-500">
+		<div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3">
+			<div>Please carefully review the following warnings:</div>
+
+			<ul class=" mt-1 list-disc pl-4 text-xs">
+				<li>Tools have a function calling system that allows arbitrary code execution.</li>
+				<li>Do not install tools from sources you do not fully trust.</li>
+			</ul>
+		</div>
+
+		<div class="my-3">
+			I acknowledge that I have read and I understand the implications of my action. I am aware of
+			the risks associated with executing arbitrary code and I have verified the trustworthiness of
+			the source.
+		</div>
+	</div>
+</ConfirmDialog>

+ 127 - 0
src/lib/components/workspace/Tools/CodeEditor.svelte

@@ -0,0 +1,127 @@
+<script lang="ts">
+	import CodeEditor from '$lib/components/common/CodeEditor.svelte';
+	import { createEventDispatcher } from 'svelte';
+
+	const dispatch = createEventDispatcher();
+
+	export let value = '';
+
+	let codeEditor;
+	let boilerplate = `import os
+import requests
+from datetime import datetime
+
+
+class Tools:
+    def __init__(self):
+        pass
+
+    # Add your custom tools using pure Python code here, make sure to add type hints
+    # Use Sphinx-style docstrings to document your tools, they will be used for generating tools specifications
+    # Please refer to function_calling_filter_pipeline.py file from pipelines project for an example
+
+    def get_user_name_and_email_and_id(self, __user__: dict = {}) -> str:
+        """
+        Get the user name, Email and ID from the user object.
+        """
+
+        # Do not include :param for __user__ in the docstring as it should not be shown in the tool's specification
+        # The session user object will be passed as a parameter when the function is called
+
+        print(__user__)
+        result = ""
+
+        if "name" in __user__:
+            result += f"User: {__user__['name']}"
+        if "id" in __user__:
+            result += f" (ID: {__user__['id']})"
+        if "email" in __user__:
+            result += f" (Email: {__user__['email']})"
+
+        if result == "":
+            result = "User: Unknown"
+
+        return result
+
+    def get_current_time(self) -> str:
+        """
+        Get the current time in a more human-readable format.
+        :return: The current time.
+        """
+
+        now = datetime.now()
+        current_time = now.strftime("%I:%M:%S %p")  # Using 12-hour format with AM/PM
+        current_date = now.strftime(
+            "%A, %B %d, %Y"
+        )  # Full weekday, month name, day, and year
+
+        return f"Current Date and Time = {current_date}, {current_time}"
+
+    def calculator(self, equation: str) -> str:
+        """
+        Calculate the result of an equation.
+        :param equation: The equation to calculate.
+        """
+
+        # Avoid using eval in production code
+        # https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
+        try:
+            result = eval(equation)
+            return f"{equation} = {result}"
+        except Exception as e:
+            print(e)
+            return "Invalid equation"
+
+    def get_current_weather(self, city: str) -> str:
+        """
+        Get the current weather for a given city.
+        :param city: The name of the city to get the weather for.
+        :return: The current weather information or an error message.
+        """
+        api_key = os.getenv("OPENWEATHER_API_KEY")
+        if not api_key:
+            return (
+                "API key is not set in the environment variable 'OPENWEATHER_API_KEY'."
+            )
+
+        base_url = "http://api.openweathermap.org/data/2.5/weather"
+        params = {
+            "q": city,
+            "appid": api_key,
+            "units": "metric",  # Optional: Use 'imperial' for Fahrenheit
+        }
+
+        try:
+            response = requests.get(base_url, params=params)
+            response.raise_for_status()  # Raise HTTPError for bad responses (4xx and 5xx)
+            data = response.json()
+
+            if data.get("cod") != 200:
+                return f"Error fetching weather data: {data.get('message')}"
+
+            weather_description = data["weather"][0]["description"]
+            temperature = data["main"]["temp"]
+            humidity = data["main"]["humidity"]
+            wind_speed = data["wind"]["speed"]
+
+            return f"Weather in {city}: {temperature}°C"
+        except requests.RequestException as e:
+            return f"Error fetching weather data: {str(e)}"
+`;
+
+	export const formatHandler = async () => {
+		if (codeEditor) {
+			return await codeEditor.formatPythonCodeHandler();
+		}
+		return false;
+	};
+</script>
+
+<CodeEditor
+	bind:value
+	{boilerplate}
+	bind:this={codeEditor}
+	on:save={() => {
+		dispatch('save');
+	}}
+/>

+ 179 - 0
src/lib/components/workspace/Tools/ToolkitEditor.svelte

@@ -0,0 +1,179 @@
+<script>
+	import { getContext, createEventDispatcher, onMount } from 'svelte';
+
+	const i18n = getContext('i18n');
+
+	import CodeEditor from './CodeEditor.svelte';
+	import { goto } from '$app/navigation';
+	import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
+
+	const dispatch = createEventDispatcher();
+
+	let formElement = null;
+	let loading = false;
+	let showConfirm = false;
+
+	export let edit = false;
+	export let clone = false;
+
+	export let id = '';
+	export let name = '';
+	export let meta = {
+		description: ''
+	};
+	export let content = '';
+
+	$: if (name && !edit && !clone) {
+		id = name.replace(/\s+/g, '_').toLowerCase();
+	}
+
+	let codeEditor;
+
+	const saveHandler = async () => {
+		loading = true;
+		dispatch('save', {
+			id,
+			name,
+			meta,
+			content
+		});
+	};
+
+	const submitHandler = async () => {
+		if (codeEditor) {
+			const res = await codeEditor.formatHandler();
+
+			if (res) {
+				console.log('Code formatted successfully');
+				saveHandler();
+			}
+		}
+	};
+</script>
+
+<div class=" flex flex-col justify-between w-full overflow-y-auto h-full">
+	<div class="mx-auto w-full md:px-0 h-full">
+		<form
+			bind:this={formElement}
+			class=" flex flex-col max-h-[100dvh] h-full"
+			on:submit|preventDefault={() => {
+				if (edit) {
+					submitHandler();
+				} else {
+					showConfirm = true;
+				}
+			}}
+		>
+			<div class="mb-2.5">
+				<button
+					class="flex space-x-1"
+					on:click={() => {
+						goto('/workspace/tools');
+					}}
+					type="button"
+				>
+					<div class=" self-center">
+						<svg
+							xmlns="http://www.w3.org/2000/svg"
+							viewBox="0 0 20 20"
+							fill="currentColor"
+							class="w-4 h-4"
+						>
+							<path
+								fill-rule="evenodd"
+								d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z"
+								clip-rule="evenodd"
+							/>
+						</svg>
+					</div>
+					<div class=" self-center font-medium text-sm">{$i18n.t('Back')}</div>
+				</button>
+			</div>
+
+			<div class="flex flex-col flex-1 overflow-auto h-0 rounded-lg">
+				<div class="w-full mb-2 flex flex-col gap-1.5">
+					<div class="flex gap-2 w-full">
+						<input
+							class="w-full px-3 py-2 text-sm font-medium bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
+							type="text"
+							placeholder="Toolkit Name (e.g. My ToolKit)"
+							bind:value={name}
+							required
+						/>
+
+						<input
+							class="w-full px-3 py-2 text-sm font-medium disabled:text-gray-300 dark:disabled:text-gray-700 bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
+							type="text"
+							placeholder="Toolkit ID (e.g. my_toolkit)"
+							bind:value={id}
+							required
+							disabled={edit}
+						/>
+					</div>
+					<input
+						class="w-full px-3 py-2 text-sm font-medium bg-gray-50 dark:bg-gray-850 dark:text-gray-200 rounded-lg outline-none"
+						type="text"
+						placeholder="Toolkit Description (e.g. A toolkit for performing various operations)"
+						bind:value={meta.description}
+						required
+					/>
+				</div>
+
+				<div class="mb-2 flex-1 overflow-auto h-0 rounded-lg">
+					<CodeEditor
+						bind:value={content}
+						bind:this={codeEditor}
+						on:save={() => {
+							if (formElement) {
+								formElement.requestSubmit();
+							}
+						}}
+					/>
+				</div>
+
+				<div class="pb-3 flex justify-between">
+					<div class="flex-1 pr-3">
+						<div class="text-xs text-gray-500 line-clamp-2">
+							<span class=" font-semibold dark:text-gray-200">Warning:</span> Tools are a function
+							calling system with arbitrary code execution <br />—
+							<span class=" font-medium dark:text-gray-400"
+								>don't install random tools from sources you don't trust.</span
+							>
+						</div>
+					</div>
+
+					<button
+						class="px-3 py-1.5 text-sm font-medium bg-emerald-600 hover:bg-emerald-700 text-gray-50 transition rounded-lg"
+						type="submit"
+					>
+						{$i18n.t('Save')}
+					</button>
+				</div>
+			</div>
+		</form>
+	</div>
+</div>
+
+<ConfirmDialog
+	bind:show={showConfirm}
+	on:confirm={() => {
+		submitHandler();
+	}}
+>
+	<div class="text-sm text-gray-500">
+		<div class=" bg-yellow-500/20 text-yellow-700 dark:text-yellow-200 rounded-lg px-4 py-3">
+			<div>Please carefully review the following warnings:</div>
+
+			<ul class=" mt-1 list-disc pl-4 text-xs">
+				<li>Tools have a function calling system that allows arbitrary code execution.</li>
+				<li>Do not install tools from sources you do not fully trust.</li>
+			</ul>
+		</div>
+
+		<div class="my-3">
+			I acknowledge that I have read and I understand the implications of my action. I am aware of
+			the risks associated with executing arbitrary code and I have verified the trustworthiness of
+			the source.
+		</div>
+	</div>
+</ConfirmDialog>

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "لوحة التحكم",
 	"Admin Settings": "اعدادات المشرف",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "التعليمات المتقدمة",
 	"Advanced Params": "المعلمات المتقدمة",
 	"all": "الكل",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "تصدير وثائق الخرائط",
 	"Export Models": "نماذج التصدير",
 	"Export Prompts": "مطالبات التصدير",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "فشل في إنشاء مفتاح API.",
 	"Failed to read clipboard contents": "فشل في قراءة محتويات الحافظة",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "استيراد خرائط المستندات",
 	"Import Models": "استيراد النماذج",
 	"Import Prompts": "مطالبات الاستيراد",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "قم بتضمين علامة `-api` عند تشغيل Stable-diffusion-webui",
 	"Info": "معلومات",
 	"Input commands": "إدخال الأوامر",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "عدد نتائج البحث",
+	"Search Tools": "",
 	"Searched {{count}} sites_zero": "تم البحث في {{count}} sites_zero",
 	"Searched {{count}} sites_one": "تم البحث في {{count}} sites_one",
 	"Searched {{count}} sites_two": "تم البحث في {{count}} sites_two",
@@ -514,9 +518,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "الى كتابة المحادثه",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "اليوم",
 	"Toggle settings": "فتح وأغلاق الاعدادات",
 	"Toggle sidebar": "فتح وأغلاق الشريط الجانبي",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "هل تواجه مشكلة في الوصول",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Панел на Администратор",
 	"Admin Settings": "Настройки на Администратор",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Разширени Параметри",
 	"Advanced Params": "Разширени параметри",
 	"all": "всички",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Експортване на документен мапинг",
 	"Export Models": "Експортиране на модели",
 	"Export Prompts": "Експортване на промптове",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Неуспешно създаване на API ключ.",
 	"Failed to read clipboard contents": "Грешка при четене на съдържанието от клипборда",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Импортване на документен мапинг",
 	"Import Models": "Импортиране на модели",
 	"Import Prompts": "Импортване на промптове",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Включете флага `--api`, когато стартирате stable-diffusion-webui",
 	"Info": "Информация",
 	"Input commands": "Въведете команди",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Брой резултати от търсенето",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Търси се в {{count}} sites_one",
 	"Searched {{count}} sites_other": "Търси се в {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "към чат входа.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "днес",
 	"Toggle settings": "Toggle settings",
 	"Toggle sidebar": "Toggle sidebar",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Проблеми с достъпът до Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "এডমিন প্যানেল",
 	"Admin Settings": "এডমিন সেটিংস",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "এডভান্সড প্যারামিটার্স",
 	"Advanced Params": "অ্যাডভান্সড প্যারাম",
 	"all": "সব",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "ডকুমেন্টসমূহ ম্যাপিং এক্সপোর্ট করুন",
 	"Export Models": "রপ্তানি মডেল",
 	"Export Prompts": "প্রম্পটগুলো একপোর্ট করুন",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "API Key তৈরি করা যায়নি।",
 	"Failed to read clipboard contents": "ক্লিপবোর্ডের বিষয়বস্তু পড়া সম্ভব হয়নি",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "ডকুমেন্টসমূহ ম্যাপিং ইমপোর্ট করুন",
 	"Import Models": "মডেল আমদানি করুন",
 	"Import Prompts": "প্রম্পটগুলো ইমপোর্ট করুন",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui চালু করার সময় `--api` ফ্ল্যাগ সংযুক্ত করুন",
 	"Info": "তথ্য",
 	"Input commands": "ইনপুট কমান্ডস",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "অনুসন্ধানের ফলাফল গণনা",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "{{কাউন্ট}} অনুসন্ধান করা হয়েছে sites_one",
 	"Searched {{count}} sites_other": "{{কাউন্ট}} অনুসন্ধান করা হয়েছে sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "চ্যাট ইনপুটে",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "আজ",
 	"Toggle settings": "সেটিংস টোগল",
 	"Toggle sidebar": "সাইডবার টোগল",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Ollama এক্সেস করতে সমস্যা হচ্ছে?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Panell d'Administració",
 	"Admin Settings": "Configuració d'Administració",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Paràmetres Avançats",
 	"Advanced Params": "Paràmetres avançats",
 	"all": "tots",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Exporta el Mapatge de Documents",
 	"Export Models": "Models d'exportació",
 	"Export Prompts": "Exporta Prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "No s'ha pogut crear la clau d'API.",
 	"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importa el Mapa de Documents",
 	"Import Models": "Models d'importació",
 	"Import Prompts": "Importa Prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Inclou la bandera `--api` quan executis stable-diffusion-webui",
 	"Info": "Informació",
 	"Input commands": "Entra ordres",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Recompte de resultats de cerca",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Cercat {{count}} sites_one",
 	"Searched {{count}} sites_many": "Cercat {{recompte}} sites_many",
 	"Searched {{count}} sites_other": "Cercat {{recompte}} sites_other",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "a l'entrada del xat.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Avui",
 	"Toggle settings": "Commuta configuracions",
 	"Toggle sidebar": "Commuta barra lateral",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problemes accedint a Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Admin Panel",
 	"Admin Settings": "Mga setting sa administratibo",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "advanced settings",
 	"Advanced Params": "",
 	"all": "tanan",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "I-export ang pagmapa sa dokumento",
 	"Export Models": "",
 	"Export Prompts": "Export prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "Napakyas sa pagbasa sa sulod sa clipboard",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Import nga pagmapa sa dokumento",
 	"Import Models": "",
 	"Import Prompts": "Import prompt",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Iapil ang `--api` nga bandila kung nagdagan nga stable-diffusion-webui",
 	"Info": "",
 	"Input commands": "Pagsulod sa input commands",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "",
 	"Searched {{count}} sites_other": "",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "sa entrada sa iring.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "",
 	"Toggle settings": "I-toggle ang mga setting",
 	"Toggle sidebar": "I-toggle ang sidebar",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Ibabaw nga P",
 	"Trouble accessing Ollama?": "Adunay mga problema sa pag-access sa Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Admin Panel",
 	"Admin Settings": "Admin Einstellungen",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Erweiterte Parameter",
 	"Advanced Params": "Erweiterte Parameter",
 	"all": "Alle",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Dokumentenmapping exportieren",
 	"Export Models": "Modelle exportieren",
 	"Export Prompts": "Prompts exportieren",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "API Key erstellen fehlgeschlagen",
 	"Failed to read clipboard contents": "Fehler beim Lesen des Zwischenablageninhalts",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Dokumentenmapping importieren",
 	"Import Models": "Modelle importieren",
 	"Import Prompts": "Prompts importieren",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Füge das `--api`-Flag hinzu, wenn du stable-diffusion-webui nutzt",
 	"Info": "Info",
 	"Input commands": "Eingabebefehle",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Anzahl der Suchergebnisse",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Gesucht {{count}} sites_one",
 	"Searched {{count}} sites_other": "Gesucht {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "to chat input.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Heute",
 	"Toggle settings": "Einstellungen umschalten",
 	"Toggle sidebar": "Seitenleiste umschalten",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Probleme beim Zugriff auf Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Admin Panel",
 	"Admin Settings": "Admin Settings",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Advanced Parameters",
 	"Advanced Params": "",
 	"all": "all",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Export Mappings of Dogos",
 	"Export Models": "",
 	"Export Prompts": "Export Promptos",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "Failed to read clipboard borks",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Import Doge Mapping",
 	"Import Models": "",
 	"Import Prompts": "Import Promptos",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Include `--api` flag when running stable-diffusion-webui",
 	"Info": "",
 	"Input commands": "Input commands",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "",
 	"Searched {{count}} sites_other": "",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "to chat input. Very chat.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "",
 	"Toggle settings": "Toggle settings much toggle",
 	"Toggle sidebar": "Toggle sidebar much toggle",
+	"Tools": "",
 	"Top K": "Top K very top",
 	"Top P": "Top P very top",
 	"Trouble accessing Ollama?": "Trouble accessing Ollama? Much trouble?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "",
 	"Admin Settings": "",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "",
 	"Advanced Params": "",
 	"all": "",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "",
 	"Export Models": "",
 	"Export Prompts": "",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "",
 	"Import Models": "",
 	"Import Prompts": "",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "",
 	"Info": "",
 	"Input commands": "",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "",
 	"Searched {{count}} sites_other": "",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "",
 	"Toggle settings": "",
 	"Toggle sidebar": "",
+	"Tools": "",
 	"Top K": "",
 	"Top P": "",
 	"Trouble accessing Ollama?": "",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "",
 	"Admin Settings": "",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "",
 	"Advanced Params": "",
 	"all": "",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "",
 	"Export Models": "",
 	"Export Prompts": "",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "",
 	"Import Models": "",
 	"Import Prompts": "",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "",
 	"Info": "",
 	"Input commands": "",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "",
 	"Searched {{count}} sites_other": "",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "",
 	"Toggle settings": "",
 	"Toggle sidebar": "",
+	"Tools": "",
 	"Top K": "",
 	"Top P": "",
 	"Trouble accessing Ollama?": "",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Panel de Administración",
 	"Admin Settings": "Configuración de Administrador",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Parámetros Avanzados",
 	"Advanced Params": "Parámetros avanzados",
 	"all": "todo",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Exportar el mapeo de documentos",
 	"Export Models": "Modelos de exportación",
 	"Export Prompts": "Exportar Prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "No se pudo crear la clave API.",
 	"Failed to read clipboard contents": "No se pudo leer el contenido del portapapeles",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importar Mapeo de Documentos",
 	"Import Models": "Importar modelos",
 	"Import Prompts": "Importar Prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Incluir el indicador `--api` al ejecutar stable-diffusion-webui",
 	"Info": "Información",
 	"Input commands": "Ingresar comandos",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Recuento de resultados de búsqueda",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Buscado {{count}} sites_one",
 	"Searched {{count}} sites_many": "Buscado {{count}} sites_many",
 	"Searched {{count}} sites_other": "Buscó {{count}} sites_other",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "a la entrada del chat.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Hoy",
 	"Toggle settings": "Alternar configuración",
 	"Toggle sidebar": "Alternar barra lateral",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "¿Problemas para acceder a Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "پنل مدیریت",
 	"Admin Settings": "تنظیمات مدیریت",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "پارامترهای پیشرفته",
 	"Advanced Params": "پارام های پیشرفته",
 	"all": "همه",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "اکسپورت از نگاشت اسناد",
 	"Export Models": "مدل های صادرات",
 	"Export Prompts": "اکسپورت از پرامپت\u200cها",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "ایجاد کلید API با خطا مواجه شد.",
 	"Failed to read clipboard contents": "خواندن محتوای کلیپ بورد ناموفق بود",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "ایمپورت نگاشت اسناد",
 	"Import Models": "واردات مدلها",
 	"Import Prompts": "ایمپورت پرامپت\u200cها",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "فلگ `--api` را هنکام اجرای stable-diffusion-webui استفاده کنید.",
 	"Info": "اطلاعات",
 	"Input commands": "ورودی دستورات",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "تعداد نتایج جستجو",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "جستجو {{count}} sites_one",
 	"Searched {{count}} sites_other": "جستجو {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "در ورودی گپ.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "امروز",
 	"Toggle settings": "نمایش/عدم نمایش تنظیمات",
 	"Toggle sidebar": "نمایش/عدم نمایش نوار کناری",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "در دسترسی به اولاما مشکل دارید؟",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Hallintapaneeli",
 	"Admin Settings": "Hallinta-asetukset",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Edistyneet parametrit",
 	"Advanced Params": "Edistyneet parametrit",
 	"all": "kaikki",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Vie asiakirjakartoitus",
 	"Export Models": "Vie malleja",
 	"Export Prompts": "Vie kehotteet",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "API-avaimen luonti epäonnistui.",
 	"Failed to read clipboard contents": "Leikepöydän sisällön lukeminen epäonnistui",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Tuo asiakirjakartoitus",
 	"Import Models": "Mallien tuominen",
 	"Import Prompts": "Tuo kehotteita",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Sisällytä `--api`-parametri suorittaessasi stable-diffusion-webui",
 	"Info": "Info",
 	"Input commands": "Syötä komennot",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Hakutulosten määrä",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Haettu {{count}} sites_one",
 	"Searched {{count}} sites_other": "Haku {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "keskustelusyötteeseen.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Tänään",
 	"Toggle settings": "Kytke asetukset",
 	"Toggle sidebar": "Kytke sivupalkki",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Ongelmia Ollama-yhteydessä?",

+ 7 - 1
src/lib/i18n/locales/fr-CA/translation.json

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Panneau d'administration",
 	"Admin Settings": "Paramètres d'administration",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Paramètres avancés",
 	"Advanced Params": "Params avancés",
 	"all": "tous",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Exporter le mappage des documents",
 	"Export Models": "Modèles d’exportation",
 	"Export Prompts": "Exporter les prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Impossible de créer la clé API.",
 	"Failed to read clipboard contents": "Échec de la lecture du contenu du presse-papiers",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importer le mappage des documents",
 	"Import Models": "Importer des modèles",
 	"Import Prompts": "Importer les prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Inclure l'indicateur `--api` lors de l'exécution de stable-diffusion-webui",
 	"Info": "L’info",
 	"Input commands": "Entrez des commandes d'entrée",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Nombre de résultats de la recherche",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Recherche dans {{count}} sites_one",
 	"Searched {{count}} sites_many": "Recherche dans {{count}} sites_many",
 	"Searched {{count}} sites_other": "Recherche dans {{count}} sites_other",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "à l'entrée du chat.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Aujourd'hui",
 	"Toggle settings": "Basculer les paramètres",
 	"Toggle sidebar": "Basculer la barre latérale",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Des problèmes pour accéder à Ollama ?",
@@ -527,7 +533,7 @@
 	"Update and Copy Link": "Mettre à jour et copier le lien",
 	"Update password": "Mettre à jour le mot de passe",
 	"Upload a GGUF model": "Téléverser un modèle GGUF",
-	"Upload Files": "Télécharger des fichiers",
+	"Upload Files": "Téléverser des fichiers",
 	"Upload Pipeline": "",
 	"Upload Progress": "Progression du Téléversement",
 	"URL Mode": "Mode URL",

+ 7 - 1
src/lib/i18n/locales/fr-FR/translation.json

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Panneau d'Administration",
 	"Admin Settings": "Paramètres d'Administration",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Paramètres Avancés",
 	"Advanced Params": "Params Avancés",
 	"all": "tous",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Exporter la Correspondance des Documents",
 	"Export Models": "Exporter les Modèles",
 	"Export Prompts": "Exporter les Prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Échec de la création de la clé d'API.",
 	"Failed to read clipboard contents": "Échec de la lecture du contenu du presse-papiers",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importer la Correspondance des Documents",
 	"Import Models": "Importer des Modèles",
 	"Import Prompts": "Importer des Prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Inclure le drapeau `--api` lors de l'exécution de stable-diffusion-webui",
 	"Info": "Info",
 	"Input commands": "Entrez les commandes d'entrée",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Nombre de résultats de recherche",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Recherché {{count}} sites_one",
 	"Searched {{count}} sites_many": "Recherché {{count}} sites_many",
 	"Searched {{count}} sites_other": "Recherché {{count}} sites_other",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "à l'entrée du chat.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Aujourd'hui",
 	"Toggle settings": "Basculer les paramètres",
 	"Toggle sidebar": "Basculer la barre latérale",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problèmes d'accès à Ollama ?",
@@ -527,7 +533,7 @@
 	"Update and Copy Link": "Mettre à Jour et Copier le Lien",
 	"Update password": "Mettre à Jour le Mot de Passe",
 	"Upload a GGUF model": "Téléverser un modèle GGUF",
-	"Upload Files": "Télécharger des fichiers",
+	"Upload Files": "Téléverser des fichiers",
 	"Upload Pipeline": "",
 	"Upload Progress": "Progression du Téléversement",
 	"URL Mode": "Mode URL",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "לוח בקרה למנהל",
 	"Admin Settings": "הגדרות מנהל",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "פרמטרים מתקדמים",
 	"Advanced Params": "פרמטרים מתקדמים",
 	"all": "הכל",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "ייצוא מיפוי מסמכים",
 	"Export Models": "ייצוא מודלים",
 	"Export Prompts": "ייצוא פקודות",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "יצירת מפתח API נכשלה.",
 	"Failed to read clipboard contents": "קריאת תוכן הלוח נכשלה",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "יבוא מיפוי מסמכים",
 	"Import Models": "ייבוא דגמים",
 	"Import Prompts": "יבוא פקודות",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "כלול את הדגל `--api` בעת הרצת stable-diffusion-webui",
 	"Info": "מידע",
 	"Input commands": "פקודות קלט",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "ספירת תוצאות חיפוש",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "חיפש {{count}} sites_one",
 	"Searched {{count}} sites_two": "חיפש {{count}} sites_two",
 	"Searched {{count}} sites_other": "חיפש {{count}} sites_other",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "לקלטת שיחה.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "היום",
 	"Toggle settings": "החלפת מצב של הגדרות",
 	"Toggle sidebar": "החלפת מצב של סרגל הצד",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "קשה לגשת לOllama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "व्यवस्थापक पैनल",
 	"Admin Settings": "व्यवस्थापक सेटिंग्स",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "उन्नत पैरामीटर",
 	"Advanced Params": "उन्नत परम",
 	"all": "सभी",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "निर्यात दस्तावेज़ मैपिंग",
 	"Export Models": "निर्यात मॉडल",
 	"Export Prompts": "प्रॉम्प्ट निर्यात करें",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "एपीआई कुंजी बनाने में विफल.",
 	"Failed to read clipboard contents": "क्लिपबोर्ड सामग्री पढ़ने में विफल",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "दस्तावेज़ मैपिंग आयात करें",
 	"Import Models": "आयात मॉडल",
 	"Import Prompts": "प्रॉम्प्ट आयात करें",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui चलाते समय `--api` ध्वज शामिल करें",
 	"Info": "सूचना-विषयक",
 	"Input commands": "इनपुट क命",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "खोज परिणामों की संख्या",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "{{count}} sites_one खोजा गया",
 	"Searched {{count}} sites_other": "{{count}} sites_other खोजा गया",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "इनपुट चैट करने के लिए.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "आज",
 	"Toggle settings": "सेटिंग्स टॉगल करें",
 	"Toggle sidebar": "साइडबार टॉगल करें",
+	"Tools": "",
 	"Top K": "शीर्ष  K",
 	"Top P": "शीर्ष  P",
 	"Trouble accessing Ollama?": "Ollama तक पहुँचने में परेशानी हो रही है?",

+ 83 - 77
src/lib/i18n/locales/hr-HR/translation.json

@@ -4,7 +4,7 @@
 	"(e.g. `sh webui.sh --api`)": "(npr. `sh webui.sh --api`)",
 	"(latest)": "(najnovije)",
 	"{{ models }}": "{{ modeli }}",
-	"{{ owner }}: You cannot delete a base model": "{{ vlasnik }}: ne možete izbrisati osnovni model",
+	"{{ owner }}: You cannot delete a base model": "{{ owner }}: Ne možete obrisati osnovni model",
 	"{{modelName}} is thinking...": "{{modelName}} razmišlja...",
 	"{{user}}'s Chats": "Razgovori korisnika {{user}}",
 	"{{webUIName}} Backend Required": "{{webUIName}} Backend je potreban",
@@ -14,7 +14,7 @@
 	"Account": "Račun",
 	"Account Activation Pending": "",
 	"Accurate information": "Točne informacije",
-	"Active Users": "",
+	"Active Users": "Aktivni korisnici",
 	"Add": "Dodaj",
 	"Add a model id": "Dodavanje ID-a modela",
 	"Add a short description about what this model does": "Dodajte kratak opis funkcija ovog modela",
@@ -30,17 +30,18 @@
 	"Add User": "Dodaj korisnika",
 	"Adjusting these settings will apply changes universally to all users.": "Podešavanje će se primijeniti univerzalno na sve korisnike.",
 	"admin": "administrator",
-	"Admin": "",
-	"Admin Panel": "Administratorska ploča",
-	"Admin Settings": "Administratorske postavke",
+	"Admin": "Admin",
+	"Admin Panel": "Admin ploča",
+	"Admin Settings": "Admin postavke",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Napredni parametri",
-	"Advanced Params": "Napredni parami",
+	"Advanced Params": "Napredni parametri",
 	"all": "sve",
 	"All Documents": "Svi dokumenti",
 	"All Users": "Svi korisnici",
 	"Allow": "Dopusti",
 	"Allow Chat Deletion": "Dopusti brisanje razgovora",
-	"Allow non-local voices": "",
+	"Allow non-local voices": "Dopusti nelokalne glasove",
 	"alphanumeric characters and hyphens": "alfanumerički znakovi i crtice",
 	"Already have an account?": "Već imate račun?",
 	"an assistant": "asistent",
@@ -52,7 +53,7 @@
 	"API keys": "API ključevi",
 	"April": "Travanj",
 	"Archive": "Arhiva",
-	"Archive All Chats": "Arhiviraj sve razgovore",
+	"Archive All Chats": "Arhivirajte sve razgovore",
 	"Archived Chats": "Arhivirani razgovori",
 	"are allowed - Activate this command by typing": "su dopušteni - Aktivirajte ovu naredbu upisivanjem",
 	"Are you sure?": "Jeste li sigurni?",
@@ -66,15 +67,15 @@
 	"available!": "dostupno!",
 	"Back": "Natrag",
 	"Bad Response": "Loš odgovor",
-	"Banners": "Bannere",
-	"Base Model (From)": "Osnovni model (šalje)",
+	"Banners": "Baneri",
+	"Base Model (From)": "Osnovni model (Od)",
 	"before": "prije",
 	"Being lazy": "Biti lijen",
-	"Brave Search API Key": "Ključ API-ja za hrabro pretraživanje",
+	"Brave Search API Key": "Brave tražilica - API ključ",
 	"Bypass SSL verification for Websites": "Zaobiđi SSL provjeru za web stranice",
-	"Call": "",
-	"Call feature is not supported when using Web STT engine": "",
-	"Camera": "",
+	"Call": "Poziv",
+	"Call feature is not supported when using Web STT engine": "Značajka poziva nije podržana kada se koristi Web STT mehanizam",
+	"Camera": "Kamera",
 	"Cancel": "Otkaži",
 	"Capabilities": "Mogućnosti",
 	"Change Password": "Promijeni lozinku",
@@ -92,7 +93,7 @@
 	"Chunk Params": "Parametri dijelova",
 	"Chunk Size": "Veličina dijela",
 	"Citation": "Citiranje",
-	"Clear memory": "",
+	"Clear memory": "Očisti memoriju",
 	"Click here for help.": "Kliknite ovdje za pomoć.",
 	"Click here to": "Kliknite ovdje za",
 	"Click here to select": "Kliknite ovdje za odabir",
@@ -101,7 +102,7 @@
 	"Click here to select documents.": "Kliknite ovdje da odaberete dokumente.",
 	"click here.": "kliknite ovdje.",
 	"Click on the user role button to change a user's role.": "Kliknite na gumb uloge korisnika za promjenu uloge korisnika.",
-	"Clone": "Klon",
+	"Clone": "Kloniraj",
 	"Close": "Zatvori",
 	"Collection": "Kolekcija",
 	"ComfyUI": "ComfyUI",
@@ -111,7 +112,7 @@
 	"Concurrent Requests": "Istodobni zahtjevi",
 	"Confirm Password": "Potvrdite lozinku",
 	"Connections": "Povezivanja",
-	"Contact Admin for WebUI Access": "",
+	"Contact Admin for WebUI Access": "Kontaktirajte admina za WebUI pristup",
 	"Content": "Sadržaj",
 	"Context Length": "Dužina konteksta",
 	"Continue Response": "Nastavi odgovor",
@@ -121,7 +122,7 @@
 	"Copy last response": "Kopiraj zadnji odgovor",
 	"Copy Link": "Kopiraj vezu",
 	"Copying to clipboard was successful!": "Kopiranje u međuspremnik je uspješno!",
-	"Create a model": "Stvaranje modela",
+	"Create a model": "Izradite model",
 	"Create Account": "Stvori račun",
 	"Create new key": "Stvori novi ključ",
 	"Create new secret key": "Stvori novi tajni ključ",
@@ -132,7 +133,7 @@
 	"Custom": "Prilagođeno",
 	"Customize models for a specific purpose": "Prilagodba modela za određenu svrhu",
 	"Dark": "Tamno",
-	"Dashboard": "",
+	"Dashboard": "Radna ploča",
 	"Database": "Baza podataka",
 	"December": "Prosinac",
 	"Default": "Zadano",
@@ -158,11 +159,11 @@
 	"Discover a prompt": "Otkrijte prompt",
 	"Discover, download, and explore custom prompts": "Otkrijte, preuzmite i istražite prilagođene prompte",
 	"Discover, download, and explore model presets": "Otkrijte, preuzmite i istražite unaprijed postavljene modele",
-	"Dismissible": "",
+	"Dismissible": "Odbaciti",
 	"Display the username instead of You in the Chat": "Prikaži korisničko ime umjesto Vas u razgovoru",
 	"Document": "Dokument",
 	"Document Settings": "Postavke dokumenta",
-	"Documentation": "",
+	"Documentation": "Dokumentacija",
 	"Documents": "Dokumenti",
 	"does not make any external connections, and your data stays securely on your locally hosted server.": "ne uspostavlja vanjske veze, a vaši podaci ostaju sigurno na vašem lokalno hostiranom poslužitelju.",
 	"Don't Allow": "Ne dopuštaj",
@@ -177,7 +178,7 @@
 	"Edit Doc": "Uredi dokument",
 	"Edit User": "Uredi korisnika",
 	"Email": "Email",
-	"Embedding Batch Size": "",
+	"Embedding Batch Size": "Embedding - Veličina batch-a",
 	"Embedding Model": "Embedding model",
 	"Embedding Model Engine": "Embedding model pogon",
 	"Embedding model set to \"{{embedding_model}}\"": "Embedding model postavljen na \"{{embedding_model}}\"",
@@ -188,7 +189,7 @@
 	"Ensure your CSV file includes 4 columns in this order: Name, Email, Password, Role.": "Provjerite da vaša CSV datoteka uključuje 4 stupca u ovom redoslijedu: Name, Email, Password, Role.",
 	"Enter {{role}} message here": "Unesite {{role}} poruku ovdje",
 	"Enter a detail about yourself for your LLMs to recall": "Unesite pojedinosti o sebi da bi učitali memoriju u LLM",
-	"Enter Brave Search API Key": "Unesite ključ API-ja za hrabro pretraživanje",
+	"Enter Brave Search API Key": "Unesite Brave Search API ključ",
 	"Enter Chunk Overlap": "Unesite preklapanje dijelova",
 	"Enter Chunk Size": "Unesite veličinu dijela",
 	"Enter Github Raw URL": "Unesite Github sirovi URL",
@@ -199,10 +200,10 @@
 	"Enter model tag (e.g. {{modelTag}})": "Unesite oznaku modela (npr. {{modelTag}})",
 	"Enter Number of Steps (e.g. 50)": "Unesite broj koraka (npr. 50)",
 	"Enter Score": "Unesite ocjenu",
-	"Enter Searxng Query URL": "Unos URL-a upita Searxng",
-	"Enter Serper API Key": "Unesite API ključ serpera",
-	"Enter Serply API Key": "",
-	"Enter Serpstack API Key": "Unesite API ključ Serpstack",
+	"Enter Searxng Query URL": "Unesite URL upita Searxng",
+	"Enter Serper API Key": "Unesite Serper API ključ",
+	"Enter Serply API Key": "Unesite Serply API ključ",
+	"Enter Serpstack API Key": "Unesite Serpstack API ključ",
 	"Enter stop sequence": "Unesite sekvencu zaustavljanja",
 	"Enter Top K": "Unesite Top K",
 	"Enter URL (e.g. http://127.0.0.1:7860/)": "Unesite URL (npr. http://127.0.0.1:7860/)",
@@ -215,15 +216,16 @@
 	"Experimental": "Eksperimentalno",
 	"Export": "Izvoz",
 	"Export All Chats (All Users)": "Izvoz svih razgovora (svi korisnici)",
-	"Export chat (.json)": "",
+	"Export chat (.json)": "Izvoz četa (.json)",
 	"Export Chats": "Izvoz razgovora",
 	"Export Documents Mapping": "Izvoz mapiranja dokumenata",
-	"Export Models": "Izvezi modele",
+	"Export Models": "Izvoz modela",
 	"Export Prompts": "Izvoz prompta",
-	"External Models": "",
+	"Export Tools": "Izvoz alata",
+	"External Models": "Vanjski modeli",
 	"Failed to create API Key.": "Neuspješno stvaranje API ključa.",
 	"Failed to read clipboard contents": "Neuspješno čitanje sadržaja međuspremnika",
-	"Failed to update settings": "",
+	"Failed to update settings": "Greška kod ažuriranja postavki",
 	"February": "Veljača",
 	"Feel free to add specific details": "Slobodno dodajte specifične detalje",
 	"File Mode": "Način datoteke",
@@ -236,7 +238,7 @@
 	"Frequency Penalty": "Kazna za učestalost",
 	"General": "Općenito",
 	"General Settings": "Opće postavke",
-	"Generate Image": "",
+	"Generate Image": "Gneriraj sliku",
 	"Generating search query": "Generiranje upita za pretraživanje",
 	"Generation Info": "Informacije o generaciji",
 	"Good Response": "Dobar odgovor",
@@ -257,11 +259,12 @@
 	"Import Documents Mapping": "Uvoz mapiranja dokumenata",
 	"Import Models": "Uvoz modela",
 	"Import Prompts": "Uvoz prompta",
+	"Import Tools": "Uvoz alata",
 	"Include `--api` flag when running stable-diffusion-webui": "Uključite zastavicu `--api` prilikom pokretanja stable-diffusion-webui",
 	"Info": "Informacije",
 	"Input commands": "Unos naredbi",
 	"Install from Github URL": "Instaliraj s Github URL-a",
-	"Instant Auto-Send After Voice Transcription": "",
+	"Instant Auto-Send After Voice Transcription": "Trenutačno automatsko slanje nakon glasovne transkripcije",
 	"Interface": "Sučelje",
 	"Invalid Tag": "Nevažeća oznaka",
 	"January": "Siječanj",
@@ -274,17 +277,17 @@
 	"JWT Token": "JWT token",
 	"Keep Alive": "Održavanje živim",
 	"Keyboard shortcuts": "Tipkovnički prečaci",
-	"Knowledge": "",
+	"Knowledge": "Znanje",
 	"Language": "Jezik",
 	"Last Active": "Zadnja aktivnost",
 	"Light": "Svijetlo",
-	"Listening...": "",
+	"Listening...": "Slušam...",
 	"LLMs can make mistakes. Verify important information.": "LLM-ovi mogu pogriješiti. Provjerite važne informacije.",
-	"Local Models": "",
+	"Local Models": "Lokalni modeli",
 	"LTR": "LTR",
 	"Made by OpenWebUI Community": "Izradio OpenWebUI Community",
 	"Make sure to enclose them with": "Provjerite da ih zatvorite s",
-	"Manage": "",
+	"Manage": "Upravljaj",
 	"Manage Models": "Upravljanje modelima",
 	"Manage Ollama Models": "Upravljanje Ollama modelima",
 	"Manage Pipelines": "Upravljanje cjevovodima",
@@ -304,13 +307,13 @@
 	"Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' je uspješno preuzet.",
 	"Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je već u redu za preuzimanje.",
 	"Model {{modelId}} not found": "Model {{modelId}} nije pronađen",
-	"Model {{modelName}} is not vision capable": "Model {{modelName}} nije sposoban za vid",
+	"Model {{modelName}} is not vision capable": "Model {{modelName}} ne čita vizualne impute",
 	"Model {{name}} is now {{status}}": "Model {{name}} sada je {{status}}",
 	"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Otkriven put datotečnog sustava modela. Kratko ime modela je potrebno za ažuriranje, nije moguće nastaviti.",
 	"Model ID": "ID modela",
 	"Model not selected": "Model nije odabran",
-	"Model Params": "Model Params",
-	"Model Whitelisting": "Bijela lista modela",
+	"Model Params": "Model parametri",
+	"Model Whitelisting": "Model - Bijela lista",
 	"Model(s) Whitelisted": "Model(i) na bijeloj listi",
 	"Modelfile Content": "Sadržaj datoteke modela",
 	"Models": "Modeli",
@@ -320,11 +323,11 @@
 	"Name your model": "Dodijelite naziv modelu",
 	"New Chat": "Novi razgovor",
 	"New Password": "Nova lozinka",
-	"No documents found": "",
+	"No documents found": "Dokumenti nisu pronađeni",
 	"No results found": "Nema rezultata",
 	"No search query generated": "Nije generiran upit za pretraživanje",
 	"No source available": "Nema dostupnog izvora",
-	"None": "Nijedan",
+	"None": "Ništa",
 	"Not factually correct": "Nije činjenično točno",
 	"Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Napomena: Ako postavite minimalnu ocjenu, pretraga će vratiti samo dokumente s ocjenom većom ili jednakom minimalnoj ocjeni.",
 	"Notifications": "Obavijesti",
@@ -337,7 +340,7 @@
 	"Ollama": "Ollama",
 	"Ollama API": "Ollama API",
 	"Ollama API disabled": "Ollama API je onemogućen",
-	"Ollama API is disabled": "",
+	"Ollama API is disabled": "Ollama API je onemogućen",
 	"Ollama Version": "Ollama verzija",
 	"On": "Uključeno",
 	"Only": "Samo",
@@ -360,12 +363,12 @@
 	"PDF document (.pdf)": "PDF dokument (.pdf)",
 	"PDF Extract Images (OCR)": "PDF izdvajanje slika (OCR)",
 	"pending": "u tijeku",
-	"Permission denied when accessing media devices": "",
-	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing media devices": "Dopuštenje je odbijeno prilikom pristupa medijskim uređajima",
+	"Permission denied when accessing microphone": "Dopuštenje je odbijeno prilikom pristupa mikrofonu",
 	"Permission denied when accessing microphone: {{error}}": "Pristup mikrofonu odbijen: {{error}}",
 	"Personalization": "Prilagodba",
-	"Pipelines": "Cjevovodima",
-	"Pipelines Valves": "Cjevovodi, ventili",
+	"Pipelines": "Cjevovodi",
+	"Pipelines Valves": "Ventili za cjevovode",
 	"Plain text (.txt)": "Običan tekst (.txt)",
 	"Playground": "Igralište",
 	"Positive attitude": "Pozitivan stav",
@@ -384,7 +387,7 @@
 	"Read Aloud": "Čitaj naglas",
 	"Record voice": "Snimanje glasa",
 	"Redirecting you to OpenWebUI Community": "Preusmjeravanje na OpenWebUI zajednicu",
-	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Nazivajte se \"Korisnik\" (npr. \"Korisnik uči španjolski\")",
 	"Refused when it shouldn't have": "Odbijen kada nije trebao biti",
 	"Regenerate": "Regeneriraj",
 	"Release Notes": "Bilješke o izdanju",
@@ -396,14 +399,14 @@
 	"Reranking Model": "Model za ponovno rangiranje",
 	"Reranking model disabled": "Model za ponovno rangiranje onemogućen",
 	"Reranking model set to \"{{reranking_model}}\"": "Model za ponovno rangiranje postavljen na \"{{reranking_model}}\"",
-	"Reset Upload Directory": "",
+	"Reset Upload Directory": "Poništi upload direktorij",
 	"Reset Vector Storage": "Resetiraj pohranu vektora",
 	"Response AutoCopy to Clipboard": "Automatsko kopiranje odgovora u međuspremnik",
 	"Role": "Uloga",
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
-	"Running": "",
+	"Running": "Pokrenuto",
 	"Save": "Spremi",
 	"Save & Create": "Spremi i stvori",
 	"Save & Update": "Spremi i ažuriraj",
@@ -415,37 +418,38 @@
 	"Search a model": "Pretraži model",
 	"Search Chats": "Pretraži razgovore",
 	"Search Documents": "Pretraga dokumenata",
-	"Search Models": "Modeli pretraživanja",
+	"Search Models": "Pretražite modele",
 	"Search Prompts": "Pretraga prompta",
-	"Search Query Generation Prompt": "",
-	"Search Query Generation Prompt Length Threshold": "",
+	"Search Query Generation Prompt": "Upit za generiranje upita za pretraživanje",
+	"Search Query Generation Prompt Length Threshold": "Prag duljine upita za generiranje upita za pretraživanje",
 	"Search Result Count": "Broj rezultata pretraživanja",
+	"Search Tools": "Alati za pretraživanje",
 	"Searched {{count}} sites_one": "Pretraženo {{count}} sites_one",
 	"Searched {{count}} sites_few": "Pretraženo {{count}} sites_few",
 	"Searched {{count}} sites_other": "Pretraženo {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
-	"Searxng Query URL": "URL upita Searxng",
+	"Searxng Query URL": "Searxng URL upita",
 	"See readme.md for instructions": "Pogledajte readme.md za upute",
 	"See what's new": "Pogledajte što je novo",
 	"Seed": "Sjeme",
 	"Select a base model": "Odabir osnovnog modela",
-	"Select a engine": "",
+	"Select a engine": "Odaberite pogon",
 	"Select a mode": "Odaberite način",
 	"Select a model": "Odaberite model",
 	"Select a pipeline": "Odabir kanala",
 	"Select a pipeline url": "Odabir URL-a kanala",
 	"Select an Ollama instance": "Odaberite Ollama instancu",
-	"Select Documents": "",
+	"Select Documents": "Odaberite dokumente",
 	"Select model": "Odaberite model",
-	"Select only one model to call": "",
+	"Select only one model to call": "Odaberite samo jedan model za poziv",
 	"Selected model(s) do not support image inputs": "Odabrani modeli ne podržavaju unose slika",
 	"Send": "Pošalji",
 	"Send a Message": "Pošaljite poruku",
 	"Send message": "Pošalji poruku",
 	"September": "Rujan",
 	"Serper API Key": "Serper API ključ",
-	"Serply API Key": "",
-	"Serpstack API Key": "Tipka API za serpstack",
+	"Serply API Key": "Serply API ključ",
+	"Serpstack API Key": "Serpstack API API ključ",
 	"Server connection verified": "Veza s poslužiteljem potvrđena",
 	"Set as default": "Postavi kao zadano",
 	"Set Default Model": "Postavi zadani model",
@@ -453,11 +457,11 @@
 	"Set Image Size": "Postavi veličinu slike",
 	"Set reranking model (e.g. {{model}})": "Postavi model za ponovno rangiranje (npr. {{model}})",
 	"Set Steps": "Postavi korake",
-	"Set Task Model": "Postavi model zadatka",
+	"Set Task Model": "Postavite model zadatka",
 	"Set Voice": "Postavi glas",
 	"Settings": "Postavke",
 	"Settings saved successfully!": "Postavke su uspješno spremljene!",
-	"Settings updated successfully": "",
+	"Settings updated successfully": "Postavke uspješno ažurirane",
 	"Share": "Podijeli",
 	"Share Chat": "Podijeli razgovor",
 	"Share to OpenWebUI Community": "Podijeli u OpenWebUI zajednici",
@@ -475,7 +479,7 @@
 	"Speech recognition error: {{error}}": "Pogreška prepoznavanja govora: {{error}}",
 	"Speech-to-Text Engine": "Stroj za prepoznavanje govora",
 	"Stop Sequence": "Zaustavi sekvencu",
-	"STT Model": "",
+	"STT Model": "STT model",
 	"STT Settings": "STT postavke",
 	"Submit": "Pošalji",
 	"Subtitle (e.g. about the Roman Empire)": "Podnaslov (npr. o Rimskom carstvu)",
@@ -494,9 +498,9 @@
 	"Thanks for your feedback!": "Hvala na povratnim informacijama!",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Ocjena treba biti vrijednost između 0,0 (0%) i 1,0 (100%).",
 	"Theme": "Tema",
-	"Thinking...": "",
+	"Thinking...": "Razmišljam",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Ovo osigurava da su vaši vrijedni razgovori sigurno spremljeni u bazu podataka. Hvala vam!",
-	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Ovo je eksperimentalna značajka, možda neće funkcionirati prema očekivanjima i podložna je promjenama u bilo kojem trenutku.",
 	"This setting does not sync across browsers or devices.": "Ova postavka se ne sinkronizira između preglednika ili uređaja.",
 	"Thorough explanation": "Detaljno objašnjenje",
 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Savjet: Ažurirajte više mjesta za varijable uzastopno pritiskom na tipku tab u unosu razgovora nakon svake zamjene.",
@@ -508,18 +512,20 @@
 	"to": "do",
 	"To access the available model names for downloading,": "Za pristup dostupnim nazivima modela za preuzimanje,",
 	"To access the GGUF models available for downloading,": "Za pristup GGUF modelima dostupnim za preuzimanje,",
-	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
-	"To add documents here, upload them to the \"Documents\" workspace first.": "",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Za pristup WebUI-u obratite se administratoru. Administratori mogu upravljati statusima korisnika s Admin panela.",
+	"To add documents here, upload them to the \"Documents\" workspace first.": "Da biste ovdje dodali dokumente, prvo ih prenesite u radni prostor \"Dokumenti\".",
 	"to chat input.": "u unos razgovora.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Danas",
 	"Toggle settings": "Prebaci postavke",
 	"Toggle sidebar": "Prebaci bočnu traku",
+	"Tools": "Alati",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problemi s pristupom Ollama?",
-	"TTS Model": "",
+	"TTS Model": "TTS model",
 	"TTS Settings": "TTS postavke",
-	"TTS Voice": "",
+	"TTS Voice": "TTS glas",
 	"Type": "Tip",
 	"Type Hugging Face Resolve (Download) URL": "Upišite Hugging Face Resolve (Download) URL",
 	"Uh-oh! There was an issue connecting to {{provider}}.": "Uh-oh! Pojavio se problem s povezivanjem na {{provider}}.",
@@ -527,8 +533,8 @@
 	"Update and Copy Link": "Ažuriraj i kopiraj vezu",
 	"Update password": "Ažuriraj lozinku",
 	"Upload a GGUF model": "Učitaj GGUF model",
-	"Upload Files": "Prenesi datoteke",
-	"Upload Pipeline": "",
+	"Upload Files": "Prijenos datoteka",
+	"Upload Pipeline": "Prijenos kanala",
 	"Upload Progress": "Napredak učitavanja",
 	"URL Mode": "URL način",
 	"Use '#' in the prompt input to load and select your documents.": "Koristite '#' u unosu prompta za učitavanje i odabir vaših dokumenata.",
@@ -547,31 +553,31 @@
 	"Warning": "Upozorenje",
 	"Warning: If you update or change your embedding model, you will need to re-import all documents.": "Upozorenje: Ako ažurirate ili promijenite svoj model za umetanje, morat ćete ponovno uvesti sve dokumente.",
 	"Web": "Web",
-	"Web API": "",
+	"Web API": "Web API",
 	"Web Loader Settings": "Postavke web učitavanja",
 	"Web Params": "Web parametri",
-	"Web Search": "Web-pretraživanje",
-	"Web Search Engine": "Web-tražilica",
+	"Web Search": "Internet pretraga",
+	"Web Search Engine": "Web tražilica",
 	"Webhook URL": "URL webkuke",
 	"WebUI Add-ons": "Dodaci za WebUI",
 	"WebUI Settings": "WebUI postavke",
 	"WebUI will make requests to": "WebUI će slati zahtjeve na",
 	"What’s New in": "Što je novo u",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Kada je povijest isključena, novi razgovori na ovom pregledniku neće se pojaviti u vašoj povijesti na bilo kojem od vaših uređaja.",
-	"Whisper (Local)": "",
-	"Widescreen Mode": "",
+	"Whisper (Local)": "Whisper (lokalno)",
+	"Widescreen Mode": "Mod širokog zaslona",
 	"Workspace": "Radna ploča",
 	"Write a prompt suggestion (e.g. Who are you?)": "Napišite prijedlog prompta (npr. Tko si ti?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Napišite sažetak u 50 riječi koji sažima [temu ili ključnu riječ].",
 	"Yesterday": "Jučer",
 	"You": "Vi",
-	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Možete personalizirati svoje interakcije s LLM-ima dodavanjem uspomena putem gumba 'Upravljanje' u nastavku, čineći ih korisnijima i prilagođenijima vama.",
 	"You cannot clone a base model": "Ne možete klonirati osnovni model",
 	"You have no archived conversations.": "Nemate arhiviranih razgovora.",
 	"You have shared this chat": "Podijelili ste ovaj razgovor",
 	"You're a helpful assistant.": "Vi ste korisni asistent.",
 	"You're now logged in.": "Sada ste prijavljeni.",
-	"Your account status is currently pending activation.": "",
+	"Your account status is currently pending activation.": "Status vašeg računa trenutno čeka aktivaciju.",
 	"Youtube": "YouTube",
 	"Youtube Loader Settings": "YouTube postavke učitavanja"
 }

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Pannello di amministrazione",
 	"Admin Settings": "Impostazioni amministratore",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Parametri avanzati",
 	"Advanced Params": "Parametri avanzati",
 	"all": "tutti",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Esporta mappatura documenti",
 	"Export Models": "Esporta modelli",
 	"Export Prompts": "Esporta prompt",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Impossibile creare la chiave API.",
 	"Failed to read clipboard contents": "Impossibile leggere il contenuto degli appunti",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importa mappatura documenti",
 	"Import Models": "Importazione di modelli",
 	"Import Prompts": "Importa prompt",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Includi il flag `--api` quando esegui stable-diffusion-webui",
 	"Info": "Informazioni",
 	"Input commands": "Comandi di input",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Conteggio dei risultati della ricerca",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Ricercato {{count}} sites_one",
 	"Searched {{count}} sites_many": "Ricercato {{count}} sites_many",
 	"Searched {{count}} sites_other": "Ricercato {{count}} sites_other",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "all'input della chat.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Oggi",
 	"Toggle settings": "Attiva/disattiva impostazioni",
 	"Toggle sidebar": "Attiva/disattiva barra laterale",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problemi di accesso a Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "管理者パネル",
 	"Admin Settings": "管理者設定",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "詳細パラメーター",
 	"Advanced Params": "高度なパラメータ",
 	"all": "すべて",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "ドキュメントマッピングをエクスポート",
 	"Export Models": "モデルのエクスポート",
 	"Export Prompts": "プロンプトをエクスポート",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "APIキーの作成に失敗しました。",
 	"Failed to read clipboard contents": "クリップボードの内容を読み取れませんでした",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "ドキュメントマッピングをインポート",
 	"Import Models": "モデルのインポート",
 	"Import Prompts": "プロンプトをインポート",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webuiを実行する際に`--api`フラグを含める",
 	"Info": "情報",
 	"Input commands": "入力コマンド",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "検索結果数",
+	"Search Tools": "",
 	"Searched {{count}} sites_other": "{{count}} sites_other検索",
 	"Searching \"{{searchQuery}}\"": "",
 	"Searxng Query URL": "Searxng クエリ URL",
@@ -509,9 +513,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "チャット入力へ。",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "今日",
 	"Toggle settings": "設定を切り替え",
 	"Toggle sidebar": "サイドバーを切り替え",
+	"Tools": "",
 	"Top K": "トップ K",
 	"Top P": "トップ P",
 	"Trouble accessing Ollama?": "Ollama へのアクセスに問題がありますか?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "ადმინ პანელი",
 	"Admin Settings": "ადმინისტრატორის ხელსაწყოები",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "დამატებითი პარამეტრები",
 	"Advanced Params": "მოწინავე პარამები",
 	"all": "ყველა",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "დოკუმენტების კავშირის ექსპორტი",
 	"Export Models": "ექსპორტის მოდელები",
 	"Export Prompts": "მოთხოვნების ექსპორტი",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "API ღილაკის შექმნა ვერ მოხერხდა.",
 	"Failed to read clipboard contents": "ბუფერში შიგთავსის წაკითხვა ვერ მოხერხდა",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "დოკუმენტების კავშირის იმპორტი",
 	"Import Models": "იმპორტის მოდელები",
 	"Import Prompts": "მოთხოვნების იმპორტი",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "ჩართეთ `--api` დროშა stable-diffusion-webui-ის გაშვებისას",
 	"Info": "ინფორმაცია",
 	"Input commands": "შეყვანით ბრძანებებს",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "ძიების შედეგების რაოდენობა",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Searched {{count}} sites_one",
 	"Searched {{count}} sites_other": "Searched {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "ჩატში",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "დღეს",
 	"Toggle settings": "პარამეტრების გადართვა",
 	"Toggle sidebar": "გვერდითი ზოლის გადართვა",
+	"Tools": "",
 	"Top K": "ტოპ K",
 	"Top P": "ტოპ P",
 	"Trouble accessing Ollama?": "Ollama-ს ვერ უკავშირდები?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "관리자 패널",
 	"Admin Settings": "관리자 설정",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "고급 매개변수",
 	"Advanced Params": "고급 매개 변수",
 	"all": "모두",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "문서 매핑 내보내기",
 	"Export Models": "모델 내보내기",
 	"Export Prompts": "프롬프트 내보내기",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "API 키 생성에 실패했습니다.",
 	"Failed to read clipboard contents": "클립보드 내용을 읽는 데 실패했습니다.",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "문서 매핑 가져오기",
 	"Import Models": "모델 가져오기",
 	"Import Prompts": "프롬프트 가져오기",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui를 실행할 때 '--api' 플래그 포함",
 	"Info": "정보",
 	"Input commands": "입력 명령",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "검색 결과 개수",
+	"Search Tools": "",
 	"Searched {{count}} sites_other": "{{count}} sites_other 검색됨",
 	"Searching \"{{searchQuery}}\"": "",
 	"Searxng Query URL": "Searxng 쿼리 URL",
@@ -509,9 +513,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "채팅 입력으로.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "오늘",
 	"Toggle settings": "설정 전환",
 	"Toggle sidebar": "사이드바 전환",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Ollama에 접근하는 데 문제가 있나요?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Administratorių panelė",
 	"Admin Settings": "Administratorių nustatymai",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Gilieji nustatymai",
 	"Advanced Params": "",
 	"all": "visi",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Eksportuoti dokumentų žemėlapį",
 	"Export Models": "",
 	"Export Prompts": "Eksportuoti užklausas",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Nepavyko sukurti API rakto",
 	"Failed to read clipboard contents": "Nepavyko perskaityti kopijuoklės",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importuoti dokumentų žemėlapį",
 	"Import Models": "",
 	"Import Prompts": "Importuoti užklausas",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Pridėti `--api` kai vykdomas stable-diffusion-webui",
 	"Info": "",
 	"Input commands": "Įvesties komandos",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "",
 	"Searched {{count}} sites_few": "",
 	"Searched {{count}} sites_many": "",
@@ -512,9 +516,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "į pokalbio įvestį",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Šiandien",
 	"Toggle settings": "Atverti/užverti parametrus",
 	"Toggle sidebar": "Atverti/užverti šoninį meniu",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problemos prieinant prie Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Administrasjonspanel",
 	"Admin Settings": "Administrasjonsinnstillinger",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Avanserte parametere",
 	"Advanced Params": "Avanserte parametere",
 	"all": "alle",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Eksporter dokumentkartlegging",
 	"Export Models": "Eksporter modeller",
 	"Export Prompts": "Eksporter prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Kunne ikke opprette API-nøkkel.",
 	"Failed to read clipboard contents": "Kunne ikke lese utklippstavleinnhold",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importer dokumentkartlegging",
 	"Import Models": "Importer modeller",
 	"Import Prompts": "Importer prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Inkluder `--api`-flagget når du kjører stable-diffusion-webui",
 	"Info": "Info",
 	"Input commands": "Inntast kommandoer",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Antall søkeresultater",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Søkte på {{count}} side",
 	"Searched {{count}} sites_other": "Søkte på {{count}} sider",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "til chatinput.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "I dag",
 	"Toggle settings": "Veksle innstillinger",
 	"Toggle sidebar": "Veksle sidefelt",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problemer med tilgang til Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Administratieve Paneel",
 	"Admin Settings": "Administratieve Settings",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Geavanceerde Parameters",
 	"Advanced Params": "Geavanceerde parameters",
 	"all": "alle",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Exporteer Documenten Mapping",
 	"Export Models": "Modellen exporteren",
 	"Export Prompts": "Exporteer Prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Kan API Key niet aanmaken.",
 	"Failed to read clipboard contents": "Kan klembord inhoud niet lezen",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importeer Documenten Mapping",
 	"Import Models": "Modellen importeren",
 	"Import Prompts": "Importeer Prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Voeg `--api` vlag toe bij het uitvoeren van stable-diffusion-webui",
 	"Info": "Info",
 	"Input commands": "Voer commando's in",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Aantal zoekresultaten",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Gezocht op {{count}} sites_one",
 	"Searched {{count}} sites_other": "Gezocht op {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "naar chat input.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Vandaag",
 	"Toggle settings": "Wissel instellingen",
 	"Toggle sidebar": "Wissel sidebar",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problemen met toegang tot Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "ਪ੍ਰਬੰਧਕ ਪੈਨਲ",
 	"Admin Settings": "ਪ੍ਰਬੰਧਕ ਸੈਟਿੰਗਾਂ",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "ਉੱਚ ਸਤਰ ਦੇ ਪੈਰਾਮੀਟਰ",
 	"Advanced Params": "ਐਡਵਾਂਸਡ ਪਰਮਜ਼",
 	"all": "ਸਾਰੇ",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "ਡਾਕੂਮੈਂਟ ਮੈਪਿੰਗ ਨਿਰਯਾਤ ਕਰੋ",
 	"Export Models": "ਨਿਰਯਾਤ ਮਾਡਲ",
 	"Export Prompts": "ਪ੍ਰੰਪਟ ਨਿਰਯਾਤ ਕਰੋ",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "API ਕੁੰਜੀ ਬਣਾਉਣ ਵਿੱਚ ਅਸਫਲ।",
 	"Failed to read clipboard contents": "ਕਲਿੱਪਬੋਰਡ ਸਮੱਗਰੀ ਪੜ੍ਹਣ ਵਿੱਚ ਅਸਫਲ",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "ਡਾਕੂਮੈਂਟ ਮੈਪਿੰਗ ਆਯਾਤ ਕਰੋ",
 	"Import Models": "ਮਾਡਲ ਆਯਾਤ ਕਰੋ",
 	"Import Prompts": "ਪ੍ਰੰਪਟ ਆਯਾਤ ਕਰੋ",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "ਸਟੇਬਲ-ਡਿਫਿਊਸ਼ਨ-ਵੈਬਯੂਆਈ ਚਲਾਉਣ ਸਮੇਂ `--api` ਝੰਡਾ ਸ਼ਾਮਲ ਕਰੋ",
 	"Info": "ਜਾਣਕਾਰੀ",
 	"Input commands": "ਇਨਪੁਟ ਕਮਾਂਡਾਂ",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "ਖੋਜ ਨਤੀਜੇ ਦੀ ਗਿਣਤੀ",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "ਖੋਜਿਆ {{count}} sites_one",
 	"Searched {{count}} sites_other": "ਖੋਜਿਆ {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "ਗੱਲਬਾਤ ਇਨਪੁਟ ਲਈ।",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "ਅੱਜ",
 	"Toggle settings": "ਸੈਟਿੰਗਾਂ ਟੌਗਲ ਕਰੋ",
 	"Toggle sidebar": "ਸਾਈਡਬਾਰ ਟੌਗਲ ਕਰੋ",
+	"Tools": "",
 	"Top K": "ਸਿਖਰ K",
 	"Top P": "ਸਿਖਰ P",
 	"Trouble accessing Ollama?": "ਓਲਾਮਾ ਤੱਕ ਪਹੁੰਚਣ ਵਿੱਚ ਮੁਸ਼ਕਲ?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Panel administracyjny",
 	"Admin Settings": "Ustawienia administratora",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Zaawansowane parametry",
 	"Advanced Params": "Zaawansowane parametry",
 	"all": "wszyscy",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Eksportuj mapowanie dokumentów",
 	"Export Models": "Eksportuj modele",
 	"Export Prompts": "Eksportuj prompty",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Nie udało się utworzyć klucza API.",
 	"Failed to read clipboard contents": "Nie udało się odczytać zawartości schowka",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importuj mapowanie dokumentów",
 	"Import Models": "Importowanie modeli",
 	"Import Prompts": "Importuj prompty",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Dołącz flagę `--api` podczas uruchamiania stable-diffusion-webui",
 	"Info": "Informacji",
 	"Input commands": "Wprowadź komendy",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Liczba wyników wyszukiwania",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Wyszukiwano {{count}} sites_one",
 	"Searched {{count}} sites_few": "Wyszukiwano {{count}} sites_few",
 	"Searched {{count}} sites_many": "Wyszukiwano {{count}} sites_many",
@@ -512,9 +516,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "do pola wprowadzania czatu.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Dzisiaj",
 	"Toggle settings": "Przełącz ustawienia",
 	"Toggle sidebar": "Przełącz panel boczny",
+	"Tools": "",
 	"Top K": "Najlepsze K",
 	"Top P": "Najlepsze P",
 	"Trouble accessing Ollama?": "Problemy z dostępem do Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Painel do Administrador",
 	"Admin Settings": "Configurações do Administrador",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Parâmetros Avançados",
 	"Advanced Params": "Params Avançados",
 	"all": "todos",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Exportar Mapeamento de Documentos",
 	"Export Models": "Modelos de Exportação",
 	"Export Prompts": "Exportar Prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Falha ao criar a Chave da API.",
 	"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importar Mapeamento de Documentos",
 	"Import Models": "Modelos de Importação",
 	"Import Prompts": "Importar Prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Inclua a flag `--api` ao executar stable-diffusion-webui",
 	"Info": "Informação",
 	"Input commands": "Comandos de entrada",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Contagem de resultados de pesquisa",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Pesquisado {{count}} sites_one",
 	"Searched {{count}} sites_many": "Pesquisado {{count}} sites_many",
 	"Searched {{count}} sites_other": "Pesquisado {{count}} sites_other",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "para a entrada de bate-papo.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Hoje",
 	"Toggle settings": "Alternar configurações",
 	"Toggle sidebar": "Alternar barra lateral",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problemas para acessar o Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "Admin",
 	"Admin Panel": "Painel do Administrador",
 	"Admin Settings": "Configurações do Administrador",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Parâmetros Avançados",
 	"Advanced Params": "Params Avançados",
 	"all": "todos",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Exportar Mapeamento de Documentos",
 	"Export Models": "Modelos de Exportação",
 	"Export Prompts": "Exportar Prompts",
+	"Export Tools": "",
 	"External Models": "Modelos Externos",
 	"Failed to create API Key.": "Falha ao criar a Chave da API.",
 	"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importar Mapeamento de Documentos",
 	"Import Models": "Importar Modelos",
 	"Import Prompts": "Importar Prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Inclua a flag `--api` ao executar stable-diffusion-webui",
 	"Info": "Informação",
 	"Input commands": "Comandos de entrada",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "Prompt de geração de consulta de pesquisa",
 	"Search Query Generation Prompt Length Threshold": "Limite de comprimento do prompt de geração de consulta de pesquisa",
 	"Search Result Count": "Contagem de resultados da pesquisa",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Pesquisado {{count}} sites_one",
 	"Searched {{count}} sites_many": "Pesquisado {{count}} sites_many",
 	"Searched {{count}} sites_other": "Pesquisado {{count}} sites_other",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Para aceder ao WebUI, entre em contato com o administrador. Os administradores podem gerir o status dos utilizadores no Painel de Administração.",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "para a entrada da conversa.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Hoje",
 	"Toggle settings": "Alternar configurações",
 	"Toggle sidebar": "Alternar barra lateral",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Problemas a aceder ao Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Панель админ",
 	"Admin Settings": "Настройки админ",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Расширенные Параметры",
 	"Advanced Params": "Расширенные параметры",
 	"all": "всё",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Экспортировать отображение документов",
 	"Export Models": "Экспорт моделей",
 	"Export Prompts": "Экспортировать промты",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Не удалось создать ключ API.",
 	"Failed to read clipboard contents": "Не удалось прочитать содержимое буфера обмена",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Импорт сопоставления документов",
 	"Import Models": "Импорт моделей",
 	"Import Prompts": "Импорт подсказок",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Добавьте флаг `--api` при запуске stable-diffusion-webui",
 	"Info": "Информация",
 	"Input commands": "Введите команды",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Количество результатов поиска",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Поиск {{count}} sites_one",
 	"Searched {{count}} sites_few": "Поиск {{count}} sites_few",
 	"Searched {{count}} sites_many": "Поиск {{count}} sites_many",
@@ -512,9 +516,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "в чате.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Сегодня",
 	"Toggle settings": "Переключить настройки",
 	"Toggle sidebar": "Переключить боковую панель",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Проблемы с доступом к Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Админ табла",
 	"Admin Settings": "Админ подешавања",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Напредни параметри",
 	"Advanced Params": "Напредни парамови",
 	"all": "сви",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Извези мапирање докумената",
 	"Export Models": "Извези моделе",
 	"Export Prompts": "Извези упите",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Неуспешно стварање API кључа.",
 	"Failed to read clipboard contents": "Неуспешно читање садржаја оставе",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Увези мапирање докумената",
 	"Import Models": "Увези моделе",
 	"Import Prompts": "Увези упите",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Укључи `--api` заставицу при покретању stable-diffusion-webui",
 	"Info": "Инфо",
 	"Input commands": "Унеси наредбе",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Број резултата претраге",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Претражио {{цоунт}} ситес_оне",
 	"Searched {{count}} sites_few": "Претражио {{цоунт}} ситес_феw",
 	"Searched {{count}} sites_other": "Претражио {{цоунт}} ситес_отхер",
@@ -511,9 +515,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "у унос ћаскања.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Данас",
 	"Toggle settings": "Пребаци подешавања",
 	"Toggle sidebar": "Пребаци бочну траку",
+	"Tools": "",
 	"Top K": "Топ К",
 	"Top P": "Топ П",
 	"Trouble accessing Ollama?": "Проблеми са приступом Ollama-и?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Administrationspanel",
 	"Admin Settings": "Administratörsinställningar",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Avancerade parametrar",
 	"Advanced Params": "Avancerade parametrar",
 	"all": "alla",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Exportera dokumentmappning",
 	"Export Models": "Exportera modeller",
 	"Export Prompts": "Exportera prompts",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "Misslyckades med att skapa API-nyckel.",
 	"Failed to read clipboard contents": "Misslyckades med att läsa urklippsinnehåll",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Importera dokumentmappning",
 	"Import Models": "Importera modeller",
 	"Import Prompts": "Importera prompts",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Inkludera `--api`-flagga när du kör stabil-diffusion-webui",
 	"Info": "Information",
 	"Input commands": "Indatakommandon",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Antal sökresultat",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Sökte på {{count}} sites_one",
 	"Searched {{count}} sites_other": "Sökte på {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "till chattinmatning.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Idag",
 	"Toggle settings": "Växla inställningar",
 	"Toggle sidebar": "Växla sidofält",
+	"Tools": "",
 	"Top K": "Topp K",
 	"Top P": "Topp P",
 	"Trouble accessing Ollama?": "Problem med att komma åt Ollama?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "",
 	"Admin Settings": "",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "",
 	"Advanced Params": "",
 	"all": "",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "",
 	"Export Models": "",
 	"Export Prompts": "",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "",
 	"Failed to read clipboard contents": "",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "",
 	"Import Models": "",
 	"Import Prompts": "",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "",
 	"Info": "",
 	"Input commands": "",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "",
 	"Searched {{count}} sites_other": "",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "",
 	"Toggle settings": "",
 	"Toggle sidebar": "",
+	"Tools": "",
 	"Top K": "",
 	"Top P": "",
 	"Trouble accessing Ollama?": "",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "Yönetici Paneli",
 	"Admin Settings": "Yönetici Ayarları",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Gelişmiş Parametreler",
 	"Advanced Params": "Gelişmiş Parametreler",
 	"all": "tümü",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Belge Eşlemesini Dışa Aktar",
 	"Export Models": "Modelleri Dışa Aktar",
 	"Export Prompts": "Promptları Dışa Aktar",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "API Anahtarı oluşturulamadı.",
 	"Failed to read clipboard contents": "Pano içeriği okunamadı",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Belge Eşlemesini İçe Aktar",
 	"Import Models": "Modelleri İçe Aktar",
 	"Import Prompts": "Promptları İçe Aktar",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "stable-diffusion-webui çalıştırılırken `--api` bayrağını dahil edin",
 	"Info": "Bilgi",
 	"Input commands": "Giriş komutları",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Arama Sonucu Sayısı",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Arandı {{count}} sites_one",
 	"Searched {{count}} sites_other": "Arandı {{count}} sites_other",
 	"Searching \"{{searchQuery}}\"": "",
@@ -510,9 +514,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "sohbet girişine.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Bugün",
 	"Toggle settings": "Ayarları Aç/Kapat",
 	"Toggle sidebar": "Kenar Çubuğunu Aç/Kapat",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Ollama'ya erişmede sorun mu yaşıyorsunuz?",

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

@@ -33,6 +33,7 @@
 	"Admin": "Адмін",
 	"Admin Panel": "Адмін-панель",
 	"Admin Settings": "Налаштування адміністратора",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Розширені параметри",
 	"Advanced Params": "Розширені параметри",
 	"all": "всі",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "Експортувати відображення документів",
 	"Export Models": "Експорт моделей",
 	"Export Prompts": "Експортувати промти",
+	"Export Tools": "",
 	"External Models": "Зовнішні моделі",
 	"Failed to create API Key.": "Не вдалося створити API ключ.",
 	"Failed to read clipboard contents": "Не вдалося прочитати вміст буфера обміну",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "Імпортувати відображення документів",
 	"Import Models": "Імпорт моделей",
 	"Import Prompts": "Імпортувати промти",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Включіть прапор `--api` при запуску stable-diffusion-webui",
 	"Info": "Інфо",
 	"Input commands": "Команди вводу",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "Кількість результатів пошуку",
+	"Search Tools": "",
 	"Searched {{count}} sites_one": "Переглянуто {{count}} сайт",
 	"Searched {{count}} sites_few": "Переглянуто {{count}} сайти",
 	"Searched {{count}} sites_many": "Переглянуто {{count}} сайтів",
@@ -512,9 +516,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Щоб отримати доступ до веб-інтерфейсу, зверніться до адміністратора. Адміністратори можуть керувати статусами користувачів з Панелі адміністратора.",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "в чаті.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Сьогодні",
 	"Toggle settings": "Переключити налаштування",
 	"Toggle sidebar": "Переключити бокову панель",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Проблеми з доступом до Ollama?",

+ 48 - 42
src/lib/i18n/locales/vi-VN/translation.json

@@ -12,9 +12,9 @@
 	"a user": "người sử dụng",
 	"About": "Giới thiệu",
 	"Account": "Tài khoản",
-	"Account Activation Pending": "",
+	"Account Activation Pending": "Tài khoản đang chờ kích hoạt",
 	"Accurate information": "Thông tin chính xác",
-	"Active Users": "",
+	"Active Users": "Người dùng đang hoạt động",
 	"Add": "Thêm",
 	"Add a model id": "Thêm model id",
 	"Add a short description about what this model does": "Thêm mô tả ngắn về những khả năng của model",
@@ -30,9 +30,10 @@
 	"Add User": "Thêm người dùng",
 	"Adjusting these settings will apply changes universally to all users.": "Các thay đổi cài đặt này sẽ áp dụng cho tất cả người sử dụng.",
 	"admin": "quản trị viên",
-	"Admin": "",
+	"Admin": "Quản trị",
 	"Admin Panel": "Trang Quản trị",
 	"Admin Settings": "Cài đặt hệ thống",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "Các tham số Nâng cao",
 	"Advanced Params": "Các tham số Nâng cao",
 	"all": "tất cả",
@@ -72,8 +73,8 @@
 	"Being lazy": "Lười biếng",
 	"Brave Search API Key": "Khóa API tìm kiếm dũng cảm",
 	"Bypass SSL verification for Websites": "Bỏ qua xác thực SSL cho các trang web",
-	"Call": "",
-	"Call feature is not supported when using Web STT engine": "",
+	"Call": "Gọi",
+	"Call feature is not supported when using Web STT engine": "Tính năng gọi điện không được hỗ trợ khi sử dụng công cụ Web STT",
 	"Camera": "",
 	"Cancel": "Hủy bỏ",
 	"Capabilities": "Năng lực",
@@ -92,12 +93,12 @@
 	"Chunk Params": "Tham số khối (chunk)",
 	"Chunk Size": "Kích thước khối (size)",
 	"Citation": "Trích dẫn",
-	"Clear memory": "",
+	"Clear memory": "Xóa bộ nhớ",
 	"Click here for help.": "Bấm vào đây để được trợ giúp.",
 	"Click here to": "Nhấn vào đây để",
 	"Click here to select": "Bấm vào đây để chọn",
 	"Click here to select a csv file.": "Nhấn vào đây để chọn tệp csv",
-	"Click here to select a py file.": "",
+	"Click here to select a py file.": "Nhấn vào đây để chọn tệp py",
 	"Click here to select documents.": "Bấm vào đây để chọn tài liệu.",
 	"click here.": "bấm vào đây.",
 	"Click on the user role button to change a user's role.": "Bấm vào nút trong cột VAI TRÒ để thay đổi quyền của người sử dụng.",
@@ -111,7 +112,7 @@
 	"Concurrent Requests": "Các truy vấn đồng thời",
 	"Confirm Password": "Xác nhận Mật khẩu",
 	"Connections": "Kết nối",
-	"Contact Admin for WebUI Access": "",
+	"Contact Admin for WebUI Access": "Liên hệ với Quản trị viên để được cấp quyền truy cập",
 	"Content": "Nội dung",
 	"Context Length": "Độ dài ngữ cảnh (Context Length)",
 	"Continue Response": "Tiếp tục trả lời",
@@ -158,11 +159,11 @@
 	"Discover a prompt": "Khám phá thêm prompt mới",
 	"Discover, download, and explore custom prompts": "Tìm kiếm, tải về và khám phá thêm các prompt tùy chỉnh",
 	"Discover, download, and explore model presets": "Tìm kiếm, tải về và khám phá thêm các thiết lập mô hình sẵn",
-	"Dismissible": "",
+	"Dismissible": "Có thể loại bỏ",
 	"Display the username instead of You in the Chat": "Hiển thị tên người sử dụng thay vì 'Bạn' trong nội dung chat",
 	"Document": "Tài liệu",
 	"Document Settings": "Cấu hình kho tài liệu",
-	"Documentation": "",
+	"Documentation": "Tài liệu",
 	"Documents": "Tài liệu",
 	"does not make any external connections, and your data stays securely on your locally hosted server.": "không thực hiện bất kỳ kết nối ngoài nào, và dữ liệu của bạn vẫn được lưu trữ an toàn trên máy chủ lưu trữ cục bộ của bạn.",
 	"Don't Allow": "Không Cho phép",
@@ -201,7 +202,7 @@
 	"Enter Score": "Nhập Score",
 	"Enter Searxng Query URL": "Nhập Query URL cho Searxng",
 	"Enter Serper API Key": "Nhập Serper API Key",
-	"Enter Serply API Key": "",
+	"Enter Serply API Key": "Nhập Serply API Key",
 	"Enter Serpstack API Key": "Nhập Serpstack API Key",
 	"Enter stop sequence": "Nhập stop sequence",
 	"Enter Top K": "Nhập Top K",
@@ -215,15 +216,16 @@
 	"Experimental": "Thử nghiệm",
 	"Export": "Xuất khẩu",
 	"Export All Chats (All Users)": "Tải về tất cả nội dung chat (tất cả mọi người)",
-	"Export chat (.json)": "",
+	"Export chat (.json)": "Tải chat (.json)",
 	"Export Chats": "Tải nội dung chat về máy",
 	"Export Documents Mapping": "Tải cấu trúc tài liệu về máy",
 	"Export Models": "Tải Models về máy",
 	"Export Prompts": "Tải các prompt về máy",
-	"External Models": "",
+	"Export Tools": "",
+	"External Models": "Các model ngoài",
 	"Failed to create API Key.": "Lỗi khởi tạo API Key",
 	"Failed to read clipboard contents": "Không thể đọc nội dung clipboard",
-	"Failed to update settings": "",
+	"Failed to update settings": "Lỗi khi cập nhật các cài đặt",
 	"February": "Tháng 2",
 	"Feel free to add specific details": "Mô tả chi tiết về chất lượng của câu hỏi và phương án trả lời",
 	"File Mode": "Chế độ Tệp văn bản",
@@ -236,7 +238,7 @@
 	"Frequency Penalty": "Hình phạt tần số",
 	"General": "Cài đặt chung",
 	"General Settings": "Cấu hình chung",
-	"Generate Image": "",
+	"Generate Image": "Sinh ảnh",
 	"Generating search query": "Tạo truy vấn tìm kiếm",
 	"Generation Info": "Thông tin chung",
 	"Good Response": "Trả lời tốt",
@@ -257,11 +259,12 @@
 	"Import Documents Mapping": "Nạp cấu trúc tài liệu",
 	"Import Models": "Nạp model",
 	"Import Prompts": "Nạp các prompt lên hệ thống",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "Bao gồm flag `--api` khi chạy stable-diffusion-webui",
 	"Info": "Thông tin",
 	"Input commands": "Nhập các câu lệnh",
 	"Install from Github URL": "Cài đặt từ Github URL",
-	"Instant Auto-Send After Voice Transcription": "",
+	"Instant Auto-Send After Voice Transcription": "Tự động gửi ngay lập tức sau khi phiên dịch giọng nói",
 	"Interface": "Giao diện",
 	"Invalid Tag": "Tag không hợp lệ",
 	"January": "Tháng 1",
@@ -274,17 +277,17 @@
 	"JWT Token": "Token JWT",
 	"Keep Alive": "Giữ kết nối",
 	"Keyboard shortcuts": "Phím tắt",
-	"Knowledge": "",
+	"Knowledge": "Kiến thức",
 	"Language": "Ngôn ngữ",
 	"Last Active": "Truy cập gần nhất",
 	"Light": "Sáng",
-	"Listening...": "",
+	"Listening...": "Đang nghe...",
 	"LLMs can make mistakes. Verify important information.": "Hệ thống có thể tạo ra nội dung không chính xác hoặc sai. Hãy kiểm chứng kỹ lưỡng thông tin trước khi tiếp nhận và sử dụng.",
 	"Local Models": "",
 	"LTR": "LTR",
 	"Made by OpenWebUI Community": "Được tạo bởi Cộng đồng OpenWebUI",
 	"Make sure to enclose them with": "Hãy chắc chắn bao quanh chúng bằng",
-	"Manage": "",
+	"Manage": "Quản lý",
 	"Manage Models": "Quản lý mô hình",
 	"Manage Ollama Models": "Quản lý mô hình với Ollama",
 	"Manage Pipelines": "Quản lý Pipelines",
@@ -320,7 +323,7 @@
 	"Name your model": "Tên model",
 	"New Chat": "Tạo chat mới",
 	"New Password": "Mật khẩu mới",
-	"No documents found": "",
+	"No documents found": "Không tìm thấy tài liệu nào",
 	"No results found": "Không tìm thấy kết quả",
 	"No search query generated": "Không có truy vấn tìm kiếm nào được tạo ra",
 	"No source available": "Không có nguồn",
@@ -337,7 +340,7 @@
 	"Ollama": "Ollama",
 	"Ollama API": "Ollama API",
 	"Ollama API disabled": "API Ollama bị vô hiệu hóa",
-	"Ollama API is disabled": "",
+	"Ollama API is disabled": "Ollama API đang bị vô hiệu hóa",
 	"Ollama Version": "Phiên bản Ollama",
 	"On": "Bật",
 	"Only": "Only",
@@ -360,12 +363,12 @@
 	"PDF document (.pdf)": "Tập tin PDF (.pdf)",
 	"PDF Extract Images (OCR)": "Trích xuất ảnh từ PDF (OCR)",
 	"pending": "đang chờ phê duyệt",
-	"Permission denied when accessing media devices": "",
-	"Permission denied when accessing microphone": "",
+	"Permission denied when accessing media devices": "Quyền truy cập các thiết bị đa phương tiện bị từ chối",
+	"Permission denied when accessing microphone": "Quyền truy cập micrô bị từ chối",
 	"Permission denied when accessing microphone: {{error}}": "Quyền truy cập micrô bị từ chối: {{error}}",
 	"Personalization": "Cá nhân hóa",
-	"Pipelines": "Đường ống",
-	"Pipelines Valves": "Van đường ống",
+	"Pipelines": "",
+	"Pipelines Valves": "",
 	"Plain text (.txt)": "Văn bản thô (.txt)",
 	"Playground": "Thử nghiệm (Playground)",
 	"Positive attitude": "Thái độ tích cực",
@@ -384,7 +387,7 @@
 	"Read Aloud": "Đọc ra loa",
 	"Record voice": "Ghi âm",
 	"Redirecting you to OpenWebUI Community": "Đang chuyển hướng bạn đến Cộng đồng OpenWebUI",
-	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "",
+	"Refer to yourself as \"User\" (e.g., \"User is learning Spanish\")": "Hãy coi bản thân mình như \"Người dùng\" (ví dụ: \"Người dùng đang học Tiếng Tây Ban Nha\")",
 	"Refused when it shouldn't have": "Từ chối trả lời mà nhẽ không nên làm vậy",
 	"Regenerate": "Tạo sinh lại câu trả lời",
 	"Release Notes": "Mô tả những cập nhật mới",
@@ -403,7 +406,7 @@
 	"Rosé Pine": "Rosé Pine",
 	"Rosé Pine Dawn": "Rosé Pine Dawn",
 	"RTL": "RTL",
-	"Running": "",
+	"Running": "Đang chạy",
 	"Save": "Lưu",
 	"Save & Create": "Lưu & Tạo",
 	"Save & Update": "Lưu & Cập nhật",
@@ -417,25 +420,26 @@
 	"Search Documents": "Tìm tài liệu",
 	"Search Models": "Tìm model",
 	"Search Prompts": "Tìm prompt",
-	"Search Query Generation Prompt": "",
-	"Search Query Generation Prompt Length Threshold": "",
+	"Search Query Generation Prompt": "Prompt tạo câu truy vấn, tìm kiếm",
+	"Search Query Generation Prompt Length Threshold": "Ngưỡng độ dài prompt tạo câu truy vấn, tìm kiếm",
 	"Search Result Count": "Số kết quả tìm kiếm",
+	"Search Tools": "",
 	"Searched {{count}} sites_other": "Đã tìm {{count}} sites_other",
-	"Searching \"{{searchQuery}}\"": "",
+	"Searching \"{{searchQuery}}\"": "Đang tìm \"{{searchQuery}}\"",
 	"Searxng Query URL": "URL truy vấn Searxng",
 	"See readme.md for instructions": "Xem readme.md để biết hướng dẫn",
 	"See what's new": "Xem những cập nhật mới",
 	"Seed": "Seed",
 	"Select a base model": "Chọn một base model",
-	"Select a engine": "",
+	"Select a engine": "Chọn dịch vụ",
 	"Select a mode": "Chọn một chế độ",
 	"Select a model": "Chọn mô hình",
 	"Select a pipeline": "Chọn một quy trình",
 	"Select a pipeline url": "Chọn url quy trình",
 	"Select an Ollama instance": "Chọn một thực thể Ollama",
-	"Select Documents": "",
+	"Select Documents": "Chọn tài liệu",
 	"Select model": "Chọn model",
-	"Select only one model to call": "",
+	"Select only one model to call": "Chọn model để gọi",
 	"Selected model(s) do not support image inputs": "Model được lựa chọn không hỗ trợ đầu vào là hình ảnh",
 	"Send": "Gửi",
 	"Send a Message": "Gửi yêu cầu",
@@ -455,13 +459,13 @@
 	"Set Voice": "Đặt Giọng nói",
 	"Settings": "Cài đặt",
 	"Settings saved successfully!": "Cài đặt đã được lưu thành công!",
-	"Settings updated successfully": "",
+	"Settings updated successfully": "Các cài đặt đã được cập nhật thành công",
 	"Share": "Chia sẻ",
 	"Share Chat": "Chia sẻ Chat",
 	"Share to OpenWebUI Community": "Chia sẻ đến Cộng đồng OpenWebUI",
 	"short-summary": "tóm tắt ngắn",
 	"Show": "Hiển thị",
-	"Show Admin Details in Account Pending Overlay": "",
+	"Show Admin Details in Account Pending Overlay": "Hiển thị thông tin của Quản trị viên trên màn hình hiển thị Tài khoản đang chờ xử lý",
 	"Show shortcuts": "Hiển thị phím tắt",
 	"Showcased creativity": "Thể hiện sự sáng tạo",
 	"sidebar": "thanh bên",
@@ -492,9 +496,9 @@
 	"Thanks for your feedback!": "Cám ơn bạn đã gửi phản hồi!",
 	"The score should be a value between 0.0 (0%) and 1.0 (100%).": "Điểm (score) phải có giá trị từ 0,0 (0%) đến 1,0 (100%).",
 	"Theme": "Chủ đề",
-	"Thinking...": "",
+	"Thinking...": "Đang suy luận...",
 	"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "Điều này đảm bảo rằng các nội dung chat có giá trị của bạn được lưu an toàn vào cơ sở dữ liệu backend của bạn. Cảm ơn bạn!",
-	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "",
+	"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Đây là tính năng thử nghiệm, có thể không hoạt động như mong đợi và có thể thay đổi bất kỳ lúc nào.",
 	"This setting does not sync across browsers or devices.": "Cài đặt này không đồng bộ hóa trên các trình duyệt hoặc thiết bị.",
 	"Thorough explanation": "Giải thích kỹ lưỡng",
 	"Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.": "Mẹo: Cập nhật nhiều khe biến liên tiếp bằng cách nhấn phím tab trong đầu vào trò chuyện sau mỗi việc thay thế.",
@@ -506,12 +510,14 @@
 	"to": " - ",
 	"To access the available model names for downloading,": "Để truy cập các tên mô hình có sẵn để tải xuống,",
 	"To access the GGUF models available for downloading,": "Để truy cập các mô hình GGUF có sẵn để tải xuống,",
-	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
-	"To add documents here, upload them to the \"Documents\" workspace first.": "",
+	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Để truy cập vui lòng liên hệ với quản trị viên.",
+	"To add documents here, upload them to the \"Documents\" workspace first.": "Để thêm tài liệu, trước tiên hãy upload chúng lên khu vực \"Tài liệu\".",
 	"to chat input.": "đến đầu vào trò chuyện.",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "Hôm nay",
 	"Toggle settings": "Bật/tắt cài đặt",
 	"Toggle sidebar": "Bật/tắt thanh bên",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "Gặp vấn đề khi truy cập Ollama?",
@@ -557,19 +563,19 @@
 	"What’s New in": "Thông tin mới về",
 	"When history is turned off, new chats on this browser won't appear in your history on any of your devices.": "Khi chế độ lịch sử chat đã tắt, các nội dung chat mới trên trình duyệt này sẽ không xuất hiện trên bất kỳ thiết bị nào của bạn.",
 	"Whisper (Local)": "",
-	"Widescreen Mode": "",
+	"Widescreen Mode": "Chế độ màn hình rộng",
 	"Workspace": "Workspace",
 	"Write a prompt suggestion (e.g. Who are you?)": "Hãy viết một prompt (vd: Bạn là ai?)",
 	"Write a summary in 50 words that summarizes [topic or keyword].": "Viết một tóm tắt trong vòng 50 từ cho [chủ đề hoặc từ khóa].",
 	"Yesterday": "Hôm qua",
 	"You": "Bạn",
-	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "",
+	"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Bạn có thể cá nhân hóa các tương tác của mình với LLM bằng cách thêm bộ nhớ thông qua nút 'Quản lý' bên dưới, làm cho chúng hữu ích hơn và phù hợp với bạn hơn.",
 	"You cannot clone a base model": "Bạn không thể nhân bản base model",
 	"You have no archived conversations.": "Bạn chưa lưu trữ một nội dung chat nào",
 	"You have shared this chat": "Bạn vừa chia sẻ chat này",
 	"You're a helpful assistant.": "Bạn là một trợ lý hữu ích.",
 	"You're now logged in.": "Bạn đã đăng nhập.",
-	"Your account status is currently pending activation.": "",
+	"Your account status is currently pending activation.": "Tài khoản của bạn hiện đang ở trạng thái chờ kích hoạt.",
 	"Youtube": "Youtube",
 	"Youtube Loader Settings": "Cài đặt Youtube Loader"
 }

+ 8 - 2
src/lib/i18n/locales/zh-CN/translation.json

@@ -33,6 +33,7 @@
 	"Admin": "管理员联系方式",
 	"Admin Panel": "管理员面板",
 	"Admin Settings": "管理员设置",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "高级参数",
 	"Advanced Params": "高级参数",
 	"all": "所有",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "导出文档映射",
 	"Export Models": "导出模型",
 	"Export Prompts": "导出提示词",
+	"Export Tools": "导出工具",
 	"External Models": "外部模型",
 	"Failed to create API Key.": "无法创建 API 密钥。",
 	"Failed to read clipboard contents": "无法读取剪贴板内容",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "导入文档映射",
 	"Import Models": "导入模型",
 	"Import Prompts": "导入提示词",
+	"Import Tools": "导入工具",
 	"Include `--api` flag when running stable-diffusion-webui": "运行 stable-diffusion-webui 时包含 `--api` 标志",
 	"Info": "信息",
 	"Input commands": "输入命令",
@@ -420,8 +423,9 @@
 	"Search Query Generation Prompt": "搜索查询生成提示",
 	"Search Query Generation Prompt Length Threshold": "搜索查询生成提示长度阈值",
 	"Search Result Count": "搜索结果数量",
+	"Search Tools": "搜索工具",
 	"Searched {{count}} sites_other": "检索到 {{count}} 个网站",
-	"Searching \"{{searchQuery}}\"": "",
+	"Searching \"{{searchQuery}}\"": "搜索 \"{{searchQuery}}\" 中",
 	"Searxng Query URL": "Searxng 查询 URL",
 	"See readme.md for instructions": "查看 readme.md 以获取说明",
 	"See what's new": "查阅最新更新内容",
@@ -442,7 +446,7 @@
 	"Send message": "发送消息",
 	"September": "九月",
 	"Serper API Key": "Serper API 密钥",
-	"Serply API Key": "",
+	"Serply API Key": "Serply API 密钥",
 	"Serpstack API Key": "Serpstack API 密钥",
 	"Server connection verified": "已验证服务器连接",
 	"Set as default": "设为默认",
@@ -509,9 +513,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "请联系管理员以访问。管理员可以在后台管理面板中管理用户状态。",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "要在此处添加文档,请先将它们上传到工作空间中的“文档”内。",
 	"to chat input.": "到对话输入。",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "今天",
 	"Toggle settings": "切换设置",
 	"Toggle sidebar": "切换侧边栏",
+	"Tools": "工具",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "访问 Ollama 时遇到问题?",

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

@@ -33,6 +33,7 @@
 	"Admin": "",
 	"Admin Panel": "管理員控制台",
 	"Admin Settings": "管理設定",
+	"Admins have access to all tools at all times; users need tools assigned per model in the workspace.": "",
 	"Advanced Parameters": "進階參數",
 	"Advanced Params": "進階參數",
 	"all": "所有",
@@ -220,6 +221,7 @@
 	"Export Documents Mapping": "匯出文件映射",
 	"Export Models": "匯出模型",
 	"Export Prompts": "匯出提示詞",
+	"Export Tools": "",
 	"External Models": "",
 	"Failed to create API Key.": "無法創建 API 金鑰。",
 	"Failed to read clipboard contents": "無法讀取剪貼簿內容",
@@ -257,6 +259,7 @@
 	"Import Documents Mapping": "匯入文件映射",
 	"Import Models": "匯入模型",
 	"Import Prompts": "匯入提示詞",
+	"Import Tools": "",
 	"Include `--api` flag when running stable-diffusion-webui": "在運行 stable-diffusion-webui 時加上 `--api` 標誌",
 	"Info": "資訊",
 	"Input commands": "輸入命令",
@@ -420,6 +423,7 @@
 	"Search Query Generation Prompt": "",
 	"Search Query Generation Prompt Length Threshold": "",
 	"Search Result Count": "搜尋結果數量",
+	"Search Tools": "",
 	"Searched {{count}} sites_other": "掃描 {{count}} 個網站_其他",
 	"Searching \"{{searchQuery}}\"": "",
 	"Searxng Query URL": "Searxng 查詢 URL",
@@ -509,9 +513,11 @@
 	"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "",
 	"To add documents here, upload them to the \"Documents\" workspace first.": "",
 	"to chat input.": "到聊天輸入框來啟動此命令。",
+	"To select toolkits here, add them to the \"Tools\" workspace first.": "",
 	"Today": "今天",
 	"Toggle settings": "切換設定",
 	"Toggle sidebar": "切換側邊欄",
+	"Tools": "",
 	"Top K": "Top K",
 	"Top P": "Top P",
 	"Trouble accessing Ollama?": "存取 Ollama 時遇到問題?",

+ 10 - 16
src/lib/stores/index.ts

@@ -23,24 +23,11 @@ export const chatId = writable('');
 
 export const chats = writable([]);
 export const tags = writable([]);
-export const models: Writable<Model[]> = writable([]);
 
-export const modelfiles = writable([]);
+export const models: Writable<Model[]> = writable([]);
 export const prompts: Writable<Prompt[]> = writable([]);
-export const documents = writable([
-	{
-		collection_name: 'collection_name',
-		filename: 'filename',
-		name: 'name',
-		title: 'title'
-	},
-	{
-		collection_name: 'collection_name1',
-		filename: 'filename1',
-		name: 'name1',
-		title: 'title1'
-	}
-]);
+export const documents: Writable<Document[]> = writable([]);
+export const tools = writable([]);
 
 export const banners: Writable<Banner[]> = writable([]);
 
@@ -135,6 +122,13 @@ type Prompt = {
 	timestamp: number;
 };
 
+type Document = {
+	collection_name: string;
+	filename: string;
+	name: string;
+	title: string;
+};
+
 type Config = {
 	status: boolean;
 	name: string;

+ 11 - 17
src/routes/(app)/+layout.svelte

@@ -8,11 +8,14 @@
 	import { goto } from '$app/navigation';
 
 	import { getModels as _getModels } from '$lib/apis';
-	import { getOllamaVersion } from '$lib/apis/ollama';
-	import { getPrompts } from '$lib/apis/prompts';
+	import { getAllChatTags } from '$lib/apis/chats';
 
+	import { getPrompts } from '$lib/apis/prompts';
 	import { getDocs } from '$lib/apis/documents';
-	import { getAllChatTags } from '$lib/apis/chats';
+	import { getTools } from '$lib/apis/tools';
+
+	import { getBanners } from '$lib/apis/configs';
+	import { getUserSettings } from '$lib/apis/users';
 
 	import {
 		user,
@@ -25,33 +28,21 @@
 		banners,
 		showChangelog,
 		config,
-		showCallOverlay
+		showCallOverlay,
+		tools
 	} from '$lib/stores';
-	import { REQUIRED_OLLAMA_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
-	import { compareVersion } from '$lib/utils';
 
 	import SettingsModal from '$lib/components/chat/SettingsModal.svelte';
 	import Sidebar from '$lib/components/layout/Sidebar.svelte';
-	import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
 	import ChangelogModal from '$lib/components/ChangelogModal.svelte';
-	import Tooltip from '$lib/components/common/Tooltip.svelte';
-	import { getBanners } from '$lib/apis/configs';
-	import { getUserSettings } from '$lib/apis/users';
-	import Help from '$lib/components/layout/Help.svelte';
 	import AccountPending from '$lib/components/layout/Overlay/AccountPending.svelte';
-	import { error } from '@sveltejs/kit';
-	import CallOverlay from '$lib/components/chat/MessageInput/CallOverlay.svelte';
 
 	const i18n = getContext('i18n');
 
-	let ollamaVersion = '';
 	let loaded = false;
-	let showShortcutsButtonElement: HTMLButtonElement;
 	let DB = null;
 	let localDBChats = [];
 
-	let showShortcuts = false;
-
 	const getModels = async () => {
 		return _getModels(localStorage.token);
 	};
@@ -99,6 +90,9 @@
 				(async () => {
 					documents.set(await getDocs(localStorage.token));
 				})(),
+				(async () => {
+					tools.set(await getTools(localStorage.token));
+				})(),
 				(async () => {
 					banners.set(await getBanners(localStorage.token));
 				})(),

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

@@ -14,7 +14,11 @@
 	</title>
 </svelte:head>
 
-<div class=" flex flex-col w-full min-h-screen max-h-screen">
+<div
+	class=" flex flex-col w-full min-h-screen max-h-screen {$showSidebar
+		? 'md:max-w-[calc(100%-260px)]'
+		: ''}"
+>
 	<div class=" px-4 pt-3 mt-0.5 mb-1">
 		<div class=" flex items-center gap-1">
 			<div class="{$showSidebar ? 'md:hidden' : ''} mr-1 self-start flex flex-none items-center">
@@ -61,6 +65,15 @@
 				{$i18n.t('Documents')}
 			</a>
 
+			<a
+				class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/tools')
+					? 'bg-gray-50 dark:bg-gray-850'
+					: ''} transition"
+				href="/workspace/tools"
+			>
+				{$i18n.t('Tools')}
+			</a>
+
 			<a
 				class="min-w-fit rounded-lg p-1.5 px-3 {$page.url.pathname.includes('/workspace/playground')
 					? 'bg-gray-50 dark:bg-gray-850'

+ 24 - 6
src/routes/(app)/workspace/models/create/+page.svelte

@@ -2,7 +2,7 @@
 	import { v4 as uuidv4 } from 'uuid';
 	import { toast } from 'svelte-sonner';
 	import { goto } from '$app/navigation';
-	import { settings, user, config, models } from '$lib/stores';
+	import { settings, user, config, models, tools } from '$lib/stores';
 
 	import { onMount, tick, getContext } from 'svelte';
 	import { addNewModel, getModelById, getModelInfos } from '$lib/apis/models';
@@ -12,6 +12,8 @@
 	import Checkbox from '$lib/components/common/Checkbox.svelte';
 	import Tags from '$lib/components/common/Tags.svelte';
 	import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte';
+	import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte';
+	import { stringify } from 'postcss';
 
 	const i18n = getContext('i18n');
 
@@ -54,16 +56,16 @@
 		vision: true
 	};
 
+	let toolIds = [];
 	let knowledge = [];
 
 	$: if (name) {
 		id = name.replace(/\s+/g, '-').toLowerCase();
 	}
 
-	let baseModel = null;
-	$: {
-		baseModel = $models.find((m) => m.id === info.base_model_id);
-		console.log(baseModel);
+	const addUsage = (base_model_id) => {
+		const baseModel = $models.find((m) => m.id === base_model_id);
+
 		if (baseModel) {
 			if (baseModel.owned_by === 'openai') {
 				capabilities.usage = baseModel.info?.meta?.capabilities?.usage ?? false;
@@ -72,7 +74,7 @@
 			}
 			capabilities = capabilities;
 		}
-	}
+	};
 
 	const submitHandler = async () => {
 		loading = true;
@@ -89,6 +91,14 @@
 			}
 		}
 
+		if (toolIds.length > 0) {
+			info.meta.toolIds = toolIds;
+		} else {
+			if (info.meta.toolIds) {
+				delete info.meta.toolIds;
+			}
+		}
+
 		info.params.stop = params.stop ? params.stop.split(',').filter((s) => s.trim()) : null;
 		Object.keys(info.params).forEach((key) => {
 			if (info.params[key] === '' || info.params[key] === null) {
@@ -155,6 +165,7 @@
 		params.stop = params?.stop ? (params?.stop ?? []).join(',') : null;
 
 		capabilities = { ...capabilities, ...(model?.info?.meta?.capabilities ?? {}) };
+		toolIds = model?.info?.meta?.toolIds ?? [];
 
 		info = {
 			...info,
@@ -360,6 +371,9 @@
 					class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg"
 					placeholder="Select a base model (e.g. llama3, gpt-4o)"
 					bind:value={info.base_model_id}
+					on:change={(e) => {
+						addUsage(e.target.value);
+					}}
 					required
 				>
 					<option value={null} class=" text-gray-900">{$i18n.t('Select a base model')}</option>
@@ -552,6 +566,10 @@
 			<Knowledge bind:knowledge />
 		</div>
 
+		<div class="my-2">
+			<ToolsSelector bind:selectedToolIds={toolIds} tools={$tools} />
+		</div>
+
 		<div class="my-1">
 			<div class="flex w-full justify-between mb-1">
 				<div class=" self-center text-sm font-semibold">{$i18n.t('Capabilities')}</div>

+ 19 - 1
src/routes/(app)/workspace/models/edit/+page.svelte

@@ -5,7 +5,7 @@
 
 	import { onMount, getContext } from 'svelte';
 	import { page } from '$app/stores';
-	import { settings, user, config, models } from '$lib/stores';
+	import { settings, user, config, models, tools } from '$lib/stores';
 	import { splitStream } from '$lib/utils';
 
 	import { getModelInfos, updateModelById } from '$lib/apis/models';
@@ -15,6 +15,7 @@
 	import Checkbox from '$lib/components/common/Checkbox.svelte';
 	import Tags from '$lib/components/common/Tags.svelte';
 	import Knowledge from '$lib/components/workspace/Models/Knowledge.svelte';
+	import ToolsSelector from '$lib/components/workspace/Models/ToolsSelector.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -60,6 +61,7 @@
 	};
 
 	let knowledge = [];
+	let toolIds = [];
 
 	const updateHandler = async () => {
 		loading = true;
@@ -76,6 +78,14 @@
 			}
 		}
 
+		if (toolIds.length > 0) {
+			info.meta.toolIds = toolIds;
+		} else {
+			if (info.meta.toolIds) {
+				delete info.meta.toolIds;
+			}
+		}
+
 		info.params.stop = params.stop ? params.stop.split(',').filter((s) => s.trim()) : null;
 		Object.keys(info.params).forEach((key) => {
 			if (info.params[key] === '' || info.params[key] === null) {
@@ -133,6 +143,10 @@
 					knowledge = [...model?.info?.meta?.knowledge];
 				}
 
+				if (model?.info?.meta?.toolIds) {
+					toolIds = [...model?.info?.meta?.toolIds];
+				}
+
 				if (model?.owned_by === 'openai') {
 					capabilities.usage = false;
 				}
@@ -515,6 +529,10 @@
 				<Knowledge bind:knowledge />
 			</div>
 
+			<div class="my-2">
+				<ToolsSelector bind:selectedToolIds={toolIds} tools={$tools} />
+			</div>
+
 			<div class="my-2">
 				<div class="flex w-full justify-between mb-1">
 					<div class=" self-center text-sm font-semibold">{$i18n.t('Capabilities')}</div>

+ 5 - 0
src/routes/(app)/workspace/tools/+page.svelte

@@ -0,0 +1,5 @@
+<script>
+	import Tools from '$lib/components/workspace/Tools.svelte';
+</script>
+
+<Tools />

+ 57 - 0
src/routes/(app)/workspace/tools/create/+page.svelte

@@ -0,0 +1,57 @@
+<script>
+	import { goto } from '$app/navigation';
+	import { createNewTool, getTools } from '$lib/apis/tools';
+	import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
+	import { tools } from '$lib/stores';
+	import { onMount } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	let mounted = false;
+	let clone = false;
+	let tool = null;
+
+	const saveHandler = async (data) => {
+		console.log(data);
+		const res = await createNewTool(localStorage.token, {
+			id: data.id,
+			name: data.name,
+			meta: data.meta,
+			content: data.content
+		}).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success('Tool created successfully');
+			tools.set(await getTools(localStorage.token));
+
+			await goto('/workspace/tools');
+		}
+	};
+
+	onMount(() => {
+		if (sessionStorage.tool) {
+			tool = JSON.parse(sessionStorage.tool);
+			sessionStorage.removeItem('tool');
+
+			console.log(tool);
+			clone = true;
+		}
+
+		mounted = true;
+	});
+</script>
+
+{#if mounted}
+	<ToolkitEditor
+		id={tool?.id ?? ''}
+		name={tool?.name ?? ''}
+		meta={tool?.meta ?? { description: '' }}
+		content={tool?.content ?? ''}
+		{clone}
+		on:save={(e) => {
+			saveHandler(e.detail);
+		}}
+	/>
+{/if}

+ 66 - 0
src/routes/(app)/workspace/tools/edit/+page.svelte

@@ -0,0 +1,66 @@
+<script>
+	import { goto } from '$app/navigation';
+	import { page } from '$app/stores';
+	import { getToolById, getTools, updateToolById } from '$lib/apis/tools';
+	import Spinner from '$lib/components/common/Spinner.svelte';
+	import ToolkitEditor from '$lib/components/workspace/Tools/ToolkitEditor.svelte';
+	import { tools } from '$lib/stores';
+	import { onMount } from 'svelte';
+	import { toast } from 'svelte-sonner';
+
+	let tool = null;
+
+	const saveHandler = async (data) => {
+		console.log(data);
+		const res = await updateToolById(localStorage.token, tool.id, {
+			id: data.id,
+			name: data.name,
+			meta: data.meta,
+			content: data.content
+		}).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			toast.success('Tool updated successfully');
+			tools.set(await getTools(localStorage.token));
+
+			// await goto('/workspace/tools');
+		}
+	};
+
+	onMount(async () => {
+		console.log('mounted');
+		const id = $page.url.searchParams.get('id');
+
+		if (id) {
+			tool = await getToolById(localStorage.token, id).catch((error) => {
+				toast.error(error);
+				goto('/workspace/tools');
+				return null;
+			});
+
+			console.log(tool);
+		}
+	});
+</script>
+
+{#if tool}
+	<ToolkitEditor
+		edit={true}
+		id={tool.id}
+		name={tool.name}
+		meta={tool.meta}
+		content={tool.content}
+		on:save={(e) => {
+			saveHandler(e.detail);
+		}}
+	/>
+{:else}
+	<div class="flex items-center justify-center h-full">
+		<div class=" pb-16">
+			<Spinner />
+		</div>
+	</div>
+{/if}

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

@@ -1,5 +1,10 @@
 <script>
 	import { io } from 'socket.io-client';
+	import { spring } from 'svelte/motion';
+
+	let loadingProgress = spring(0, {
+		stiffness: 0.05
+	});
 
 	import { onMount, tick, setContext } from 'svelte';
 	import {
@@ -116,8 +121,35 @@
 
 		await tick();
 
-		document.getElementById('splash-screen')?.remove();
-		loaded = true;
+		if (
+			document.documentElement.classList.contains('her') &&
+			document.getElementById('progress-bar')
+		) {
+			loadingProgress.subscribe((value) => {
+				const progressBar = document.getElementById('progress-bar');
+
+				if (progressBar) {
+					progressBar.style.width = `${value * 0.24}rem`;
+				}
+			});
+
+			await loadingProgress.set(100);
+
+			document.getElementById('splash-screen')?.remove();
+
+			const audio = new Audio(`/audio/greeting.mp3`);
+			const playAudio = () => {
+				audio.play();
+				document.removeEventListener('click', playAudio);
+			};
+
+			document.addEventListener('click', playAudio);
+
+			loaded = true;
+		} else {
+			document.getElementById('splash-screen')?.remove();
+			loaded = true;
+		}
 
 		return () => {
 			window.removeEventListener('resize', onResize);

二进制
static/audio/greeting.mp3