Merge remote-tracking branch 'upstream/main'

This commit is contained in:
hurxxxx 2025-02-20 23:08:47 +09:00
commit f865a23a18
97 changed files with 3694 additions and 2152 deletions

View File

@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version: 22
- uses: actions/setup-python@v5
with:
python-version: 3.11

View File

@ -5,6 +5,25 @@ 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.5.15] - 2025-02-20
### Added
- **📄 Full Context Mode for Local Document Search (RAG)**: Toggle full context mode from Admin Settings > Documents to inject entire document content into context, improving accuracy for models with large context windows—ideal for deep context understanding.
- **🌍 Smarter Web Search with Agentic Workflows**: Web searches now intelligently gather and refine multiple relevant terms, similar to RAG handling, delivering significantly better search results for more accurate information retrieval.
- **🔎 Experimental Playwright Support for Web Loader**: Web content retrieval is taken to the next level with Playwright-powered scraping for enhanced accuracy in extracted web data.
- **☁️ Experimental Azure Storage Provider**: Early-stage support for Azure Storage allows more cloud storage flexibility directly within Open WebUI.
- **📊 Improved Jupyter Code Execution with Plots**: Interactive coding now properly displays inline plots, making data visualization more seamless inside chat interactions.
- **⏳ Adjustable Execution Timeout for Jupyter Interpreter**: Customize execution timeout (default: 60s) for Jupyter-based code execution, allowing longer or more constrained execution based on your needs.
- **▶️ "Running..." Indicator for Jupyter Code Execution**: A visual indicator now appears while code execution is in progress, providing real-time status updates on ongoing computations.
- **⚙️ General Backend & Frontend Stability Enhancements**: Extensive refactoring improves reliability, performance, and overall user experience for a more seamless Open WebUI.
- **🌍 Translation Updates**: Various international translation refinements ensure better localization and a more natural user interface experience.
### Fixed
- **📱 Mobile Hover Issue Resolved**: Users can now edit responses smoothly on mobile without interference, fixing a longstanding hover issue.
- **🔄 Temporary Chat Message Duplication Fixed**: Eliminated buggy behavior where messages were being unnecessarily repeated in temporary chat mode, ensuring a smooth and consistent conversation flow.
## [0.5.14] - 2025-02-17
### Fixed

View File

@ -684,6 +684,10 @@ GOOGLE_APPLICATION_CREDENTIALS_JSON = os.environ.get(
"GOOGLE_APPLICATION_CREDENTIALS_JSON", None
)
AZURE_STORAGE_ENDPOINT = os.environ.get("AZURE_STORAGE_ENDPOINT", None)
AZURE_STORAGE_CONTAINER_NAME = os.environ.get("AZURE_STORAGE_CONTAINER_NAME", None)
AZURE_STORAGE_KEY = os.environ.get("AZURE_STORAGE_KEY", None)
####################################
# File Upload DIR
####################################
@ -783,6 +787,9 @@ ENABLE_OPENAI_API = PersistentConfig(
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
OPENAI_API_BASE_URL = os.environ.get("OPENAI_API_BASE_URL", "")
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
GEMINI_API_BASE_URL = os.environ.get("GEMINI_API_BASE_URL", "")
if OPENAI_API_BASE_URL == "":
OPENAI_API_BASE_URL = "https://api.openai.com/v1"
@ -1395,6 +1402,11 @@ CODE_EXECUTION_JUPYTER_AUTH_PASSWORD = PersistentConfig(
os.environ.get("CODE_EXECUTION_JUPYTER_AUTH_PASSWORD", ""),
)
CODE_EXECUTION_JUPYTER_TIMEOUT = PersistentConfig(
"CODE_EXECUTION_JUPYTER_TIMEOUT",
"code_execution.jupyter.timeout",
int(os.environ.get("CODE_EXECUTION_JUPYTER_TIMEOUT", "60")),
)
ENABLE_CODE_INTERPRETER = PersistentConfig(
"ENABLE_CODE_INTERPRETER",
@ -1450,6 +1462,17 @@ CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = PersistentConfig(
),
)
CODE_INTERPRETER_JUPYTER_TIMEOUT = PersistentConfig(
"CODE_INTERPRETER_JUPYTER_TIMEOUT",
"code_interpreter.jupyter.timeout",
int(
os.environ.get(
"CODE_INTERPRETER_JUPYTER_TIMEOUT",
os.environ.get("CODE_EXECUTION_JUPYTER_TIMEOUT", "60"),
)
),
)
DEFAULT_CODE_INTERPRETER_PROMPT = """
#### Tools Available
@ -1571,6 +1594,12 @@ ENABLE_RAG_HYBRID_SEARCH = PersistentConfig(
os.environ.get("ENABLE_RAG_HYBRID_SEARCH", "").lower() == "true",
)
RAG_FULL_CONTEXT = PersistentConfig(
"RAG_FULL_CONTEXT",
"rag.full_context",
os.getenv("RAG_FULL_CONTEXT", "False").lower() == "true",
)
RAG_FILE_MAX_COUNT = PersistentConfig(
"RAG_FILE_MAX_COUNT",
"rag.file.max_count",
@ -1919,12 +1948,36 @@ RAG_WEB_SEARCH_CONCURRENT_REQUESTS = PersistentConfig(
int(os.getenv("RAG_WEB_SEARCH_CONCURRENT_REQUESTS", "10")),
)
RAG_WEB_LOADER_ENGINE = PersistentConfig(
"RAG_WEB_LOADER_ENGINE",
"rag.web.loader.engine",
os.environ.get("RAG_WEB_LOADER_ENGINE", "safe_web"),
)
RAG_WEB_SEARCH_TRUST_ENV = PersistentConfig(
"RAG_WEB_SEARCH_TRUST_ENV",
"rag.web.search.trust_env",
os.getenv("RAG_WEB_SEARCH_TRUST_ENV", False),
)
PLAYWRIGHT_WS_URI = PersistentConfig(
"PLAYWRIGHT_WS_URI",
"rag.web.loader.engine.playwright.ws.uri",
os.environ.get("PLAYWRIGHT_WS_URI", None),
)
FIRECRAWL_API_KEY = PersistentConfig(
"FIRECRAWL_API_KEY",
"firecrawl.api_key",
os.environ.get("FIRECRAWL_API_KEY", ""),
)
FIRECRAWL_API_BASE_URL = PersistentConfig(
"FIRECRAWL_API_BASE_URL",
"firecrawl.api_url",
os.environ.get("FIRECRAWL_API_BASE_URL", "https://api.firecrawl.dev"),
)
####################################
# Images
####################################
@ -2135,6 +2188,17 @@ IMAGES_OPENAI_API_KEY = PersistentConfig(
os.getenv("IMAGES_OPENAI_API_KEY", OPENAI_API_KEY),
)
IMAGES_GEMINI_API_BASE_URL = PersistentConfig(
"IMAGES_GEMINI_API_BASE_URL",
"image_generation.gemini.api_base_url",
os.getenv("IMAGES_GEMINI_API_BASE_URL", GEMINI_API_BASE_URL),
)
IMAGES_GEMINI_API_KEY = PersistentConfig(
"IMAGES_GEMINI_API_KEY",
"image_generation.gemini.api_key",
os.getenv("IMAGES_GEMINI_API_KEY", GEMINI_API_KEY),
)
IMAGE_SIZE = PersistentConfig(
"IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
)

View File

@ -106,6 +106,7 @@ from open_webui.config import (
CODE_EXECUTION_JUPYTER_AUTH,
CODE_EXECUTION_JUPYTER_AUTH_TOKEN,
CODE_EXECUTION_JUPYTER_AUTH_PASSWORD,
CODE_EXECUTION_JUPYTER_TIMEOUT,
ENABLE_CODE_INTERPRETER,
CODE_INTERPRETER_ENGINE,
CODE_INTERPRETER_PROMPT_TEMPLATE,
@ -113,6 +114,7 @@ from open_webui.config import (
CODE_INTERPRETER_JUPYTER_AUTH,
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN,
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD,
CODE_INTERPRETER_JUPYTER_TIMEOUT,
# Image
AUTOMATIC1111_API_AUTH,
AUTOMATIC1111_BASE_URL,
@ -131,6 +133,8 @@ from open_webui.config import (
IMAGE_STEPS,
IMAGES_OPENAI_API_BASE_URL,
IMAGES_OPENAI_API_KEY,
IMAGES_GEMINI_API_BASE_URL,
IMAGES_GEMINI_API_KEY,
# Audio
AUDIO_STT_ENGINE,
AUDIO_STT_MODEL,
@ -145,6 +149,10 @@ from open_webui.config import (
AUDIO_TTS_VOICE,
AUDIO_TTS_AZURE_SPEECH_REGION,
AUDIO_TTS_AZURE_SPEECH_OUTPUT_FORMAT,
PLAYWRIGHT_WS_URI,
FIRECRAWL_API_BASE_URL,
FIRECRAWL_API_KEY,
RAG_WEB_LOADER_ENGINE,
WHISPER_MODEL,
DEEPGRAM_API_KEY,
WHISPER_MODEL_AUTO_UPDATE,
@ -152,6 +160,7 @@ from open_webui.config import (
# Retrieval
RAG_TEMPLATE,
DEFAULT_RAG_TEMPLATE,
RAG_FULL_CONTEXT,
RAG_EMBEDDING_MODEL,
RAG_EMBEDDING_MODEL_AUTO_UPDATE,
RAG_EMBEDDING_MODEL_TRUST_REMOTE_CODE,
@ -515,6 +524,8 @@ app.state.config.RELEVANCE_THRESHOLD = RAG_RELEVANCE_THRESHOLD
app.state.config.FILE_MAX_SIZE = RAG_FILE_MAX_SIZE
app.state.config.FILE_MAX_COUNT = RAG_FILE_MAX_COUNT
app.state.config.RAG_FULL_CONTEXT = RAG_FULL_CONTEXT
app.state.config.ENABLE_RAG_HYBRID_SEARCH = ENABLE_RAG_HYBRID_SEARCH
app.state.config.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION = (
ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION
@ -576,7 +587,11 @@ app.state.config.EXA_API_KEY = EXA_API_KEY
app.state.config.RAG_WEB_SEARCH_RESULT_COUNT = RAG_WEB_SEARCH_RESULT_COUNT
app.state.config.RAG_WEB_SEARCH_CONCURRENT_REQUESTS = RAG_WEB_SEARCH_CONCURRENT_REQUESTS
app.state.config.RAG_WEB_LOADER_ENGINE = RAG_WEB_LOADER_ENGINE
app.state.config.RAG_WEB_SEARCH_TRUST_ENV = RAG_WEB_SEARCH_TRUST_ENV
app.state.config.PLAYWRIGHT_WS_URI = PLAYWRIGHT_WS_URI
app.state.config.FIRECRAWL_API_BASE_URL = FIRECRAWL_API_BASE_URL
app.state.config.FIRECRAWL_API_KEY = FIRECRAWL_API_KEY
app.state.EMBEDDING_FUNCTION = None
app.state.ef = None
@ -631,6 +646,7 @@ app.state.config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN = CODE_EXECUTION_JUPYTER_AUTH
app.state.config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD = (
CODE_EXECUTION_JUPYTER_AUTH_PASSWORD
)
app.state.config.CODE_EXECUTION_JUPYTER_TIMEOUT = CODE_EXECUTION_JUPYTER_TIMEOUT
app.state.config.ENABLE_CODE_INTERPRETER = ENABLE_CODE_INTERPRETER
app.state.config.CODE_INTERPRETER_ENGINE = CODE_INTERPRETER_ENGINE
@ -644,6 +660,7 @@ app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN = (
app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = (
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD
)
app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT = CODE_INTERPRETER_JUPYTER_TIMEOUT
########################################
#
@ -658,6 +675,9 @@ app.state.config.ENABLE_IMAGE_PROMPT_GENERATION = ENABLE_IMAGE_PROMPT_GENERATION
app.state.config.IMAGES_OPENAI_API_BASE_URL = IMAGES_OPENAI_API_BASE_URL
app.state.config.IMAGES_OPENAI_API_KEY = IMAGES_OPENAI_API_KEY
app.state.config.IMAGES_GEMINI_API_BASE_URL = IMAGES_GEMINI_API_BASE_URL
app.state.config.IMAGES_GEMINI_API_KEY = IMAGES_GEMINI_API_KEY
app.state.config.IMAGE_GENERATION_MODEL = IMAGE_GENERATION_MODEL
app.state.config.AUTOMATIC1111_BASE_URL = AUTOMATIC1111_BASE_URL
@ -967,7 +987,7 @@ async def chat_completion(
"files": form_data.get("files", None),
"features": form_data.get("features", None),
"variables": form_data.get("variables", None),
"model": model_info,
"model": model_info.model_dump() if model_info else model,
"direct": model_item.get("direct", False),
**(
{"function_calling": "native"}

View File

@ -84,6 +84,19 @@ def query_doc(
raise e
def get_doc(collection_name: str, user: UserModel = None):
try:
result = VECTOR_DB_CLIENT.get(collection_name=collection_name)
if result:
log.info(f"query_doc:result {result.ids} {result.metadatas}")
return result
except Exception as e:
print(e)
raise e
def query_doc_with_hybrid_search(
collection_name: str,
query: str,
@ -137,6 +150,27 @@ def query_doc_with_hybrid_search(
raise e
def merge_get_results(get_results: list[dict]) -> dict:
# Initialize lists to store combined data
combined_documents = []
combined_metadatas = []
combined_ids = []
for data in get_results:
combined_documents.extend(data["documents"][0])
combined_metadatas.extend(data["metadatas"][0])
combined_ids.extend(data["ids"][0])
# Create the output dictionary
result = {
"documents": [combined_documents],
"metadatas": [combined_metadatas],
"ids": [combined_ids],
}
return result
def merge_and_sort_query_results(
query_results: list[dict], k: int, reverse: bool = False
) -> list[dict]:
@ -144,31 +178,45 @@ def merge_and_sort_query_results(
combined_distances = []
combined_documents = []
combined_metadatas = []
combined_ids = []
for data in query_results:
combined_distances.extend(data["distances"][0])
combined_documents.extend(data["documents"][0])
combined_metadatas.extend(data["metadatas"][0])
# DISTINCT(chunk_id,file_id) - in case if id (chunk_ids) become ordinals
combined_ids.extend(
[
f"{id}-{meta['file_id']}"
for id, meta in zip(data["ids"][0], data["metadatas"][0])
]
)
# Create a list of tuples (distance, document, metadata)
combined = list(zip(combined_distances, combined_documents, combined_metadatas))
# Create a list of tuples (distance, document, metadata, ids)
combined = list(
zip(combined_distances, combined_documents, combined_metadatas, combined_ids)
)
# Sort the list based on distances
combined.sort(key=lambda x: x[0], reverse=reverse)
# We don't have anything :-(
if not combined:
sorted_distances = []
sorted_documents = []
sorted_metadatas = []
else:
sorted_distances = []
sorted_documents = []
sorted_metadatas = []
# Otherwise we don't have anything :-(
if combined:
# Unzip the sorted list
sorted_distances, sorted_documents, sorted_metadatas = zip(*combined)
all_distances, all_documents, all_metadatas, all_ids = zip(*combined)
seen_ids = set()
# Slicing the lists to include only k elements
sorted_distances = list(sorted_distances)[:k]
sorted_documents = list(sorted_documents)[:k]
sorted_metadatas = list(sorted_metadatas)[:k]
for index, id in enumerate(all_ids):
if id not in seen_ids:
sorted_distances.append(all_distances[index])
sorted_documents.append(all_documents[index])
sorted_metadatas.append(all_metadatas[index])
seen_ids.add(id)
if len(sorted_distances) >= k:
break
# Create the output dictionary
result = {
@ -180,6 +228,23 @@ def merge_and_sort_query_results(
return result
def get_all_items_from_collections(collection_names: list[str]) -> dict:
results = []
for collection_name in collection_names:
if collection_name:
try:
result = get_doc(collection_name=collection_name)
if result is not None:
results.append(result.model_dump())
except Exception as e:
log.exception(f"Error when querying the collection: {e}")
else:
pass
return merge_get_results(results)
def query_collection(
collection_names: list[str],
queries: list[str],
@ -297,8 +362,11 @@ def get_sources_from_files(
reranking_function,
r,
hybrid_search,
full_context=False,
):
log.debug(f"files: {files} {queries} {embedding_function} {reranking_function}")
log.debug(
f"files: {files} {queries} {embedding_function} {reranking_function} {full_context}"
)
extracted_collections = []
relevant_contexts = []
@ -336,36 +404,43 @@ def get_sources_from_files(
log.debug(f"skipping {file} as it has already been extracted")
continue
try:
context = None
if file.get("type") == "text":
context = file["content"]
else:
if hybrid_search:
try:
context = query_collection_with_hybrid_search(
if full_context:
try:
context = get_all_items_from_collections(collection_names)
except Exception as e:
log.exception(e)
else:
try:
context = None
if file.get("type") == "text":
context = file["content"]
else:
if hybrid_search:
try:
context = query_collection_with_hybrid_search(
collection_names=collection_names,
queries=queries,
embedding_function=embedding_function,
k=k,
reranking_function=reranking_function,
r=r,
)
except Exception as e:
log.debug(
"Error when using hybrid search, using"
" non hybrid search as fallback."
)
if (not hybrid_search) or (context is None):
context = query_collection(
collection_names=collection_names,
queries=queries,
embedding_function=embedding_function,
k=k,
reranking_function=reranking_function,
r=r,
)
except Exception as e:
log.debug(
"Error when using hybrid search, using"
" non hybrid search as fallback."
)
if (not hybrid_search) or (context is None):
context = query_collection(
collection_names=collection_names,
queries=queries,
embedding_function=embedding_function,
k=k,
)
except Exception as e:
log.exception(e)
except Exception as e:
log.exception(e)
extracted_collections.extend(collection_names)

View File

@ -1,22 +1,38 @@
import socket
import aiohttp
import asyncio
import urllib.parse
import validators
from typing import Any, AsyncIterator, Dict, Iterator, List, Sequence, Union
from langchain_community.document_loaders import (
WebBaseLoader,
)
from langchain_core.documents import Document
from open_webui.constants import ERROR_MESSAGES
from open_webui.config import ENABLE_RAG_LOCAL_WEB_FETCH
from open_webui.env import SRC_LOG_LEVELS
import logging
import socket
import ssl
import urllib.parse
import urllib.request
from collections import defaultdict
from datetime import datetime, time, timedelta
from typing import (
Any,
AsyncIterator,
Dict,
Iterator,
List,
Optional,
Sequence,
Union,
Literal,
)
import aiohttp
import certifi
import validators
from langchain_community.document_loaders import PlaywrightURLLoader, WebBaseLoader
from langchain_community.document_loaders.firecrawl import FireCrawlLoader
from langchain_community.document_loaders.base import BaseLoader
from langchain_core.documents import Document
from open_webui.constants import ERROR_MESSAGES
from open_webui.config import (
ENABLE_RAG_LOCAL_WEB_FETCH,
PLAYWRIGHT_WS_URI,
RAG_WEB_LOADER_ENGINE,
FIRECRAWL_API_BASE_URL,
FIRECRAWL_API_KEY,
)
from open_webui.env import SRC_LOG_LEVELS
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@ -68,6 +84,314 @@ def resolve_hostname(hostname):
return ipv4_addresses, ipv6_addresses
def extract_metadata(soup, url):
metadata = {"source": url}
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.")
return metadata
def verify_ssl_cert(url: str) -> bool:
"""Verify SSL certificate for the given URL."""
if not url.startswith("https://"):
return True
try:
hostname = url.split("://")[-1].split("/")[0]
context = ssl.create_default_context(cafile=certifi.where())
with context.wrap_socket(ssl.socket(), server_hostname=hostname) as s:
s.connect((hostname, 443))
return True
except ssl.SSLError:
return False
except Exception as e:
log.warning(f"SSL verification failed for {url}: {str(e)}")
return False
class SafeFireCrawlLoader(BaseLoader):
def __init__(
self,
web_paths,
verify_ssl: bool = True,
trust_env: bool = False,
requests_per_second: Optional[float] = None,
continue_on_failure: bool = True,
api_key: Optional[str] = None,
api_url: Optional[str] = None,
mode: Literal["crawl", "scrape", "map"] = "crawl",
proxy: Optional[Dict[str, str]] = None,
params: Optional[Dict] = None,
):
"""Concurrent document loader for FireCrawl operations.
Executes multiple FireCrawlLoader instances concurrently using thread pooling
to improve bulk processing efficiency.
Args:
web_paths: List of URLs/paths to process.
verify_ssl: If True, verify SSL certificates.
trust_env: If True, use proxy settings from environment variables.
requests_per_second: Number of requests per second to limit to.
continue_on_failure (bool): If True, continue loading other URLs on failure.
api_key: API key for FireCrawl service. Defaults to None
(uses FIRE_CRAWL_API_KEY environment variable if not provided).
api_url: Base URL for FireCrawl API. Defaults to official API endpoint.
mode: Operation mode selection:
- 'crawl': Website crawling mode (default)
- 'scrape': Direct page scraping
- 'map': Site map generation
proxy: Proxy override settings for the FireCrawl API.
params: The parameters to pass to the Firecrawl API.
Examples include crawlerOptions.
For more details, visit: https://github.com/mendableai/firecrawl-py
"""
proxy_server = proxy.get("server") if proxy else None
if trust_env and not proxy_server:
env_proxies = urllib.request.getproxies()
env_proxy_server = env_proxies.get("https") or env_proxies.get("http")
if env_proxy_server:
if proxy:
proxy["server"] = env_proxy_server
else:
proxy = {"server": env_proxy_server}
self.web_paths = web_paths
self.verify_ssl = verify_ssl
self.requests_per_second = requests_per_second
self.last_request_time = None
self.trust_env = trust_env
self.continue_on_failure = continue_on_failure
self.api_key = api_key
self.api_url = api_url
self.mode = mode
self.params = params
def lazy_load(self) -> Iterator[Document]:
"""Load documents concurrently using FireCrawl."""
for url in self.web_paths:
try:
self._safe_process_url_sync(url)
loader = FireCrawlLoader(
url=url,
api_key=self.api_key,
api_url=self.api_url,
mode=self.mode,
params=self.params,
)
yield from loader.lazy_load()
except Exception as e:
if self.continue_on_failure:
log.exception(e, "Error loading %s", url)
continue
raise e
async def alazy_load(self):
"""Async version of lazy_load."""
for url in self.web_paths:
try:
await self._safe_process_url(url)
loader = FireCrawlLoader(
url=url,
api_key=self.api_key,
api_url=self.api_url,
mode=self.mode,
params=self.params,
)
async for document in loader.alazy_load():
yield document
except Exception as e:
if self.continue_on_failure:
log.exception(e, "Error loading %s", url)
continue
raise e
def _verify_ssl_cert(self, url: str) -> bool:
return verify_ssl_cert(url)
async def _wait_for_rate_limit(self):
"""Wait to respect the rate limit if specified."""
if self.requests_per_second and self.last_request_time:
min_interval = timedelta(seconds=1.0 / self.requests_per_second)
time_since_last = datetime.now() - self.last_request_time
if time_since_last < min_interval:
await asyncio.sleep((min_interval - time_since_last).total_seconds())
self.last_request_time = datetime.now()
def _sync_wait_for_rate_limit(self):
"""Synchronous version of rate limit wait."""
if self.requests_per_second and self.last_request_time:
min_interval = timedelta(seconds=1.0 / self.requests_per_second)
time_since_last = datetime.now() - self.last_request_time
if time_since_last < min_interval:
time.sleep((min_interval - time_since_last).total_seconds())
self.last_request_time = datetime.now()
async def _safe_process_url(self, url: str) -> bool:
"""Perform safety checks before processing a URL."""
if self.verify_ssl and not self._verify_ssl_cert(url):
raise ValueError(f"SSL certificate verification failed for {url}")
await self._wait_for_rate_limit()
return True
def _safe_process_url_sync(self, url: str) -> bool:
"""Synchronous version of safety checks."""
if self.verify_ssl and not self._verify_ssl_cert(url):
raise ValueError(f"SSL certificate verification failed for {url}")
self._sync_wait_for_rate_limit()
return True
class SafePlaywrightURLLoader(PlaywrightURLLoader):
"""Load HTML pages safely with Playwright, supporting SSL verification, rate limiting, and remote browser connection.
Attributes:
web_paths (List[str]): List of URLs to load.
verify_ssl (bool): If True, verify SSL certificates.
trust_env (bool): If True, use proxy settings from environment variables.
requests_per_second (Optional[float]): Number of requests per second to limit to.
continue_on_failure (bool): If True, continue loading other URLs on failure.
headless (bool): If True, the browser will run in headless mode.
proxy (dict): Proxy override settings for the Playwright session.
playwright_ws_url (Optional[str]): WebSocket endpoint URI for remote browser connection.
"""
def __init__(
self,
web_paths: List[str],
verify_ssl: bool = True,
trust_env: bool = False,
requests_per_second: Optional[float] = None,
continue_on_failure: bool = True,
headless: bool = True,
remove_selectors: Optional[List[str]] = None,
proxy: Optional[Dict[str, str]] = None,
playwright_ws_url: Optional[str] = None,
):
"""Initialize with additional safety parameters and remote browser support."""
proxy_server = proxy.get("server") if proxy else None
if trust_env and not proxy_server:
env_proxies = urllib.request.getproxies()
env_proxy_server = env_proxies.get("https") or env_proxies.get("http")
if env_proxy_server:
if proxy:
proxy["server"] = env_proxy_server
else:
proxy = {"server": env_proxy_server}
# We'll set headless to False if using playwright_ws_url since it's handled by the remote browser
super().__init__(
urls=web_paths,
continue_on_failure=continue_on_failure,
headless=headless if playwright_ws_url is None else False,
remove_selectors=remove_selectors,
proxy=proxy,
)
self.verify_ssl = verify_ssl
self.requests_per_second = requests_per_second
self.last_request_time = None
self.playwright_ws_url = playwright_ws_url
self.trust_env = trust_env
def lazy_load(self) -> Iterator[Document]:
"""Safely load URLs synchronously with support for remote browser."""
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
# Use remote browser if ws_endpoint is provided, otherwise use local browser
if self.playwright_ws_url:
browser = p.chromium.connect(self.playwright_ws_url)
else:
browser = p.chromium.launch(headless=self.headless, proxy=self.proxy)
for url in self.urls:
try:
self._safe_process_url_sync(url)
page = browser.new_page()
response = page.goto(url)
if response is None:
raise ValueError(f"page.goto() returned None for url {url}")
text = self.evaluator.evaluate(page, browser, response)
metadata = {"source": url}
yield Document(page_content=text, metadata=metadata)
except Exception as e:
if self.continue_on_failure:
log.exception(e, "Error loading %s", url)
continue
raise e
browser.close()
async def alazy_load(self) -> AsyncIterator[Document]:
"""Safely load URLs asynchronously with support for remote browser."""
from playwright.async_api import async_playwright
async with async_playwright() as p:
# Use remote browser if ws_endpoint is provided, otherwise use local browser
if self.playwright_ws_url:
browser = await p.chromium.connect(self.playwright_ws_url)
else:
browser = await p.chromium.launch(
headless=self.headless, proxy=self.proxy
)
for url in self.urls:
try:
await self._safe_process_url(url)
page = await browser.new_page()
response = await page.goto(url)
if response is None:
raise ValueError(f"page.goto() returned None for url {url}")
text = await self.evaluator.evaluate_async(page, browser, response)
metadata = {"source": url}
yield Document(page_content=text, metadata=metadata)
except Exception as e:
if self.continue_on_failure:
log.exception(e, "Error loading %s", url)
continue
raise e
await browser.close()
def _verify_ssl_cert(self, url: str) -> bool:
return verify_ssl_cert(url)
async def _wait_for_rate_limit(self):
"""Wait to respect the rate limit if specified."""
if self.requests_per_second and self.last_request_time:
min_interval = timedelta(seconds=1.0 / self.requests_per_second)
time_since_last = datetime.now() - self.last_request_time
if time_since_last < min_interval:
await asyncio.sleep((min_interval - time_since_last).total_seconds())
self.last_request_time = datetime.now()
def _sync_wait_for_rate_limit(self):
"""Synchronous version of rate limit wait."""
if self.requests_per_second and self.last_request_time:
min_interval = timedelta(seconds=1.0 / self.requests_per_second)
time_since_last = datetime.now() - self.last_request_time
if time_since_last < min_interval:
time.sleep((min_interval - time_since_last).total_seconds())
self.last_request_time = datetime.now()
async def _safe_process_url(self, url: str) -> bool:
"""Perform safety checks before processing a URL."""
if self.verify_ssl and not self._verify_ssl_cert(url):
raise ValueError(f"SSL certificate verification failed for {url}")
await self._wait_for_rate_limit()
return True
def _safe_process_url_sync(self, url: str) -> bool:
"""Synchronous version of safety checks."""
if self.verify_ssl and not self._verify_ssl_cert(url):
raise ValueError(f"SSL certificate verification failed for {url}")
self._sync_wait_for_rate_limit()
return True
class SafeWebBaseLoader(WebBaseLoader):
"""WebBaseLoader with enhanced error handling for URLs."""
@ -143,20 +467,12 @@ class SafeWebBaseLoader(WebBaseLoader):
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.")
metadata = extract_metadata(soup, path)
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}")
log.exception(e, "Error loading %s", path)
async def alazy_load(self) -> AsyncIterator[Document]:
"""Async lazy load text from the url(s) in web_path."""
@ -179,6 +495,12 @@ class SafeWebBaseLoader(WebBaseLoader):
return [document async for document in self.alazy_load()]
RAG_WEB_LOADER_ENGINES = defaultdict(lambda: SafeWebBaseLoader)
RAG_WEB_LOADER_ENGINES["playwright"] = SafePlaywrightURLLoader
RAG_WEB_LOADER_ENGINES["safe_web"] = SafeWebBaseLoader
RAG_WEB_LOADER_ENGINES["firecrawl"] = SafeFireCrawlLoader
def get_web_loader(
urls: Union[str, Sequence[str]],
verify_ssl: bool = True,
@ -188,10 +510,29 @@ def get_web_loader(
# Check if the URLs are valid
safe_urls = safe_validate_urls([urls] if isinstance(urls, str) else urls)
return SafeWebBaseLoader(
web_path=safe_urls,
verify_ssl=verify_ssl,
requests_per_second=requests_per_second,
continue_on_failure=True,
trust_env=trust_env,
web_loader_args = {
"web_paths": safe_urls,
"verify_ssl": verify_ssl,
"requests_per_second": requests_per_second,
"continue_on_failure": True,
"trust_env": trust_env,
}
if PLAYWRIGHT_WS_URI.value:
web_loader_args["playwright_ws_url"] = PLAYWRIGHT_WS_URI.value
if RAG_WEB_LOADER_ENGINE.value == "firecrawl":
web_loader_args["api_key"] = FIRECRAWL_API_KEY.value
web_loader_args["api_url"] = FIRECRAWL_API_BASE_URL.value
# Create the appropriate WebLoader based on the configuration
WebLoaderClass = RAG_WEB_LOADER_ENGINES[RAG_WEB_LOADER_ENGINE.value]
web_loader = WebLoaderClass(**web_loader_args)
log.debug(
"Using RAG_WEB_LOADER_ENGINE %s for %s URLs",
web_loader.__class__.__name__,
len(safe_urls),
)
return web_loader

View File

@ -37,6 +37,7 @@ from open_webui.config import (
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import (
AIOHTTP_CLIENT_TIMEOUT,
ENV,
SRC_LOG_LEVELS,
DEVICE_TYPE,
@ -266,7 +267,10 @@ async def speech(request: Request, user=Depends(get_verified_user)):
try:
# print(payload)
async with aiohttp.ClientSession() as session:
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
async with aiohttp.ClientSession(
timeout=timeout, trust_env=True
) as session:
async with session.post(
url=f"{request.app.state.config.TTS_OPENAI_API_BASE_URL}/audio/speech",
json=payload,
@ -323,7 +327,10 @@ async def speech(request: Request, user=Depends(get_verified_user)):
)
try:
async with aiohttp.ClientSession() as session:
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
async with aiohttp.ClientSession(
timeout=timeout, trust_env=True
) as session:
async with session.post(
f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
json={
@ -380,7 +387,10 @@ async def speech(request: Request, user=Depends(get_verified_user)):
data = f"""<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="{locale}">
<voice name="{language}">{payload["input"]}</voice>
</speak>"""
async with aiohttp.ClientSession() as session:
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
async with aiohttp.ClientSession(
timeout=timeout, trust_env=True
) as session:
async with session.post(
f"https://{region}.tts.speech.microsoft.com/cognitiveservices/v1",
headers={

View File

@ -546,7 +546,8 @@ async def signout(request: Request, response: Response):
if logout_url:
response.delete_cookie("oauth_id_token")
return RedirectResponse(
url=f"{logout_url}?id_token_hint={oauth_id_token}"
headers=response.headers,
url=f"{logout_url}?id_token_hint={oauth_id_token}",
)
else:
raise HTTPException(

View File

@ -75,6 +75,7 @@ class CodeInterpreterConfigForm(BaseModel):
CODE_EXECUTION_JUPYTER_AUTH: Optional[str]
CODE_EXECUTION_JUPYTER_AUTH_TOKEN: Optional[str]
CODE_EXECUTION_JUPYTER_AUTH_PASSWORD: Optional[str]
CODE_EXECUTION_JUPYTER_TIMEOUT: Optional[int]
ENABLE_CODE_INTERPRETER: bool
CODE_INTERPRETER_ENGINE: str
CODE_INTERPRETER_PROMPT_TEMPLATE: Optional[str]
@ -82,6 +83,7 @@ class CodeInterpreterConfigForm(BaseModel):
CODE_INTERPRETER_JUPYTER_AUTH: Optional[str]
CODE_INTERPRETER_JUPYTER_AUTH_TOKEN: Optional[str]
CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD: Optional[str]
CODE_INTERPRETER_JUPYTER_TIMEOUT: Optional[int]
@router.get("/code_execution", response_model=CodeInterpreterConfigForm)
@ -92,6 +94,7 @@ async def get_code_execution_config(request: Request, user=Depends(get_admin_use
"CODE_EXECUTION_JUPYTER_AUTH": request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH,
"CODE_EXECUTION_JUPYTER_AUTH_TOKEN": request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN,
"CODE_EXECUTION_JUPYTER_AUTH_PASSWORD": request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD,
"CODE_EXECUTION_JUPYTER_TIMEOUT": request.app.state.config.CODE_EXECUTION_JUPYTER_TIMEOUT,
"ENABLE_CODE_INTERPRETER": request.app.state.config.ENABLE_CODE_INTERPRETER,
"CODE_INTERPRETER_ENGINE": request.app.state.config.CODE_INTERPRETER_ENGINE,
"CODE_INTERPRETER_PROMPT_TEMPLATE": request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE,
@ -99,6 +102,7 @@ async def get_code_execution_config(request: Request, user=Depends(get_admin_use
"CODE_INTERPRETER_JUPYTER_AUTH": request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH,
"CODE_INTERPRETER_JUPYTER_AUTH_TOKEN": request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN,
"CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD": request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD,
"CODE_INTERPRETER_JUPYTER_TIMEOUT": request.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT,
}
@ -120,6 +124,9 @@ async def set_code_execution_config(
request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD = (
form_data.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD
)
request.app.state.config.CODE_EXECUTION_JUPYTER_TIMEOUT = (
form_data.CODE_EXECUTION_JUPYTER_TIMEOUT
)
request.app.state.config.ENABLE_CODE_INTERPRETER = form_data.ENABLE_CODE_INTERPRETER
request.app.state.config.CODE_INTERPRETER_ENGINE = form_data.CODE_INTERPRETER_ENGINE
@ -141,6 +148,9 @@ async def set_code_execution_config(
request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD = (
form_data.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD
)
request.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT = (
form_data.CODE_INTERPRETER_JUPYTER_TIMEOUT
)
return {
"CODE_EXECUTION_ENGINE": request.app.state.config.CODE_EXECUTION_ENGINE,
@ -148,6 +158,7 @@ async def set_code_execution_config(
"CODE_EXECUTION_JUPYTER_AUTH": request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH,
"CODE_EXECUTION_JUPYTER_AUTH_TOKEN": request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN,
"CODE_EXECUTION_JUPYTER_AUTH_PASSWORD": request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD,
"CODE_EXECUTION_JUPYTER_TIMEOUT": request.app.state.config.CODE_EXECUTION_JUPYTER_TIMEOUT,
"ENABLE_CODE_INTERPRETER": request.app.state.config.ENABLE_CODE_INTERPRETER,
"CODE_INTERPRETER_ENGINE": request.app.state.config.CODE_INTERPRETER_ENGINE,
"CODE_INTERPRETER_PROMPT_TEMPLATE": request.app.state.config.CODE_INTERPRETER_PROMPT_TEMPLATE,
@ -155,6 +166,7 @@ async def set_code_execution_config(
"CODE_INTERPRETER_JUPYTER_AUTH": request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH,
"CODE_INTERPRETER_JUPYTER_AUTH_TOKEN": request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN,
"CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD": request.app.state.config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD,
"CODE_INTERPRETER_JUPYTER_TIMEOUT": request.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT,
}

View File

@ -225,17 +225,24 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user)):
filename = file.meta.get("name", file.filename)
encoded_filename = quote(filename) # RFC5987 encoding
content_type = file.meta.get("content_type")
filename = file.meta.get("name", file.filename)
encoded_filename = quote(filename)
headers = {}
if file.meta.get("content_type") not in [
"application/pdf",
"text/plain",
]:
headers = {
**headers,
"Content-Disposition": f"attachment; filename*=UTF-8''{encoded_filename}",
}
return FileResponse(file_path, headers=headers)
if content_type == "application/pdf" or filename.lower().endswith(
".pdf"
):
headers["Content-Disposition"] = (
f"inline; filename*=UTF-8''{encoded_filename}"
)
content_type = "application/pdf"
elif content_type != "text/plain":
headers["Content-Disposition"] = (
f"attachment; filename*=UTF-8''{encoded_filename}"
)
return FileResponse(file_path, headers=headers, media_type=content_type)
else:
raise HTTPException(

View File

@ -55,6 +55,10 @@ async def get_config(request: Request, user=Depends(get_admin_user)):
"COMFYUI_WORKFLOW": request.app.state.config.COMFYUI_WORKFLOW,
"COMFYUI_WORKFLOW_NODES": request.app.state.config.COMFYUI_WORKFLOW_NODES,
},
"gemini": {
"GEMINI_API_BASE_URL": request.app.state.config.IMAGES_GEMINI_API_BASE_URL,
"GEMINI_API_KEY": request.app.state.config.IMAGES_GEMINI_API_KEY,
},
}
@ -78,6 +82,11 @@ class ComfyUIConfigForm(BaseModel):
COMFYUI_WORKFLOW_NODES: list[dict]
class GeminiConfigForm(BaseModel):
GEMINI_API_BASE_URL: str
GEMINI_API_KEY: str
class ConfigForm(BaseModel):
enabled: bool
engine: str
@ -85,6 +94,7 @@ class ConfigForm(BaseModel):
openai: OpenAIConfigForm
automatic1111: Automatic1111ConfigForm
comfyui: ComfyUIConfigForm
gemini: GeminiConfigForm
@router.post("/config/update")
@ -103,6 +113,11 @@ async def update_config(
)
request.app.state.config.IMAGES_OPENAI_API_KEY = form_data.openai.OPENAI_API_KEY
request.app.state.config.IMAGES_GEMINI_API_BASE_URL = (
form_data.gemini.GEMINI_API_BASE_URL
)
request.app.state.config.IMAGES_GEMINI_API_KEY = form_data.gemini.GEMINI_API_KEY
request.app.state.config.AUTOMATIC1111_BASE_URL = (
form_data.automatic1111.AUTOMATIC1111_BASE_URL
)
@ -155,6 +170,10 @@ async def update_config(
"COMFYUI_WORKFLOW": request.app.state.config.COMFYUI_WORKFLOW,
"COMFYUI_WORKFLOW_NODES": request.app.state.config.COMFYUI_WORKFLOW_NODES,
},
"gemini": {
"GEMINI_API_BASE_URL": request.app.state.config.IMAGES_GEMINI_API_BASE_URL,
"GEMINI_API_KEY": request.app.state.config.IMAGES_GEMINI_API_KEY,
},
}
@ -224,6 +243,12 @@ def get_image_model(request):
if request.app.state.config.IMAGE_GENERATION_MODEL
else "dall-e-2"
)
elif request.app.state.config.IMAGE_GENERATION_ENGINE == "gemini":
return (
request.app.state.config.IMAGE_GENERATION_MODEL
if request.app.state.config.IMAGE_GENERATION_MODEL
else "imagen-3.0-generate-002"
)
elif request.app.state.config.IMAGE_GENERATION_ENGINE == "comfyui":
return (
request.app.state.config.IMAGE_GENERATION_MODEL
@ -299,6 +324,10 @@ def get_models(request: Request, user=Depends(get_verified_user)):
{"id": "dall-e-2", "name": "DALL·E 2"},
{"id": "dall-e-3", "name": "DALL·E 3"},
]
elif request.app.state.config.IMAGE_GENERATION_ENGINE == "gemini":
return [
{"id": "imagen-3-0-generate-002", "name": "imagen-3.0 generate-002"},
]
elif request.app.state.config.IMAGE_GENERATION_ENGINE == "comfyui":
# TODO - get models from comfyui
headers = {
@ -483,6 +512,41 @@ async def image_generations(
images.append({"url": url})
return images
elif request.app.state.config.IMAGE_GENERATION_ENGINE == "gemini":
headers = {}
headers["Content-Type"] = "application/json"
headers["x-goog-api-key"] = request.app.state.config.IMAGES_GEMINI_API_KEY
model = get_image_model(request)
data = {
"instances": {"prompt": form_data.prompt},
"parameters": {
"sampleCount": form_data.n,
"outputOptions": {"mimeType": "image/png"},
},
}
# Use asyncio.to_thread for the requests.post call
r = await asyncio.to_thread(
requests.post,
url=f"{request.app.state.config.IMAGES_GEMINI_API_BASE_URL}/models/{model}:predict",
json=data,
headers=headers,
)
r.raise_for_status()
res = r.json()
images = []
for image in res["predictions"]:
image_data, content_type = load_b64_image_data(
image["bytesBase64Encoded"]
)
url = upload_image(request, data, image_data, content_type, user)
images.append({"url": url})
return images
elif request.app.state.config.IMAGE_GENERATION_ENGINE == "comfyui":
data = {
"prompt": form_data.prompt,

View File

@ -26,7 +26,7 @@ from fastapi import (
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, ConfigDict, validator
from starlette.background import BackgroundTask
@ -936,10 +936,23 @@ async def generate_completion(
class ChatMessage(BaseModel):
role: str
content: str
content: Optional[str] = None
tool_calls: Optional[list[dict]] = None
images: Optional[list[str]] = None
@validator("content", pre=True)
@classmethod
def check_at_least_one_field(cls, field_value, values, **kwargs):
# Raise an error if both 'content' and 'tool_calls' are None
if field_value is None and (
"tool_calls" not in values or values["tool_calls"] is None
):
raise ValueError(
"At least one of 'content' or 'tool_calls' must be provided"
)
return field_value
class GenerateChatCompletionForm(BaseModel):
model: str

View File

@ -351,6 +351,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
return {
"status": True,
"pdf_extract_images": request.app.state.config.PDF_EXTRACT_IMAGES,
"RAG_FULL_CONTEXT": request.app.state.config.RAG_FULL_CONTEXT,
"enable_google_drive_integration": request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION,
"content_extraction": {
"engine": request.app.state.config.CONTENT_EXTRACTION_ENGINE,
@ -463,6 +464,7 @@ class WebConfig(BaseModel):
class ConfigUpdateForm(BaseModel):
RAG_FULL_CONTEXT: Optional[bool] = None
pdf_extract_images: Optional[bool] = None
enable_google_drive_integration: Optional[bool] = None
file: Optional[FileConfig] = None
@ -482,6 +484,12 @@ async def update_rag_config(
else request.app.state.config.PDF_EXTRACT_IMAGES
)
request.app.state.config.RAG_FULL_CONTEXT = (
form_data.RAG_FULL_CONTEXT
if form_data.RAG_FULL_CONTEXT is not None
else request.app.state.config.RAG_FULL_CONTEXT
)
request.app.state.config.ENABLE_GOOGLE_DRIVE_INTEGRATION = (
form_data.enable_google_drive_integration
if form_data.enable_google_drive_integration is not None
@ -588,6 +596,7 @@ async def update_rag_config(
return {
"status": True,
"pdf_extract_images": request.app.state.config.PDF_EXTRACT_IMAGES,
"RAG_FULL_CONTEXT": request.app.state.config.RAG_FULL_CONTEXT,
"file": {
"max_size": request.app.state.config.FILE_MAX_SIZE,
"max_count": request.app.state.config.FILE_MAX_COUNT,

View File

@ -56,6 +56,7 @@ async def execute_code(
if request.app.state.config.CODE_EXECUTION_JUPYTER_AUTH == "password"
else None
),
request.app.state.config.CODE_EXECUTION_JUPYTER_TIMEOUT,
)
return output

View File

@ -15,12 +15,18 @@ from open_webui.config import (
S3_SECRET_ACCESS_KEY,
GCS_BUCKET_NAME,
GOOGLE_APPLICATION_CREDENTIALS_JSON,
AZURE_STORAGE_ENDPOINT,
AZURE_STORAGE_CONTAINER_NAME,
AZURE_STORAGE_KEY,
STORAGE_PROVIDER,
UPLOAD_DIR,
)
from google.cloud import storage
from google.cloud.exceptions import GoogleCloudError, NotFound
from open_webui.constants import ERROR_MESSAGES
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
from azure.core.exceptions import ResourceNotFoundError
class StorageProvider(ABC):
@ -221,6 +227,74 @@ class GCSStorageProvider(StorageProvider):
LocalStorageProvider.delete_all_files()
class AzureStorageProvider(StorageProvider):
def __init__(self):
self.endpoint = AZURE_STORAGE_ENDPOINT
self.container_name = AZURE_STORAGE_CONTAINER_NAME
storage_key = AZURE_STORAGE_KEY
if storage_key:
# Configure using the Azure Storage Account Endpoint and Key
self.blob_service_client = BlobServiceClient(
account_url=self.endpoint, credential=storage_key
)
else:
# Configure using the Azure Storage Account Endpoint and DefaultAzureCredential
# If the key is not configured, then the DefaultAzureCredential will be used to support Managed Identity authentication
self.blob_service_client = BlobServiceClient(
account_url=self.endpoint, credential=DefaultAzureCredential()
)
self.container_client = self.blob_service_client.get_container_client(
self.container_name
)
def upload_file(self, file: BinaryIO, filename: str) -> Tuple[bytes, str]:
"""Handles uploading of the file to Azure Blob Storage."""
contents, file_path = LocalStorageProvider.upload_file(file, filename)
try:
blob_client = self.container_client.get_blob_client(filename)
blob_client.upload_blob(contents, overwrite=True)
return contents, f"{self.endpoint}/{self.container_name}/{filename}"
except Exception as e:
raise RuntimeError(f"Error uploading file to Azure Blob Storage: {e}")
def get_file(self, file_path: str) -> str:
"""Handles downloading of the file from Azure Blob Storage."""
try:
filename = file_path.split("/")[-1]
local_file_path = f"{UPLOAD_DIR}/{filename}"
blob_client = self.container_client.get_blob_client(filename)
with open(local_file_path, "wb") as download_file:
download_file.write(blob_client.download_blob().readall())
return local_file_path
except ResourceNotFoundError as e:
raise RuntimeError(f"Error downloading file from Azure Blob Storage: {e}")
def delete_file(self, file_path: str) -> None:
"""Handles deletion of the file from Azure Blob Storage."""
try:
filename = file_path.split("/")[-1]
blob_client = self.container_client.get_blob_client(filename)
blob_client.delete_blob()
except ResourceNotFoundError as e:
raise RuntimeError(f"Error deleting file from Azure Blob Storage: {e}")
# Always delete from local storage
LocalStorageProvider.delete_file(file_path)
def delete_all_files(self) -> None:
"""Handles deletion of all files from Azure Blob Storage."""
try:
blobs = self.container_client.list_blobs()
for blob in blobs:
self.container_client.delete_blob(blob.name)
except Exception as e:
raise RuntimeError(f"Error deleting all files from Azure Blob Storage: {e}")
# Always delete from local storage
LocalStorageProvider.delete_all_files()
def get_storage_provider(storage_provider: str):
if storage_provider == "local":
Storage = LocalStorageProvider()
@ -228,6 +302,8 @@ def get_storage_provider(storage_provider: str):
Storage = S3StorageProvider()
elif storage_provider == "gcs":
Storage = GCSStorageProvider()
elif storage_provider == "azure":
Storage = AzureStorageProvider()
else:
raise RuntimeError(f"Unsupported storage provider: {storage_provider}")
return Storage

View File

@ -7,6 +7,8 @@ from moto import mock_aws
from open_webui.storage import provider
from gcp_storage_emulator.server import create_server
from google.cloud import storage
from azure.storage.blob import BlobServiceClient, ContainerClient, BlobClient
from unittest.mock import MagicMock
def mock_upload_dir(monkeypatch, tmp_path):
@ -22,6 +24,7 @@ def test_imports():
provider.LocalStorageProvider
provider.S3StorageProvider
provider.GCSStorageProvider
provider.AzureStorageProvider
provider.Storage
@ -32,6 +35,8 @@ def test_get_storage_provider():
assert isinstance(Storage, provider.S3StorageProvider)
Storage = provider.get_storage_provider("gcs")
assert isinstance(Storage, provider.GCSStorageProvider)
Storage = provider.get_storage_provider("azure")
assert isinstance(Storage, provider.AzureStorageProvider)
with pytest.raises(RuntimeError):
provider.get_storage_provider("invalid")
@ -48,6 +53,7 @@ def test_class_instantiation():
provider.LocalStorageProvider()
provider.S3StorageProvider()
provider.GCSStorageProvider()
provider.AzureStorageProvider()
class TestLocalStorageProvider:
@ -272,3 +278,147 @@ class TestGCSStorageProvider:
assert not (upload_dir / self.filename_extra).exists()
assert self.Storage.bucket.get_blob(self.filename) == None
assert self.Storage.bucket.get_blob(self.filename_extra) == None
class TestAzureStorageProvider:
def __init__(self):
super().__init__()
@pytest.fixture(scope="class")
def setup_storage(self, monkeypatch):
# Create mock Blob Service Client and related clients
mock_blob_service_client = MagicMock()
mock_container_client = MagicMock()
mock_blob_client = MagicMock()
# Set up return values for the mock
mock_blob_service_client.get_container_client.return_value = (
mock_container_client
)
mock_container_client.get_blob_client.return_value = mock_blob_client
# Monkeypatch the Azure classes to return our mocks
monkeypatch.setattr(
azure.storage.blob,
"BlobServiceClient",
lambda *args, **kwargs: mock_blob_service_client,
)
monkeypatch.setattr(
azure.storage.blob,
"ContainerClient",
lambda *args, **kwargs: mock_container_client,
)
monkeypatch.setattr(
azure.storage.blob, "BlobClient", lambda *args, **kwargs: mock_blob_client
)
self.Storage = provider.AzureStorageProvider()
self.Storage.endpoint = "https://myaccount.blob.core.windows.net"
self.Storage.container_name = "my-container"
self.file_content = b"test content"
self.filename = "test.txt"
self.filename_extra = "test_extra.txt"
self.file_bytesio_empty = io.BytesIO()
# Apply mocks to the Storage instance
self.Storage.blob_service_client = mock_blob_service_client
self.Storage.container_client = mock_container_client
def test_upload_file(self, monkeypatch, tmp_path):
upload_dir = mock_upload_dir(monkeypatch, tmp_path)
# Simulate an error when container does not exist
self.Storage.container_client.get_blob_client.side_effect = Exception(
"Container does not exist"
)
with pytest.raises(Exception):
self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
# Reset side effect and create container
self.Storage.container_client.get_blob_client.side_effect = None
self.Storage.create_container()
contents, azure_file_path = self.Storage.upload_file(
io.BytesIO(self.file_content), self.filename
)
# Assertions
self.Storage.container_client.get_blob_client.assert_called_with(self.filename)
self.Storage.container_client.get_blob_client().upload_blob.assert_called_once_with(
self.file_content, overwrite=True
)
assert contents == self.file_content
assert (
azure_file_path
== f"https://myaccount.blob.core.windows.net/{self.Storage.container_name}/{self.filename}"
)
assert (upload_dir / self.filename).exists()
assert (upload_dir / self.filename).read_bytes() == self.file_content
with pytest.raises(ValueError):
self.Storage.upload_file(self.file_bytesio_empty, self.filename)
def test_get_file(self, monkeypatch, tmp_path):
upload_dir = mock_upload_dir(monkeypatch, tmp_path)
self.Storage.create_container()
# Mock upload behavior
self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
# Mock blob download behavior
self.Storage.container_client.get_blob_client().download_blob().readall.return_value = (
self.file_content
)
file_url = f"https://myaccount.blob.core.windows.net/{self.Storage.container_name}/{self.filename}"
file_path = self.Storage.get_file(file_url)
assert file_path == str(upload_dir / self.filename)
assert (upload_dir / self.filename).exists()
assert (upload_dir / self.filename).read_bytes() == self.file_content
def test_delete_file(self, monkeypatch, tmp_path):
upload_dir = mock_upload_dir(monkeypatch, tmp_path)
self.Storage.create_container()
# Mock file upload
self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
# Mock deletion
self.Storage.container_client.get_blob_client().delete_blob.return_value = None
file_url = f"https://myaccount.blob.core.windows.net/{self.Storage.container_name}/{self.filename}"
self.Storage.delete_file(file_url)
self.Storage.container_client.get_blob_client().delete_blob.assert_called_once()
assert not (upload_dir / self.filename).exists()
def test_delete_all_files(self, monkeypatch, tmp_path):
upload_dir = mock_upload_dir(monkeypatch, tmp_path)
self.Storage.create_container()
# Mock file uploads
self.Storage.upload_file(io.BytesIO(self.file_content), self.filename)
self.Storage.upload_file(io.BytesIO(self.file_content), self.filename_extra)
# Mock listing and deletion behavior
self.Storage.container_client.list_blobs.return_value = [
{"name": self.filename},
{"name": self.filename_extra},
]
self.Storage.container_client.get_blob_client().delete_blob.return_value = None
self.Storage.delete_all_files()
self.Storage.container_client.list_blobs.assert_called_once()
self.Storage.container_client.get_blob_client().delete_blob.assert_any_call()
assert not (upload_dir / self.filename).exists()
assert not (upload_dir / self.filename_extra).exists()
def test_get_file_not_found(self, monkeypatch):
self.Storage.create_container()
file_url = f"https://myaccount.blob.core.windows.net/{self.Storage.container_name}/{self.filename}"
# Mock behavior to raise an error for missing blobs
self.Storage.container_client.get_blob_client().download_blob.side_effect = (
Exception("Blob not found")
)
with pytest.raises(Exception, match="Blob not found"):
self.Storage.get_file(file_url)

View File

@ -321,89 +321,94 @@ async def chat_web_search_handler(
)
return form_data
searchQuery = queries[0]
all_results = []
await event_emitter(
{
"type": "status",
"data": {
"action": "web_search",
"description": 'Searching "{{searchQuery}}"',
"query": searchQuery,
"done": False,
},
}
)
try:
results = await process_web_search(
request,
SearchForm(
**{
for searchQuery in queries:
await event_emitter(
{
"type": "status",
"data": {
"action": "web_search",
"description": 'Searching "{{searchQuery}}"',
"query": searchQuery,
}
),
user,
"done": False,
},
}
)
if results:
await event_emitter(
{
"type": "status",
"data": {
"action": "web_search",
"description": "Searched {{count}} sites",
try:
results = await process_web_search(
request,
SearchForm(
**{
"query": searchQuery,
"urls": results["filenames"],
"done": True,
},
}
}
),
user=user,
)
files = form_data.get("files", [])
if results:
all_results.append(results)
files = form_data.get("files", [])
if request.app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT:
files.append(
{
"docs": results.get("docs", []),
"name": searchQuery,
"type": "web_search_docs",
"urls": results["filenames"],
}
)
else:
files.append(
{
"collection_name": results["collection_name"],
"name": searchQuery,
"type": "web_search_results",
"urls": results["filenames"],
}
)
form_data["files"] = files
else:
if request.app.state.config.RAG_WEB_SEARCH_FULL_CONTEXT:
files.append(
{
"docs": results.get("docs", []),
"name": searchQuery,
"type": "web_search_docs",
"urls": results["filenames"],
}
)
else:
files.append(
{
"collection_name": results["collection_name"],
"name": searchQuery,
"type": "web_search_results",
"urls": results["filenames"],
}
)
form_data["files"] = files
except Exception as e:
log.exception(e)
await event_emitter(
{
"type": "status",
"data": {
"action": "web_search",
"description": "No search results found",
"description": 'Error searching "{{searchQuery}}"',
"query": searchQuery,
"done": True,
"error": True,
},
}
)
except Exception as e:
log.exception(e)
if all_results:
urls = []
for results in all_results:
if "filenames" in results:
urls.extend(results["filenames"])
await event_emitter(
{
"type": "status",
"data": {
"action": "web_search",
"description": 'Error searching "{{searchQuery}}"',
"query": searchQuery,
"description": "Searched {{count}} sites",
"urls": urls,
"done": True,
},
}
)
else:
await event_emitter(
{
"type": "status",
"data": {
"action": "web_search",
"description": "No search results found",
"done": True,
"error": True,
},
@ -560,9 +565,9 @@ async def chat_completion_files_handler(
reranking_function=request.app.state.rf,
r=request.app.state.config.RELEVANCE_THRESHOLD,
hybrid_search=request.app.state.config.ENABLE_RAG_HYBRID_SEARCH,
full_context=request.app.state.config.RAG_FULL_CONTEXT,
),
)
except Exception as e:
log.exception(e)
@ -1359,7 +1364,15 @@ async def process_chat_response(
tool_calls = []
last_assistant_message = get_last_assistant_message(form_data["messages"])
last_assistant_message = None
try:
if form_data["messages"][-1]["role"] == "assistant":
last_assistant_message = get_last_assistant_message(
form_data["messages"]
)
except Exception as e:
pass
content = (
message.get("content", "")
if message
@ -1748,6 +1761,7 @@ async def process_chat_response(
== "password"
else None
),
request.app.state.config.CODE_INTERPRETER_JUPYTER_TIMEOUT,
)
else:
output = {

View File

@ -140,7 +140,14 @@ class OAuthManager:
log.debug("Running OAUTH Group management")
oauth_claim = auth_manager_config.OAUTH_GROUPS_CLAIM
user_oauth_groups: list[str] = user_data.get(oauth_claim, list())
# Nested claim search for groups claim
if oauth_claim:
claim_data = user_data
nested_claims = oauth_claim.split(".")
for nested_claim in nested_claims:
claim_data = claim_data.get(nested_claim, {})
user_oauth_groups = claim_data if isinstance(claim_data, list) else None
user_current_groups: list[GroupModel] = Groups.get_groups_by_member_id(user.id)
all_available_groups: list[GroupModel] = Groups.get_groups()
@ -239,11 +246,46 @@ class OAuthManager:
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
provider_sub = f"{provider}@{sub}"
email_claim = auth_manager_config.OAUTH_EMAIL_CLAIM
email = user_data.get(email_claim, "").lower()
email = user_data.get(email_claim, "")
# We currently mandate that email addresses are provided
if not email:
log.warning(f"OAuth callback failed, email is missing: {user_data}")
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
# If the provider is GitHub,and public email is not provided, we can use the access token to fetch the user's email
if provider == "github":
try:
access_token = token.get("access_token")
headers = {"Authorization": f"Bearer {access_token}"}
async with aiohttp.ClientSession() as session:
async with session.get(
"https://api.github.com/user/emails", headers=headers
) as resp:
if resp.ok:
emails = await resp.json()
# use the primary email as the user's email
primary_email = next(
(e["email"] for e in emails if e.get("primary")),
None,
)
if primary_email:
email = primary_email
else:
log.warning(
"No primary email found in GitHub response"
)
raise HTTPException(
400, detail=ERROR_MESSAGES.INVALID_CRED
)
else:
log.warning("Failed to fetch GitHub email")
raise HTTPException(
400, detail=ERROR_MESSAGES.INVALID_CRED
)
except Exception as e:
log.warning(f"Error fetching GitHub email: {e}")
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
else:
log.warning(f"OAuth callback failed, email is missing: {user_data}")
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
email = email.lower()
if (
"*" not in auth_manager_config.OAUTH_ALLOWED_DOMAINS
and email.split("@")[-1] not in auth_manager_config.OAUTH_ALLOWED_DOMAINS
@ -285,9 +327,7 @@ class OAuthManager:
# If the user does not exist, check if signups are enabled
if auth_manager_config.ENABLE_OAUTH_SIGNUP:
# Check if an existing user with the same email already exists
existing_user = Users.get_user_by_email(
user_data.get("email", "").lower()
)
existing_user = Users.get_user_by_email(email)
if existing_user:
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)

View File

@ -4,6 +4,7 @@ from open_webui.utils.misc import (
)
from typing import Callable, Optional
import json
# inplace function: form_data is modified
@ -66,38 +67,49 @@ def apply_model_params_to_body_openai(params: dict, form_data: dict) -> dict:
def apply_model_params_to_body_ollama(params: dict, form_data: dict) -> dict:
opts = [
"temperature",
"top_p",
"seed",
"mirostat",
"mirostat_eta",
"mirostat_tau",
"num_ctx",
"num_batch",
"num_keep",
"repeat_last_n",
"tfs_z",
"top_k",
"min_p",
"use_mmap",
"use_mlock",
"num_thread",
"num_gpu",
]
mappings = {i: lambda x: x for i in opts}
form_data = apply_model_params_to_body(params, form_data, mappings)
# Convert OpenAI parameter names to Ollama parameter names if needed.
name_differences = {
"max_tokens": "num_predict",
"frequency_penalty": "repeat_penalty",
}
for key, value in name_differences.items():
if (param := params.get(key, None)) is not None:
form_data[value] = param
# Copy the parameter to new name then delete it, to prevent Ollama warning of invalid option provided
params[value] = params[key]
del params[key]
return form_data
# See https://github.com/ollama/ollama/blob/main/docs/api.md#request-8
mappings = {
"temperature": float,
"top_p": float,
"seed": lambda x: x,
"mirostat": int,
"mirostat_eta": float,
"mirostat_tau": float,
"num_ctx": int,
"num_batch": int,
"num_keep": int,
"num_predict": int,
"repeat_last_n": int,
"top_k": int,
"min_p": float,
"typical_p": float,
"repeat_penalty": float,
"presence_penalty": float,
"frequency_penalty": float,
"penalize_newline": bool,
"stop": lambda x: [bytes(s, "utf-8").decode("unicode_escape") for s in x],
"numa": bool,
"num_gpu": int,
"main_gpu": int,
"low_vram": bool,
"vocab_only": bool,
"use_mmap": bool,
"use_mlock": bool,
"num_thread": int,
}
return apply_model_params_to_body(params, form_data, mappings)
def convert_messages_openai_to_ollama(messages: list[dict]) -> list[dict]:
@ -108,11 +120,38 @@ def convert_messages_openai_to_ollama(messages: list[dict]) -> list[dict]:
new_message = {"role": message["role"]}
content = message.get("content", [])
tool_calls = message.get("tool_calls", None)
tool_call_id = message.get("tool_call_id", None)
# Check if the content is a string (just a simple message)
if isinstance(content, str):
# If the content is a string, it's pure text
new_message["content"] = content
# If message is a tool call, add the tool call id to the message
if tool_call_id:
new_message["tool_call_id"] = tool_call_id
elif tool_calls:
# If tool calls are present, add them to the message
ollama_tool_calls = []
for tool_call in tool_calls:
ollama_tool_call = {
"index": tool_call.get("index", 0),
"id": tool_call.get("id", None),
"function": {
"name": tool_call.get("function", {}).get("name", ""),
"arguments": json.loads(
tool_call.get("function", {}).get("arguments", {})
),
},
}
ollama_tool_calls.append(ollama_tool_call)
new_message["tool_calls"] = ollama_tool_calls
# Put the content to empty string (Ollama requires an empty string for tool calls)
new_message["content"] = ""
else:
# Otherwise, assume the content is a list of dicts, e.g., text followed by an image URL
content_text = ""
@ -173,34 +212,23 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
ollama_payload["format"] = openai_payload["format"]
# If there are advanced parameters in the payload, format them in Ollama's options field
ollama_options = {}
if openai_payload.get("options"):
ollama_payload["options"] = openai_payload["options"]
ollama_options = openai_payload["options"]
# Handle parameters which map directly
for param in ["temperature", "top_p", "seed"]:
if param in openai_payload:
ollama_options[param] = openai_payload[param]
# Re-Mapping OpenAI's `max_tokens` -> Ollama's `num_predict`
if "max_tokens" in ollama_options:
ollama_options["num_predict"] = ollama_options["max_tokens"]
del ollama_options[
"max_tokens"
] # To prevent Ollama warning of invalid option provided
# Mapping OpenAI's `max_tokens` -> Ollama's `num_predict`
if "max_completion_tokens" in openai_payload:
ollama_options["num_predict"] = openai_payload["max_completion_tokens"]
elif "max_tokens" in openai_payload:
ollama_options["num_predict"] = openai_payload["max_tokens"]
# Handle frequency / presence_penalty, which needs renaming and checking
if "frequency_penalty" in openai_payload:
ollama_options["repeat_penalty"] = openai_payload["frequency_penalty"]
if "presence_penalty" in openai_payload and "penalty" not in ollama_options:
# We are assuming presence penalty uses a similar concept in Ollama, which needs custom handling if exists.
ollama_options["new_topic_penalty"] = openai_payload["presence_penalty"]
# Add options to payload if any have been set
if ollama_options:
ollama_payload["options"] = ollama_options
# Ollama lacks a "system" prompt option. It has to be provided as a direct parameter, so we copy it down.
if "system" in ollama_options:
ollama_payload["system"] = ollama_options["system"]
del ollama_options[
"system"
] # To prevent Ollama warning of invalid option provided
if "metadata" in openai_payload:
ollama_payload["metadata"] = openai_payload["metadata"]

View File

@ -24,17 +24,8 @@ def convert_ollama_tool_call_to_openai(tool_calls: dict) -> dict:
return openai_tool_calls
def convert_response_ollama_to_openai(ollama_response: dict) -> dict:
model = ollama_response.get("model", "ollama")
message_content = ollama_response.get("message", {}).get("content", "")
tool_calls = ollama_response.get("message", {}).get("tool_calls", None)
openai_tool_calls = None
if tool_calls:
openai_tool_calls = convert_ollama_tool_call_to_openai(tool_calls)
data = ollama_response
usage = {
def convert_ollama_usage_to_openai(data: dict) -> dict:
return {
"response_token/s": (
round(
(
@ -66,14 +57,42 @@ def convert_response_ollama_to_openai(ollama_response: dict) -> dict:
"total_duration": data.get("total_duration", 0),
"load_duration": data.get("load_duration", 0),
"prompt_eval_count": data.get("prompt_eval_count", 0),
"prompt_tokens": int(
data.get("prompt_eval_count", 0)
), # This is the OpenAI compatible key
"prompt_eval_duration": data.get("prompt_eval_duration", 0),
"eval_count": data.get("eval_count", 0),
"completion_tokens": int(
data.get("eval_count", 0)
), # This is the OpenAI compatible key
"eval_duration": data.get("eval_duration", 0),
"approximate_total": (lambda s: f"{s // 3600}h{(s % 3600) // 60}m{s % 60}s")(
(data.get("total_duration", 0) or 0) // 1_000_000_000
),
"total_tokens": int( # This is the OpenAI compatible key
data.get("prompt_eval_count", 0) + data.get("eval_count", 0)
),
"completion_tokens_details": { # This is the OpenAI compatible key
"reasoning_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0,
},
}
def convert_response_ollama_to_openai(ollama_response: dict) -> dict:
model = ollama_response.get("model", "ollama")
message_content = ollama_response.get("message", {}).get("content", "")
tool_calls = ollama_response.get("message", {}).get("tool_calls", None)
openai_tool_calls = None
if tool_calls:
openai_tool_calls = convert_ollama_tool_call_to_openai(tool_calls)
data = ollama_response
usage = convert_ollama_usage_to_openai(data)
response = openai_chat_completion_message_template(
model, message_content, openai_tool_calls, usage
)
@ -96,45 +115,7 @@ async def convert_streaming_response_ollama_to_openai(ollama_streaming_response)
usage = None
if done:
usage = {
"response_token/s": (
round(
(
(
data.get("eval_count", 0)
/ ((data.get("eval_duration", 0) / 10_000_000))
)
* 100
),
2,
)
if data.get("eval_duration", 0) > 0
else "N/A"
),
"prompt_token/s": (
round(
(
(
data.get("prompt_eval_count", 0)
/ ((data.get("prompt_eval_duration", 0) / 10_000_000))
)
* 100
),
2,
)
if data.get("prompt_eval_duration", 0) > 0
else "N/A"
),
"total_duration": data.get("total_duration", 0),
"load_duration": data.get("load_duration", 0),
"prompt_eval_count": data.get("prompt_eval_count", 0),
"prompt_eval_duration": data.get("prompt_eval_duration", 0),
"eval_count": data.get("eval_count", 0),
"eval_duration": data.get("eval_duration", 0),
"approximate_total": (
lambda s: f"{s // 3600}h{(s % 3600) // 60}m{s % 60}s"
)((data.get("total_duration", 0) or 0) // 1_000_000_000),
}
usage = convert_ollama_usage_to_openai(data)
data = openai_chat_chunk_message_template(
model, message_content if not done else None, openai_tool_calls, usage

View File

@ -1,10 +1,10 @@
fastapi==0.115.7
uvicorn[standard]==0.30.6
pydantic==2.9.2
pydantic==2.10.6
python-multipart==0.0.18
python-socketio==5.11.3
python-jose==3.3.0
python-jose==3.4.0
passlib[bcrypt]==1.7.4
requests==2.32.3
@ -45,7 +45,7 @@ chromadb==0.6.2
pymilvus==2.5.0
qdrant-client~=1.12.0
opensearch-py==2.8.0
playwright==1.49.1 # Caution: version must match docker-compose.playwright.yaml
transformers
sentence-transformers==3.3.1
@ -59,7 +59,7 @@ fpdf2==2.8.2
pymdown-extensions==10.14.2
docx2txt==0.8
python-pptx==1.0.0
unstructured==0.16.11
unstructured==0.16.17
nltk==3.9.1
Markdown==3.7
pypandoc==1.13
@ -103,5 +103,12 @@ pytest-docker~=3.1.1
googleapis-common-protos==1.63.2
google-cloud-storage==2.19.0
azure-identity==1.20.0
azure-storage-blob==12.24.1
## LDAP
ldap3==2.9.1
## Firecrawl
firecrawl-py==1.12.0

View File

@ -3,6 +3,17 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd "$SCRIPT_DIR" || exit
# Add conditional Playwright browser installation
if [[ "${RAG_WEB_LOADER_ENGINE,,}" == "playwright" ]]; then
if [[ -z "${PLAYWRIGHT_WS_URI}" ]]; then
echo "Installing Playwright browsers..."
playwright install chromium
playwright install-deps chromium
fi
python -c "import nltk; nltk.download('punkt_tab')"
fi
KEY_FILE=.webui_secret_key
PORT="${PORT:-8080}"

View File

@ -6,6 +6,17 @@ SETLOCAL ENABLEDELAYEDEXPANSION
SET "SCRIPT_DIR=%~dp0"
cd /d "%SCRIPT_DIR%" || exit /b
:: Add conditional Playwright browser installation
IF /I "%RAG_WEB_LOADER_ENGINE%" == "playwright" (
IF "%PLAYWRIGHT_WS_URI%" == "" (
echo Installing Playwright browsers...
playwright install chromium
playwright install-deps chromium
)
python -c "import nltk; nltk.download('punkt_tab')"
)
SET "KEY_FILE=.webui_secret_key"
IF "%PORT%"=="" SET PORT=8080
IF "%HOST%"=="" SET HOST=0.0.0.0

View File

@ -0,0 +1,10 @@
services:
playwright:
image: mcr.microsoft.com/playwright:v1.49.1-noble # Version must match requirements.txt
container_name: playwright
command: npx -y playwright@1.49.1 run-server --port 3000 --host 0.0.0.0
open-webui:
environment:
- 'RAG_WEB_LOADER_ENGINE=playwright'
- 'PLAYWRIGHT_WS_URI=ws://playwright:3000'

14
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "open-webui",
"version": "0.5.14",
"version": "0.5.15",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.5.14",
"version": "0.5.15",
"dependencies": {
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-python": "^6.1.6",
@ -63,6 +63,7 @@
"svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7",
"turndown": "^7.2.0",
"undici": "^7.3.0",
"uuid": "^9.0.1",
"vite-plugin-static-copy": "^2.2.0"
},
@ -11528,6 +11529,15 @@
"node": "*"
}
},
"node_modules/undici": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.3.0.tgz",
"integrity": "sha512-Qy96NND4Dou5jKoSJ2gm8ax8AJM/Ey9o9mz7KN1bb9GP+G0l20Zw8afxTnY2f4b7hmhn/z8aC2kfArVQlAhFBw==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.5.14",
"version": "0.5.15",
"private": true,
"scripts": {
"dev": "npm run pyodide:fetch && vite dev --host",
@ -106,6 +106,7 @@
"svelte-sonner": "^0.3.19",
"tippy.js": "^6.3.7",
"turndown": "^7.2.0",
"undici": "^7.3.0",
"uuid": "^9.0.1",
"vite-plugin-static-copy": "^2.2.0"
},

View File

@ -8,11 +8,11 @@ license = { file = "LICENSE" }
dependencies = [
"fastapi==0.115.7",
"uvicorn[standard]==0.30.6",
"pydantic==2.9.2",
"pydantic==2.10.6",
"python-multipart==0.0.18",
"python-socketio==5.11.3",
"python-jose==3.3.0",
"python-jose==3.4.0",
"passlib[bcrypt]==1.7.4",
"requests==2.32.3",
@ -53,6 +53,7 @@ dependencies = [
"pymilvus==2.5.0",
"qdrant-client~=1.12.0",
"opensearch-py==2.8.0",
"playwright==1.49.1",
"transformers",
"sentence-transformers==3.3.1",
@ -65,7 +66,7 @@ dependencies = [
"pymdown-extensions==10.14.2",
"docx2txt==0.8",
"python-pptx==1.0.0",
"unstructured==0.16.11",
"unstructured==0.16.17",
"nltk==3.9.1",
"Markdown==3.7",
"pypandoc==1.13",
@ -108,7 +109,13 @@ dependencies = [
"googleapis-common-protos==1.63.2",
"google-cloud-storage==2.19.0",
"azure-identity==1.20.0",
"azure-storage-blob==12.24.1",
"ldap3==2.9.1",
"firecrawl-py==1.12.0",
"gcp-storage-emulator>=2024.8.3",
]
readme = "README.md"

View File

@ -74,6 +74,7 @@ usage() {
echo " --enable-api[port=PORT] Enable API and expose it on the specified port."
echo " --webui[port=PORT] Set the port for the web user interface."
echo " --data[folder=PATH] Bind mount for ollama data folder (by default will create the 'ollama' volume)."
echo " --playwright Enable Playwright support for web scraping."
echo " --build Build the docker image before running the compose project."
echo " --drop Drop the compose project."
echo " -q, --quiet Run script in headless mode."
@ -100,6 +101,7 @@ webui_port=3000
headless=false
build_image=false
kill_compose=false
enable_playwright=false
# Function to extract value from the parameter
extract_value() {
@ -129,6 +131,9 @@ while [[ $# -gt 0 ]]; do
value=$(extract_value "$key")
data_dir=${value:-"./ollama-data"}
;;
--playwright)
enable_playwright=true
;;
--drop)
kill_compose=true
;;
@ -182,6 +187,9 @@ else
DEFAULT_COMPOSE_COMMAND+=" -f docker-compose.data.yaml"
export OLLAMA_DATA_DIR=$data_dir # Set OLLAMA_DATA_DIR environment variable
fi
if [[ $enable_playwright == true ]]; then
DEFAULT_COMPOSE_COMMAND+=" -f docker-compose.playwright.yaml"
fi
if [[ -n $webui_port ]]; then
export OPEN_WEBUI_PORT=$webui_port # Set OPEN_WEBUI_PORT environment variable
fi
@ -201,6 +209,7 @@ echo -e " ${GREEN}${BOLD}GPU Count:${NC} ${OLLAMA_GPU_COUNT:-Not Enabled}"
echo -e " ${GREEN}${BOLD}WebAPI Port:${NC} ${OLLAMA_WEBAPI_PORT:-Not Enabled}"
echo -e " ${GREEN}${BOLD}Data Folder:${NC} ${data_dir:-Using ollama volume}"
echo -e " ${GREEN}${BOLD}WebUI Port:${NC} $webui_port"
echo -e " ${GREEN}${BOLD}Playwright:${NC} ${enable_playwright:-false}"
echo
if [[ $headless == true ]]; then

View File

@ -16,8 +16,39 @@ const packages = [
];
import { loadPyodide } from 'pyodide';
import { setGlobalDispatcher, ProxyAgent } from 'undici';
import { writeFile, readFile, copyFile, readdir, rmdir } from 'fs/promises';
/**
* Loading network proxy configurations from the environment variables.
* And the proxy config with lowercase name has the highest priority to use.
*/
function initNetworkProxyFromEnv() {
// we assume all subsequent requests in this script are HTTPS:
// https://cdn.jsdelivr.net
// https://pypi.org
// https://files.pythonhosted.org
const allProxy = process.env.all_proxy || process.env.ALL_PROXY;
const httpsProxy = process.env.https_proxy || process.env.HTTPS_PROXY;
const httpProxy = process.env.http_proxy || process.env.HTTP_PROXY;
const preferedProxy = httpsProxy || allProxy || httpProxy;
/**
* use only http(s) proxy because socks5 proxy is not supported currently:
* @see https://github.com/nodejs/undici/issues/2224
*/
if (!preferedProxy || !preferedProxy.startsWith('http')) return;
let preferedProxyURL;
try {
preferedProxyURL = new URL(preferedProxy).toString();
} catch {
console.warn(`Invalid network proxy URL: "${preferedProxy}"`);
return;
}
const dispatcher = new ProxyAgent({ uri: preferedProxyURL });
setGlobalDispatcher(dispatcher);
console.log(`Initialized network proxy "${preferedProxy}" from env`);
}
async function downloadPackages() {
console.log('Setting up pyodide + micropip');
@ -84,5 +115,6 @@ async function copyPyodide() {
}
}
initNetworkProxyFromEnv();
await downloadPackages();
await copyPyodide();

View File

@ -101,7 +101,7 @@ li p {
/* Dark theme scrollbar styles */
.dark ::-webkit-scrollbar-thumb {
background-color: rgba(33, 33, 33, 0.8); /* Darker color for dark theme */
background-color: rgba(42, 42, 42, 0.8); /* Darker color for dark theme */
border-color: rgba(0, 0, 0, var(--tw-border-opacity));
}

View File

@ -91,45 +91,65 @@
</div>
</div>
<div class=" flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Jupyter Auth')}
</div>
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
<div class=" flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Jupyter Auth')}
</div>
<div>
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH}
placeholder={$i18n.t('Select an auth method')}
>
<option selected value="">{$i18n.t('None')}</option>
<option value="token">{$i18n.t('Token')}</option>
<option value="password">{$i18n.t('Password')}</option>
</select>
</div>
</div>
{#if config.CODE_EXECUTION_JUPYTER_AUTH}
<div class="flex w-full gap-2">
<div class="flex-1">
{#if config.CODE_EXECUTION_JUPYTER_AUTH === 'password'}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
/>
{:else}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN}
autocomplete="off"
/>
{/if}
<div>
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH}
placeholder={$i18n.t('Select an auth method')}
>
<option selected value="">{$i18n.t('None')}</option>
<option value="token">{$i18n.t('Token')}</option>
<option value="password">{$i18n.t('Password')}</option>
</select>
</div>
</div>
{/if}
{#if config.CODE_EXECUTION_JUPYTER_AUTH}
<div class="flex w-full gap-2">
<div class="flex-1">
{#if config.CODE_EXECUTION_JUPYTER_AUTH === 'password'}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
/>
{:else}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN}
autocomplete="off"
/>
{/if}
</div>
</div>
{/if}
</div>
<div class="flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Code Execution Timeout')}
</div>
<div class="">
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
<input
class="dark:bg-gray-900 w-fit rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
type="number"
bind:value={config.CODE_EXECUTION_JUPYTER_TIMEOUT}
placeholder={$i18n.t('e.g. 60')}
autocomplete="off"
/>
</Tooltip>
</div>
</div>
{/if}
</div>
@ -197,45 +217,65 @@
</div>
</div>
<div class="flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Jupyter Auth')}
</div>
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
<div class="flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Jupyter Auth')}
</div>
<div>
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
placeholder={$i18n.t('Select an auth method')}
>
<option selected value="">{$i18n.t('None')}</option>
<option value="token">{$i18n.t('Token')}</option>
<option value="password">{$i18n.t('Password')}</option>
</select>
</div>
</div>
{#if config.CODE_INTERPRETER_JUPYTER_AUTH}
<div class="flex w-full gap-2">
<div class="flex-1">
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
/>
{:else}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN}
autocomplete="off"
/>
{/if}
<div>
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
placeholder={$i18n.t('Select an auth method')}
>
<option selected value="">{$i18n.t('None')}</option>
<option value="token">{$i18n.t('Token')}</option>
<option value="password">{$i18n.t('Password')}</option>
</select>
</div>
</div>
{/if}
{#if config.CODE_INTERPRETER_JUPYTER_AUTH}
<div class="flex w-full gap-2">
<div class="flex-1">
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
/>
{:else}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN}
autocomplete="off"
/>
{/if}
</div>
</div>
{/if}
</div>
<div class="flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Code Execution Timeout')}
</div>
<div class="">
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
<input
class="dark:bg-gray-900 w-fit rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
type="number"
bind:value={config.CODE_INTERPRETER_JUPYTER_TIMEOUT}
placeholder={$i18n.t('e.g. 60')}
autocomplete="off"
/>
</Tooltip>
</div>
</div>
{/if}
<hr class="border-gray-100 dark:border-gray-850 my-2" />

View File

@ -27,7 +27,6 @@
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Switch from '$lib/components/common/Switch.svelte';
import { text } from '@sveltejs/kit';
import Textarea from '$lib/components/common/Textarea.svelte';
const i18n = getContext('i18n');
@ -56,6 +55,8 @@
let chunkOverlap = 0;
let pdfExtractImages = true;
let RAG_FULL_CONTEXT = false;
let enableGoogleDriveIntegration = false;
let OpenAIUrl = '';
@ -182,6 +183,7 @@
max_size: fileMaxSize === '' ? null : fileMaxSize,
max_count: fileMaxCount === '' ? null : fileMaxCount
},
RAG_FULL_CONTEXT: RAG_FULL_CONTEXT,
chunk: {
text_splitter: textSplitter,
chunk_overlap: chunkOverlap,
@ -242,6 +244,8 @@
chunkSize = res.chunk.chunk_size;
chunkOverlap = res.chunk.chunk_overlap;
RAG_FULL_CONTEXT = res.RAG_FULL_CONTEXT;
contentExtractionEngine = res.content_extraction.engine;
tikaServerUrl = res.content_extraction.tika_server_url;
showTikaServerUrl = contentExtractionEngine === 'tika';
@ -388,6 +392,19 @@
{/if}
</button>
</div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Full Context Mode')}</div>
<div class="flex items-center relative">
<Tooltip
content={RAG_FULL_CONTEXT
? 'Inject entire contents as context for comprehensive processing, this is recommended for complex queries.'
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
>
<Switch bind:state={RAG_FULL_CONTEXT} />
</Tooltip>
</div>
</div>
</div>
<hr class="border-gray-100 dark:border-gray-850" />

View File

@ -261,6 +261,9 @@
} else if (config.engine === 'openai' && config.openai.OPENAI_API_KEY === '') {
toast.error($i18n.t('OpenAI API Key is required.'));
config.enabled = false;
} else if (config.engine === 'gemini' && config.gemini.GEMINI_API_KEY === '') {
toast.error($i18n.t('Gemini API Key is required.'));
config.enabled = false;
}
}
@ -294,6 +297,7 @@
<option value="openai">{$i18n.t('Default (Open AI)')}</option>
<option value="comfyui">{$i18n.t('ComfyUI')}</option>
<option value="automatic1111">{$i18n.t('Automatic1111')}</option>
<option value="gemini">{$i18n.t('Gemini')}</option>
</select>
</div>
</div>
@ -605,6 +609,24 @@
/>
</div>
</div>
{:else if config?.engine === 'gemini'}
<div>
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Gemini API Config')}</div>
<div class="flex gap-2 mb-1">
<input
class="flex-1 w-full text-sm bg-transparent outline-none"
placeholder={$i18n.t('API Base URL')}
bind:value={config.gemini.GEMINI_API_BASE_URL}
required
/>
<SensitiveInput
placeholder={$i18n.t('API Key')}
bind:value={config.gemini.GEMINI_API_KEY}
/>
</div>
</div>
{/if}
</div>

View File

@ -51,7 +51,7 @@
onMount(async () => {
taskConfig = await getTaskConfig(localStorage.token);
promptSuggestions = $config?.default_prompt_suggestions;
promptSuggestions = $config?.default_prompt_suggestions ?? [];
banners = await getBanners(localStorage.token);
});

View File

@ -85,8 +85,9 @@
return true;
} else {
let name = user.name.toLowerCase();
let email = user.email.toLowerCase();
const query = search.toLowerCase();
return name.includes(query);
return name.includes(query) || email.includes(query);
}
})
.sort((a, b) => {

View File

@ -430,7 +430,7 @@
</div>
{/if}
{#if webSearchEnabled || ($settings?.webSearch ?? false) === 'always'}
{#if webSearchEnabled || ($config?.features?.enable_web_search && ($settings?.webSearch ?? false)) === 'always'}
<div class="flex items-center justify-between w-full">
<div class="flex items-center gap-2.5 text-sm dark:text-gray-500">
<div class="pl-1">

View File

@ -7,6 +7,7 @@
const i18n = getContext('i18n');
export let id = '';
export let sources = [];
let citations = [];
@ -100,7 +101,7 @@
<div class="flex text-xs font-medium flex-wrap">
{#each citations as citation, idx}
<button
id={`source-${idx}`}
id={`source-${id}-${idx}`}
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-white dark:bg-gray-900 rounded-xl max-w-96"
on:click={() => {
showCitationModal = true;
@ -179,7 +180,7 @@
<div class="flex text-xs font-medium flex-wrap">
{#each citations as citation, idx}
<button
id={`source-${idx}`}
id={`source-${id}-${idx}`}
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
on:click={() => {
showCitationModal = true;

View File

@ -123,6 +123,12 @@
};
const executePython = async (code) => {
result = null;
stdout = null;
stderr = null;
executing = true;
if ($config?.code?.engine === 'jupyter') {
const output = await executeCode(localStorage.token, code).catch((error) => {
toast.error(`${error}`);
@ -130,22 +136,74 @@
});
if (output) {
stdout = output.stdout;
stderr = output.stderr;
result = output.result;
if (output['stdout']) {
stdout = output['stdout'];
const stdoutLines = stdout.split('\n');
for (const [idx, line] of stdoutLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (stdout.startsWith(`${line}\n`)) {
stdout = stdout.replace(`${line}\n`, ``);
} else if (stdout.startsWith(`${line}`)) {
stdout = stdout.replace(`${line}`, ``);
}
}
}
}
if (output['result']) {
result = output['result'];
const resultLines = result.split('\n');
for (const [idx, line] of resultLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (result.startsWith(`${line}\n`)) {
result = result.replace(`${line}\n`, ``);
} else if (result.startsWith(`${line}`)) {
result = result.replace(`${line}`, ``);
}
}
}
}
output['stderr'] && (stderr = output['stderr']);
}
executing = false;
} else {
executePythonAsWorker(code);
}
};
const executePythonAsWorker = async (code) => {
result = null;
stdout = null;
stderr = null;
executing = true;
let packages = [
code.includes('requests') ? 'requests' : null,
code.includes('bs4') ? 'beautifulsoup4' : null,
@ -205,7 +263,40 @@
];
}
stdout = stdout.replace(`${line}\n`, ``);
if (stdout.startsWith(`${line}\n`)) {
stdout = stdout.replace(`${line}\n`, ``);
} else if (stdout.startsWith(`${line}`)) {
stdout = stdout.replace(`${line}`, ``);
}
}
}
}
if (data['result']) {
result = data['result'];
const resultLines = result.split('\n');
for (const [idx, line] of resultLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (result.startsWith(`${line}\n`)) {
result = result.replace(`${line}\n`, ``);
} else if (result.startsWith(`${line}`)) {
result = result.replace(`${line}`, ``);
}
}
}
}
@ -391,7 +482,7 @@
class="bg-gray-50 dark:bg-[#202123] dark:text-white max-w-full overflow-x-auto scrollbar-hidden"
/>
{#if executing || stdout || stderr || result}
{#if executing || stdout || stderr || result || files}
<div
class="bg-gray-50 dark:bg-[#202123] dark:text-white rounded-b-lg! py-4 px-4 flex flex-col gap-2"
>
@ -404,7 +495,13 @@
{#if stdout || stderr}
<div class=" ">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">{stdout || stderr}</div>
<div
class="text-sm {stdout?.split('\n')?.length > 100
? `max-h-96`
: ''} overflow-y-auto"
>
{stdout || stderr}
</div>
</div>
{/if}
{#if result || files}

View File

@ -120,6 +120,11 @@
sourceIds={(sources ?? []).reduce((acc, s) => {
let ids = [];
s.document.forEach((document, index) => {
if (model?.info?.meta?.capabilities?.citations == false) {
ids.push('N/A');
return ids;
}
const metadata = s.metadata?.[index];
const id = metadata?.source ?? 'N/A';

View File

@ -29,7 +29,7 @@
{:else if token.text.includes(`<iframe src="${WEBUI_BASE_URL}/api/v1/files/`)}
{@html `${token.text}`}
{:else if token.text.includes(`<source_id`)}
<Source {token} onClick={onSourceClick} />
<Source {id} {token} onClick={onSourceClick} />
{:else}
{token.text}
{/if}

View File

@ -76,7 +76,7 @@
{#if token.type === 'hr'}
<hr class=" border-gray-100 dark:border-gray-850" />
{:else if token.type === 'heading'}
<svelte:element this={headerComponent(token.depth)}>
<svelte:element this={headerComponent(token.depth)} dir="auto">
<MarkdownInlineTokens id={`${id}-${tokenIdx}-h`} tokens={token.tokens} {onSourceClick} />
</svelte:element>
{:else if token.type === 'code'}
@ -176,7 +176,7 @@
{#if token.ordered}
<ol start={token.start || 1}>
{#each token.items as item, itemIdx}
<li dir="auto">
<li dir="auto" class="text-start">
{#if item?.task}
<input
class=" translate-y-[1px] -translate-x-1"
@ -208,7 +208,7 @@
{:else}
<ul>
{#each token.items as item, itemIdx}
<li dir="auto">
<li dir="auto" class="text-start">
{#if item?.task}
<input
class=" translate-y-[1px] -translate-x-1"
@ -239,7 +239,12 @@
</ul>
{/if}
{:else if token.type === 'details'}
<Collapsible title={token.summary} attributes={token?.attributes} className="w-full space-y-1">
<Collapsible
title={token.summary}
attributes={token?.attributes}
className="w-full space-y-1"
dir="auto"
>
<div class=" mb-1.5" slot="content">
<svelte:self
id={`${id}-${tokenIdx}-d`}

View File

@ -1,8 +1,9 @@
<script lang="ts">
export let id;
export let token;
export let onClick: Function = () => {};
let attributes: Record<string, string> = {};
let attributes: Record<string, string | undefined> = {};
function extractAttributes(input: string): Record<string, string> {
const regex = /(\w+)="([^"]*)"/g;
@ -35,13 +36,15 @@
$: attributes = extractAttributes(token.text);
</script>
<button
class="text-xs font-medium w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/60 dark:hover:text-white bg-gray-50 text-black/60 hover:text-black transition rounded-lg"
on:click={() => {
onClick(attributes.data);
}}
>
<span class="line-clamp-1">
{formattedTitle(attributes.title)}
</span>
</button>
{#if attributes.title !== 'N/A'}
<button
class="text-xs font-medium w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/60 dark:hover:text-white bg-gray-50 text-black/60 hover:text-black transition rounded-lg"
on:click={() => {
onClick(id, attributes.data);
}}
>
<span class="line-clamp-1">
{attributes.title ? formattedTitle(attributes.title) : ''}
</span>
</button>
{/if}

View File

@ -732,9 +732,9 @@
onTaskClick={async (e) => {
console.log(e);
}}
onSourceClick={async (e) => {
console.log(e);
let sourceButton = document.getElementById(`source-${e}`);
onSourceClick={async (id, idx) => {
console.log(id, idx);
let sourceButton = document.getElementById(`source-${message.id}-${idx}`);
const sourcesCollapsible = document.getElementById(`collapsible-sources`);
if (sourceButton) {
@ -753,7 +753,7 @@
});
// Try clicking the source button again
sourceButton = document.getElementById(`source-${e}`);
sourceButton = document.getElementById(`source-${message.id}-${idx}`);
sourceButton && sourceButton.click();
}
}}
@ -790,7 +790,7 @@
{/if}
{#if (message?.sources || message?.citations) && (model?.info?.meta?.capabilities?.citations ?? true)}
<Citations sources={message?.sources ?? message?.citations} />
<Citations id={message?.id} sources={message?.sources ?? message?.citations} />
{/if}
{#if message.code_executions}

View File

@ -1,6 +1,7 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import { formatFileSize, getLineCount } from '$lib/utils';
import { WEBUI_API_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
@ -12,14 +13,15 @@
export let item;
export let show = false;
export let edit = false;
let enableFullContent = false;
$: isPDF =
item?.meta?.content_type === 'application/pdf' ||
(item?.name && item?.name.toLowerCase().endsWith('.pdf'));
onMount(() => {
console.log(item);
if (item?.context === 'full') {
enableFullContent = true;
}
@ -33,9 +35,16 @@
<div>
<div class=" font-medium text-lg dark:text-gray-100">
<a
href={item.url ? (item.type === 'file' ? `${item.url}/content` : `${item.url}`) : '#'}
target="_blank"
href="#"
class="hover:underline line-clamp-1"
on:click|preventDefault={() => {
if (!isPDF && item.url) {
window.open(
item.type === 'file' ? `${item.url}/content` : `${item.url}`,
'_blank'
);
}
}}
>
{item?.name ?? 'File'}
</a>
@ -101,8 +110,18 @@
</div>
</div>
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{item?.file?.data?.content ?? 'No content'}
<div class="max-h-[75vh] overflow-auto">
{#if isPDF}
<iframe
title={item?.name}
src={`${WEBUI_API_BASE_URL}/files/${item.id}/content`}
class="w-full h-[70vh] border-0 rounded-lg mt-4"
/>
{:else}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{item?.file?.data?.content ?? 'No content'}
</div>
{/if}
</div>
</div>
</Modal>

View File

@ -6,7 +6,6 @@
export let show = true;
export let size = 'md';
export let containerClassName = 'p-3';
export let className = 'bg-gray-50 dark:bg-gray-900 rounded-2xl';
@ -74,7 +73,7 @@
}}
>
<div
class=" m-auto max-w-full {sizeToWidth(size)} {size !== 'full'
class="m-auto max-w-full {sizeToWidth(size)} {size !== 'full'
? 'mx-2'
: ''} shadow-3xl min-h-fit scrollbar-hidden {className}"
in:flyAndScale

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "أسقط أية ملفات هنا لإضافتها إلى المحادثة",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. الوحدات الزمنية الصالحة هي 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "أدخل Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "الرابط (e.g. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL (e.g. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "عام",
"General Settings": "الاعدادات العامة",
"Generate an image": "",

File diff suppressed because it is too large Load Diff

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "আলোচনায় যুক্ত করার জন্য যে কোন ফাইল এখানে ড্রপ করুন",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "যেমন '30s','10m'. সময়ের অনুমোদিত অনুমোদিত এককগুলি হচ্ছে 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Top K লিখুন",
"Enter URL (e.g. http://127.0.0.1:7860/)": "ইউআরএল দিন (যেমন http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "ইউআরএল দিন (যেমন http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "সাধারণ",
"General Settings": "সাধারণ সেটিংসমূহ",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Execució de codi",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Codi formatat correctament",
"Code Interpreter": "Intèrpret de codi",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Dibuixar",
"Drop any files here to add to the conversation": "Deixa qualsevol arxiu aquí per afegir-lo a la conversa",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p. ex. '30s','10m'. Les unitats de temps vàlides són 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "p. ex. Un filtre per eliminar paraules malsonants del text",
"e.g. My Filter": "p. ex. El meu filtre",
"e.g. My Tools": "p. ex. Les meves eines",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Introdueix la clau API de Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Entra la URL pública de WebUI. Aquesta URL s'utilitzarà per generar els enllaços en les notificacions.",
"Enter Tika Server URL": "Introdueix l'URL del servidor Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Introdueix Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Introdueix l'URL (p. ex. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Introdueix l'URL (p. ex. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Les funcions permeten l'execució de codi arbitrari",
"Functions allow arbitrary code execution.": "Les funcions permeten l'execució de codi arbitrari.",
"Functions imported successfully": "Les funcions s'han importat correctament",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "General",
"General Settings": "Preferències generals",
"Generate an image": "Generar una imatge",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Ihulog ang bisan unsang file dinhi aron idugang kini sa panag-istoryahanay",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p. ",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Pagsulod sa Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Pagsulod sa URL (e.g. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Heneral",
"General Settings": "kinatibuk-ang mga setting",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Provádění kódu",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kód byl úspěšně naformátován.",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Namalovat",
"Drop any files here to add to the conversation": "Sem přetáhněte libovolné soubory, které chcete přidat do konverzace",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "např. '30s','10m'. Platné časové jednotky jsou 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Zadejte API klíč Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Zadejte URL serveru Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Zadejte horní K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Zadejte URL (např. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Zadejte URL (např. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funkce umožňují vykonávat libovolný kód.",
"Functions allow arbitrary code execution.": "Funkce umožňují provádění libovolného kódu.",
"Functions imported successfully": "Funkce byly úspěšně importovány",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Obecný",
"General Settings": "Obecná nastavení",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kode formateret korrekt",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Upload filer her for at tilføje til samtalen",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "f.eks. '30s', '10m'. Tilladte værdier er 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Indtast Tavily API-nøgle",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Indtast Tika Server URL",
"Enter timeout in seconds": "",
"Enter Top K": "Indtast Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Indtast URL (f.eks. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Indtast URL (f.eks. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funktioner tillader kørsel af vilkårlig kode",
"Functions allow arbitrary code execution.": "Funktioner tillader kørsel af vilkårlig kode.",
"Functions imported successfully": "Funktioner importeret.",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Generelt",
"General Settings": "Generelle indstillinger",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Codeausführung",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Code erfolgreich formatiert",
"Code Interpreter": "Code-Interpreter",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Zeichnen",
"Drop any files here to add to the conversation": "Ziehen Sie beliebige Dateien hierher, um sie der Unterhaltung hinzuzufügen",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "z. B. '30s','10m'. Gültige Zeiteinheiten sind 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "z. B. Ein Filter, um Schimpfwörter aus Text zu entfernen",
"e.g. My Filter": "z. B. Mein Filter",
"e.g. My Tools": "z. B. Meine Werkzeuge",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Geben Sie den Tavily-API-Schlüssel ein",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Geben sie die öffentliche URL Ihrer WebUI ein. Diese URL wird verwendet, um Links in den Benachrichtigungen zu generieren.",
"Enter Tika Server URL": "Geben Sie die Tika-Server-URL ein",
"Enter timeout in seconds": "",
"Enter Top K": "Geben Sie Top K ein",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Geben Sie die URL ein (z. B. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Geben Sie die URL ein (z. B. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funktionen ermöglichen die Ausführung beliebigen Codes",
"Functions allow arbitrary code execution.": "Funktionen ermöglichen die Ausführung beliebigen Codes.",
"Functions imported successfully": "Funktionen erfolgreich importiert",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Allgemein",
"General Settings": "Allgemeine Einstellungen",
"Generate an image": "Bild erzeugen",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Drop files here to add to conversation",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "e.g. '30s','10m'. Much time units are 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Enter Top Wow",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Enter URL (e.g. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Woweral",
"General Settings": "General Doge Settings",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Εκτέλεση κώδικα",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Ο κώδικας μορφοποιήθηκε επιτυχώς",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Σχεδίαση",
"Drop any files here to add to the conversation": "Αφήστε οποιαδήποτε αρχεία εδώ για να προστεθούν στη συνομιλία",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "π.χ. '30s','10m'. Οι έγκυρες μονάδες χρόνου είναι 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "π.χ. Ένα φίλτρο για να αφαιρέσετε βρισιές από το κείμενο",
"e.g. My Filter": "π.χ. Το Φίλτρου Μου",
"e.g. My Tools": "π.χ. Τα Εργαλεία Μου",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Εισάγετε το Κλειδί API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Εισάγετε το URL διακομιστή Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Εισάγετε το Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Εισάγετε το URL (π.χ. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Εισάγετε το URL (π.χ. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Οι λειτουργίες επιτρέπουν την εκτέλεση αυθαίρετου κώδικα",
"Functions allow arbitrary code execution.": "Οι λειτουργίες επιτρέπουν την εκτέλεση αυθαίρετου κώδικα.",
"Functions imported successfully": "Οι λειτουργίες εισήχθησαν με επιτυχία",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Γενικά",
"General Settings": "Γενικές Ρυθμίσεις",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter URL (e.g. http://localhost:11434)": "",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "",
"General Settings": "",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter URL (e.g. http://localhost:11434)": "",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "",
"General Settings": "",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Ejecución de código",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Se ha formateado correctamente el código.",
"Code Interpreter": "Interprete de Código",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Dibujar",
"Drop any files here to add to the conversation": "Suelta cualquier archivo aquí para agregarlo a la conversación",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p.ej. '30s','10m'. Unidades válidas de tiempo son 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "p.ej. Un filtro para eliminar la profanidad del texto",
"e.g. My Filter": "p.ej. Mi Filtro",
"e.g. My Tools": "p.ej. Mis Herramientas",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Ingrese la clave API de Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Ingrese la URL pública de su WebUI. Esta URL se utilizará para generar enlaces en las notificaciones.",
"Enter Tika Server URL": "Ingrese la URL del servidor Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Ingrese el Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Ingrese la URL (p.ej., http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Ingrese la URL (p.ej., http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funciones habilitan la ejecución de código arbitrario",
"Functions allow arbitrary code execution.": "Funciones habilitan la ejecución de código arbitrario.",
"Functions imported successfully": "Funciones importadas exitosamente",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "General",
"General Settings": "Opciones Generales",
"Generate an image": "Generar una imagen",

View File

@ -182,6 +182,7 @@
"Code execution": "Kodearen exekuzioa",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kodea ongi formateatu da",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Marraztu",
"Drop any files here to add to the conversation": "Jaregin edozein fitxategi hemen elkarrizketara gehitzeko",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "adib. '30s','10m'. Denbora unitate baliodunak dira 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "adib. Testutik lizunkeriak kentzeko iragazki bat",
"e.g. My Filter": "adib. Nire Iragazkia",
"e.g. My Tools": "adib. Nire Tresnak",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Sartu Tavily API Gakoa",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Sartu Tika Zerbitzari URLa",
"Enter timeout in seconds": "",
"Enter Top K": "Sartu Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Sartu URLa (adib. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Sartu URLa (adib. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funtzioek kode arbitrarioa exekutatzea ahalbidetzen dute",
"Functions allow arbitrary code execution.": "Funtzioek kode arbitrarioa exekutatzea ahalbidetzen dute.",
"Functions imported successfully": "Funtzioak ongi inportatu dira",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Orokorra",
"General Settings": "Ezarpen Orokorrak",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "هر فایلی را اینجا رها کنید تا به مکالمه اضافه شود",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "به طور مثال '30s','10m'. واحد\u200cهای زمانی معتبر 's', 'm', 'h' هستند.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "مقدار Top K را وارد کنید",
"Enter URL (e.g. http://127.0.0.1:7860/)": "مقدار URL را وارد کنید (مثال http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "مقدار URL را وارد کنید (مثال http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "درون\u200cریزی توابع با موفقیت انجام شد",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "عمومی",
"General Settings": "تنظیمات عمومی",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Koodin suorittaminen",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Koodin muotoilu onnistui",
"Code Interpreter": "Ohjelmatulkki",
"Code Interpreter Engine": "Ohjelmatulkin moottori",
@ -321,6 +322,7 @@
"Draw": "Piirros",
"Drop any files here to add to the conversation": "Pudota tiedostoja tähän lisätäksesi ne keskusteluun",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "esim. '30s', '10m'. Kelpoiset aikayksiköt ovat 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "esim. suodatin, joka poistaa kirosanoja tekstistä",
"e.g. My Filter": "esim. Oma suodatin",
"e.g. My Tools": "esim. Omat työkalut",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Kirjoita Tavily API -avain",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Kirjoita julkinen WebUI verkko-osoitteesi. Verkko-osoitetta käytetään osoitteiden luontiin ilmoituksissa.",
"Enter Tika Server URL": "Kirjoita Tika Server URL",
"Enter timeout in seconds": "",
"Enter Top K": "Kirjoita Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Kirjoita URL-osoite (esim. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Kirjoita URL-osoite (esim. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Toiminnot sallivat mielivaltaisen koodin suorittamisen",
"Functions allow arbitrary code execution.": "Toiminnot sallivat mielivaltaisen koodin suorittamisen.",
"Functions imported successfully": "Toiminnot tuotu onnistuneesti",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Yleinen",
"General Settings": "Yleiset asetukset",
"Generate an image": "Luo kuva",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Le code a été formaté avec succès",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Déposez des fichiers ici pour les ajouter à la conversation",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "par ex. '30s', '10 min'. Les unités de temps valides sont 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Entrez la clé API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Entrez les Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (par ex. {http://127.0.0.1:7860/})",
"Enter URL (e.g. http://localhost:11434)": "Entrez l'URL (par ex. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "Fonctions importées avec succès",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Général",
"General Settings": "Paramètres Généraux",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Exécution de code",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Le code a été formaté avec succès",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Match nul",
"Drop any files here to add to the conversation": "Déposez des fichiers ici pour les ajouter à la conversation",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "par ex. '30s', '10 min'. Les unités de temps valides sont 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "par ex. un filtre pour retirer les vulgarités du texte",
"e.g. My Filter": "par ex. Mon Filtre",
"e.g. My Tools": "par ex. Mes Outils",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Entrez la clé API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Entrez l'URL publique de votre WebUI. Cette URL sera utilisée pour générer des liens dans les notifications.",
"Enter Tika Server URL": "Entrez l'URL du serveur Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Entrez les Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Entrez l'URL (par ex. {http://127.0.0.1:7860/})",
"Enter URL (e.g. http://localhost:11434)": "Entrez l'URL (par ex. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Les fonctions permettent l'exécution de code arbitraire",
"Functions allow arbitrary code execution.": "Les fonctions permettent l'exécution de code arbitraire.",
"Functions imported successfully": "Fonctions importées avec succès",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Général",
"General Settings": "Paramètres généraux",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "גרור כל קובץ לכאן כדי להוסיף לשיחה",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "למשל '30s', '10m'. יחידות זמן חוקיות הן 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "הזן Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "הזן כתובת URL (למשל http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "הזן כתובת URL (למשל http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "כללי",
"General Settings": "הגדרות כלליות",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "बातचीत में जोड़ने के लिए कोई भी फ़ाइल यहां छोड़ें",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "जैसे '30s', '10m', मान्य समय इकाइयाँ 's', 'm', 'h' हैं।",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "शीर्ष K दर्ज करें",
"Enter URL (e.g. http://127.0.0.1:7860/)": "यूआरएल दर्ज करें (उदा. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "यूआरएल दर्ज करें (उदा. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "सामान्य",
"General Settings": "सामान्य सेटिंग्स",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Spustite bilo koje datoteke ovdje za dodavanje u razgovor",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "npr. '30s','10m'. Važeće vremenske jedinice su 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"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/)",
"Enter URL (e.g. http://localhost:11434)": "Unesite URL (npr. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Općenito",
"General Settings": "Opće postavke",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Kód végrehajtás",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kód sikeresen formázva",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Rajzolás",
"Drop any files here to add to the conversation": "Húzz ide fájlokat a beszélgetéshez való hozzáadáshoz",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "pl. '30s','10m'. Érvényes időegységek: 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Add meg a Tavily API kulcsot",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Add meg a Tika szerver URL-t",
"Enter timeout in seconds": "",
"Enter Top K": "Add meg a Top K értéket",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Add meg az URL-t (pl. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Add meg az URL-t (pl. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "A funkciók tetszőleges kód végrehajtását teszik lehetővé",
"Functions allow arbitrary code execution.": "A funkciók tetszőleges kód végrehajtását teszik lehetővé.",
"Functions imported successfully": "Funkciók sikeresen importálva",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Általános",
"General Settings": "Általános beállítások",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kode berhasil diformat",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Letakkan file apa pun di sini untuk ditambahkan ke percakapan",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "misalnya '30-an', '10m'. Satuan waktu yang valid adalah 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Masukkan Kunci API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Masukkan Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Masukkan URL (mis. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Masukkan URL (mis. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "Fungsi berhasil diimpor",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Umum",
"General Settings": "Pengaturan Umum",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Cód a fhorghníomhú",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Cód formáidithe go rathúil",
"Code Interpreter": "Ateangaire Cód",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Tarraing",
"Drop any files here to add to the conversation": "Scaoil aon chomhaid anseo le cur leis an gcomhrá",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "m.sh. '30s', '10m'. Is iad aonaid ama bailí ná 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "m.h. Scagaire chun profanity a bhaint as téacs",
"e.g. My Filter": "m.sh. Mo Scagaire",
"e.g. My Tools": "e.g. Mo Uirlisí",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Cuir isteach eochair API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Cuir isteach URL poiblí do WebUI. Bainfear úsáid as an URL seo chun naisc a ghiniúint sna fógraí.",
"Enter Tika Server URL": "Cuir isteach URL freastalaí Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Cuir isteach Barr K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Iontráil URL (m.sh. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Iontráil URL (m.sh. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Ligeann feidhmeanna forghníomhú cód",
"Functions allow arbitrary code execution.": "Ceadaíonn feidhmeanna forghníomhú cód treallach.",
"Functions imported successfully": "Feidhmeanna allmhairi",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Ginearálta",
"General Settings": "Socruithe Ginearálta",
"Generate an image": "Gin íomhá",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Trascina qui i file da aggiungere alla conversazione",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "ad esempio '30s','10m'. Le unità di tempo valide sono 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Inserisci Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Inserisci URL (ad esempio http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Inserisci URL (ad esempio http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Generale",
"General Settings": "Impostazioni generali",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "コードフォーマットに成功しました",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "会話を追加するには、ここにファイルをドロップしてください",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例: '30秒'、'10分'。有効な時間単位は '秒'、'分'、'時間' です。",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Tavily API Keyを入力してください。",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Tika Server URLを入力してください。",
"Enter timeout in seconds": "",
"Enter Top K": "トップ K を入力してください",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL を入力してください (例: http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL を入力してください (例: http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "Functionsのインポートが成功しました",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "一般",
"General Settings": "一般設定",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "გადაიტანეთ ფაილები აქ, რათა დაამატოთ ისინი მიმოწერაში",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "მაგალითად, '30წ', '10მ'. მოქმედი დროის ერთეულები: 'წ', 'წთ', 'სთ'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "შეიყვანეთ Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "შეიყვანეთ მისამართი (მაგალითად http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "შეიყვანეთ მისამართი (მაგალითად http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "ზოგადი",
"General Settings": "ზოგადი პარამეტრები",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "코드 실행",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "성공적으로 코드가 생성되었습니다",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "그리기",
"Drop any files here to add to the conversation": "대화에 추가할 파일을 여기에 드롭하세요.",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "예: '30초','10분'. 유효한 시간 단위는 '초', '분', '시'입니다.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Tavily API 키 입력",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "WebUI의 공개 URL을 입력해 주세요. 이 URL은 알림에서 링크를 생성하는 데 사용합니다.",
"Enter Tika Server URL": "Tika 서버 URL 입력",
"Enter timeout in seconds": "",
"Enter Top K": "Top K 입력",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL 입력(예: http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL 입력(예: http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "함수로 임이의 코드 실행 허용하기",
"Functions allow arbitrary code execution.": "함수가 임이의 코드를 실행하도록 허용하였습니다",
"Functions imported successfully": "성공적으로 함수가 가져왔습니다",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "일반",
"General Settings": "일반 설정",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kodas suformatuotas sėkmingai",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Įkelkite dokumentus čia, kad juos pridėti į pokalbį",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "pvz. '30s', '10m'. Laiko vienetai yra 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Įveskite Tavily API raktą",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Įveskite Tika serverio nuorodą",
"Enter timeout in seconds": "",
"Enter Top K": "Įveskite Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Įveskite nuorodą (pvz. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Įveskite nuorododą (pvz. http://localhost:11434",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funkcijos leidžia nekontroliuojamo kodo vykdymą",
"Functions allow arbitrary code execution.": "Funkcijos leidžia nekontroliuojamo kodo vykdymą",
"Functions imported successfully": "Funkcijos importuotos sėkmingai",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Bendri",
"General Settings": "Bendri nustatymai",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kod berjaya diformatkan",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Letakkan mana-mana fail di sini untuk ditambahkan pada perbualan",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "cth '30s','10m'. Unit masa yang sah ialah 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Masukkan Kunci API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Masukkan URL Pelayan Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Masukkan 'Top K'",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Masukkan URL (cth http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Masukkan URL (cth http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Fungsi membenarkan pelaksanaan kod sewenang-wenangnya",
"Functions allow arbitrary code execution.": "Fungsi membenarkan pelaksanaan kod sewenang-wenangnya.",
"Functions imported successfully": "Fungsi berjaya diimport",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Umum",
"General Settings": "Tetapan Umum",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Kodekjøring",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Koden er formatert",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Tegne",
"Drop any files here to add to the conversation": "Slipp filer her for å legge dem til i samtalen",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "f.eks. '30s','10m'. Gyldige tidsenheter er 's', 'm', 't'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "f.eks. et filter for å fjerne banning fra tekst",
"e.g. My Filter": "f.eks. Mitt filter",
"e.g. My Tools": "f.eks. Mine verktøy",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Angi API-nøkkel for Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Angi server-URL for Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Angi Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Angi URL (f.eks. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Angi URL (f.eks. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funksjoner tillater vilkårlig kodekjøring",
"Functions allow arbitrary code execution.": "Funksjoner tillater vilkårlig kodekjøring.",
"Functions imported successfully": "Funksjoner er importert",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Generelt",
"General Settings": "Generelle innstillinger",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Code uitvoeren",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Code succesvol geformateerd",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Teken",
"Drop any files here to add to the conversation": "Sleep hier bestanden om toe te voegen aan het gesprek",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "bijv. '30s', '10m'. Geldige tijdseenheden zijn 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "bijv. Een filter om gevloek uit tekst te verwijderen",
"e.g. My Filter": "bijv. Mijn filter",
"e.g. My Tools": "bijv. Mijn gereedschappen",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Voer Tavily API-sleutel in",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Voer Tika Server URL in",
"Enter timeout in seconds": "",
"Enter Top K": "Voeg Top K toe",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Voer URL in (Bijv. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Voer URL in (Bijv. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Functies staan willekeurige code-uitvoering toe",
"Functions allow arbitrary code execution.": "Functies staan willekeurige code-uitvoering toe",
"Functions imported successfully": "Functies succesvol geïmporteerd",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Algemeen",
"General Settings": "Algemene instellingen",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "ਗੱਲਬਾਤ ਵਿੱਚ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਕੋਈ ਵੀ ਫਾਈਲ ਇੱਥੇ ਛੱਡੋ",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "ਉਦਾਹਰਣ ਲਈ '30ਸ','10ਮਿ'. ਸਹੀ ਸਮਾਂ ਇਕਾਈਆਂ ਹਨ 'ਸ', 'ਮ', 'ਘੰ'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "ਸਿਖਰ K ਦਰਜ ਕਰੋ",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL ਦਰਜ ਕਰੋ (ਉਦਾਹਰਣ ਲਈ http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "ਆਮ",
"General Settings": "ਆਮ ਸੈਟਿੰਗਾਂ",
"Generate an image": "",

File diff suppressed because it is too large Load Diff

View File

@ -182,6 +182,7 @@
"Code execution": "Execução de código",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Código formatado com sucesso",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Empate",
"Drop any files here to add to the conversation": "Solte qualquer arquivo aqui para adicionar à conversa",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "por exemplo, '30s', '10m'. Unidades de tempo válidas são 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "Exemplo: Um filtro para remover palavrões do texto",
"e.g. My Filter": "Exemplo: Meu Filtro",
"e.g. My Tools": "Exemplo: Minhas Ferramentas",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Digite a Chave API do Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Digite a URL do Servidor Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Digite o Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Digite a URL (por exemplo, http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Digite a URL (por exemplo, http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funções permitem a execução arbitrária de código",
"Functions allow arbitrary code execution.": "Funções permitem a execução arbitrária de código.",
"Functions imported successfully": "Funções importadas com sucesso",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Geral",
"General Settings": "Configurações Gerais",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Largue os ficheiros aqui para adicionar à conversa",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "por exemplo, '30s', '10m'. Unidades de tempo válidas são 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Escreva o Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Escreva o URL (por exemplo, http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Escreva o URL (por exemplo, http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Geral",
"General Settings": "Configurações Gerais",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Executarea codului",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Cod formatat cu succes",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Desenează",
"Drop any files here to add to the conversation": "Plasează orice fișiere aici pentru a le adăuga la conversație",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "de ex. '30s', '10m'. Unitățile de timp valide sunt 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Introduceți Cheia API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Introduceți URL-ul Serverului Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Introduceți Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Introduceți URL-ul (de ex. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Introduceți URL-ul (de ex. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funcțiile permit executarea arbitrară a codului",
"Functions allow arbitrary code execution.": "Funcțiile permit executarea arbitrară a codului.",
"Functions imported successfully": "Funcțiile au fost importate cu succes",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "General",
"General Settings": "Setări Generale",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Выполнение кода",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Код успешно отформатирован",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Рисовать",
"Drop any files here to add to the conversation": "Перетащите сюда файлы, чтобы добавить их в разговор",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "например, '30s','10m'. Допустимые единицы времени: 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Введите ключ API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Введите URL-адрес сервера Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Введите Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Введите URL-адрес (например, http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Введите URL-адрес (например, http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Функции позволяют выполнять произвольный код",
"Functions allow arbitrary code execution.": "Функции позволяют выполнять произвольный код.",
"Functions imported successfully": "Функции успешно импортированы",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Общее",
"General Settings": "Общие настройки",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Vykonávanie kódu",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kód bol úspešne naformátovaný.",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Nakresliť",
"Drop any files here to add to the conversation": "Sem presuňte akékoľvek súbory, ktoré chcete pridať do konverzácie",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "napr. '30s','10m'. Platné časové jednotky sú 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Zadajte API kľúč Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Zadajte URL servera Tika",
"Enter timeout in seconds": "",
"Enter Top K": "Zadajte horné K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Zadajte URL (napr. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Zadajte URL (napr. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Funkcie umožňujú vykonávať ľubovoľný kód.",
"Functions allow arbitrary code execution.": "Funkcie umožňujú vykonávanie ľubovoľného kódu.",
"Functions imported successfully": "Funkcie boli úspešne importované",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Všeobecné",
"General Settings": "Všeobecné nastavenia",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Извршавање кода",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Код форматиран успешно",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Нацртај",
"Drop any files here to add to the conversation": "Убаците било које датотеке овде да их додате у разговор",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "нпр. '30s', '10m'. Важеће временске јединице су 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Унесите Топ К",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Унесите адресу (нпр. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Унесите адресу (нпр. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Опште",
"General Settings": "Општа подешавања",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Släpp filer här för att lägga till i samtalet",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "t.ex. '30s', '10m'. Giltiga tidsenheter är 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "Ange Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Ange URL (t.ex. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Ange URL (t.ex. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Allmän",
"General Settings": "Allmänna inställningar",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "จัดรูปแบบโค้ดสำเร็จแล้ว",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "วางไฟล์ใดๆ ที่นี่เพื่อเพิ่มในการสนทนา",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "เช่น '30s', '10m' หน่วยเวลาที่ถูกต้องคือ 's', 'm', 'h'",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "ใส่คีย์ API ของ Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "ใส่ URL เซิร์ฟเวอร์ของ Tika",
"Enter timeout in seconds": "",
"Enter Top K": "ใส่ Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "ใส่ URL (เช่น http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "ใส่ URL (เช่น http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "ฟังก์ชันอนุญาตการเรียกใช้โค้ดโดยพลการ",
"Functions allow arbitrary code execution.": "ฟังก์ชันอนุญาตการเรียกใช้โค้ดโดยพลการ",
"Functions imported successfully": "นำเข้าฟังก์ชันสำเร็จ",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "ทั่วไป",
"General Settings": "การตั้งค่าทั่วไป",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "",
"Enter timeout in seconds": "",
"Enter Top K": "",
"Enter URL (e.g. http://127.0.0.1:7860/)": "",
"Enter URL (e.g. http://localhost:11434)": "",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "",
"Functions allow arbitrary code execution.": "",
"Functions imported successfully": "",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "",
"General Settings": "",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Kod yürütme",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Kod başarıyla biçimlendirildi",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "Çiz",
"Drop any files here to add to the conversation": "Sohbete eklemek istediğiniz dosyaları buraya bırakın",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "örn. '30s', '10m'. Geçerli zaman birimleri 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "örn. Metinden küfürleri kaldırmak için bir filtre",
"e.g. My Filter": "örn. Benim Filtrem",
"e.g. My Tools": "örn. Benim Araçlarım",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Tavily API Anahtarını Girin",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Tika Sunucu URL'sini Girin",
"Enter timeout in seconds": "",
"Enter Top K": "Top K'yı girin",
"Enter URL (e.g. http://127.0.0.1:7860/)": "URL'yi Girin (örn. http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "URL'yi Girin (e.g. http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Fonksiyonlar keyfi kod yürütülmesine izin verir",
"Functions allow arbitrary code execution.": "Fonksiyonlar keyfi kod yürütülmesine izin verir.",
"Functions imported successfully": "Fonksiyonlar başarıyla içe aktarıldı",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Genel",
"General Settings": "Genel Ayarlar",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "Виконання коду",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Код успішно відформатовано",
"Code Interpreter": "Інтерпретатор коду",
"Code Interpreter Engine": "Двигун інтерпретатора коду",
@ -321,6 +322,7 @@
"Draw": "Малювати",
"Drop any files here to add to the conversation": "Перетягніть сюди файли, щоб додати до розмови",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "напр., '30s','10m'. Дійсні одиниці часу: 'с', 'хв', 'г'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "напр., фільтр для видалення нецензурної лексики з тексту",
"e.g. My Filter": "напр., Мій фільтр",
"e.g. My Tools": "напр., Мої інструменти",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Введіть ключ API Tavily",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Введіть публічний URL вашого WebUI. Цей URL буде використовуватися для генерування посилань у сповіщеннях.",
"Enter Tika Server URL": "Введіть URL-адресу сервера Tika ",
"Enter timeout in seconds": "",
"Enter Top K": "Введіть Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Введіть URL-адресу (напр., http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Введіть URL-адресу (напр., http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Функції дозволяють виконання довільного коду",
"Functions allow arbitrary code execution.": "Функції дозволяють виконання довільного коду.",
"Functions imported successfully": "Функції успішно імпортовано",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Загальні",
"General Settings": "Загальні налаштування",
"Generate an image": "Згенерувати зображення",

View File

@ -182,6 +182,7 @@
"Code execution": "کوڈ کا نفاذ",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "کوڈ کامیابی سے فارمیٹ ہو گیا",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "ڈرائنگ کریں",
"Drop any files here to add to the conversation": "گفتگو میں شامل کرنے کے لیے کوئی بھی فائل یہاں چھوڑیں",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "مثلاً '30s'، '10m' درست وقت کی اکائیاں ہیں 's'، 'm'، 'h'",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Tavily API کلید درج کریں",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "ٹیکا سرور یو آر ایل درج کریں",
"Enter timeout in seconds": "",
"Enter Top K": "اوپر کے K درج کریں",
"Enter URL (e.g. http://127.0.0.1:7860/)": "یو آر ایل درج کریں (جیسے کہ http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "یو آر ایل درج کریں (مثلاً http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "فنکشنز کوڈ کے بلاواسطہ نفاذ کی اجازت دیتے ہیں",
"Functions allow arbitrary code execution.": "افعال صوابدیدی کوڈ کے اجرا کی اجازت دیتے ہیں",
"Functions imported successfully": "فنکشنز کامیابی سے درآمد ہو گئے ہیں",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "عمومی",
"General Settings": "عمومی ترتیبات",
"Generate an image": "",

View File

@ -182,6 +182,7 @@
"Code execution": "",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution Timeout": "",
"Code formatted successfully": "Mã được định dạng thành công",
"Code Interpreter": "",
"Code Interpreter Engine": "",
@ -321,6 +322,7 @@
"Draw": "",
"Drop any files here to add to the conversation": "Thả bất kỳ tệp nào ở đây để thêm vào nội dung chat",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "vd: '30s','10m'. Đơn vị thời gian hợp lệ là 's', 'm', 'h'.",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "",
"e.g. My Filter": "",
"e.g. My Tools": "",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "Nhập Tavily API Key",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "",
"Enter Tika Server URL": "Nhập URL cho Tika Server",
"Enter timeout in seconds": "",
"Enter Top K": "Nhập Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "Nhập URL (vd: http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "Nhập URL (vd: http://localhost:11434)",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "Các Function cho phép thực thi mã tùy ý",
"Functions allow arbitrary code execution.": "Các Function cho phép thực thi mã tùy ý.",
"Functions imported successfully": "Các function đã được nạp thành công",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "Cài đặt chung",
"General Settings": "Cấu hình chung",
"Generate an image": "",

View File

@ -20,7 +20,7 @@
"Account Activation Pending": "账号待激活",
"Accurate information": "提供的信息很准确",
"Actions": "自动化",
"Activate": "",
"Activate": "激活",
"Activate this command by typing \"/{{COMMAND}}\" to chat input.": "通过输入 \"/{{COMMAND}}\" 激活此命令",
"Active Users": "当前在线用户",
"Add": "添加",
@ -100,7 +100,7 @@
"Audio": "语音",
"August": "八月",
"Authenticate": "认证",
"Authentication": "",
"Authentication": "身份验证",
"Auto-Copy Response to Clipboard": "自动复制回复到剪贴板",
"Auto-playback response": "自动念出回复内容",
"Autocomplete Generation": "输入框内容猜测补全",
@ -167,7 +167,7 @@
"Click here to": "点击",
"Click here to download user import template file.": "点击此处下载用户导入所需的模板文件。",
"Click here to learn more about faster-whisper and see the available models.": "点击此处了解更多关于faster-whisper的信息并查看可用的模型。",
"Click here to see available models.": "单击此处查看可用。",
"Click here to see available models.": "单击此处查看可用型。",
"Click here to select": "点击这里选择",
"Click here to select a csv file.": "点击此处选择 csv 文件。",
"Click here to select a py file.": "点击此处选择 py 文件。",
@ -180,12 +180,13 @@
"Clone of {{TITLE}}": "{{TITLE}} 的副本",
"Close": "关闭",
"Code execution": "代码执行",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution": "代码执行",
"Code Execution Engine": "代码执行引擎",
"Code Execution Timeout": "",
"Code formatted successfully": "代码格式化成功",
"Code Interpreter": "代码解释器",
"Code Interpreter Engine": "代码解释引擎",
"Code Interpreter Prompt Template": "代码解释器提示模板",
"Code Interpreter Prompt Template": "代码解释器提示模板",
"Collection": "文件集",
"Color": "颜色",
"ComfyUI": "ComfyUI",
@ -202,7 +203,7 @@
"Confirm Password": "确认密码",
"Confirm your action": "确定吗?",
"Confirm your new password": "确认新密码",
"Connect to your own OpenAI compatible API endpoints.": "连接到您自己的 OpenAI 兼容 API 端点。",
"Connect to your own OpenAI compatible API endpoints.": "连接到你自己的与 OpenAI 兼容的 API 接口端点。",
"Connections": "外部连接",
"Constrains effort on reasoning for reasoning models. Only applicable to reasoning models from specific providers that support reasoning effort. (Default: medium)": "限制推理模型的推理努力。仅适用于支持推理努力的特定提供商的推理模型。(默认值:中等)",
"Contact Admin for WebUI Access": "请联系管理员以获取访问权限",
@ -214,7 +215,7 @@
"Continue with Email": "使用邮箱登录",
"Continue with LDAP": "使用 LDAP 登录",
"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "控制消息文本如何拆分以用于 TTS 请求。“Punctuation”拆分为句子“paragraphs”拆分为段落“none”将消息保留为单个字符串。",
"Control the repetition of token sequences in the generated text. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 1.1) will be more lenient. At 1, it is disabled. (Default: 1.1)": "",
"Control the repetition of token sequences in the generated text. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 1.1) will be more lenient. At 1, it is disabled. (Default: 1.1)": "控制生成文本中 Token 的重复。较高的值(例如 1.5)会更强烈地惩罚重复,而较低的值(例如 1.1)则更宽松。设置为 1 时此功能被禁用。默认值1.1",
"Controls": "对话高级设置",
"Controls the balance between coherence and diversity of the output. A lower value will result in more focused and coherent text. (Default: 5.0)": "控制输出的连贯性和多样性之间的平衡。较低的值将导致更集中和连贯的文本。默认值5.0",
"Copied": "已复制",
@ -270,7 +271,7 @@
"Delete folder?": "删除分组?",
"Delete function?": "删除函数?",
"Delete Message": "删除消息",
"Delete message?": "",
"Delete message?": "删除消息?",
"Delete prompt?": "删除提示词?",
"delete this link": "此处删除这个链接",
"Delete tool?": "删除工具?",
@ -282,14 +283,14 @@
"Description": "描述",
"Didn't fully follow instructions": "没有完全遵照指示",
"Direct Connections": "直接连接",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "直接连接允许用户连接到他们自己的与 OpenAI 兼容的 API 端点。",
"Direct Connections allow users to connect to their own OpenAI compatible API endpoints.": "直接连接功能允许用户连接至其自有的、兼容 OpenAI 的 API 端点。",
"Direct Connections settings updated": "直接连接设置已更新",
"Disabled": "禁用",
"Discover a function": "发现更多函数",
"Discover a model": "发现更多模型",
"Discover a prompt": "发现更多提示词",
"Discover a tool": "发现更多工具",
"Discover how to use Open WebUI and seek support from the community.": "",
"Discover how to use Open WebUI and seek support from the community.": "了解如何使用 Open WebUI 并寻求社区支持。",
"Discover wonders": "发现奇迹",
"Discover, download, and explore custom functions": "发现、下载并探索更多函数",
"Discover, download, and explore custom prompts": "发现、下载并探索更多自定义提示词",
@ -314,13 +315,14 @@
"Don't like the style": "不喜欢这个文风",
"Done": "完成",
"Download": "下载",
"Download as SVG": "",
"Download as SVG": "下载为 SVG",
"Download canceled": "下载已取消",
"Download Database": "下载数据库",
"Drag and drop a file to upload or select a file to view": "拖动文件上传或选择文件查看",
"Draw": "平局",
"Drop any files here to add to the conversation": "拖动文件到此处以添加到对话中",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例如 '30s''10m'。有效的时间单位是秒:'s',分:'m',时:'h'。",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "例如:一个用于过滤文本中不当内容的过滤器",
"e.g. My Filter": "例如:我的过滤器",
"e.g. My Tools": "例如:我的工具",
@ -370,7 +372,7 @@
"Enter Chunk Overlap": "输入块重叠 (Chunk Overlap)",
"Enter Chunk Size": "输入块大小 (Chunk Size)",
"Enter description": "输入简介描述",
"Enter domains separated by commas (e.g., example.com,site.org)": "输入以逗号分隔的域名例如example.com,site.org",
"Enter domains separated by commas (e.g., example.com,site.org)": "输入以逗号分隔的域名例如example.comsite.org",
"Enter Exa API Key": "输入 Exa API 密钥",
"Enter Github Raw URL": "输入 Github Raw 地址",
"Enter Google PSE API Key": "输入 Google PSE API 密钥",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "输入 Tavily API 密钥",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "输入 WebUI 的公共 URL。此 URL 将用于在通知中生成链接。",
"Enter Tika Server URL": "输入 Tika 服务器地址",
"Enter timeout in seconds": "",
"Enter Top K": "输入 Top K",
"Enter URL (e.g. http://127.0.0.1:7860/)": "输入地址 (例如http://127.0.0.1:7860/)",
"Enter URL (e.g. http://localhost:11434)": "输入地址 (例如http://localhost:11434)",
@ -455,7 +458,7 @@
"Failed to save models configuration": "无法保存模型配置",
"Failed to update settings": "无法更新设置",
"Failed to upload file.": "上传文件失败",
"Features": "",
"Features": "功能",
"Features Permissions": "功能权限",
"February": "二月",
"Feedback History": "反馈历史",
@ -485,7 +488,7 @@
"Form": "手动创建",
"Format your variables using brackets like this:": "使用括号格式化你的变量,如下所示:",
"Frequency Penalty": "频率惩罚",
"Full Context Mode": "",
"Full Context Mode": "完整上下文模式",
"Function": "函数",
"Function Calling": "函数调用 (Function Calling)",
"Function created successfully": "函数创建成功",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "注意:函数有权执行任意代码",
"Functions allow arbitrary code execution.": "注意:函数有权执行任意代码。",
"Functions imported successfully": "函数导入成功",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "通用",
"General Settings": "通用设置",
"Generate an image": "生成图像",
@ -601,7 +607,7 @@
"Leave empty to include all models or select specific models": "留空表示包含所有模型或请选择模型",
"Leave empty to use the default prompt, or enter a custom prompt": "留空以使用默认提示词,或输入自定义提示词。",
"Leave model field empty to use the default model.": "将模型字段留空以使用默认模型。",
"License": "",
"License": "授权",
"Light": "浅色",
"Listening...": "正在倾听...",
"Llama.cpp": "Llama.cpp",
@ -761,7 +767,7 @@
"Playground": "AI 对话游乐场",
"Please carefully review the following warnings:": "请仔细阅读以下警告信息:",
"Please do not close the settings page while loading the model.": "加载模型时请不要关闭设置页面。",
"Please enter a prompt": "请输一个 Prompt",
"Please enter a prompt": "请输一个 Prompt",
"Please fill in all fields.": "请填写所有字段。",
"Please select a model first.": "请先选择一个模型。",
"Please select a model.": "请选择一个模型。",
@ -770,7 +776,7 @@
"Positive attitude": "积极的态度",
"Prefix ID": "Prefix ID",
"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "Prefix ID 用于通过为模型 ID 添加前缀来避免与其他连接发生冲突 - 留空则禁用此功能",
"Presence Penalty": "",
"Presence Penalty": "重复惩罚Presence Penalty",
"Previous 30 days": "过去 30 天",
"Previous 7 days": "过去 7 天",
"Profile Image": "用户头像",
@ -807,7 +813,7 @@
"Rename": "重命名",
"Reorder Models": "重新排序模型",
"Repeat Last N": "重复最后 N 次",
"Repeat Penalty (Ollama)": "",
"Repeat Penalty (Ollama)": "重复惩罚Ollama",
"Reply in Thread": "在主题中回复",
"Request Mode": "请求模式",
"Reranking Model": "重排模型",
@ -870,7 +876,7 @@
"Select a pipeline": "选择一个管道",
"Select a pipeline url": "选择一个管道 URL",
"Select a tool": "选择一个工具",
"Select an auth method": "选择身份验证方",
"Select an auth method": "选择身份验证方",
"Select an Ollama instance": "选择一个 Ollama 实例。",
"Select Engine": "选择引擎",
"Select Knowledge": "选择知识",
@ -904,10 +910,10 @@
"Set the number of worker threads used for computation. This option controls how many threads are used to process incoming requests concurrently. Increasing this value can improve performance under high concurrency workloads but may also consume more CPU resources.": "设置用于计算的工作线程数量。该选项可控制并发处理传入请求的线程数量。增加该值可以提高高并发工作负载下的性能,但也可能消耗更多的 CPU 资源。",
"Set Voice": "设置音色",
"Set whisper model": "设置 whisper 模型",
"Sets a flat bias against tokens that have appeared at least once. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled. (Default: 0)": "",
"Sets a scaling bias against tokens to penalize repetitions, based on how many times they have appeared. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled. (Default: 1.1)": "",
"Sets a flat bias against tokens that have appeared at least once. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled. (Default: 0)": "这个设置项用于调整对重复 tokens 的抑制强度。当某个 token 至少出现过一次后,系统会通过 flat bias 参数施加惩罚力度:数值越大(如 1.5),抑制重复的效果越强烈;数值较小(如 0.9)则相对宽容。当设为 0 时,系统会完全关闭这个重复抑制功能(默认值为 0",
"Sets a scaling bias against tokens to penalize repetitions, based on how many times they have appeared. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled. (Default: 1.1)": "这个参数用于通过 scaling bias 机制抑制重复内容:当某些 tokens 重复出现时,系统会根据它们已出现的次数自动施加惩罚。数值越大(如 1.5)惩罚力度越强,能更有效减少重复;数值较小(如 0.9)则允许更多重复。当设为 0 时完全关闭该功能,默认值设置为 1.1 保持适度抑制。",
"Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)": "设置模型回溯多远以防止重复。(默认值640 = 禁用,-1 = num_ctx",
"Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt. (Default: random)": "设置生成文本时使用的随机数种子。将其设置为一个特定的数字将使模型在同一提示下生成相同的文本。 默认值:随机",
"Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt. (Default: random)": "设置 random number seed 可以控制模型生成文本的随机起点。如果指定一个具体数字,当输入相同的提示语时,模型每次都会生成完全相同的文本内容(默认是随机选取 seed",
"Sets the size of the context window used to generate the next token. (Default: 2048)": "设置用于生成下一个 Token 的上下文大小。(默认值2048",
"Sets the stop sequences to use. When this pattern is encountered, the LLM will stop generating text and return. Multiple stop patterns may be set by specifying multiple separate stop parameters in a modelfile.": "设置要使用的停止序列。遇到这种模式时,大语言模型将停止生成文本并返回。可以通过在模型文件中指定多个单独的停止参数来设置多个停止模式。",
"Settings": "设置",
@ -952,7 +958,7 @@
"Tags Generation Prompt": "标签生成提示词",
"Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (default: 1)": "Tail free sampling 用于减少输出中可能性较低的标记的影响。数值越大(如 2.0),影响就越小,而数值为 1.0 则会禁用此设置。(默认值1",
"Tap to interrupt": "点击以中断",
"Tasks": "",
"Tasks": "任务",
"Tavily API Key": "Tavily API 密钥",
"Tell us more:": "请告诉我们更多细节",
"Temperature": "温度 (Temperature)",
@ -975,7 +981,7 @@
"The score should be a value between 0.0 (0%) and 1.0 (100%).": "分值应介于 0.00%)和 1.0100%)之间。",
"The temperature of the model. Increasing the temperature will make the model answer more creatively. (Default: 0.8)": "模型的温度。提高温度将使模型更具创造性地回答。默认值0.8",
"Theme": "主题",
"Thinking...": "正在思考...",
"Thinking...": "正在深度思考...",
"This action cannot be undone. Do you wish to continue?": "此操作无法撤销。是否确认继续?",
"This ensures that your valuable conversations are securely saved to your backend database. Thank you!": "这将确保您的宝贵对话被安全地保存到后台数据库中。感谢!",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "这是一个实验功能,可能不会如预期那样工作,而且可能随时发生变化。",
@ -989,8 +995,8 @@
"This will delete all models including custom models and cannot be undone.": "这将删除所有模型,包括自定义模型,且无法撤销。",
"This will reset the knowledge base and sync all files. Do you wish to continue?": "这将重置知识库并替换所有文件为目录下文件。确认继续?",
"Thorough explanation": "解释较为详细",
"Thought for {{DURATION}}": "已推理 持续 {{DURATION}}",
"Thought for {{DURATION}} seconds": "已推理 持续 {{DURATION}} 秒",
"Thought for {{DURATION}}": "已深度思考 用时 {{DURATION}}",
"Thought for {{DURATION}} seconds": "已深度思考 用时 {{DURATION}} 秒",
"Tika": "Tika",
"Tika Server URL required.": "请输入 Tika 服务器地址。",
"Tiktoken": "Tiktoken",
@ -1011,7 +1017,7 @@
"To select actions here, add them to the \"Functions\" workspace first.": "要在这里选择自动化,请先将其添加到工作空间中的“函数”。",
"To select filters here, add them to the \"Functions\" workspace first.": "要在这里选择过滤器,请先将其添加到工作空间中的“函数”。",
"To select toolkits here, add them to the \"Tools\" workspace first.": "要在这里选择工具包,请先将其添加到工作空间中的“工具”。",
"Toast notifications for new updates": "新更新的弹窗提示",
"Toast notifications for new updates": "更新后弹窗提示更新内容",
"Today": "今天",
"Toggle settings": "切换设置",
"Toggle sidebar": "切换侧边栏",
@ -1056,7 +1062,7 @@
"Updated": "已更新",
"Updated at": "更新于",
"Updated At": "更新于",
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "",
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "升级到授权计划以获得增强功能,包括自定义主题与品牌以及专属支持。",
"Upload": "上传",
"Upload a GGUF model": "上传一个 GGUF 模型",
"Upload directory": "上传目录",
@ -1095,7 +1101,7 @@
"Warning:": "警告:",
"Warning: Enabling this will allow users to upload arbitrary code on the server.": "警告:启用此功能将允许用户在服务器上上传任意代码。",
"Warning: If you update or change your embedding model, you will need to re-import all documents.": "警告:如果您修改了语义向量模型,则需要重新导入所有文档。",
"Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "",
"Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "警告Jupyter 执行允许任意代码执行,存在严重的安全风险——请极其谨慎地操作。",
"Web": "网页",
"Web API": "网页 API",
"Web Loader Settings": "网页爬取设置",

View File

@ -20,7 +20,7 @@
"Account Activation Pending": "帳號待啟用",
"Accurate information": "準確資訊",
"Actions": "動作",
"Activate": "",
"Activate": "啟用",
"Activate this command by typing \"/{{COMMAND}}\" to chat input.": "在對話輸入框中輸入 \"/{{COMMAND}}\" 來啟用此命令。",
"Active Users": "活躍使用者",
"Add": "新增",
@ -100,7 +100,7 @@
"Audio": "音訊",
"August": "8 月",
"Authenticate": "驗證",
"Authentication": "",
"Authentication": "驗證",
"Auto-Copy Response to Clipboard": "自動將回應複製到剪貼簿",
"Auto-playback response": "自動播放回應",
"Autocomplete Generation": "自動完成生成",
@ -180,8 +180,9 @@
"Clone of {{TITLE}}": "{{TITLE}} 的副本",
"Close": "關閉",
"Code execution": "程式碼執行",
"Code Execution": "",
"Code Execution Engine": "",
"Code Execution": "程式碼執行",
"Code Execution Engine": "程式碼執行引擎",
"Code Execution Timeout": "",
"Code formatted successfully": "程式碼格式化成功",
"Code Interpreter": "程式碼解釋器",
"Code Interpreter Engine": "程式碼解釋器引擎",
@ -214,7 +215,7 @@
"Continue with Email": "使用 Email 繼續",
"Continue with LDAP": "使用 LDAP 繼續",
"Control how message text is split for TTS requests. 'Punctuation' splits into sentences, 'paragraphs' splits into paragraphs, and 'none' keeps the message as a single string.": "控制文字轉語音TTS請求中如何分割訊息文字。「標點符號」分割為句子「段落」分割為段落「無」則保持訊息為單一字串。",
"Control the repetition of token sequences in the generated text. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 1.1) will be more lenient. At 1, it is disabled. (Default: 1.1)": "",
"Control the repetition of token sequences in the generated text. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 1.1) will be more lenient. At 1, it is disabled. (Default: 1.1)": "控制在生成文本中 token 序列的重複程度。 數值越高(例如 1.5)將會更強烈地懲罰重複,而數值越低(例如 1.1)則會較為寬鬆。 若數值為 1則停用此功能。預設值1.1",
"Controls": "控制項",
"Controls the balance between coherence and diversity of the output. A lower value will result in more focused and coherent text. (Default: 5.0)": "控制輸出的連貫性和多樣性之間的平衡。較低的值會產生更專注和連貫的文字。預設5.0",
"Copied": "已複製",
@ -269,8 +270,8 @@
"Delete chat?": "刪除對話紀錄?",
"Delete folder?": "刪除資料夾?",
"Delete function?": "刪除函式?",
"Delete Message": "刪除訊息",
"Delete message?": "",
"Delete Message": "刪除訊息",
"Delete message?": "刪除訊息?",
"Delete prompt?": "刪除提示詞?",
"delete this link": "刪除此連結",
"Delete tool?": "刪除工具?",
@ -289,7 +290,7 @@
"Discover a model": "發掘模型",
"Discover a prompt": "發掘提示詞",
"Discover a tool": "發掘工具",
"Discover how to use Open WebUI and seek support from the community.": "",
"Discover how to use Open WebUI and seek support from the community.": "探索如何使用 Open WebUI 並從社群尋求支援。",
"Discover wonders": "發掘奇蹟",
"Discover, download, and explore custom functions": "發掘、下載及探索自訂函式",
"Discover, download, and explore custom prompts": "發掘、下載及探索自訂提示詞",
@ -314,13 +315,14 @@
"Don't like the style": "不喜歡這個樣式",
"Done": "完成",
"Download": "下載",
"Download as SVG": "",
"Download as SVG": "以 SVG 格式下載",
"Download canceled": "已取消下載",
"Download Database": "下載資料庫",
"Drag and drop a file to upload or select a file to view": "拖放檔案以上傳或選擇檔案以檢視",
"Draw": "繪製",
"Drop any files here to add to the conversation": "拖拽任意檔案到此處以新增至對話",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "例如:'30s'、'10m'。有效的時間單位為 's'、'm'、'h'。",
"e.g. 60": "",
"e.g. A filter to remove profanity from text": "例如:從文字中移除髒話的篩選器",
"e.g. My Filter": "例如:我的篩選器",
"e.g. My Tools": "例如:我的工具",
@ -408,6 +410,7 @@
"Enter Tavily API Key": "輸入 Tavily API 金鑰",
"Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "請輸入您 WebUI 的公開 URL。此 URL 將用於在通知中產生連結。",
"Enter Tika Server URL": "輸入 Tika 伺服器 URL",
"Enter timeout in seconds": "",
"Enter Top K": "輸入 Top K 值",
"Enter URL (e.g. http://127.0.0.1:7860/)": "輸入 URL例如http://127.0.0.1:7860/",
"Enter URL (e.g. http://localhost:11434)": "輸入 URL例如http://localhost:11434",
@ -455,7 +458,7 @@
"Failed to save models configuration": "儲存模型設定失敗",
"Failed to update settings": "更新設定失敗",
"Failed to upload file.": "上傳檔案失敗。",
"Features": "",
"Features": "功能",
"Features Permissions": "功能權限",
"February": "2 月",
"Feedback History": "回饋歷史",
@ -485,7 +488,7 @@
"Form": "表單",
"Format your variables using brackets like this:": "使用方括號格式化您的變數,如下所示:",
"Frequency Penalty": "頻率懲罰",
"Full Context Mode": "",
"Full Context Mode": "完整上下文模式",
"Function": "函式",
"Function Calling": "函式呼叫",
"Function created successfully": "成功建立函式",
@ -500,6 +503,9 @@
"Functions allow arbitrary code execution": "函式允許執行任意程式碼",
"Functions allow arbitrary code execution.": "函式允許執行任意程式碼。",
"Functions imported successfully": "成功匯入函式",
"Gemini": "",
"Gemini API Config": "",
"Gemini API Key is required.": "",
"General": "一般",
"General Settings": "一般設定",
"Generate an image": "產生圖片",
@ -601,7 +607,7 @@
"Leave empty to include all models or select specific models": "留空以包含所有模型或選擇特定模型",
"Leave empty to use the default prompt, or enter a custom prompt": "留空以使用預設提示詞,或輸入自訂提示詞",
"Leave model field empty to use the default model.": "留空模型欄位以使用預設模型。",
"License": "",
"License": "授權",
"Light": "淺色",
"Listening...": "正在聆聽...",
"Llama.cpp": "Llama.cpp",
@ -770,7 +776,7 @@
"Positive attitude": "積極的態度",
"Prefix ID": "前綴 ID",
"Prefix ID is used to avoid conflicts with other connections by adding a prefix to the model IDs - leave empty to disable": "前綴 ID 用於透過為模型 ID 新增前綴以避免與其他連線衝突 - 留空以停用",
"Presence Penalty": "",
"Presence Penalty": "在場懲罰",
"Previous 30 days": "過去 30 天",
"Previous 7 days": "過去 7 天",
"Profile Image": "個人檔案圖片",
@ -807,7 +813,7 @@
"Rename": "重新命名",
"Reorder Models": "重新排序模型",
"Repeat Last N": "重複最後 N 個",
"Repeat Penalty (Ollama)": "",
"Repeat Penalty (Ollama)": "重複懲罰 (Ollama)",
"Reply in Thread": "在討論串中回覆",
"Request Mode": "請求模式",
"Reranking Model": "重新排序模型",
@ -904,8 +910,8 @@
"Set the number of worker threads used for computation. This option controls how many threads are used to process incoming requests concurrently. Increasing this value can improve performance under high concurrency workloads but may also consume more CPU resources.": "設定用於計算的工作執行緒數量。此選項控制使用多少執行緒來同時處理傳入的請求。增加此值可以在高併發工作負載下提升效能,但也可能消耗更多 CPU 資源。",
"Set Voice": "設定語音",
"Set whisper model": "設定 whisper 模型",
"Sets a flat bias against tokens that have appeared at least once. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled. (Default: 0)": "",
"Sets a scaling bias against tokens to penalize repetitions, based on how many times they have appeared. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled. (Default: 1.1)": "",
"Sets a flat bias against tokens that have appeared at least once. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled. (Default: 0)": "針對至少出現一次的 token設定固定的負面偏見。 數值越高(例如 1.5)將會更強烈地懲罰重複,而數值越低(例如 0.9)則會較為寬鬆。 若數值為 0則停用此功能。預設值0",
"Sets a scaling bias against tokens to penalize repetitions, based on how many times they have appeared. A higher value (e.g., 1.5) will penalize repetitions more strongly, while a lower value (e.g., 0.9) will be more lenient. At 0, it is disabled. (Default: 1.1)": "針對 token 設定比例偏差以懲罰重複,其基於 token 出現的次數。 數值越高(例如 1.5)將會更強烈地懲罰重複,而數值越低(例如 0.9)則會較為寬鬆。 若數值為 0則停用此功能。預設值1.1",
"Sets how far back for the model to look back to prevent repetition. (Default: 64, 0 = disabled, -1 = num_ctx)": "設定模型向後查看以防止重複的距離。預設640 = 停用,-1 = num_ctx",
"Sets the random number seed to use for generation. Setting this to a specific number will make the model generate the same text for the same prompt. (Default: random)": "設定用於生成的隨機數種子。將其設定為特定數字會使模型對相同的提示詞產生相同的文字。(預設:隨機)",
"Sets the size of the context window used to generate the next token. (Default: 2048)": "設定用於生成下一個 token 的上下文視窗大小。預設2048",
@ -952,7 +958,7 @@
"Tags Generation Prompt": "標籤生成提示詞",
"Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting. (default: 1)": "使用無尾採樣來減少較不可能的 token 對輸出的影響。較高的值(例如 2.0)會減少更多影響,而值為 1.0 則停用此設定。預設1",
"Tap to interrupt": "點選以中斷",
"Tasks": "",
"Tasks": "任務",
"Tavily API Key": "Tavily API 金鑰",
"Tell us more:": "告訴我們更多:",
"Temperature": "溫度",
@ -1056,7 +1062,7 @@
"Updated": "已更新",
"Updated at": "更新於",
"Updated At": "更新於",
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "",
"Upgrade to a licensed plan for enhanced capabilities, including custom theming and branding, and dedicated support.": "升級至授權方案以獲得更強大功能,包括客製化主題與品牌,和專屬支援。",
"Upload": "上傳",
"Upload a GGUF model": "上傳 GGUF 模型",
"Upload directory": "上傳目錄",
@ -1095,7 +1101,7 @@
"Warning:": "警告:",
"Warning: Enabling this will allow users to upload arbitrary code on the server.": "警告:啟用此功能將允許使用者在伺服器上上傳任意程式碼。",
"Warning: If you update or change your embedding model, you will need to re-import all documents.": "警告:如果您更新或更改嵌入模型,您將需要重新匯入所有文件。",
"Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "",
"Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "警告Jupyter 執行允許任意程式碼執行,構成嚴重安全風險——請務必極度謹慎。",
"Web": "網頁",
"Web API": "網頁 API",
"Web Loader Settings": "網頁載入器設定",
@ -1109,7 +1115,7 @@
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI 將向 \"{{url}}/api/chat\" 發送請求",
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI 將向 \"{{url}}/chat/completions\" 發送請求",
"What are you trying to achieve?": "您正在試圖完成什麽?",
"What are you working on?": "您正在工作什麽?",
"What are you working on?": "您現在的工作是什麽?",
"Whats New in": "新功能",
"When enabled, the model will respond to each chat message in real-time, generating a response as soon as the user sends a message. This mode is useful for live chat applications, but may impact performance on slower hardware.": "啟用時,模型將即時回應每個對話訊息,在使用者傳送訊息後立即生成回應。此模式適用於即時對話應用程式,但在較慢的硬體上可能會影響效能。",
"wherever you are": "無論您在何處",

View File

@ -134,6 +134,9 @@ function inlineKatex(options) {
},
tokenizer(src, tokens) {
return katexTokenizer(src, tokens, false);
},
renderer(token) {
return `${token?.text ?? ''}`;
}
};
}
@ -147,6 +150,9 @@ function blockKatex(options) {
},
tokenizer(src, tokens) {
return katexTokenizer(src, tokens, true);
},
renderer(token) {
return `${token?.text ?? ''}`;
}
};
}

View File

@ -28,6 +28,12 @@
let ldapUsername = '';
const querystringValue = (key) => {
const querystring = window.location.search;
const urlParams = new URLSearchParams(querystring);
return urlParams.get(key);
};
const setSessionUser = async (sessionUser) => {
if (sessionUser) {
console.log(sessionUser);
@ -39,7 +45,9 @@
$socket.emit('user-join', { auth: { token: sessionUser.token } });
await user.set(sessionUser);
await config.set(await getBackendConfig());
goto('/');
const redirectPath = querystringValue('redirect') || '/';
goto(redirectPath);
}
};

View File

@ -36,3 +36,5 @@
@apply cursor-pointer;
}
}
@custom-variant hover (&:hover);