diff --git a/LICENSE b/LICENSE index faa0129c65..99f39e7fef 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,5 @@ +Open WebUI License + Copyright (c) 2023- Open WebUI Inc. [Created by Timothy Jaeryang Baek] All rights reserved. @@ -15,11 +17,27 @@ modification, are permitted provided that the following conditions are met: contributors may be used to endorse or promote products derived from this software without specific prior written permission. -4. Notwithstanding any other provision of this License, and as a material condition of the rights granted herein, licensees are strictly prohibited from altering, removing, obscuring, or replacing any "Open WebUI" branding, including but not limited to the name, logo, or any visual, textual, or symbolic identifiers that distinguish the software and its interfaces, in any deployment or distribution, regardless of the number of users, except as explicitly set forth in Clauses 5 and 6 below. +4. Notwithstanding any other provision of this License, and as a material + condition of the rights granted herein, licensees are strictly prohibited + from altering, removing, obscuring, or replacing any "Open WebUI" + branding, including but not limited to the name, logo, or any visual, + textual, or symbolic identifiers that distinguish the software and its + interfaces, in any deployment or distribution, except in the following + circumstances: (i) deployments or distributions where the total number + of end users (defined as individual natural persons with direct access + to the application) does not exceed fifty (50) within any rolling + thirty (30) day period; (ii) the licensee has obtained specific prior + written permission from the copyright holder; or (iii) where the + licensee has obtained a duly executed enterprise license expressly + permitting such modification. For all other cases, any removal or + alteration of the "Open WebUI" branding shall constitute a material + breach of license. -5. The branding restriction enumerated in Clause 4 shall not apply in the following limited circumstances: (i) deployments or distributions where the total number of end users (defined as individual natural persons with direct access to the application) does not exceed fifty (50) within any rolling thirty (30) day period; (ii) cases in which the licensee is an official contributor to the codebase—with a substantive code change successfully merged into the main branch of the official codebase maintained by the copyright holder—who has obtained specific prior written permission for branding adjustment from the copyright holder; or (iii) where the licensee has obtained a duly executed enterprise license expressly permitting such modification. For all other cases, any removal or alteration of the "Open WebUI" branding shall constitute a material breach of license. +Materials governed by prior licenses retain those original license +terms, as specified in LICENSE_HISTORY. -6. All code, modifications, or derivative works incorporated into this project prior to the incorporation of this branding clause remain licensed under the BSD 3-Clause License, and prior contributors retain all BSD-3 rights therein; if any such contributor requests the removal of their BSD-3-licensed code, the copyright holder will do so, and any replacement code will be licensed under the project's primary license then in effect. By contributing after this clause's adoption, you agree to the project's Contributor License Agreement (CLA) and to these updated terms for all new contributions. +By contributing to this project, you agree to the project's Contributor +License Agreement (CONTRIBUTOR_LICENSE_AGREEMENT). THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index b599ff575d..65f29a4ad7 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -1,3 +1,4 @@ +import asyncio import json import logging import os @@ -35,7 +36,7 @@ from open_webui.env import ( WEBUI_NAME, log, ) -from open_webui.internal.db import Base, get_db +from open_webui.internal.db import Base, get_db, get_async_db from open_webui.utils.redis import get_redis_connection @@ -90,6 +91,7 @@ def load_json_config(): def save_to_db(data): + """Sync save — used ONLY at startup/import time.""" with get_db() as db: existing_config = db.query(Config).first() if not existing_config: @@ -102,12 +104,39 @@ def save_to_db(data): db.commit() +async def async_save_to_db(data): + """Async save — used for ALL runtime config persistence.""" + from sqlalchemy import select + + async with get_async_db() as db: + result = await db.execute(select(Config).limit(1)) + existing_config = result.scalars().first() + if not existing_config: + new_config = Config(data=data, version=0) + db.add(new_config) + else: + existing_config.data = data + existing_config.updated_at = datetime.now() + db.add(existing_config) + await db.commit() + + def reset_config(): + """Sync reset — used ONLY at startup.""" with get_db() as db: db.query(Config).delete() db.commit() +async def async_reset_config(): + """Async reset — used at runtime.""" + from sqlalchemy import delete as sa_delete + + async with get_async_db() as db: + await db.execute(sa_delete(Config)) + await db.commit() + + # When initializing, check if config.json exists and migrate it to the database if os.path.exists(f'{DATA_DIR}/config.json'): data = load_json_config() @@ -144,6 +173,7 @@ PERSISTENT_CONFIG_REGISTRY = [] def save_config(config): + """Sync save — used ONLY at startup/import time.""" global CONFIG_DATA global PERSISTENT_CONFIG_REGISTRY try: @@ -159,6 +189,23 @@ def save_config(config): return True +async def async_save_config(config): + """Async save — used for ALL runtime config persistence.""" + global CONFIG_DATA + global PERSISTENT_CONFIG_REGISTRY + try: + await async_save_to_db(config) + CONFIG_DATA = config + + # Trigger updates on all registered PersistentConfig entries + for config_item in PERSISTENT_CONFIG_REGISTRY: + config_item.update() + except Exception as e: + log.exception(e) + return False + return True + + T = TypeVar('T') ENABLE_PERSISTENT_CONFIG = os.environ.get('ENABLE_PERSISTENT_CONFIG', 'True').lower() == 'true' @@ -202,6 +249,7 @@ class PersistentConfig(Generic[T]): log.info(f'Updated {self.env_name} to new value {self.value}') def save(self): + """Sync save — used ONLY at startup/import time.""" log.info(f"Saving '{self.env_name}' to the database") path_parts = self.config_path.split('.') sub_config = CONFIG_DATA @@ -213,6 +261,19 @@ class PersistentConfig(Generic[T]): save_to_db(CONFIG_DATA) self.config_value = self.value + async def async_save(self): + """Async save — used for ALL runtime config persistence.""" + log.info(f"Saving '{self.env_name}' to the database") + path_parts = self.config_path.split('.') + sub_config = CONFIG_DATA + for key in path_parts[:-1]: + if key not in sub_config: + sub_config[key] = {} + sub_config = sub_config[key] + sub_config[path_parts[-1]] = self.value + await async_save_to_db(CONFIG_DATA) + self.config_value = self.value + class AppConfig: _redis: Union[redis.Redis, redis.cluster.RedisCluster] = None @@ -246,12 +307,27 @@ class AppConfig: self._state[key] = value else: self._state[key].value = value - self._state[key].save() + + # At runtime (inside the event loop) persist via the async engine + # to avoid blocking the loop and contending with the async DB pool. + # At startup/import time, fall back to sync. + try: + loop = asyncio.get_running_loop() + loop.create_task(self._async_persist(key)) + except RuntimeError: + self._state[key].save() if self._redis and ENABLE_PERSISTENT_CONFIG: redis_key = f'{self._redis_key_prefix}:config:{key}' self._redis.set(redis_key, json.dumps(self._state[key].value)) + async def _async_persist(self, key): + """Persist a single config key via the async engine.""" + try: + await self._state[key].async_save() + except Exception as e: + log.error(f'Failed to async-persist config key {key}: {e}') + def __getattr__(self, key): if key not in self._state: raise AttributeError(f"Config key '{key}' not found") @@ -915,6 +991,7 @@ if CUSTOM_NAME: #################################### STORAGE_PROVIDER = os.environ.get('STORAGE_PROVIDER', 'local') # defaults to local, s3 +STORAGE_LOCAL_CACHE = os.environ.get('STORAGE_LOCAL_CACHE', 'true').lower() == 'true' S3_ACCESS_KEY_ID = os.environ.get('S3_ACCESS_KEY_ID', None) S3_SECRET_ACCESS_KEY = os.environ.get('S3_SECRET_ACCESS_KEY', None) @@ -1148,10 +1225,16 @@ ENABLE_SIGNUP = PersistentConfig( ENABLE_LOGIN_FORM = PersistentConfig( 'ENABLE_LOGIN_FORM', - 'ui.ENABLE_LOGIN_FORM', + 'ui.enable_login_form', os.environ.get('ENABLE_LOGIN_FORM', 'True').lower() == 'true', ) +ENABLE_PASSWORD_CHANGE_FORM = PersistentConfig( + 'ENABLE_PASSWORD_CHANGE_FORM', + 'ui.enable_password_change_form', + os.environ.get('ENABLE_PASSWORD_CHANGE_FORM', 'True').lower() == 'true', +) + ENABLE_PASSWORD_AUTH = os.environ.get('ENABLE_PASSWORD_AUTH', 'True').lower() == 'true' DEFAULT_LOCALE = PersistentConfig( diff --git a/backend/open_webui/constants.py b/backend/open_webui/constants.py index c0c79fdf50..ad1bdf4a20 100644 --- a/backend/open_webui/constants.py +++ b/backend/open_webui/constants.py @@ -91,6 +91,17 @@ class ERROR_MESSAGES(str, Enum): INVALID_PASSWORD = lambda err='': err if err else 'The password does not meet the required validation criteria.' + AUTOMATION_LIMIT_EXCEEDED = lambda size='': f'Automation limit reached ({size})' + AUTOMATION_TOO_FREQUENT = lambda interval='': f'Schedule too frequent. Minimum interval is {interval} seconds.' + AUTOMATION_INVALID_RRULE = lambda err='': f'Invalid RRULE: {err}' + AUTOMATION_NO_FUTURE_RUNS = 'RRULE has no future occurrences' + + FEATURE_DISABLED = lambda name='': f'{name} is disabled' + INPUT_TOO_LONG = lambda size='': f'Input prompt exceeds maximum length of {size}' + SERVER_CONNECTION_ERROR = 'Open WebUI: Server Connection Error' + REQUIRED_FIELD_EMPTY = lambda name='': f'Required field {name} is empty' + OAUTH_NOT_CONFIGURED = lambda name='': f"Provider '{name}' is not configured" + class TASKS(str, Enum): def __str__(self) -> str: diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index 9a16fd4ba8..08323be125 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -375,7 +375,7 @@ else: except Exception: DATABASE_POOL_RECYCLE = 3600 -DATABASE_ENABLE_SQLITE_WAL = os.environ.get('DATABASE_ENABLE_SQLITE_WAL', 'False').lower() == 'true' +DATABASE_ENABLE_SQLITE_WAL = os.environ.get('DATABASE_ENABLE_SQLITE_WAL', 'True').lower() == 'true' DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL = os.environ.get('DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL', None) if DATABASE_USER_ACTIVE_STATUS_UPDATE_INTERVAL is not None: @@ -431,9 +431,7 @@ except ValueError: # enabled, the kernel sends TCP keepalive probes on idle connections so # half-closed sockets (e.g. after a silent firewall/LB reset or a NIC # flap) are detected before the next command lands on them. -REDIS_SOCKET_KEEPALIVE = ( - os.environ.get('REDIS_SOCKET_KEEPALIVE', 'False').lower() == 'true' -) +REDIS_SOCKET_KEEPALIVE = os.environ.get('REDIS_SOCKET_KEEPALIVE', 'False').lower() == 'true' # How often (in seconds) redis-py should PING an idle pooled connection # before reusing it. Opt-in: defaults to unset (empty string) so behavior @@ -519,6 +517,11 @@ PASSWORD_VALIDATION_HINT = os.environ.get('PASSWORD_VALIDATION_HINT', '') BYPASS_MODEL_ACCESS_CONTROL = os.environ.get('BYPASS_MODEL_ACCESS_CONTROL', 'False').lower() == 'true' +# When enabled, skips pydub-based preprocessing (format conversion, compression, +# and chunked splitting) before sending files to processing engines. Useful when +# the upstream provider handles these steps or when ffmpeg is unavailable. +BYPASS_PYDUB_PREPROCESSING = os.environ.get('BYPASS_PYDUB_PREPROCESSING', 'False').lower() == 'true' + # When disabled (default), the OpenAI catch-all proxy endpoint (/{path:path}) # is blocked. Enable only if you need direct passthrough to upstream OpenAI- # compatible APIs for endpoints not natively handled by Open WebUI. @@ -806,6 +809,36 @@ else: AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER = AIOHTTP_CLIENT_TIMEOUT +#################################### +# AIOHTTP Connection Pool +#################################### + +AIOHTTP_POOL_CONNECTIONS = os.environ.get('AIOHTTP_POOL_CONNECTIONS', '') +if AIOHTTP_POOL_CONNECTIONS == '': + AIOHTTP_POOL_CONNECTIONS = None +else: + try: + AIOHTTP_POOL_CONNECTIONS = int(AIOHTTP_POOL_CONNECTIONS) + except ValueError: + AIOHTTP_POOL_CONNECTIONS = None + +AIOHTTP_POOL_CONNECTIONS_PER_HOST = os.environ.get('AIOHTTP_POOL_CONNECTIONS_PER_HOST', '') +if AIOHTTP_POOL_CONNECTIONS_PER_HOST == '': + AIOHTTP_POOL_CONNECTIONS_PER_HOST = None +else: + try: + AIOHTTP_POOL_CONNECTIONS_PER_HOST = int(AIOHTTP_POOL_CONNECTIONS_PER_HOST) + except ValueError: + AIOHTTP_POOL_CONNECTIONS_PER_HOST = None + +AIOHTTP_POOL_DNS_TTL = os.environ.get('AIOHTTP_POOL_DNS_TTL', '300') +try: + AIOHTTP_POOL_DNS_TTL = int(AIOHTTP_POOL_DNS_TTL) + if AIOHTTP_POOL_DNS_TTL < 0: + AIOHTTP_POOL_DNS_TTL = 300 +except ValueError: + AIOHTTP_POOL_DNS_TTL = 300 + RAG_EMBEDDING_TIMEOUT = os.environ.get('RAG_EMBEDDING_TIMEOUT', '') if RAG_EMBEDDING_TIMEOUT == '': @@ -909,6 +942,9 @@ AUDIT_INCLUDED_PATHS = os.getenv('AUDIT_INCLUDED_PATHS', '').split(',') AUDIT_INCLUDED_PATHS = [path.strip() for path in AUDIT_INCLUDED_PATHS] AUDIT_INCLUDED_PATHS = [path.lstrip('/') for path in AUDIT_INCLUDED_PATHS if path] +# When enabled, GET requests are also audited (disabled by default to avoid log noise) +ENABLE_AUDIT_GET_REQUESTS = os.getenv('ENABLE_AUDIT_GET_REQUESTS', 'False').lower() == 'true' + #################################### # OPENTELEMETRY diff --git a/backend/open_webui/internal/db.py b/backend/open_webui/internal/db.py index a9e5e089ab..3818543fc7 100644 --- a/backend/open_webui/internal/db.py +++ b/backend/open_webui/internal/db.py @@ -214,6 +214,7 @@ if 'sqlite' in ASYNC_SQLALCHEMY_DATABASE_URL: ) if DATABASE_ENABLE_SQLITE_WAL: + @event.listens_for(async_engine.sync_engine, 'connect') def _set_sqlite_wal(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() @@ -229,7 +230,6 @@ else: pool_timeout=DATABASE_POOL_TIMEOUT, pool_recycle=DATABASE_POOL_RECYCLE, pool_pre_ping=True, - poolclass=QueuePool, ) else: async_engine = create_async_engine( diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index d959351d4c..63580f990d 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -21,7 +21,7 @@ from typing import Optional from aiocache import cached import aiohttp import anyio.to_thread -import requests + from redis import Redis @@ -46,7 +46,6 @@ from fastapi.staticfiles import StaticFiles from starlette_compress import CompressMiddleware from starlette.exceptions import HTTPException as StarletteHTTPException -from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.sessions import SessionMiddleware from starlette.responses import Response, StreamingResponse from starlette.datastructures import Headers @@ -58,8 +57,15 @@ from starsessions import ( from starsessions.stores.redis import RedisStore from open_webui.utils import logger +from open_webui.utils.asgi_middleware import ( + AuthTokenMiddleware, + CommitSessionMiddleware, + RedirectMiddleware, + WebsocketUpgradeGuardMiddleware, +) from open_webui.utils.audit import AuditLevel, AuditLoggingMiddleware from open_webui.utils.logger import start_logger +from open_webui.utils.session_pool import get_session from open_webui.socket.main import ( MODELS, app as socket_app, @@ -67,6 +73,7 @@ from open_webui.socket.main import ( periodic_session_pool_cleanup, get_event_emitter, get_models_in_use, + get_user_id_from_session_pool, ) from open_webui.routers import ( analytics, @@ -114,7 +121,7 @@ from open_webui.internal.db import ScopedSession, engine, get_async_session from open_webui.models.functions import Functions from open_webui.models.models import Models from open_webui.models.users import UserModel, Users -from open_webui.models.chats import Chats +from open_webui.models.chats import Chats, ChatForm from open_webui.config import ( # Ollama @@ -378,6 +385,7 @@ from open_webui.config import ( JWT_EXPIRES_IN, ENABLE_SIGNUP, ENABLE_LOGIN_FORM, + ENABLE_PASSWORD_CHANGE_FORM, ENABLE_API_KEYS, ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS, API_KEYS_ALLOWED_ENDPOINTS, @@ -469,12 +477,14 @@ from open_webui.config import ( AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH, AppConfig, reset_config, + async_reset_config, ) from open_webui.env import ( ENABLE_CUSTOM_MODEL_FALLBACK, LICENSE_KEY, AUDIT_EXCLUDED_PATHS, AUDIT_INCLUDED_PATHS, + ENABLE_AUDIT_GET_REQUESTS, AUDIT_LOG_LEVEL, CHANGELOG, REDIS_URL, @@ -553,6 +563,7 @@ from open_webui.utils.oauth import ( get_oauth_client_info_with_static_credentials, encrypt_data, decrypt_data, + resolve_oauth_client_info, OAuthManager, OAuthClientManager, OAuthClientInformationFull, @@ -565,13 +576,14 @@ from open_webui.tasks import ( list_task_ids_by_item_id, create_task, stop_task, + stop_item_tasks, list_tasks, ) # Import from tasks.py from open_webui.utils.redis import get_sentinels_from_env -from open_webui.constants import ERROR_MESSAGES +from open_webui.constants import ERROR_MESSAGES, TASKS if SAFE_MODE: print('SAFE MODE ENABLED') @@ -622,7 +634,7 @@ async def lifespan(app: FastAPI): start_logger() if RESET_CONFIG_ON_START: - reset_config() + await async_reset_config() if LICENSE_KEY: get_license_data(app, LICENSE_KEY) @@ -718,6 +730,11 @@ async def lifespan(app: FastAPI): yield + # Shutdown: clean up shared resources + from open_webui.utils.session_pool import close_session + + await close_session() + if hasattr(app.state, 'redis_task_command_listener'): app.state.redis_task_command_listener.cancel() @@ -845,6 +862,7 @@ app.state.BASE_MODELS = [] app.state.config.WEBUI_URL = WEBUI_URL app.state.config.ENABLE_SIGNUP = ENABLE_SIGNUP app.state.config.ENABLE_LOGIN_FORM = ENABLE_LOGIN_FORM +app.state.config.ENABLE_PASSWORD_CHANGE_FORM = ENABLE_PASSWORD_CHANGE_FORM app.state.config.ENABLE_API_KEYS = ENABLE_API_KEYS app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS = ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS @@ -1346,149 +1364,19 @@ if ENABLE_COMPRESSION_MIDDLEWARE: app.add_middleware(CompressMiddleware) -class RedirectMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next): - # Check if the request is a GET request - if request.method == 'GET': - path = request.url.path - query_params = dict(parse_qs(urlparse(str(request.url)).query)) - - redirect_params = {} - - # Check for the specific watch path and the presence of 'v' parameter - if path.endswith('/watch') and 'v' in query_params: - # Extract the first 'v' parameter - youtube_video_id = query_params['v'][0] - redirect_params['youtube'] = youtube_video_id - - if 'shared' in query_params and len(query_params['shared']) > 0: - # PWA share_target support - - text = query_params['shared'][0] - if text: - urls = re.match(r'https://\S+', text) - if urls: - from open_webui.retrieval.loaders.youtube import _parse_video_id - - if youtube_video_id := _parse_video_id(urls[0]): - redirect_params['youtube'] = youtube_video_id - else: - redirect_params['load-url'] = urls[0] - else: - redirect_params['q'] = text - - if redirect_params: - redirect_url = f'/?{urlencode(redirect_params)}' - return RedirectResponse(url=redirect_url) - - # Proceed with the normal flow of other requests - response = await call_next(request) - return response - - +# All HTTP middlewares below are pure-ASGI implementations. The previous +# `BaseHTTPMiddleware` / `@app.middleware('http')` versions wrapped the +# downstream app in an anyio task group whose cancel scope cancelled +# in-flight DB calls (and any other awaits) on client disconnect / +# response completion — which surfaced as noisy SQLAlchemy +# `terminate_force_close` tracebacks under aiosqlite and as random +# CancelledError storms across the request path. See +# `open_webui.utils.asgi_middleware` for the rationale. app.add_middleware(RedirectMiddleware) app.add_middleware(SecurityHeadersMiddleware) - - -class APIKeyRestrictionMiddleware: - def __init__(self, app): - self.app = app - - async def __call__(self, scope, receive, send): - if scope['type'] == 'http': - request = Request(scope) - auth_header = request.headers.get('Authorization') - token = None - - if auth_header: - parts = auth_header.split(' ', 1) - if len(parts) == 2: - token = parts[1] - - # Only apply restrictions if an sk- API key is used - if token and token.startswith('sk-'): - # Check if restrictions are enabled - if app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS: - allowed_paths = [ - path.strip() - for path in str(app.state.config.API_KEYS_ALLOWED_ENDPOINTS).split(',') - if path.strip() - ] - - request_path = request.url.path - - # Match exact path or prefix path - is_allowed = any( - request_path == allowed or request_path.startswith(allowed + '/') for allowed in allowed_paths - ) - - if not is_allowed: - await JSONResponse( - status_code=status.HTTP_403_FORBIDDEN, - content={'detail': 'API key not allowed to access this endpoint.'}, - )(scope, receive, send) - return - - await self.app(scope, receive, send) - - -app.add_middleware(APIKeyRestrictionMiddleware) - - -@app.middleware('http') -async def commit_session_after_request(request: Request, call_next): - response = await call_next(request) - # log.debug("Commit session after request") - try: - ScopedSession.commit() - finally: - # CRITICAL: remove() returns the connection to the pool. - # Without this, connections remain "checked out" and accumulate - # as "idle in transaction" in PostgreSQL. - ScopedSession.remove() - return response - - -@app.middleware('http') -async def check_url(request: Request, call_next): - start_time = int(time.time()) - request.state.token = get_http_authorization_cred(request.headers.get('Authorization')) - # Fallback to cookie token for browser sessions - if request.state.token is None and request.cookies.get('token'): - from fastapi.security import HTTPAuthorizationCredentials - - request.state.token = HTTPAuthorizationCredentials(scheme='Bearer', credentials=request.cookies.get('token')) - - # Fallback to x-api-key header for Anthropic Messages API routes - if request.state.token is None and request.headers.get('x-api-key'): - request_path = request.url.path - if request_path in ('/api/message', '/api/v1/messages') or request_path.startswith('/ollama/v1/messages'): - from fastapi.security import HTTPAuthorizationCredentials - - request.state.token = HTTPAuthorizationCredentials( - scheme='Bearer', credentials=request.headers.get('x-api-key') - ) - - request.state.enable_api_keys = app.state.config.ENABLE_API_KEYS - response = await call_next(request) - process_time = int(time.time()) - start_time - response.headers['X-Process-Time'] = str(process_time) - return response - - -@app.middleware('http') -async def inspect_websocket(request: Request, call_next): - if '/ws/socket.io' in request.url.path and request.query_params.get('transport') == 'websocket': - upgrade = (request.headers.get('Upgrade') or '').lower() - connection = (request.headers.get('Connection') or '').lower().split(',') - # Check that there's the correct headers for an upgrade, else reject the connection - # This is to work around this upstream issue: https://github.com/miguelgrinberg/python-engineio/issues/367 - if upgrade != 'websocket' or 'upgrade' not in connection: - return JSONResponse( - status_code=status.HTTP_400_BAD_REQUEST, - content={'detail': 'Invalid WebSocket upgrade request'}, - ) - return await call_next(request) +app.add_middleware(CommitSessionMiddleware) +app.add_middleware(AuthTokenMiddleware, fastapi_app=app) +app.add_middleware(WebsocketUpgradeGuardMiddleware) app.add_middleware( @@ -1560,6 +1448,7 @@ if audit_level != AuditLevel.NONE: audit_level=audit_level, excluded_paths=AUDIT_EXCLUDED_PATHS, included_paths=AUDIT_INCLUDED_PATHS, + audit_get_requests=ENABLE_AUDIT_GET_REQUESTS, max_body_size=MAX_BODY_LOG_SIZE, ) ################################## @@ -1727,13 +1616,30 @@ async def chat_completion( if model_info_params.get('reasoning_tags') is not None: reasoning_tags = model_info_params.get('reasoning_tags') + # parent_id signals intent: + # null → new chat (root message, no parent) + # value → follow-up (user message's parentId = prev assistant) + # absent → legacy caller, no chat management + is_new_chat = 'parent_id' in form_data and form_data['parent_id'] is None and not form_data.get('chat_id') + parent_id = form_data.pop('parent_id', None) + form_data.pop('new_chat', None) # Legacy field + + # Multi-model: {model_id: assistant_message_id} + # Single-model fallback: built from 'model' + 'id' + message_ids = form_data.pop('message_ids', None) + if not message_ids: + message_ids = {model_id: form_data.pop('id', None)} + else: + form_data.pop('id', None) + + user_message = form_data.pop('user_message', None) or form_data.pop('parent_message', None) metadata = { 'user_id': user.id, 'chat_id': form_data.pop('chat_id', None), - 'message_id': form_data.pop('id', None), - 'parent_message': form_data.pop('parent_message', None), - 'parent_message_id': form_data.pop('parent_id', None), + 'user_message': user_message, + 'user_message_id': user_message.get('id') if user_message else None, 'session_id': form_data.pop('session_id', None), + 'folder_id': form_data.pop('folder_id', None), 'filter_ids': form_data.pop('filter_ids', []), 'tool_ids': form_data.get('tool_ids', None), 'tool_servers': form_data.pop('tool_servers', None), @@ -1756,36 +1662,160 @@ async def chat_completion( }, } + if is_new_chat: + metadata['chat_id'] = str(uuid4()) + if metadata.get('chat_id') and user: - if not metadata['chat_id'].startswith('local:'): # temporary chats are not stored - # Verify chat ownership — lightweight EXISTS check avoids - # deserializing the full chat JSON blob just to confirm the row exists - if ( - not await Chats.is_chat_owner(metadata['chat_id'], user.id) and user.role != 'admin' - ): # admins can access any chat - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=ERROR_MESSAGES.DEFAULT(), + chat_id = metadata['chat_id'] + if not chat_id.startswith('local:'): # temporary chats are not stored + if is_new_chat: + # Build the full history upfront with ALL assistant placeholders + user_message = metadata.get('user_message') or {} + user_message_id = user_message.get('id') if user_message else None + + history_messages = {} + all_assistant_ids = [assistant_id for assistant_id in message_ids.values() if assistant_id] + + if user_message_id and user_message: + user_message['childrenIds'] = all_assistant_ids + history_messages[user_message_id] = user_message + + for target_model_id, assistant_message_id in message_ids.items(): + if assistant_message_id: + history_messages[assistant_message_id] = { + 'id': assistant_message_id, + 'parentId': user_message_id, + 'childrenIds': [], + 'role': 'assistant', + 'content': '', + 'done': False, + 'model': target_model_id, + 'timestamp': int(time.time()), + } + + await Chats.insert_new_chat( + chat_id, + user.id, + ChatForm( + chat={ + 'id': chat_id, + 'title': 'New Chat', + 'models': list(message_ids.keys()), + 'history': { + 'currentId': all_assistant_ids[0] if all_assistant_ids else user_message_id, + 'messages': history_messages, + }, + 'messages': [ + {'role': 'user', 'content': user_message.get('content', '')}, + ] + if user_message_id + else [], + 'tags': [], + 'timestamp': int(time.time() * 1000), + }, + folder_id=metadata.get('folder_id'), + ), ) - # Insert chat files from parent message if any - parent_message = metadata.get('parent_message') or {} - parent_message_files = parent_message.get('files', []) - if parent_message_files: - try: - await Chats.insert_chat_files( - metadata['chat_id'], - parent_message.get('id'), - [ - file_item.get('id') - for file_item in parent_message_files - if file_item.get('type') == 'file' - ], - user.id, + # Insert chat files from user message if any + user_message_files = user_message.get('files', []) + if user_message_files: + try: + await Chats.insert_chat_files( + chat_id, + user_message_id, + [ + file_item.get('id') + for file_item in user_message_files + if file_item.get('type') == 'file' + ], + user.id, + ) + except Exception as e: + log.debug(f'Error inserting chat files: {e}') + pass + else: + # Existing chat — verify ownership + if not await Chats.is_chat_owner(chat_id, user.id) and user.role != 'admin': + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=ERROR_MESSAGES.DEFAULT(), ) - except Exception as e: - log.debug(f'Error inserting chat files: {e}') - pass + + # Save user message to DB + user_message = metadata.get('user_message') or {} + if user_message and user_message.get('id'): + await Chats.upsert_message_to_chat_by_id_and_message_id( + chat_id, + user_message['id'], + user_message, + ) + + # Link grandparent → user message (childrenIds) + grandparent_id = user_message.get('parentId') + if grandparent_id: + grandparent = await Chats.get_message_by_id_and_message_id(chat_id, grandparent_id) + if grandparent: + child_ids = grandparent.get('childrenIds', []) + if user_message['id'] not in child_ids: + child_ids.append(user_message['id']) + await Chats.upsert_message_to_chat_by_id_and_message_id( + chat_id, grandparent_id, {'childrenIds': child_ids} + ) + + # Insert chat files from user message if any + user_message_files = user_message.get('files', []) + if user_message_files: + try: + await Chats.insert_chat_files( + chat_id, + user_message.get('id'), + [ + file_item.get('id') + for file_item in user_message_files + if file_item.get('type') == 'file' + ], + user.id, + ) + except Exception as e: + log.debug(f'Error inserting chat files: {e}') + pass + + # Save ALL assistant placeholders + user_message_id = metadata.get('user_message_id') + all_assistant_ids = [assistant_id for assistant_id in message_ids.values() if assistant_id] + + # Link user message → all assistant messages (childrenIds) + if user_message_id and all_assistant_ids: + existing_user_message = await Chats.get_message_by_id_and_message_id(chat_id, user_message_id) + if existing_user_message: + child_ids = existing_user_message.get('childrenIds', []) + for assistant_id in all_assistant_ids: + if assistant_id not in child_ids: + child_ids.append(assistant_id) + await Chats.upsert_message_to_chat_by_id_and_message_id( + chat_id, + user_message_id, + {'childrenIds': child_ids}, + ) + + # Save each assistant placeholder + for target_model_id, assistant_message_id in message_ids.items(): + if assistant_message_id: + await Chats.upsert_message_to_chat_by_id_and_message_id( + chat_id, + assistant_message_id, + { + 'id': assistant_message_id, + 'parentId': user_message_id, + 'childrenIds': [], + 'role': 'assistant', + 'content': '', + 'done': False, + 'model': target_model_id, + 'timestamp': int(time.time()), + }, + ) request.state.metadata = metadata form_data['metadata'] = metadata @@ -1797,24 +1827,26 @@ async def chat_completion( detail=str(e), ) - async def process_chat(request, form_data, user, metadata, model): + async def process_chat(request, form_data, user, metadata, model, tasks=None): try: form_data, metadata, events = await process_chat_payload(request, form_data, user, metadata, model) response = await chat_completion_handler(request, form_data, user) - if metadata.get('chat_id') and metadata.get('message_id'): + + # When the upstream provider returns an error (e.g. HTTP 400 + # content-filter, quota exceeded), generate_chat_completion + # returns a JSONResponse instead of raising. Detect this and + # raise so the except-block below emits chat:message:error + + # chat:tasks:cancel, unblocking the frontend. + if isinstance(response, JSONResponse) and response.status_code >= 400: try: - if not metadata['chat_id'].startswith('local:'): - await Chats.upsert_message_to_chat_by_id_and_message_id( - metadata['chat_id'], - metadata['message_id'], - { - 'parentId': metadata.get('parent_message_id', None), - 'model': model_id, - }, - ) + error_body = json.loads(response.body.decode('utf-8', 'replace')) + detail = error_body.get('error', error_body) if isinstance(error_body, dict) else error_body + if isinstance(detail, dict): + detail = detail.get('message', detail.get('detail', str(detail))) except Exception: - pass + detail = f'Provider returned HTTP {response.status_code}' + raise Exception(detail) ctx = await build_chat_response_context(request, form_data, user, model, metadata, tasks, events) @@ -1823,17 +1855,18 @@ async def chat_completion( log.info('Chat processing was cancelled') try: event_emitter = await get_event_emitter(metadata) - await asyncio.shield( - event_emitter( - {'type': 'chat:tasks:cancel'}, + if event_emitter: + await asyncio.shield( + event_emitter( + {'type': 'chat:tasks:cancel'}, + ) ) - ) except Exception as e: pass finally: raise # re-raise to ensure proper task cancellation handling except Exception as e: - log.debug(f'Error processing chat payload: {e}') + log.error('Error processing chat payload: %s', e) if metadata.get('chat_id') and metadata.get('message_id'): # Update the chat message with the error try: @@ -1842,21 +1875,22 @@ async def chat_completion( metadata['chat_id'], metadata['message_id'], { - 'parentId': metadata.get('parent_message_id', None), + 'parentId': metadata.get('user_message_id', None), 'error': {'content': str(e)}, }, ) event_emitter = await get_event_emitter(metadata) - await event_emitter( - { - 'type': 'chat:message:error', - 'data': {'error': {'content': str(e)}}, - } - ) - await event_emitter( - {'type': 'chat:tasks:cancel'}, - ) + if event_emitter: + await event_emitter( + { + 'type': 'chat:message:error', + 'data': {'error': {'content': str(e)}}, + } + ) + await event_emitter( + {'type': 'chat:tasks:cancel'}, + ) except Exception: pass @@ -1892,20 +1926,72 @@ async def chat_completion( except Exception as e: log.debug(f'Error emitting chat:active: {e}') - if metadata.get('session_id') and metadata.get('chat_id') and metadata.get('message_id'): - # Asynchronous Chat Processing - task_id, _ = await create_task( - request.app.state.redis, - process_chat(request, form_data, user, metadata, model), - id=metadata['chat_id'], - ) - # Emit chat:active=true when task starts - event_emitter = await get_event_emitter(metadata, update_db=False) - if event_emitter: - await event_emitter({'type': 'chat:active', 'data': {'active': True}}) - return {'status': True, 'task_id': task_id} + # Fan out: one task per model + if metadata.get('session_id') and metadata.get('chat_id'): + task_ids = [] + chat_id = metadata['chat_id'] + + for idx, (target_model_id, assistant_message_id) in enumerate(message_ids.items()): + if not assistant_message_id: + continue + + # Per-model metadata: own message_id + model + per_model_metadata = { + **metadata, + 'message_id': assistant_message_id, + } + + # Per-model form_data: own model + model_form_data = { + **form_data, + 'model': target_model_id, + 'metadata': per_model_metadata, + } + + # Resolve the model object for this specific model + resolved_model = request.app.state.MODELS.get(target_model_id, model) + + # Only the first model runs title/tags generation; + # subsequent models only run follow-ups. + task_id, _ = await create_task( + request.app.state.redis, + process_chat( + request, + model_form_data, + user, + per_model_metadata, + resolved_model, + tasks + if idx == 0 + else { + k: v + for k, v in (tasks or {}).items() + if k not in (TASKS.TITLE_GENERATION, TASKS.TAGS_GENERATION) + } + or None, + ), + id=chat_id, + ) + task_ids.append(task_id) + + # Emit chat:active=true + if task_ids: + event_emitter = await get_event_emitter( + {**metadata, 'message_id': list(message_ids.values())[0]}, + update_db=False, + ) + if event_emitter: + await event_emitter({'type': 'chat:active', 'data': {'active': True}}) + + return { + 'status': True, + 'task_ids': task_ids, + 'chat_id': chat_id, + } else: - return await process_chat(request, form_data, user, metadata, model) + # Legacy/direct: single model, synchronous + metadata['message_id'] = list(message_ids.values())[0] + return await process_chat(request, form_data, user, metadata, model, tasks) # Alias for chat_completion (Legacy) @@ -1979,6 +2065,8 @@ async def generate_messages( @app.post('/api/chat/completed') async def chat_completed(request: Request, form_data: dict, user=Depends(get_verified_user)): + """Deprecated: outlet filters now run inline during chat completion. + Kept for backward compatibility with external integrations.""" try: model_item = form_data.pop('model_item', {}) @@ -2012,7 +2100,7 @@ async def chat_action(request: Request, action_id: str, form_data: dict, user=De @app.post('/api/tasks/stop/{task_id}') -async def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_verified_user)): +async def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_admin_user)): try: result = await stop_task(request.app.state.redis, task_id) return result @@ -2021,15 +2109,21 @@ async def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_ve @app.get('/api/tasks') -async def list_tasks_endpoint(request: Request, user=Depends(get_verified_user)): +async def list_tasks_endpoint(request: Request, user=Depends(get_admin_user)): return {'tasks': await list_tasks(request.app.state.redis)} -@app.get('/api/tasks/chat/{chat_id}') +@app.get('/api/tasks/chat/{chat_id:path}') async def list_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=Depends(get_verified_user)): - chat = await Chats.get_chat_by_id(chat_id) - if chat is None or chat.user_id != user.id: - return {'task_ids': []} + if chat_id.startswith('local:'): + socket_id = chat_id[len('local:') :] + owner_id = get_user_id_from_session_pool(socket_id) + if owner_id != user.id and user.role != 'admin': + return {'task_ids': []} + else: + chat = await Chats.get_chat_by_id(chat_id) + if chat is None or (chat.user_id != user.id and user.role != 'admin'): + return {'task_ids': []} task_ids = await list_task_ids_by_item_id(request.app.state.redis, chat_id) @@ -2037,6 +2131,21 @@ async def list_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=De return {'task_ids': task_ids} +@app.post('/api/tasks/chat/{chat_id:path}/stop') +async def stop_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=Depends(get_verified_user)): + if chat_id.startswith('local:'): + socket_id = chat_id[len('local:') :] + owner_id = get_user_id_from_session_pool(socket_id) + if owner_id != user.id and user.role != 'admin': + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND) + else: + chat = await Chats.get_chat_by_id(chat_id) + if chat is None or (chat.user_id != user.id and user.role != 'admin'): + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND) + result = await stop_item_tasks(request.app.state.redis, chat_id) + return result + + ################################## # # Config Endpoints @@ -2091,6 +2200,7 @@ async def get_app_config(request: Request): 'enable_api_keys': app.state.config.ENABLE_API_KEYS, 'enable_signup': app.state.config.ENABLE_SIGNUP, 'enable_login_form': app.state.config.ENABLE_LOGIN_FORM, + 'enable_password_change_form': app.state.config.ENABLE_PASSWORD_CHANGE_FORM, 'enable_websocket': ENABLE_WEBSOCKET_SUPPORT, 'enable_version_update_check': ENABLE_VERSION_UPDATE_CHECK, 'enable_public_active_users_count': ENABLE_PUBLIC_ACTIVE_USERS_COUNT, @@ -2301,10 +2411,8 @@ if len(app.state.config.TOOL_SERVER_CONNECTIONS) > 0: auth_type = tool_server_connection.get('auth_type', 'none') if server_id and auth_type in ('oauth_2.1', 'oauth_2.1_static'): - oauth_client_info = tool_server_connection.get('info', {}).get('oauth_client_info', '') - try: - oauth_client_info = decrypt_data(oauth_client_info) + oauth_client_info = resolve_oauth_client_info(tool_server_connection) app.state.oauth_client_manager.add_client( f'mcp:{server_id}', OAuthClientInformationFull(**oauth_client_info), @@ -2365,18 +2473,25 @@ async def register_client(request, client_id: str) -> bool: try: if auth_type == 'oauth_2.1_static': - # Static credentials: rebuild from stored credentials + fresh metadata - existing_client_info = connection.get('info', {}).get('oauth_client_info', '') - if not existing_client_info: - log.error(f'No stored OAuth client info for static client {client_id}') - return False - existing_data = decrypt_data(existing_client_info) + # Static credentials: rebuild from admin-provided credentials + fresh metadata + info = connection.get('info', {}) + oauth_client_id = info.get('oauth_client_id') or '' + oauth_client_secret = info.get('oauth_client_secret') or '' + if not oauth_client_id or not oauth_client_secret: + # Fall back to blob for backward compatibility + existing_client_info = info.get('oauth_client_info', '') + if not existing_client_info: + log.error(f'No stored OAuth client info for static client {client_id}') + return False + existing_data = decrypt_data(existing_client_info) + oauth_client_id = oauth_client_id or existing_data.get('client_id', '') + oauth_client_secret = oauth_client_secret or existing_data.get('client_secret', '') oauth_client_info = await get_oauth_client_info_with_static_credentials( request, client_id, server_url, - oauth_client_id=existing_data.get('client_id', ''), - oauth_client_secret=existing_data.get('client_secret', ''), + oauth_client_id=oauth_client_id, + oauth_client_secret=oauth_client_secret, ) else: oauth_client_info = await get_oauth_client_info_with_dynamic_client_registration( @@ -2509,7 +2624,13 @@ async def oauth_backchannel_logout( @app.get('/manifest.json') async def get_manifest_json(): if app.state.EXTERNAL_PWA_MANIFEST_URL: - return requests.get(app.state.EXTERNAL_PWA_MANIFEST_URL).json() + session = await get_session() + async with session.get( + app.state.EXTERNAL_PWA_MANIFEST_URL, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return await r.json() else: return { 'name': app.state.WEBUI_NAME, diff --git a/backend/open_webui/migrations/versions/e1f2a3b4c5d6_add_is_pinned_to_note.py b/backend/open_webui/migrations/versions/e1f2a3b4c5d6_add_is_pinned_to_note.py new file mode 100644 index 0000000000..0d80558746 --- /dev/null +++ b/backend/open_webui/migrations/versions/e1f2a3b4c5d6_add_is_pinned_to_note.py @@ -0,0 +1,23 @@ +"""Add is_pinned to note table + +Revision ID: e1f2a3b4c5d6 +Revises: b7c8d9e0f1a2 +Create Date: 2026-04-14 22:00:00.000000 + +""" + +from alembic import op +import sqlalchemy as sa + +revision = 'e1f2a3b4c5d6' +down_revision = 'b7c8d9e0f1a2' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('note', sa.Column('is_pinned', sa.Boolean(), nullable=True)) + + +def downgrade(): + op.drop_column('note', 'is_pinned') diff --git a/backend/open_webui/models/access_grants.py b/backend/open_webui/models/access_grants.py index f064306a2c..f031495912 100644 --- a/backend/open_webui/models/access_grants.py +++ b/backend/open_webui/models/access_grants.py @@ -295,8 +295,7 @@ class AccessGrantsTable: async with get_async_db_context(db) as db: # Check for existing grant result = await db.execute( - select(AccessGrant) - .filter_by( + select(AccessGrant).filter_by( resource_type=resource_type, resource_id=resource_id, principal_type=principal_type, @@ -334,8 +333,7 @@ class AccessGrantsTable: """Remove a single access grant.""" async with get_async_db_context(db) as db: result = await db.execute( - delete(AccessGrant) - .filter_by( + delete(AccessGrant).filter_by( resource_type=resource_type, resource_id=resource_id, principal_type=principal_type, @@ -355,8 +353,7 @@ class AccessGrantsTable: """Remove all access grants for a resource.""" async with get_async_db_context(db) as db: result = await db.execute( - delete(AccessGrant) - .filter_by( + delete(AccessGrant).filter_by( resource_type=resource_type, resource_id=resource_id, ) @@ -451,8 +448,7 @@ class AccessGrantsTable: """ async with get_async_db_context(db) as db: result = await db.execute( - select(AccessGrant) - .filter_by( + select(AccessGrant).filter_by( resource_type=resource_type, resource_id=resource_id, ) @@ -470,8 +466,7 @@ class AccessGrantsTable: """Get all grants for a specific resource.""" async with get_async_db_context(db) as db: result = await db.execute( - select(AccessGrant) - .filter_by( + select(AccessGrant).filter_by( resource_type=resource_type, resource_id=resource_id, ) @@ -490,8 +485,7 @@ class AccessGrantsTable: return {} async with get_async_db_context(db) as db: result = await db.execute( - select(AccessGrant) - .filter( + select(AccessGrant).filter( AccessGrant.resource_type == resource_type, AccessGrant.resource_id.in_(resource_ids), ) @@ -634,8 +628,7 @@ class AccessGrantsTable: async with get_async_db_context(db) as db: result = await db.execute( - select(AccessGrant) - .filter_by( + select(AccessGrant).filter_by( resource_type=resource_type, resource_id=resource_id, permission=permission, diff --git a/backend/open_webui/models/auths.py b/backend/open_webui/models/auths.py index ca5070878e..2c8c6ba99f 100644 --- a/backend/open_webui/models/auths.py +++ b/backend/open_webui/models/auths.py @@ -141,7 +141,9 @@ class AuthsTable: except Exception: return None - async def authenticate_user_by_api_key(self, api_key: str, db: Optional[AsyncSession] = None) -> Optional[UserModel]: + async def authenticate_user_by_api_key( + self, api_key: str, db: Optional[AsyncSession] = None + ) -> Optional[UserModel]: log.info(f'authenticate_user_by_api_key') # if no api_key, return None if not api_key: @@ -159,9 +161,7 @@ class AuthsTable: async with get_async_db_context(db) as db: # Single JOIN query instead of two separate queries result = await db.execute( - select(Auth, User) - .join(User, Auth.id == User.id) - .filter(Auth.email == email, Auth.active == True) + select(Auth, User).join(User, Auth.id == User.id).filter(Auth.email == email, Auth.active == True) ) row = result.first() if row: diff --git a/backend/open_webui/models/automations.py b/backend/open_webui/models/automations.py index fab3788eb0..c891c3204e 100644 --- a/backend/open_webui/models/automations.py +++ b/backend/open_webui/models/automations.py @@ -145,9 +145,7 @@ class AutomationTable: async def count_by_user(self, user_id: str, db: Optional[AsyncSession] = None) -> int: async with get_async_db_context(db) as db: - result = await db.execute( - select(func.count()).select_from(Automation).filter_by(user_id=user_id) - ) + result = await db.execute(select(func.count()).select_from(Automation).filter_by(user_id=user_id)) return result.scalar() async def get_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[AutomationModel]: @@ -185,9 +183,7 @@ class AutomationTable: stmt = stmt.order_by(Automation.created_at.desc()) # Get total count - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: @@ -343,18 +339,14 @@ class AutomationRunTable: .subquery() ) result = await db.execute( - select(AutomationRun) - .join( + select(AutomationRun).join( subq, (AutomationRun.automation_id == subq.c.automation_id) & (AutomationRun.created_at == subq.c.max_created), ) ) rows = result.scalars().all() - return { - row.automation_id: AutomationRunModel.model_validate(row) - for row in rows - } + return {row.automation_id: AutomationRunModel.model_validate(row) for row in rows} async def get_by_automation( self, diff --git a/backend/open_webui/models/channels.py b/backend/open_webui/models/channels.py index 9b5403e6e1..942c06d6b3 100644 --- a/backend/open_webui/models/channels.py +++ b/backend/open_webui/models/channels.py @@ -414,9 +414,13 @@ class ChannelTable: all_channels = list(membership_channels) + list(standard_channels) channel_ids = [c.id for c in all_channels] grants_map = await AccessGrants.get_grants_by_resources('channel', channel_ids, db=db) - return [await self._to_channel_model(c, access_grants=grants_map.get(c.id, []), db=db) for c in all_channels] + return [ + await self._to_channel_model(c, access_grants=grants_map.get(c.id, []), db=db) for c in all_channels + ] - async def get_dm_channel_by_user_ids(self, user_ids: list[str], db: Optional[AsyncSession] = None) -> Optional[ChannelModel]: + async def get_dm_channel_by_user_ids( + self, user_ids: list[str], db: Optional[AsyncSession] = None + ) -> Optional[ChannelModel]: async with get_async_db_context(db) as db: # Ensure uniqueness in case a list with duplicates is passed unique_user_ids = list(set(user_ids)) @@ -462,9 +466,7 @@ class ChannelTable: # 1. Collect all user_ids including groups + inviter requested_users = await self._collect_unique_user_ids(invited_by, user_ids, group_ids) - result = await db.execute( - select(ChannelMember.user_id).filter(ChannelMember.channel_id == channel_id) - ) + result = await db.execute(select(ChannelMember.user_id).filter(ChannelMember.channel_id == channel_id)) existing_users = {row[0] for row in result.all()} new_user_ids = requested_users - existing_users @@ -512,7 +514,9 @@ class ChannelTable: membership = result.scalars().first() return membership is not None - async def join_channel(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[ChannelMemberModel]: + async def join_channel( + self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> Optional[ChannelMemberModel]: async with get_async_db_context(db) as db: # Check if the membership already exists result = await db.execute( @@ -581,11 +585,11 @@ class ChannelTable: membership = result.scalars().first() return ChannelMemberModel.model_validate(membership) if membership else None - async def get_members_by_channel_id(self, channel_id: str, db: Optional[AsyncSession] = None) -> list[ChannelMemberModel]: + async def get_members_by_channel_id( + self, channel_id: str, db: Optional[AsyncSession] = None + ) -> list[ChannelMemberModel]: async with get_async_db_context(db) as db: - result = await db.execute( - select(ChannelMember).filter(ChannelMember.channel_id == channel_id) - ) + result = await db.execute(select(ChannelMember).filter(ChannelMember.channel_id == channel_id)) memberships = result.scalars().all() return [ChannelMemberModel.model_validate(membership) for membership in memberships] @@ -613,7 +617,9 @@ class ChannelTable: await db.commit() return True - async def update_member_last_read_at(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> bool: + async def update_member_last_read_at( + self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> bool: async with get_async_db_context(db) as db: result = await db.execute( select(ChannelMember).filter( @@ -658,11 +664,13 @@ class ChannelTable: async def is_user_channel_member(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> bool: async with get_async_db_context(db) as db: result = await db.execute( - select(ChannelMember).filter( + select(ChannelMember) + .filter( ChannelMember.channel_id == channel_id, ChannelMember.user_id == user_id, ChannelMember.is_active.is_(True), - ).limit(1) + ) + .limit(1) ) membership = result.scalars().first() return membership is not None @@ -726,11 +734,13 @@ class ChannelTable: # --- Case A: group or dm => user must be an active member --- if channel.type in ['group', 'dm']: result = await db.execute( - select(ChannelMember).filter( + select(ChannelMember) + .filter( ChannelMember.channel_id == channel.id, ChannelMember.user_id == user_id, ChannelMember.is_active.is_(True), - ).limit(1) + ) + .limit(1) ) membership = result.scalars().first() if membership: @@ -774,11 +784,13 @@ class ChannelTable: # If the channel is a group or dm, read access requires membership (active) if channel.type in ['group', 'dm']: result = await db.execute( - select(ChannelMember).filter( + select(ChannelMember) + .filter( ChannelMember.channel_id == id, ChannelMember.user_id == user_id, ChannelMember.is_active.is_(True), - ).limit(1) + ) + .limit(1) ) membership = result.scalars().first() if membership: @@ -863,9 +875,7 @@ class ChannelTable: ) -> bool: try: async with get_async_db_context(db) as db: - result = await db.execute( - select(ChannelFile).filter_by(channel_id=channel_id, file_id=file_id) - ) + result = await db.execute(select(ChannelFile).filter_by(channel_id=channel_id, file_id=file_id)) channel_file = result.scalars().first() if not channel_file: return False @@ -878,7 +888,9 @@ class ChannelTable: except Exception: return False - async def remove_file_from_channel_by_id(self, channel_id: str, file_id: str, db: Optional[AsyncSession] = None) -> bool: + async def remove_file_from_channel_by_id( + self, channel_id: str, file_id: str, db: Optional[AsyncSession] = None + ) -> bool: try: async with get_async_db_context(db) as db: await db.execute(delete(ChannelFile).filter_by(channel_id=channel_id, file_id=file_id)) @@ -921,13 +933,17 @@ class ChannelTable: await db.commit() return webhook - async def get_webhooks_by_channel_id(self, channel_id: str, db: Optional[AsyncSession] = None) -> list[ChannelWebhookModel]: + async def get_webhooks_by_channel_id( + self, channel_id: str, db: Optional[AsyncSession] = None + ) -> list[ChannelWebhookModel]: async with get_async_db_context(db) as db: result = await db.execute(select(ChannelWebhook).filter(ChannelWebhook.channel_id == channel_id)) webhooks = result.scalars().all() return [ChannelWebhookModel.model_validate(w) for w in webhooks] - async def get_webhook_by_id(self, webhook_id: str, db: Optional[AsyncSession] = None) -> Optional[ChannelWebhookModel]: + async def get_webhook_by_id( + self, webhook_id: str, db: Optional[AsyncSession] = None + ) -> Optional[ChannelWebhookModel]: async with get_async_db_context(db) as db: result = await db.execute(select(ChannelWebhook).filter(ChannelWebhook.id == webhook_id)) webhook = result.scalars().first() diff --git a/backend/open_webui/models/chat_messages.py b/backend/open_webui/models/chat_messages.py index 087662ff7c..bd9c720fa4 100644 --- a/backend/open_webui/models/chat_messages.py +++ b/backend/open_webui/models/chat_messages.py @@ -272,13 +272,10 @@ class ChatMessageTable: """Get distinct chat_ids that used a specific model.""" async with get_async_db_context(db) as db: - stmt = ( - select( - ChatMessage.chat_id, - func.max(ChatMessage.created_at).label('last_message_at'), - ) - .filter(ChatMessage.model_id == model_id) - ) + stmt = select( + ChatMessage.chat_id, + func.max(ChatMessage.created_at).label('last_message_at'), + ).filter(ChatMessage.model_id == model_id) if start_date: stmt = stmt.filter(ChatMessage.created_at >= start_date) if end_date: @@ -313,13 +310,10 @@ class ChatMessageTable: async with get_async_db_context(db) as db: from open_webui.models.groups import GroupMember - stmt = ( - select(ChatMessage.model_id, func.count(ChatMessage.id).label('count')) - .filter( - ChatMessage.role == 'assistant', - ChatMessage.model_id.isnot(None), - ~ChatMessage.user_id.like('shared-%'), - ) + stmt = select(ChatMessage.model_id, func.count(ChatMessage.id).label('count')).filter( + ChatMessage.role == 'assistant', + ChatMessage.model_id.isnot(None), + ~ChatMessage.user_id.like('shared-%'), ) if start_date: @@ -365,19 +359,16 @@ class ChatMessageTable: else: raise NotImplementedError(f'Unsupported dialect: {dialect}') - stmt = ( - select( - ChatMessage.model_id, - func.coalesce(func.sum(input_tokens), 0).label('input_tokens'), - func.coalesce(func.sum(output_tokens), 0).label('output_tokens'), - func.count(ChatMessage.id).label('message_count'), - ) - .filter( - ChatMessage.role == 'assistant', - ChatMessage.model_id.isnot(None), - ChatMessage.usage.isnot(None), - ~ChatMessage.user_id.like('shared-%'), - ) + stmt = select( + ChatMessage.model_id, + func.coalesce(func.sum(input_tokens), 0).label('input_tokens'), + func.coalesce(func.sum(output_tokens), 0).label('output_tokens'), + func.count(ChatMessage.id).label('message_count'), + ).filter( + ChatMessage.role == 'assistant', + ChatMessage.model_id.isnot(None), + ChatMessage.usage.isnot(None), + ~ChatMessage.user_id.like('shared-%'), ) if start_date: @@ -430,19 +421,16 @@ class ChatMessageTable: else: raise NotImplementedError(f'Unsupported dialect: {dialect}') - stmt = ( - select( - ChatMessage.user_id, - func.coalesce(func.sum(input_tokens), 0).label('input_tokens'), - func.coalesce(func.sum(output_tokens), 0).label('output_tokens'), - func.count(ChatMessage.id).label('message_count'), - ) - .filter( - ChatMessage.role == 'assistant', - ChatMessage.user_id.isnot(None), - ChatMessage.usage.isnot(None), - ~ChatMessage.user_id.like('shared-%'), - ) + stmt = select( + ChatMessage.user_id, + func.coalesce(func.sum(input_tokens), 0).label('input_tokens'), + func.coalesce(func.sum(output_tokens), 0).label('output_tokens'), + func.count(ChatMessage.id).label('message_count'), + ).filter( + ChatMessage.role == 'assistant', + ChatMessage.user_id.isnot(None), + ChatMessage.usage.isnot(None), + ~ChatMessage.user_id.like('shared-%'), ) if start_date: @@ -476,9 +464,8 @@ class ChatMessageTable: async with get_async_db_context(db) as db: from open_webui.models.groups import GroupMember - stmt = ( - select(ChatMessage.user_id, func.count(ChatMessage.id).label('count')) - .filter(~ChatMessage.user_id.like('shared-%')) + stmt = select(ChatMessage.user_id, func.count(ChatMessage.id).label('count')).filter( + ~ChatMessage.user_id.like('shared-%') ) if start_date: @@ -503,9 +490,8 @@ class ChatMessageTable: async with get_async_db_context(db) as db: from open_webui.models.groups import GroupMember - stmt = ( - select(ChatMessage.chat_id, func.count(ChatMessage.id).label('count')) - .filter(~ChatMessage.user_id.like('shared-%')) + stmt = select(ChatMessage.chat_id, func.count(ChatMessage.id).label('count')).filter( + ~ChatMessage.user_id.like('shared-%') ) if start_date: @@ -532,13 +518,10 @@ class ChatMessageTable: from datetime import datetime, timedelta from open_webui.models.groups import GroupMember - stmt = ( - select(ChatMessage.created_at, ChatMessage.model_id) - .filter( - ChatMessage.role == 'assistant', - ChatMessage.model_id.isnot(None), - ~ChatMessage.user_id.like('shared-%'), - ) + stmt = select(ChatMessage.created_at, ChatMessage.model_id).filter( + ChatMessage.role == 'assistant', + ChatMessage.model_id.isnot(None), + ~ChatMessage.user_id.like('shared-%'), ) if start_date: @@ -582,13 +565,10 @@ class ChatMessageTable: async with get_async_db_context(db) as db: from datetime import datetime, timedelta - stmt = ( - select(ChatMessage.created_at, ChatMessage.model_id) - .filter( - ChatMessage.role == 'assistant', - ChatMessage.model_id.isnot(None), - ~ChatMessage.user_id.like('shared-%'), - ) + stmt = select(ChatMessage.created_at, ChatMessage.model_id).filter( + ChatMessage.role == 'assistant', + ChatMessage.model_id.isnot(None), + ~ChatMessage.user_id.like('shared-%'), ) if start_date: diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 77bc0a5614..3bcfdce03f 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -292,9 +292,10 @@ class ChatTable: return changed - async def insert_new_chat(self, user_id: str, form_data: ChatForm, db: Optional[AsyncSession] = None) -> Optional[ChatModel]: + async def insert_new_chat( + self, id: str, user_id: str, form_data: ChatForm, db: Optional[AsyncSession] = None + ) -> Optional[ChatModel]: async with get_async_db_context(db) as db: - id = str(uuid.uuid4()) chat = ChatModel( **{ 'id': id, @@ -551,7 +552,9 @@ class ChatTable: await self.update_chat_by_id(id, chat, db=db) return message_files - async def insert_shared_chat_by_chat_id(self, chat_id: str, db: Optional[AsyncSession] = None) -> Optional[ChatModel]: + async def insert_shared_chat_by_chat_id( + self, chat_id: str, db: Optional[AsyncSession] = None + ) -> Optional[ChatModel]: async with get_async_db_context(db) as db: # Get the existing chat to share chat = await db.get(Chat, chat_id) @@ -585,7 +588,9 @@ class ChatTable: await db.commit() return shared_chat if shared_result else None - async def update_shared_chat_by_chat_id(self, chat_id: str, db: Optional[AsyncSession] = None) -> Optional[ChatModel]: + async def update_shared_chat_by_chat_id( + self, chat_id: str, db: Optional[AsyncSession] = None + ) -> Optional[ChatModel]: try: async with get_async_db_context(db) as db: chat = await db.get(Chat, chat_id) @@ -689,7 +694,9 @@ class ChatTable: db: Optional[AsyncSession] = None, ) -> list[ChatTitleIdResponse]: async with get_async_db_context(db) as db: - stmt = select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at).filter_by(user_id=user_id, archived=True) + stmt = select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at).filter_by( + user_id=user_id, archived=True + ) if filter: query_key = filter.get('query') @@ -740,7 +747,11 @@ class ChatTable: db: Optional[AsyncSession] = None, ) -> list[SharedChatResponse]: async with get_async_db_context(db) as db: - stmt = select(Chat.id, Chat.title, Chat.share_id, Chat.updated_at, Chat.created_at).filter_by(user_id=user_id).filter(Chat.share_id.isnot(None)) + stmt = ( + select(Chat.id, Chat.title, Chat.share_id, Chat.updated_at, Chat.created_at) + .filter_by(user_id=user_id) + .filter(Chat.share_id.isnot(None)) + ) if filter: query_key = filter.get('query') @@ -793,7 +804,9 @@ class ChatTable: db: Optional[AsyncSession] = None, ) -> list[ChatTitleIdResponse]: async with get_async_db_context(db) as db: - stmt = select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at, Chat.last_read_at).filter_by(user_id=user_id) + stmt = select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at, Chat.last_read_at).filter_by( + user_id=user_id + ) if not include_archived: stmt = stmt.filter_by(archived=False) @@ -846,7 +859,9 @@ class ChatTable: db: Optional[AsyncSession] = None, ) -> list[ChatTitleIdResponse]: async with get_async_db_context(db) as db: - stmt = select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at, Chat.last_read_at).filter_by(user_id=user_id) + stmt = select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at, Chat.last_read_at).filter_by( + user_id=user_id + ) if not include_folders: stmt = stmt.filter_by(folder_id=None) @@ -889,10 +904,7 @@ class ChatTable: ) -> list[ChatModel]: async with get_async_db_context(db) as db: result = await db.execute( - select(Chat) - .filter(Chat.id.in_(chat_ids)) - .filter_by(archived=False) - .order_by(Chat.updated_at.desc()) + select(Chat).filter(Chat.id.in_(chat_ids)).filter_by(archived=False).order_by(Chat.updated_at.desc()) ) all_chats = result.scalars().all() return [ChatModel.model_validate(chat) for chat in all_chats] @@ -925,7 +937,9 @@ class ChatTable: except Exception: return None - async def get_chat_by_id_and_user_id(self, id: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[ChatModel]: + async def get_chat_by_id_and_user_id( + self, id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> Optional[ChatModel]: try: async with get_async_db_context(db) as db: result = await db.execute(select(Chat).filter_by(id=id, user_id=user_id)) @@ -941,9 +955,7 @@ class ChatTable: """ try: async with get_async_db_context(db) as db: - result = await db.execute( - select(exists().where(and_(Chat.id == id, Chat.user_id == user_id))) - ) + result = await db.execute(select(exists().where(and_(Chat.id == id, Chat.user_id == user_id)))) return result.scalar() except Exception: return False @@ -997,9 +1009,7 @@ class ChatTable: else: stmt = stmt.order_by(Chat.updated_at.desc(), Chat.id) - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip is not None: @@ -1017,7 +1027,9 @@ class ChatTable: } ) - async def get_pinned_chats_by_user_id(self, user_id: str, db: Optional[AsyncSession] = None) -> list[ChatTitleIdResponse]: + async def get_pinned_chats_by_user_id( + self, user_id: str, db: Optional[AsyncSession] = None + ) -> list[ChatTitleIdResponse]: async with get_async_db_context(db) as db: result = await db.execute( select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at, Chat.last_read_at) @@ -1060,7 +1072,9 @@ class ChatTable: search_text = sanitize_text_for_db(search_text).lower().strip() if not search_text: - return await self.get_chat_list_by_user_id(user_id, include_archived, filter={}, skip=skip, limit=limit, db=db) + return await self.get_chat_list_by_user_id( + user_id, include_archived, filter={}, skip=skip, limit=limit, db=db + ) search_text_words = search_text.split(' ') @@ -1305,7 +1319,9 @@ class ChatTable: except Exception: return None - async def get_chat_tags_by_id_and_user_id(self, id: str, user_id: str, db: Optional[AsyncSession] = None) -> list[TagModel]: + async def get_chat_tags_by_id_and_user_id( + self, id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> list[TagModel]: async with get_async_db_context(db) as db: chat = await db.get(Chat, id) tag_ids = chat.meta.get('tags', []) @@ -1320,7 +1336,9 @@ class ChatTable: db: Optional[AsyncSession] = None, ) -> list[ChatTitleIdResponse]: async with get_async_db_context(db) as db: - stmt = select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at, Chat.last_read_at).filter_by(user_id=user_id) + stmt = select(Chat.id, Chat.title, Chat.updated_at, Chat.created_at, Chat.last_read_at).filter_by( + user_id=user_id + ) tag_id = tag_name.replace(' ', '_').lower() bind = await db.connection() @@ -1378,7 +1396,9 @@ class ChatTable: except Exception: return None - async def count_chats_by_tag_name_and_user_id(self, tag_name: str, user_id: str, db: Optional[AsyncSession] = None) -> int: + async def count_chats_by_tag_name_and_user_id( + self, tag_name: str, user_id: str, db: Optional[AsyncSession] = None + ) -> int: async with get_async_db_context(db) as db: stmt = select(func.count(Chat.id)).filter_by(user_id=user_id, archived=False) tag_id = tag_name.replace(' ', '_').lower() @@ -1424,11 +1444,11 @@ class ChatTable: orphans.append(tag_id) await Tags.delete_tags_by_ids_and_user_id(orphans, user_id, db=db) - async def count_chats_by_folder_id_and_user_id(self, folder_id: str, user_id: str, db: Optional[AsyncSession] = None) -> int: + async def count_chats_by_folder_id_and_user_id( + self, folder_id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> int: async with get_async_db_context(db) as db: - result = await db.execute( - select(func.count(Chat.id)).filter_by(user_id=user_id, folder_id=folder_id) - ) + result = await db.execute(select(func.count(Chat.id)).filter_by(user_id=user_id, folder_id=folder_id)) count = result.scalar() log.info(f"Count of chats for folder '{folder_id}': {count}") @@ -1470,9 +1490,7 @@ class ChatTable: async def delete_chat_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: try: async with get_async_db_context(db) as db: - await db.execute( - update(AutomationRun).filter_by(chat_id=id).values(chat_id=None) - ) + await db.execute(update(AutomationRun).filter_by(chat_id=id).values(chat_id=None)) await db.execute(delete(ChatMessage).filter_by(chat_id=id)) await db.execute(delete(Chat).filter_by(id=id)) await db.commit() @@ -1484,9 +1502,7 @@ class ChatTable: async def delete_chat_by_id_and_user_id(self, id: str, user_id: str, db: Optional[AsyncSession] = None) -> bool: try: async with get_async_db_context(db) as db: - await db.execute( - update(AutomationRun).filter_by(chat_id=id).values(chat_id=None) - ) + await db.execute(update(AutomationRun).filter_by(chat_id=id).values(chat_id=None)) await db.execute(delete(ChatMessage).filter_by(chat_id=id)) await db.execute(delete(Chat).filter_by(id=id, user_id=user_id)) await db.commit() @@ -1502,7 +1518,9 @@ class ChatTable: chat_id_subquery = select(Chat.id).filter_by(user_id=user_id).scalar_subquery() await db.execute( - update(AutomationRun).filter(AutomationRun.chat_id.in_(select(Chat.id).filter_by(user_id=user_id))).values(chat_id=None) + update(AutomationRun) + .filter(AutomationRun.chat_id.in_(select(Chat.id).filter_by(user_id=user_id))) + .values(chat_id=None) ) await db.execute( delete(ChatMessage).filter(ChatMessage.chat_id.in_(select(Chat.id).filter_by(user_id=user_id))) @@ -1514,16 +1532,16 @@ class ChatTable: except Exception: return False - async def delete_chats_by_user_id_and_folder_id(self, user_id: str, folder_id: str, db: Optional[AsyncSession] = None) -> bool: + async def delete_chats_by_user_id_and_folder_id( + self, user_id: str, folder_id: str, db: Optional[AsyncSession] = None + ) -> bool: try: async with get_async_db_context(db) as db: chat_ids_stmt = select(Chat.id).filter_by(user_id=user_id, folder_id=folder_id) await db.execute( update(AutomationRun).filter(AutomationRun.chat_id.in_(chat_ids_stmt)).values(chat_id=None) ) - await db.execute( - delete(ChatMessage).filter(ChatMessage.chat_id.in_(chat_ids_stmt)) - ) + await db.execute(delete(ChatMessage).filter(ChatMessage.chat_id.in_(chat_ids_stmt))) await db.execute(delete(Chat).filter_by(user_id=user_id, folder_id=folder_id)) await db.commit() @@ -1619,9 +1637,7 @@ class ChatTable: ) -> list[ChatFileModel]: async with get_async_db_context(db) as db: result = await db.execute( - select(ChatFile) - .filter_by(chat_id=chat_id, message_id=message_id) - .order_by(ChatFile.created_at.asc()) + select(ChatFile).filter_by(chat_id=chat_id, message_id=message_id).order_by(ChatFile.created_at.asc()) ) all_chat_files = result.scalars().all() return [ChatFileModel.model_validate(chat_file) for chat_file in all_chat_files] diff --git a/backend/open_webui/models/feedbacks.py b/backend/open_webui/models/feedbacks.py index 61124619b5..02f61f82ee 100644 --- a/backend/open_webui/models/feedbacks.py +++ b/backend/open_webui/models/feedbacks.py @@ -251,9 +251,7 @@ class FeedbackTable: stmt = stmt.order_by(Feedback.created_at.desc()) # Count BEFORE pagination - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: @@ -280,8 +278,9 @@ class FeedbackTable: async def get_all_feedback_ids(self, db: Optional[AsyncSession] = None) -> list[FeedbackIdResponse]: async with get_async_db_context(db) as db: result = await db.execute( - select(Feedback.id, Feedback.user_id, Feedback.created_at, Feedback.updated_at) - .order_by(Feedback.updated_at.desc()) + select(Feedback.id, Feedback.user_id, Feedback.created_at, Feedback.updated_at).order_by( + Feedback.updated_at.desc() + ) ) return [ FeedbackIdResponse( @@ -378,16 +377,12 @@ class FeedbackTable: async def get_feedbacks_by_type(self, type: str, db: Optional[AsyncSession] = None) -> list[FeedbackModel]: async with get_async_db_context(db) as db: - result = await db.execute( - select(Feedback).filter_by(type=type).order_by(Feedback.updated_at.desc()) - ) + result = await db.execute(select(Feedback).filter_by(type=type).order_by(Feedback.updated_at.desc())) return [FeedbackModel.model_validate(feedback) for feedback in result.scalars().all()] async def get_feedbacks_by_user_id(self, user_id: str, db: Optional[AsyncSession] = None) -> list[FeedbackModel]: async with get_async_db_context(db) as db: - result = await db.execute( - select(Feedback).filter_by(user_id=user_id).order_by(Feedback.updated_at.desc()) - ) + result = await db.execute(select(Feedback).filter_by(user_id=user_id).order_by(Feedback.updated_at.desc())) return [FeedbackModel.model_validate(feedback) for feedback in result.scalars().all()] async def update_feedback_by_id( diff --git a/backend/open_webui/models/files.py b/backend/open_webui/models/files.py index f79255f50b..cfdcfbc2d9 100644 --- a/backend/open_webui/models/files.py +++ b/backend/open_webui/models/files.py @@ -125,7 +125,9 @@ class FileUpdateForm(BaseModel): class FilesTable: - async def insert_new_file(self, user_id: str, form_data: FileForm, db: Optional[AsyncSession] = None) -> Optional[FileModel]: + async def insert_new_file( + self, user_id: str, form_data: FileForm, db: Optional[AsyncSession] = None + ) -> Optional[FileModel]: async with get_async_db_context(db) as db: file_data = form_data.model_dump() @@ -167,7 +169,9 @@ class FilesTable: except Exception: return None - async def get_file_by_id_and_user_id(self, id: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[FileModel]: + async def get_file_by_id_and_user_id( + self, id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> Optional[FileModel]: async with get_async_db_context(db) as db: try: result = await db.execute(select(File).filter_by(id=id, user_id=user_id)) @@ -179,7 +183,9 @@ class FilesTable: except Exception: return None - async def get_file_metadata_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[FileMetadataResponse]: + async def get_file_metadata_by_id( + self, id: str, db: Optional[AsyncSession] = None + ) -> Optional[FileMetadataResponse]: async with get_async_db_context(db) as db: try: file = await db.get(File, id) @@ -211,12 +217,12 @@ class FilesTable: async def get_files_by_ids(self, ids: list[str], db: Optional[AsyncSession] = None) -> list[FileModel]: async with get_async_db_context(db) as db: - result = await db.execute( - select(File).filter(File.id.in_(ids)).order_by(File.updated_at.desc()) - ) + result = await db.execute(select(File).filter(File.id.in_(ids)).order_by(File.updated_at.desc())) return [FileModel.model_validate(file) for file in result.scalars().all()] - async def get_file_metadatas_by_ids(self, ids: list[str], db: Optional[AsyncSession] = None) -> list[FileMetadataResponse]: + async def get_file_metadatas_by_ids( + self, ids: list[str], db: Optional[AsyncSession] = None + ) -> list[FileMetadataResponse]: async with get_async_db_context(db) as db: result = await db.execute( select(File.id, File.hash, File.meta, File.created_at, File.updated_at) @@ -251,18 +257,11 @@ class FilesTable: if user_id: stmt = stmt.filter_by(user_id=user_id) - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() - result = await db.execute( - stmt.order_by(File.updated_at.desc(), File.id.desc()).offset(skip).limit(limit) - ) - items = [ - FileModelResponse.model_validate(file, from_attributes=True) - for file in result.scalars().all() - ] + result = await db.execute(stmt.order_by(File.updated_at.desc(), File.id.desc()).offset(skip).limit(limit)) + items = [FileModelResponse.model_validate(file, from_attributes=True) for file in result.scalars().all()] return FileListResponse(items=items, total=total) @@ -320,9 +319,7 @@ class FilesTable: if pattern != '%': stmt = stmt.filter(File.filename.ilike(pattern, escape='\\')) - result = await db.execute( - stmt.order_by(File.created_at.desc(), File.id.desc()).offset(skip).limit(limit) - ) + result = await db.execute(stmt.order_by(File.created_at.desc(), File.id.desc()).offset(skip).limit(limit)) return [FileModel.model_validate(file) for file in result.scalars().all()] async def update_file_by_id( @@ -349,7 +346,9 @@ class FilesTable: log.exception(f'Error updating file completely by id: {e}') return None - async def update_file_hash_by_id(self, id: str, hash: Optional[str], db: Optional[AsyncSession] = None) -> Optional[FileModel]: + async def update_file_hash_by_id( + self, id: str, hash: Optional[str], db: Optional[AsyncSession] = None + ) -> Optional[FileModel]: async with get_async_db_context(db) as db: try: result = await db.execute(select(File).filter_by(id=id)) @@ -362,7 +361,9 @@ class FilesTable: except Exception: return None - async def update_file_data_by_id(self, id: str, data: dict, db: Optional[AsyncSession] = None) -> Optional[FileModel]: + async def update_file_data_by_id( + self, id: str, data: dict, db: Optional[AsyncSession] = None + ) -> Optional[FileModel]: async with get_async_db_context(db) as db: try: result = await db.execute(select(File).filter_by(id=id)) @@ -374,7 +375,9 @@ class FilesTable: except Exception as e: return None - async def update_file_metadata_by_id(self, id: str, meta: dict, db: Optional[AsyncSession] = None) -> Optional[FileModel]: + async def update_file_metadata_by_id( + self, id: str, meta: dict, db: Optional[AsyncSession] = None + ) -> Optional[FileModel]: async with get_async_db_context(db) as db: try: result = await db.execute(select(File).filter_by(id=id)) diff --git a/backend/open_webui/models/folders.py b/backend/open_webui/models/folders.py index 4e2a4e9f38..c553239482 100644 --- a/backend/open_webui/models/folders.py +++ b/backend/open_webui/models/folders.py @@ -74,14 +74,14 @@ class FolderForm(BaseModel): data: Optional[dict] = None meta: Optional[dict] = None parent_id: Optional[str] = None - model_config = ConfigDict(extra='allow') + model_config = ConfigDict(extra='forbid') class FolderUpdateForm(BaseModel): name: Optional[str] = None data: Optional[dict] = None meta: Optional[dict] = None - model_config = ConfigDict(extra='allow') + model_config = ConfigDict(extra='forbid') class FolderTable: @@ -171,9 +171,7 @@ class FolderTable: async with get_async_db_context(db) as db: # Check if folder exists result = await db.execute( - select(Folder) - .filter_by(parent_id=parent_id, user_id=user_id) - .filter(Folder.name.ilike(name)) + select(Folder).filter_by(parent_id=parent_id, user_id=user_id).filter(Folder.name.ilike(name)) ) folder = result.scalars().first() @@ -235,8 +233,7 @@ class FolderTable: form_data = form_data.model_dump(exclude_unset=True) existing_result = await db.execute( - select(Folder) - .filter_by( + select(Folder).filter_by( name=form_data.get('name'), parent_id=folder.parent_id, user_id=user_id, @@ -289,7 +286,9 @@ class FolderTable: log.error(f'update_folder: {e}') return - async def delete_folder_by_id_and_user_id(self, id: str, user_id: str, db: Optional[AsyncSession] = None) -> list[str]: + async def delete_folder_by_id_and_user_id( + self, id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> list[str]: try: folder_ids = [] async with get_async_db_context(db) as db: diff --git a/backend/open_webui/models/functions.py b/backend/open_webui/models/functions.py index db34454b43..ddac317863 100644 --- a/backend/open_webui/models/functions.py +++ b/backend/open_webui/models/functions.py @@ -160,7 +160,9 @@ class FunctionsTable: for func in functions: if func.id in existing_ids: await db.execute( - update(Function).filter_by(id=func.id).values( + update(Function) + .filter_by(id=func.id) + .values( **func.model_dump(), user_id=user_id, updated_at=int(time.time()), @@ -233,9 +235,7 @@ class FunctionsTable: async def get_function_list(self, db: Optional[AsyncSession] = None) -> list[FunctionUserResponse]: async with get_async_db_context(db) as db: - result = await db.execute( - select(Function).order_by(Function.updated_at.desc()) - ) + result = await db.execute(select(Function).order_by(Function.updated_at.desc())) functions = result.scalars().all() user_ids = list(set(func.user_id for func in functions)) @@ -261,7 +261,9 @@ class FunctionsTable: for func in functions ] - async def get_functions_by_type(self, type: str, active_only=False, db: Optional[AsyncSession] = None) -> list[FunctionModel]: + async def get_functions_by_type( + self, type: str, active_only=False, db: Optional[AsyncSession] = None + ) -> list[FunctionModel]: async with get_async_db_context(db) as db: if active_only: result = await db.execute(select(Function).filter_by(type=type, is_active=True)) @@ -342,7 +344,9 @@ class FunctionsTable: log.exception(f'Error updating function metadata by id {id}: {e}') return None - async def get_user_valves_by_id_and_user_id(self, id: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[dict]: + async def get_user_valves_by_id_and_user_id( + self, id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> Optional[dict]: try: user = await Users.get_user_by_id(user_id, db=db) user_settings = user.settings.model_dump() if user.settings else {} @@ -381,11 +385,15 @@ class FunctionsTable: log.exception(f'Error updating user valves by id {id} and user_id {user_id}: {e}') return None - async def update_function_by_id(self, id: str, updated: dict, db: Optional[AsyncSession] = None) -> Optional[FunctionModel]: + async def update_function_by_id( + self, id: str, updated: dict, db: Optional[AsyncSession] = None + ) -> Optional[FunctionModel]: async with get_async_db_context(db) as db: try: await db.execute( - update(Function).filter_by(id=id).values( + update(Function) + .filter_by(id=id) + .values( **updated, updated_at=int(time.time()), ) diff --git a/backend/open_webui/models/groups.py b/backend/open_webui/models/groups.py index bca9908580..bc199fac5b 100644 --- a/backend/open_webui/models/groups.py +++ b/backend/open_webui/models/groups.py @@ -261,12 +261,10 @@ class GroupTable: if 'share' in filter: share_value = filter['share'] - stmt = stmt.filter(Group.data.op('->>') ('share') == str(share_value)) + stmt = stmt.filter(Group.data.op('->>')('share') == str(share_value)) # Get total count - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() member_count = ( @@ -348,7 +346,9 @@ class GroupTable: return [m[0] for m in members] - async def get_group_user_ids_by_ids(self, group_ids: list[str], db: Optional[AsyncSession] = None) -> dict[str, list[str]]: + async def get_group_user_ids_by_ids( + self, group_ids: list[str], db: Optional[AsyncSession] = None + ) -> dict[str, list[str]]: async with get_async_db_context(db) as db: result = await db.execute( select(GroupMember.group_id, GroupMember.user_id).filter(GroupMember.group_id.in_(group_ids)) @@ -362,7 +362,9 @@ class GroupTable: return group_user_ids - async def set_group_user_ids_by_id(self, group_id: str, user_ids: list[str], db: Optional[AsyncSession] = None) -> None: + async def set_group_user_ids_by_id( + self, group_id: str, user_ids: list[str], db: Optional[AsyncSession] = None + ) -> None: async with get_async_db_context(db) as db: # Delete existing members await db.execute(delete(GroupMember).filter(GroupMember.group_id == group_id)) @@ -411,7 +413,9 @@ class GroupTable: try: async with get_async_db_context(db) as db: await db.execute( - update(Group).filter_by(id=id).values( + update(Group) + .filter_by(id=id) + .values( **form_data.model_dump(exclude_none=True), updated_at=int(time.time()), ) @@ -455,14 +459,10 @@ class GroupTable: # Remove the user from each group for group in groups: await db.execute( - delete(GroupMember).filter( - GroupMember.group_id == group.id, GroupMember.user_id == user_id - ) + delete(GroupMember).filter(GroupMember.group_id == group.id, GroupMember.user_id == user_id) ) - await db.execute( - update(Group).filter_by(id=group.id).values(updated_at=int(time.time())) - ) + await db.execute(update(Group).filter_by(id=group.id).values(updated_at=int(time.time()))) await db.commit() return True @@ -507,7 +507,9 @@ class GroupTable: continue return new_groups - async def sync_groups_by_group_names(self, user_id: str, group_names: list[str], db: Optional[AsyncSession] = None) -> bool: + async def sync_groups_by_group_names( + self, user_id: str, group_names: list[str], db: Optional[AsyncSession] = None + ) -> bool: async with get_async_db_context(db) as db: try: now = int(time.time()) @@ -538,9 +540,7 @@ class GroupTable: ) ) - await db.execute( - update(Group).filter(Group.id.in_(groups_to_remove)).values(updated_at=now) - ) + await db.execute(update(Group).filter(Group.id.in_(groups_to_remove)).values(updated_at=now)) # 5. Bulk insert missing memberships for group_id in groups_to_add: @@ -555,9 +555,7 @@ class GroupTable: ) if groups_to_add: - await db.execute( - update(Group).filter(Group.id.in_(groups_to_add)).values(updated_at=now) - ) + await db.execute(update(Group).filter(Group.id.in_(groups_to_add)).values(updated_at=now)) await db.commit() return True diff --git a/backend/open_webui/models/knowledge.py b/backend/open_webui/models/knowledge.py index 68cee36c20..2750ef6058 100644 --- a/backend/open_webui/models/knowledge.py +++ b/backend/open_webui/models/knowledge.py @@ -196,11 +196,13 @@ class KnowledgeTable: knowledge_bases.append( KnowledgeUserModel.model_validate( { - **(await self._to_knowledge_model( - knowledge, - access_grants=grants_map.get(knowledge.id, []), - db=db, - )).model_dump(), + **( + await self._to_knowledge_model( + knowledge, + access_grants=grants_map.get(knowledge.id, []), + db=db, + ) + ).model_dump(), 'user': user.model_dump() if user else None, } ) @@ -249,9 +251,7 @@ class KnowledgeTable: stmt = stmt.order_by(Knowledge.updated_at.desc(), Knowledge.id.asc()) - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: stmt = stmt.offset(skip) @@ -269,11 +269,13 @@ class KnowledgeTable: knowledge_bases.append( KnowledgeUserModel.model_validate( { - **(await self._to_knowledge_model( - knowledge_base, - access_grants=grants_map.get(knowledge_base.id, []), - db=db, - )).model_dump(), + **( + await self._to_knowledge_model( + knowledge_base, + access_grants=grants_map.get(knowledge_base.id, []), + db=db, + ) + ).model_dump(), 'user': (UserModel.model_validate(user).model_dump() if user else None), } ) @@ -321,9 +323,7 @@ class KnowledgeTable: stmt = stmt.order_by(File.updated_at.desc(), File.id.asc()) # Count before pagination - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: @@ -490,9 +490,7 @@ class KnowledgeTable: stmt = stmt.order_by(primary_sort, File.id.asc()) # Count BEFORE pagination - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: @@ -530,7 +528,9 @@ class KnowledgeTable: except Exception: return [] - async def get_file_metadatas_by_id(self, knowledge_id: str, db: Optional[AsyncSession] = None) -> list[FileMetadataResponse]: + async def get_file_metadatas_by_id( + self, knowledge_id: str, db: Optional[AsyncSession] = None + ) -> list[FileMetadataResponse]: try: files = await self.get_files_by_id(knowledge_id, db=db) return [FileMetadataResponse(**file.model_dump()) for file in files] @@ -579,7 +579,9 @@ class KnowledgeTable: except Exception: return False - async def remove_file_from_knowledge_by_id(self, knowledge_id: str, file_id: str, db: Optional[AsyncSession] = None) -> bool: + async def remove_file_from_knowledge_by_id( + self, knowledge_id: str, file_id: str, db: Optional[AsyncSession] = None + ) -> bool: try: async with get_async_db_context(db) as db: await db.execute(delete(KnowledgeFile).filter_by(knowledge_id=knowledge_id, file_id=file_id)) @@ -596,9 +598,7 @@ class KnowledgeTable: await db.commit() # Update the knowledge entry's updated_at timestamp - await db.execute( - update(Knowledge).filter_by(id=id).values(updated_at=int(time.time())) - ) + await db.execute(update(Knowledge).filter_by(id=id).values(updated_at=int(time.time()))) await db.commit() return await self.get_knowledge_by_id(id=id, db=db) @@ -616,7 +616,9 @@ class KnowledgeTable: try: async with get_async_db_context(db) as db: await db.execute( - update(Knowledge).filter_by(id=id).values( + update(Knowledge) + .filter_by(id=id) + .values( **form_data.model_dump(exclude={'access_grants'}), updated_at=int(time.time()), ) @@ -635,7 +637,9 @@ class KnowledgeTable: try: async with get_async_db_context(db) as db: await db.execute( - update(Knowledge).filter_by(id=id).values( + update(Knowledge) + .filter_by(id=id) + .values( data=data, updated_at=int(time.time()), ) diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py index c9af45ebf5..7f33a72eff 100644 --- a/backend/open_webui/models/messages.py +++ b/backend/open_webui/models/messages.py @@ -250,11 +250,11 @@ class MessageTable: } return None - async def get_thread_replies_by_message_id(self, id: str, db: Optional[AsyncSession] = None) -> list[MessageReplyToResponse]: + async def get_thread_replies_by_message_id( + self, id: str, db: Optional[AsyncSession] = None + ) -> list[MessageReplyToResponse]: async with get_async_db_context(db) as db: - result = await db.execute( - select(Message).filter_by(parent_id=id).order_by(Message.created_at.desc()) - ) + result = await db.execute(select(Message).filter_by(parent_id=id).order_by(Message.created_at.desc())) all_messages = result.scalars().all() messages = [] @@ -369,7 +369,9 @@ class MessageTable: ) return messages - async def get_last_message_by_channel_id(self, channel_id: str, db: Optional[AsyncSession] = None) -> Optional[MessageModel]: + async def get_last_message_by_channel_id( + self, channel_id: str, db: Optional[AsyncSession] = None + ) -> Optional[MessageModel]: async with get_async_db_context(db) as db: result = await db.execute( select(Message).filter_by(channel_id=channel_id).order_by(Message.created_at.desc()).limit(1) @@ -453,9 +455,7 @@ class MessageTable: ) -> Optional[MessageReactionModel]: async with get_async_db_context(db) as db: # check for existing reaction - result = await db.execute( - select(MessageReaction).filter_by(message_id=id, user_id=user_id, name=name) - ) + result = await db.execute(select(MessageReaction).filter_by(message_id=id, user_id=user_id, name=name)) existing_reaction = result.scalars().first() if existing_reaction: return MessageReactionModel.model_validate(existing_reaction) diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index 4664a71b85..9bd3f888c1 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -1,3 +1,4 @@ +import json import logging import time from typing import Optional @@ -200,7 +201,8 @@ class ModelsTable: model_ids = [model.id for model in all_models] grants_map = await AccessGrants.get_grants_by_resources('model', model_ids, db=db) return [ - await self._to_model_model(model, access_grants=grants_map.get(model.id, []), db=db) for model in all_models + await self._to_model_model(model, access_grants=grants_map.get(model.id, []), db=db) + for model in all_models ] async def get_models(self, db: Optional[AsyncSession] = None) -> list[ModelUserResponse]: @@ -221,11 +223,13 @@ class ModelsTable: models.append( ModelUserResponse.model_validate( { - **(await self._to_model_model( - model, - access_grants=grants_map.get(model.id, []), - db=db, - )).model_dump(), + **( + await self._to_model_model( + model, + access_grants=grants_map.get(model.id, []), + db=db, + ) + ).model_dump(), 'user': user.model_dump() if user else None, } ) @@ -239,7 +243,8 @@ class ModelsTable: model_ids = [model.id for model in all_models] grants_map = await AccessGrants.get_grants_by_resources('model', model_ids, db=db) return [ - await self._to_model_model(model, access_grants=grants_map.get(model.id, []), db=db) for model in all_models + await self._to_model_model(model, access_grants=grants_map.get(model.id, []), db=db) + for model in all_models ] async def get_models_by_user_id( @@ -315,9 +320,20 @@ class ModelsTable: tag = filter.get('tag') if tag: - like_pattern = f'%"{tag.lower()}"%' - meta_text = func.lower(cast(Model.meta, String)) - stmt = stmt.filter(meta_text.like(like_pattern)) + # SQLite stores JSON text via json.dumps(ensure_ascii=True), + # so non-ASCII chars are \uXXXX-escaped. PostgreSQL native JSONB + # stores literal Unicode. Use the right pattern for each. + if db.bind.dialect.name == 'sqlite': + if tag.isascii(): + meta_text = func.lower(cast(Model.meta, String)) + pattern = f'%{json.dumps(tag.lower())}%' + else: + meta_text = cast(Model.meta, String) + pattern = f'%{json.dumps(tag)}%' + else: + meta_text = func.lower(cast(Model.meta, String)) + pattern = f'%{json.dumps(tag.lower(), ensure_ascii=False)}%' + stmt = stmt.filter(meta_text.like(pattern)) order_by = filter.get('order_by') direction = filter.get('direction') @@ -342,9 +358,7 @@ class ModelsTable: stmt = stmt.order_by(Model.created_at.desc()) # Count BEFORE pagination - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: @@ -362,11 +376,13 @@ class ModelsTable: for model, user in items: models.append( ModelUserResponse( - **(await self._to_model_model( - model, - access_grants=grants_map.get(model.id, []), - db=db, - )).model_dump(), + **( + await self._to_model_model( + model, + access_grants=grants_map.get(model.id, []), + db=db, + ) + ).model_dump(), user=(UserResponse(**UserModel.model_validate(user).model_dump()) if user else None), ) ) @@ -416,7 +432,9 @@ class ModelsTable: except Exception: return None - async def update_model_by_id(self, id: str, model: ModelForm, db: Optional[AsyncSession] = None) -> Optional[ModelModel]: + async def update_model_by_id( + self, id: str, model: ModelForm, db: Optional[AsyncSession] = None + ) -> Optional[ModelModel]: try: async with get_async_db_context(db) as db: # update only the fields that are present in the model @@ -473,7 +491,9 @@ class ModelsTable: except Exception: return False - async def sync_models(self, user_id: str, models: list[ModelModel], db: Optional[AsyncSession] = None) -> list[ModelModel]: + async def sync_models( + self, user_id: str, models: list[ModelModel], db: Optional[AsyncSession] = None + ) -> list[ModelModel]: try: async with get_async_db_context(db) as db: # Get existing models @@ -488,7 +508,9 @@ class ModelsTable: for model in models: if model.id in existing_ids: await db.execute( - update(Model).filter_by(id=model.id).values( + update(Model) + .filter_by(id=model.id) + .values( **model.model_dump(exclude={'access_grants'}), user_id=user_id, updated_at=int(time.time()), diff --git a/backend/open_webui/models/notes.py b/backend/open_webui/models/notes.py index b06465c7ad..1a34750a7d 100644 --- a/backend/open_webui/models/notes.py +++ b/backend/open_webui/models/notes.py @@ -4,7 +4,7 @@ import uuid from typing import Optional from functools import lru_cache -from sqlalchemy import select, delete, update, or_, func, cast +from sqlalchemy import Boolean, select, delete, update, or_, func, cast from sqlalchemy.ext.asyncio import AsyncSession from open_webui.internal.db import Base, get_async_db_context from open_webui.models.groups import Groups @@ -29,6 +29,7 @@ class Note(Base): title = Column(Text) data = Column(JSON, nullable=True) meta = Column(JSON, nullable=True) + is_pinned = Column(Boolean, default=False, nullable=True) created_at = Column(BigInteger) updated_at = Column(BigInteger) @@ -43,6 +44,7 @@ class NoteModel(BaseModel): title: str data: Optional[dict] = None meta: Optional[dict] = None + is_pinned: Optional[bool] = False access_grants: list[AccessGrantModel] = Field(default_factory=list) @@ -77,6 +79,7 @@ class NoteItemResponse(BaseModel): id: str title: str data: Optional[dict] + is_pinned: Optional[bool] = False updated_at: int created_at: int user: Optional[UserResponse] = None @@ -113,7 +116,9 @@ class NoteTable: permission=permission, ) - async def insert_new_note(self, user_id: str, form_data: NoteForm, db: Optional[AsyncSession] = None) -> Optional[NoteModel]: + async def insert_new_note( + self, user_id: str, form_data: NoteForm, db: Optional[AsyncSession] = None + ) -> Optional[NoteModel]: async with get_async_db_context(db) as db: note = NoteModel( **{ @@ -159,18 +164,22 @@ class NoteTable: if filter: query_key = filter.get('query') if query_key: - # Normalize search by removing hyphens and spaces (e.g., "todo" matches "to-do" and "to do") - normalized_query = query_key.replace('-', '').replace(' ', '') - stmt = stmt.filter( - or_( - func.replace(func.replace(Note.title, '-', ''), ' ', '').ilike(f'%{normalized_query}%'), - func.replace( - func.replace(cast(Note.data['content']['md'], Text), '-', ''), - ' ', - '', - ).ilike(f'%{normalized_query}%'), + # Split query into individual words and normalize each + # (strip hyphens so "todo" matches "to-do"). + # All words must match somewhere in title OR content (AND semantics). + search_words = query_key.split() + normalized_words = [w.replace('-', '') for w in search_words if w.replace('-', '')] + for word in normalized_words: + stmt = stmt.filter( + or_( + func.replace(func.replace(Note.title, '-', ''), ' ', '').ilike(f'%{word}%'), + func.replace( + func.replace(cast(Note.data['content']['md'], Text), '-', ''), + ' ', + '', + ).ilike(f'%{word}%'), + ) ) - ) view_option = filter.get('view_option') if view_option == 'created': @@ -216,9 +225,7 @@ class NoteTable: stmt = stmt.order_by(Note.updated_at.desc()) # Count BEFORE pagination - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: @@ -236,11 +243,13 @@ class NoteTable: for note, user in items: notes.append( NoteUserResponse( - **(await self._to_note_model( - note, - access_grants=grants_map.get(note.id, []), - db=db, - )).model_dump(), + **( + await self._to_note_model( + note, + access_grants=grants_map.get(note.id, []), + db=db, + ) + ).model_dump(), user=(UserResponse(**UserModel.model_validate(user).model_dump()) if user else None), ) ) @@ -305,6 +314,39 @@ class NoteTable: await db.commit() return await self._to_note_model(note, db=db) if note else None + async def toggle_note_pinned_by_id(self, id: str, db: Optional[AsyncSession] = None) -> Optional[NoteModel]: + try: + async with get_async_db_context(db) as db: + result = await db.execute(select(Note).filter(Note.id == id)) + note = result.scalars().first() + if not note: + return None + note.is_pinned = not note.is_pinned + note.updated_at = int(time.time_ns()) + await db.commit() + return await self._to_note_model(note, db=db) + except Exception: + return None + + async def get_pinned_notes_by_user_id( + self, + user_id: str, + permission: str = 'read', + db: Optional[AsyncSession] = None, + ) -> list[NoteModel]: + async with get_async_db_context(db) as db: + user_groups = await Groups.get_groups_by_member_id(user_id, db=db) + user_group_ids = [group.id for group in user_groups] + + stmt = select(Note).filter(Note.is_pinned == True).order_by(Note.updated_at.desc()) + stmt = self._has_permission(db, stmt, {'user_id': user_id, 'group_ids': user_group_ids}, permission) + + result = await db.execute(stmt) + notes = result.scalars().all() + note_ids = [note.id for note in notes] + grants_map = await AccessGrants.get_grants_by_resources('note', note_ids, db=db) + return [await self._to_note_model(note, access_grants=grants_map.get(note.id, []), db=db) for note in notes] + async def delete_note_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: try: async with get_async_db_context(db) as db: diff --git a/backend/open_webui/models/oauth_sessions.py b/backend/open_webui/models/oauth_sessions.py index c8ff569f27..050a50d486 100644 --- a/backend/open_webui/models/oauth_sessions.py +++ b/backend/open_webui/models/oauth_sessions.py @@ -123,7 +123,7 @@ class OAuthSessionTable: 'user_id': user_id, 'provider': provider, 'token': self._encrypt_token(token), - 'expires_at': token.get('expires_at'), + 'expires_at': token.get('expires_at') or int(time.time() + 3600), 'created_at': current_time, 'updated_at': current_time, } @@ -151,7 +151,9 @@ class OAuthSessionTable: log.error(f'Error creating OAuth session: {e}') return None - async def get_session_by_id(self, session_id: str, db: Optional[AsyncSession] = None) -> Optional[OAuthSessionModel]: + async def get_session_by_id( + self, session_id: str, db: Optional[AsyncSession] = None + ) -> Optional[OAuthSessionModel]: """Get OAuth session by ID""" try: async with get_async_db_context(db) as db: @@ -235,15 +237,17 @@ class OAuthSessionTable: results = [] for session in sessions: try: - results.append(OAuthSessionModel( - id=session.id, - user_id=session.user_id, - provider=session.provider, - token=self._decrypt_token(session.token), - expires_at=session.expires_at, - created_at=session.created_at, - updated_at=session.updated_at, - )) + results.append( + OAuthSessionModel( + id=session.id, + user_id=session.user_id, + provider=session.provider, + token=self._decrypt_token(session.token), + expires_at=session.expires_at, + created_at=session.created_at, + updated_at=session.updated_at, + ) + ) except Exception as e: log.warning( f'Skipping OAuth session {session.id} due to decryption failure, deleting corrupted session: {type(e).__name__}: {e}' @@ -266,9 +270,11 @@ class OAuthSessionTable: current_time = int(time.time()) await db.execute( - update(OAuthSession).filter_by(id=session_id).values( + update(OAuthSession) + .filter_by(id=session_id) + .values( token=self._encrypt_token(token), - expires_at=token.get('expires_at'), + expires_at=token.get('expires_at') or int(time.time() + 3600), updated_at=current_time, ) ) diff --git a/backend/open_webui/models/prompts.py b/backend/open_webui/models/prompts.py index 7250d1901e..fbf5401203 100644 --- a/backend/open_webui/models/prompts.py +++ b/backend/open_webui/models/prompts.py @@ -1,3 +1,4 @@ +import json import time import uuid from typing import Optional @@ -213,11 +214,13 @@ class PromptsTable: prompts.append( PromptUserResponse.model_validate( { - **(await self._to_prompt_model( - prompt, - access_grants=grants_map.get(prompt.id, []), - db=db, - )).model_dump(), + **( + await self._to_prompt_model( + prompt, + access_grants=grants_map.get(prompt.id, []), + db=db, + ) + ).model_dump(), 'user': user.model_dump() if user else None, } ) @@ -290,10 +293,22 @@ class PromptsTable: tag = filter.get('tag') if tag: - # Search for tag in JSON array field - like_pattern = f'%"{tag.lower()}"%' - tags_text = func.lower(cast(Prompt.tags, String)) - stmt = stmt.filter(tags_text.like(like_pattern)) + # SQLite stores JSON text via json.dumps(ensure_ascii=True), + # so non-ASCII chars are \uXXXX-escaped. PostgreSQL native JSONB + # stores literal Unicode. Use the right pattern for each. + if db.bind.dialect.name == 'sqlite': + if tag.isascii(): + tags_text = func.lower(cast(Prompt.tags, String)) + pattern = f'%{json.dumps(tag.lower())}%' + else: + # LOWER() is ASCII-only; non-ASCII codepoints would + # produce different \uXXXX escapes when lowered. + tags_text = cast(Prompt.tags, String) + pattern = f'%{json.dumps(tag)}%' + else: + tags_text = func.lower(cast(Prompt.tags, String)) + pattern = f'%{json.dumps(tag.lower(), ensure_ascii=False)}%' + stmt = stmt.filter(tags_text.like(pattern)) order_by = filter.get('order_by') direction = filter.get('direction') @@ -319,9 +334,7 @@ class PromptsTable: stmt = stmt.order_by(Prompt.updated_at.desc()) # Count BEFORE pagination - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: @@ -339,11 +352,13 @@ class PromptsTable: for prompt, user in items: prompts.append( PromptUserResponse( - **(await self._to_prompt_model( - prompt, - access_grants=grants_map.get(prompt.id, []), - db=db, - )).model_dump(), + **( + await self._to_prompt_model( + prompt, + access_grants=grants_map.get(prompt.id, []), + db=db, + ) + ).model_dump(), user=(UserResponse(**UserModel.model_validate(user).model_dump()) if user else None), ) ) diff --git a/backend/open_webui/models/skills.py b/backend/open_webui/models/skills.py index 55ba204135..0fc6dfc52d 100644 --- a/backend/open_webui/models/skills.py +++ b/backend/open_webui/models/skills.py @@ -184,11 +184,13 @@ class SkillsTable: skills.append( SkillUserModel.model_validate( { - **(await self._to_skill_model( - skill, - access_grants=grants_map.get(skill.id, []), - db=db, - )).model_dump(), + **( + await self._to_skill_model( + skill, + access_grants=grants_map.get(skill.id, []), + db=db, + ) + ).model_dump(), 'user': user.model_dump() if user else None, } ) @@ -262,9 +264,7 @@ class SkillsTable: stmt = stmt.order_by(Skill.updated_at.desc()) # Count BEFORE pagination - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() if skip: @@ -282,11 +282,13 @@ class SkillsTable: for skill, user in items: skills.append( SkillUserResponse( - **(await self._to_skill_model( - skill, - access_grants=grants_map.get(skill.id, []), - db=db, - )).model_dump(), + **( + await self._to_skill_model( + skill, + access_grants=grants_map.get(skill.id, []), + db=db, + ) + ).model_dump(), user=(UserResponse(**UserModel.model_validate(user).model_dump()) if user else None), ) ) @@ -296,7 +298,9 @@ class SkillsTable: log.exception(f'Error searching skills: {e}') return SkillListResponse(items=[], total=0) - async def update_skill_by_id(self, id: str, updated: dict, db: Optional[AsyncSession] = None) -> Optional[SkillModel]: + async def update_skill_by_id( + self, id: str, updated: dict, db: Optional[AsyncSession] = None + ) -> Optional[SkillModel]: try: async with get_async_db_context(db) as db: access_grants = updated.pop('access_grants', None) diff --git a/backend/open_webui/models/tags.py b/backend/open_webui/models/tags.py index 95b97b9cc1..ee2baefc01 100644 --- a/backend/open_webui/models/tags.py +++ b/backend/open_webui/models/tags.py @@ -71,7 +71,9 @@ class TagTable: log.exception(f'Error inserting a new tag: {e}') return None - async def get_tag_by_name_and_user_id(self, name: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[TagModel]: + async def get_tag_by_name_and_user_id( + self, name: str, user_id: str, db: Optional[AsyncSession] = None + ) -> Optional[TagModel]: try: id = name.replace(' ', '_').lower() async with get_async_db_context(db) as db: @@ -86,7 +88,9 @@ class TagTable: result = await db.execute(select(Tag).filter_by(user_id=user_id)) return [TagModel.model_validate(tag) for tag in result.scalars().all()] - async def get_tags_by_ids_and_user_id(self, ids: list[str], user_id: str, db: Optional[AsyncSession] = None) -> list[TagModel]: + async def get_tags_by_ids_and_user_id( + self, ids: list[str], user_id: str, db: Optional[AsyncSession] = None + ) -> list[TagModel]: async with get_async_db_context(db) as db: result = await db.execute(select(Tag).filter(Tag.id.in_(ids), Tag.user_id == user_id)) return [TagModel.model_validate(tag) for tag in result.scalars().all()] @@ -103,7 +107,9 @@ class TagTable: log.error(f'delete_tag: {e}') return False - async def delete_tags_by_ids_and_user_id(self, ids: list[str], user_id: str, db: Optional[AsyncSession] = None) -> bool: + async def delete_tags_by_ids_and_user_id( + self, ids: list[str], user_id: str, db: Optional[AsyncSession] = None + ) -> bool: """Delete all tags whose id is in *ids* for the given user, in one query.""" if not ids: return True diff --git a/backend/open_webui/models/tools.py b/backend/open_webui/models/tools.py index fe772c4443..70035121aa 100644 --- a/backend/open_webui/models/tools.py +++ b/backend/open_webui/models/tools.py @@ -172,11 +172,13 @@ class ToolsTable: tools.append( ToolUserModel.model_validate( { - **(await self._to_tool_model( - tool, - access_grants=grants_map.get(tool.id, []), - db=db, - )).model_dump(), + **( + await self._to_tool_model( + tool, + access_grants=grants_map.get(tool.id, []), + db=db, + ) + ).model_dump(), 'user': user.model_dump() if user else None, } ) @@ -218,18 +220,20 @@ class ToolsTable: log.exception(f'Error getting tool valves by id {id}') return None - async def update_tool_valves_by_id(self, id: str, valves: dict, db: Optional[AsyncSession] = None) -> Optional[ToolValves]: + async def update_tool_valves_by_id( + self, id: str, valves: dict, db: Optional[AsyncSession] = None + ) -> Optional[ToolValves]: try: async with get_async_db_context(db) as db: - await db.execute( - update(Tool).filter_by(id=id).values(valves=valves, updated_at=int(time.time())) - ) + await db.execute(update(Tool).filter_by(id=id).values(valves=valves, updated_at=int(time.time()))) await db.commit() return await self.get_tool_by_id(id, db=db) except Exception: return None - async def get_user_valves_by_id_and_user_id(self, id: str, user_id: str, db: Optional[AsyncSession] = None) -> Optional[dict]: + async def get_user_valves_by_id_and_user_id( + self, id: str, user_id: str, db: Optional[AsyncSession] = None + ) -> Optional[dict]: try: user = await Users.get_user_by_id(user_id, db=db) user_settings = user.settings.model_dump() if user.settings else {} @@ -272,9 +276,7 @@ class ToolsTable: try: async with get_async_db_context(db) as db: access_grants = updated.pop('access_grants', None) - await db.execute( - update(Tool).filter_by(id=id).values(**updated, updated_at=int(time.time())) - ) + await db.execute(update(Tool).filter_by(id=id).values(**updated, updated_at=int(time.time()))) await db.commit() if access_grants is not None: await AccessGrants.set_access_grants('tool', id, access_grants, db=db) diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index a5a43c27b8..025e79bd8a 100644 --- a/backend/open_webui/models/users.py +++ b/backend/open_webui/models/users.py @@ -31,11 +31,13 @@ import datetime # daily bread of every session. Let none go hungry. #################### + class UserSettings(BaseModel): ui: Optional[dict] = {} model_config = ConfigDict(extra='allow') pass + class User(Base): __tablename__ = 'user' @@ -69,6 +71,7 @@ class User(Base): updated_at = Column(BigInteger) created_at = Column(BigInteger) + class UserModel(BaseModel): id: str @@ -109,11 +112,13 @@ class UserModel(BaseModel): self.profile_image_url = f'/api/v1/users/{self.id}/profile/image' return self + class UserStatusModel(UserModel): is_active: bool = False model_config = ConfigDict(from_attributes=True) + class ApiKey(Base): __tablename__ = 'api_key' @@ -126,6 +131,7 @@ class ApiKey(Base): created_at = Column(BigInteger, nullable=False) updated_at = Column(BigInteger, nullable=False) + class ApiKeyModel(BaseModel): id: str user_id: str @@ -138,10 +144,12 @@ class ApiKeyModel(BaseModel): model_config = ConfigDict(from_attributes=True) + #################### # Forms #################### + class UpdateProfileForm(BaseModel): profile_image_url: str name: str @@ -154,25 +162,31 @@ class UpdateProfileForm(BaseModel): def check_profile_image_url(cls, v: str) -> str: return validate_profile_image_url(v) + class UserGroupIdsModel(UserModel): group_ids: list[str] = [] + class UserModelResponse(UserModel): model_config = ConfigDict(extra='allow') + class UserListResponse(BaseModel): users: list[UserModelResponse] total: int + class UserGroupIdsListResponse(BaseModel): users: list[UserGroupIdsModel] total: int + class UserStatus(BaseModel): status_emoji: Optional[str] = None status_message: Optional[str] = None status_expires_at: Optional[int] = None + class UserInfoResponse(UserStatus): id: str name: str @@ -182,51 +196,63 @@ class UserInfoResponse(UserStatus): groups: Optional[list] = [] is_active: bool = False + class UserIdNameResponse(BaseModel): id: str name: str + class UserIdNameStatusResponse(UserStatus): id: str name: str is_active: Optional[bool] = None + class UserInfoListResponse(BaseModel): users: list[UserInfoResponse] total: int + class UserIdNameListResponse(BaseModel): users: list[UserIdNameResponse] total: int + class UserNameResponse(BaseModel): id: str name: str role: str + class UserResponse(UserNameResponse): email: str + class UserProfileImageResponse(UserNameResponse): email: str profile_image_url: str + class UserRoleUpdateForm(BaseModel): id: str role: str + class UserUpdateForm(BaseModel): - role: str - name: str - email: str - profile_image_url: str + role: Optional[str] = None + name: Optional[str] = None + email: Optional[str] = None + profile_image_url: Optional[str] = None password: Optional[str] = None - @field_validator('profile_image_url') + @field_validator('profile_image_url', mode='before') @classmethod - def check_profile_image_url(cls, v: str) -> str: + def check_profile_image_url(cls, v: Optional[str]) -> Optional[str]: + if v is None: + return v return validate_profile_image_url(v) + class UsersTable: async def insert_new_user( self, @@ -292,7 +318,9 @@ class UsersTable: except Exception: return None - async def get_user_by_oauth_sub(self, provider: str, sub: str, db: Optional[AsyncSession] = None) -> Optional[UserModel]: + async def get_user_by_oauth_sub( + self, provider: str, sub: str, db: Optional[AsyncSession] = None + ) -> Optional[UserModel]: try: async with get_async_db_context(db) as db: dialect_name = db.bind.dialect.name @@ -457,9 +485,7 @@ class UsersTable: stmt = stmt.order_by(User.created_at.desc()) # Count BEFORE pagination - count_result = await db.execute( - select(func.count()).select_from(stmt.subquery()) - ) + count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) total = count_result.scalar() # correct pagination logic @@ -478,20 +504,18 @@ class UsersTable: async def get_users_by_group_id(self, group_id: str, db: Optional[AsyncSession] = None) -> list[UserModel]: async with get_async_db_context(db) as db: from open_webui.models.groups import GroupMember + result = await db.execute( - select(User) - - .join(GroupMember, User.id == GroupMember.user_id) - .filter(GroupMember.group_id == group_id) + select(User).join(GroupMember, User.id == GroupMember.user_id).filter(GroupMember.group_id == group_id) ) users = result.scalars().all() return [UserModel.model_validate(user) for user in users] - async def get_users_by_user_ids(self, user_ids: list[str], db: Optional[AsyncSession] = None) -> list[UserStatusModel]: + async def get_users_by_user_ids( + self, user_ids: list[str], db: Optional[AsyncSession] = None + ) -> list[UserStatusModel]: async with get_async_db_context(db) as db: - result = await db.execute( - select(User).filter(User.id.in_(user_ids)) - ) + result = await db.execute(select(User).filter(User.id.in_(user_ids))) users = result.scalars().all() return [UserModel.model_validate(user) for user in users] @@ -536,7 +560,9 @@ class UsersTable: ) return result.scalar() - async def update_user_role_by_id(self, id: str, role: str, db: Optional[AsyncSession] = None) -> Optional[UserModel]: + async def update_user_role_by_id( + self, id: str, role: str, db: Optional[AsyncSession] = None + ) -> Optional[UserModel]: try: async with get_async_db_context(db) as db: result = await db.execute(select(User).filter_by(id=id)) @@ -674,7 +700,9 @@ class UsersTable: print(e) return None - async def update_user_settings_by_id(self, id: str, updated: dict, db: Optional[AsyncSession] = None) -> Optional[UserModel]: + async def update_user_settings_by_id( + self, id: str, updated: dict, db: Optional[AsyncSession] = None + ) -> Optional[UserModel]: try: async with get_async_db_context(db) as db: result = await db.execute(select(User).filter_by(id=id)) @@ -802,4 +830,5 @@ class UsersTable: return user.last_active_at >= three_minutes_ago return False + Users = UsersTable() diff --git a/backend/open_webui/retrieval/loaders/main.py b/backend/open_webui/retrieval/loaders/main.py index 57867d78f5..7dc9df37ce 100644 --- a/backend/open_webui/retrieval/loaders/main.py +++ b/backend/open_webui/retrieval/loaders/main.py @@ -1,3 +1,4 @@ +import asyncio import requests import logging import ftfy @@ -238,6 +239,18 @@ class Loader: return [Document(page_content=ftfy.fix_text(doc.page_content), metadata=doc.metadata) for doc in docs] + async def aload(self, filename: str, file_content_type: str, file_path: str) -> list[Document]: + """ + Async wrapper around `load`. + + Document loaders dispatched by `_get_loader` (PyMuPDF, Unstructured, + python-docx, Tika, etc.) are uniformly synchronous and CPU/IO-bound. + Calling `load` directly from an async handler would block the event + loop for the entire parse — minutes for large PDFs. This offloads + the work to a worker thread so the loop stays responsive. + """ + return await asyncio.to_thread(self.load, filename, file_content_type, file_path) + def _is_text_file(self, file_ext: str, file_content_type: str) -> bool: return file_ext in known_source_ext or ( file_content_type diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index f7d2775c52..3638409eee 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -20,6 +20,7 @@ from langchain_community.retrievers import BM25Retriever from langchain_core.documents import Document from open_webui.config import VECTOR_DB +from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT @@ -121,7 +122,7 @@ class VectorSearchRetriever(BaseRetriever): run_manager: CallbackManagerForRetrieverRun, ) -> list[Document]: embedding = await self.embedding_function(query, RAG_EMBEDDING_QUERY_PREFIX) - result = VECTOR_DB_CLIENT.search( + result = await ASYNC_VECTOR_DB_CLIENT.search( collection_name=self.collection_name, vectors=[embedding], limit=self.top_k, @@ -488,16 +489,24 @@ async def query_collection_with_hybrid_search( ) -> dict: results = [] error = False - # Fetch collection data once per collection sequentially - # Avoid fetching the same data multiple times later - collection_results = {} - for collection_name in collection_names: + # Fetch every collection's contents once up front so the + # per-query/per-document loop below can reuse them. Each fetch + # offloads to a worker thread, so run them concurrently with + # `asyncio.gather` instead of awaiting them serially — otherwise + # latency scales linearly with `len(collection_names)`. + log.debug( + 'query_collection_with_hybrid_search: prefetching %d collections', + len(collection_names), + ) + + async def _fetch_collection(name: str): try: - log.debug(f'query_collection_with_hybrid_search:VECTOR_DB_CLIENT.get:collection {collection_name}') - collection_results[collection_name] = VECTOR_DB_CLIENT.get(collection_name=collection_name) + return name, await ASYNC_VECTOR_DB_CLIENT.get(collection_name=name) except Exception as e: - log.exception(f'Failed to fetch collection {collection_name}: {e}') - collection_results[collection_name] = None + log.exception(f'Failed to fetch collection {name}: {e}') + return name, None + + collection_results = dict(await asyncio.gather(*(_fetch_collection(name) for name in collection_names))) log.info(f'Starting hybrid search for {len(queries)} queries in {len(collection_names)} collections...') @@ -1140,7 +1149,9 @@ async def get_sources_from_items( try: if full_context: - query_result = get_all_items_from_collections(collection_names) + # Sync helper makes blocking VECTOR_DB_CLIENT calls; + # offload so the async caller's event loop stays free. + query_result = await asyncio.to_thread(get_all_items_from_collections, collection_names) else: query_result = await query_collection( request, diff --git a/backend/open_webui/retrieval/vector/async_client.py b/backend/open_webui/retrieval/vector/async_client.py new file mode 100644 index 0000000000..0bea6696a9 --- /dev/null +++ b/backend/open_webui/retrieval/vector/async_client.py @@ -0,0 +1,129 @@ +""" +Async facade over the synchronous VECTOR_DB_CLIENT. + +The vector DB backends bundled with Open WebUI (Chroma, pgvector, Qdrant, +Milvus, OpenSearch, Pinecone, Weaviate, …) all expose a uniformly +synchronous API. Each method performs blocking network or disk I/O — and +some, like `insert`/`upsert`, can run for several seconds. + +When such a sync method is awaited from an async route handler, it blocks +the event loop for its entire duration, freezing every other in-flight +HTTP request, websocket message and background task. + +This module wraps the sync client in an `AsyncVectorDBClient` that +transparently dispatches each call to a worker thread via +`asyncio.to_thread`. Async callers can `await ASYNC_VECTOR_DB_CLIENT.x(...)` +in place of `VECTOR_DB_CLIENT.x(...)` and the loop stays responsive. + +The original `VECTOR_DB_CLIENT` is unchanged, so callers already running +inside `run_in_threadpool` (e.g. `save_docs_to_vector_db`) are not +affected. + +Thread-safety expectations +-------------------------- +Every async caller now invokes `VECTOR_DB_CLIENT` from a worker thread +rather than the event-loop thread, and many can run concurrently. The +sync client (and its underlying backend driver) is therefore expected +to be safe for concurrent use across threads, which is the standard +contract for the bundled drivers (chroma, pgvector via SQLAlchemy +pool, qdrant-client, opensearch-py, …). This is *not* a new exposure +introduced by this facade — `save_docs_to_vector_db` already called +the sync client from `run_in_threadpool`, so concurrent threaded +access has always been a requirement of the codebase. Adding a global +serialization lock here would defeat the responsiveness this facade +exists to provide; any backend that genuinely cannot tolerate +concurrent access should grow its own internal serialization. + +API surface +----------- +Method signatures mirror `VectorDBBase` exactly. This is deliberate: +permissive `*args/**kwargs` forwarding hides typos at the call site +(an earlier revision of this file shipped that, and a `metadata=` +typo silently broke an entire endpoint until explicit signatures +surfaced it). Callers that need a backend-specific parameter not on +`VectorDBBase` should reach for the `.sync` escape hatch and wrap +their own `asyncio.to_thread`, e.g. :: + + await asyncio.to_thread( + ASYNC_VECTOR_DB_CLIENT.sync.some_backend_specific_op, + collection_name, special_kwarg=value, + ) +""" + +from __future__ import annotations + +import asyncio +from typing import Dict, List, Optional, Union + +from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT +from open_webui.retrieval.vector.main import ( + GetResult, + SearchResult, + VectorDBBase, + VectorItem, +) + + +class AsyncVectorDBClient: + """Awaitable mirror of `VectorDBBase` that off-loads each call to a thread. + + Method signatures mirror `VectorDBBase` exactly so static analysis + catches bad kwargs at the call site instead of letting them surface + deep inside the worker thread (where the resulting ``TypeError`` is + typically swallowed by surrounding ``try/except``). + """ + + def __init__(self, sync_client: VectorDBBase) -> None: + self._sync = sync_client + + @property + def sync(self) -> VectorDBBase: + """Escape hatch for code that must call the sync client directly + (e.g. already inside a worker thread).""" + return self._sync + + async def has_collection(self, collection_name: str) -> bool: + return await asyncio.to_thread(self._sync.has_collection, collection_name) + + async def delete_collection(self, collection_name: str) -> None: + return await asyncio.to_thread(self._sync.delete_collection, collection_name) + + async def insert(self, collection_name: str, items: List[VectorItem]) -> None: + return await asyncio.to_thread(self._sync.insert, collection_name, items) + + async def upsert(self, collection_name: str, items: List[VectorItem]) -> None: + return await asyncio.to_thread(self._sync.upsert, collection_name, items) + + async def search( + self, + collection_name: str, + vectors: List[List[Union[float, int]]], + filter: Optional[Dict] = None, + limit: int = 10, + ) -> Optional[SearchResult]: + return await asyncio.to_thread(self._sync.search, collection_name, vectors, filter, limit) + + async def query( + self, + collection_name: str, + filter: Dict, + limit: Optional[int] = None, + ) -> Optional[GetResult]: + return await asyncio.to_thread(self._sync.query, collection_name, filter, limit) + + async def get(self, collection_name: str) -> Optional[GetResult]: + return await asyncio.to_thread(self._sync.get, collection_name) + + async def delete( + self, + collection_name: str, + ids: Optional[List[str]] = None, + filter: Optional[Dict] = None, + ) -> None: + return await asyncio.to_thread(self._sync.delete, collection_name, ids, filter) + + async def reset(self) -> None: + return await asyncio.to_thread(self._sync.reset) + + +ASYNC_VECTOR_DB_CLIENT = AsyncVectorDBClient(VECTOR_DB_CLIENT) diff --git a/backend/open_webui/retrieval/vector/dbs/pgvector.py b/backend/open_webui/retrieval/vector/dbs/pgvector.py index 4775ff21f4..90e65b9ad0 100644 --- a/backend/open_webui/retrieval/vector/dbs/pgvector.py +++ b/backend/open_webui/retrieval/vector/dbs/pgvector.py @@ -34,6 +34,7 @@ from open_webui.retrieval.vector.main import ( SearchResult, GetResult, ) +from open_webui.utils.misc import sanitize_text_for_db from open_webui.config import ( PGVECTOR_DB_URL, PGVECTOR_INITIALIZE_MAX_VECTOR_LENGTH, @@ -289,7 +290,9 @@ class PgvectorClient(VectorDBBase): vector = self.adjust_vector_length(item['vector']) # Use raw SQL for BYTEA/pgcrypto # Ensure metadata is converted to its JSON text representation - json_metadata = json.dumps(item['metadata']) + # Sanitize to strip null bytes / surrogates that PostgreSQL cannot store + json_metadata = sanitize_text_for_db(json.dumps(item['metadata'])) + item_text = sanitize_text_for_db(item['text']) self.session.execute( text(""" INSERT INTO document_chunk @@ -305,7 +308,7 @@ class PgvectorClient(VectorDBBase): 'id': item['id'], 'vector': vector, 'collection_name': collection_name, - 'text': item['text'], + 'text': item_text, 'metadata_text': json_metadata, 'key': PGVECTOR_PGCRYPTO_KEY, }, @@ -338,7 +341,9 @@ class PgvectorClient(VectorDBBase): if PGVECTOR_PGCRYPTO: for item in items: vector = self.adjust_vector_length(item['vector']) - json_metadata = json.dumps(item['metadata']) + # Sanitize to strip null bytes / surrogates that PostgreSQL cannot store + json_metadata = sanitize_text_for_db(json.dumps(item['metadata'])) + item_text = sanitize_text_for_db(item['text']) self.session.execute( text(""" INSERT INTO document_chunk @@ -358,7 +363,7 @@ class PgvectorClient(VectorDBBase): 'id': item['id'], 'vector': vector, 'collection_name': collection_name, - 'text': item['text'], + 'text': item_text, 'metadata_text': json_metadata, 'key': PGVECTOR_PGCRYPTO_KEY, }, diff --git a/backend/open_webui/retrieval/vector/utils.py b/backend/open_webui/retrieval/vector/utils.py index b2e2fed762..4915b024c3 100644 --- a/backend/open_webui/retrieval/vector/utils.py +++ b/backend/open_webui/retrieval/vector/utils.py @@ -1,5 +1,7 @@ from datetime import datetime +from open_webui.utils.misc import sanitize_text_for_db + KEYS_TO_EXCLUDE = ['content', 'pages', 'tables', 'paragraphs', 'sections', 'figures'] @@ -12,7 +14,8 @@ def filter_metadata(metadata: dict[str, any]) -> dict[str, any]: def process_metadata( metadata: dict[str, any], ) -> dict[str, any]: - # Removes large fields and converts non-serializable types (datetime, list, dict) to strings. + # Removes large fields, converts non-serializable types (datetime, list, dict) to strings, + # and sanitizes strings for database storage (strips null bytes and invalid surrogates). result = {} for key, value in metadata.items(): # Skip large fields @@ -20,7 +23,7 @@ def process_metadata( continue # Convert non-serializable fields to strings if isinstance(value, (datetime, list, dict)): - result[key] = str(value) + result[key] = sanitize_text_for_db(str(value)) else: - result[key] = value + result[key] = sanitize_text_for_db(value) return result diff --git a/backend/open_webui/retrieval/web/firecrawl.py b/backend/open_webui/retrieval/web/firecrawl.py index 4bb23e3797..4af302c0de 100644 --- a/backend/open_webui/retrieval/web/firecrawl.py +++ b/backend/open_webui/retrieval/web/firecrawl.py @@ -1,6 +1,7 @@ import logging from typing import Optional, List +import requests from open_webui.retrieval.web.main import SearchResult, get_filtered_results log = logging.getLogger(__name__) @@ -14,23 +15,38 @@ def search_firecrawl( filter_list: Optional[List[str]] = None, ) -> List[SearchResult]: try: - from firecrawl import FirecrawlApp + url = firecrawl_url.rstrip('/') + response = requests.post( + f'{url}/v1/search', + headers={ + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {firecrawl_api_key}', + }, + json={ + 'query': query, + 'limit': count, + 'timeout': count * 3000, + }, + timeout=count * 3 + 10, + ) + response.raise_for_status() + data = response.json().get('data', {}) - firecrawl = FirecrawlApp(api_key=firecrawl_api_key, api_url=firecrawl_url) - response = firecrawl.search(query=query, limit=count, ignore_invalid_urls=True, timeout=count * 3) - results = response.web - if filter_list: - results = get_filtered_results(results, filter_list) results = [ SearchResult( - link=result.url, - title=result.title, - snippet=result.description, + link=r.get('url', ''), + title=r.get('title', ''), + snippet=r.get('description', ''), ) - for result in results[:count] + for r in data.get('web', []) ] - log.info(f'External search results: {results}') + + if filter_list: + results = get_filtered_results(results, filter_list) + + results = results[:count] + log.info(f'FireCrawl search results: {results}') return results except Exception as e: - log.error(f'Error in External search: {e}') + log.error(f'Error in FireCrawl search: {e}') return [] diff --git a/backend/open_webui/retrieval/web/utils.py b/backend/open_webui/retrieval/web/utils.py index c9442f208b..cfe0f71b85 100644 --- a/backend/open_webui/retrieval/web/utils.py +++ b/backend/open_webui/retrieval/web/utils.py @@ -1,4 +1,5 @@ import asyncio +import ipaddress import logging import socket import ssl @@ -84,11 +85,9 @@ def validate_url(url: Union[str, Sequence[str]]): ipv4_addresses, ipv6_addresses = resolve_hostname(parsed_url.hostname) # Check if any of the resolved addresses are private # This is technically still vulnerable to DNS rebinding attacks, as we don't control WebBaseLoader - for ip in ipv4_addresses: - if validators.ipv4(ip, private=True): - raise ValueError(ERROR_MESSAGES.INVALID_URL) - for ip in ipv6_addresses: - if validators.ipv6(ip, private=True): + for ip in ipv4_addresses + ipv6_addresses: + addr = ipaddress.ip_address(ip) + if not addr.is_global: raise ValueError(ERROR_MESSAGES.INVALID_URL) return True elif isinstance(url, Sequence): @@ -193,27 +192,6 @@ class SafeFireCrawlLoader(BaseLoader, RateLimitMixin, URLProcessingMixin): 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 - - 'scrape': Direct page scraping (default) - - 'map': Site map generation - proxy: Proxy override settings for the FireCrawl API. - params: The parameters to pass to the Firecrawl API. - For more details, visit: https://docs.firecrawl.dev/sdks/python#batch-scrape - """ proxy_server = proxy.get('server') if proxy else None if trust_env and not proxy_server: env_proxies = urllib.request.getproxies() @@ -230,44 +208,43 @@ class SafeFireCrawlLoader(BaseLoader, RateLimitMixin, URLProcessingMixin): self.trust_env = trust_env self.continue_on_failure = continue_on_failure self.api_key = api_key - self.api_url = api_url + self.api_url = (api_url or 'https://api.firecrawl.dev').rstrip('/') self.timeout = timeout self.mode = mode self.params = params or {} def lazy_load(self) -> Iterator[Document]: - """Load documents using FireCrawl batch_scrape.""" - log.debug( - 'Starting FireCrawl batch scrape for %d URLs, mode: %s, params: %s', - len(self.web_paths), - self.mode, - self.params, - ) try: - from firecrawl import FirecrawlApp + headers = { + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {self.api_key}', + } - firecrawl = FirecrawlApp(api_key=self.api_key, api_url=self.api_url) - result = firecrawl.batch_scrape( - self.web_paths, - formats=['markdown'], - skip_tls_verification=not self.verify_ssl, - ignore_invalid_urls=True, - remove_base64_images=True, - max_age=300000, # 5 minutes https://docs.firecrawl.dev/features/fast-scraping#common-maxage-values - wait_timeout=self.timeout if self.timeout else len(self.web_paths) * 3, - **self.params, - ) + for url in self.web_paths: + payload = { + 'url': url, + 'formats': ['markdown'], + **self.params, + } + if self.timeout: + payload['timeout'] = self.timeout * 1000 - if result.status != 'completed': - raise RuntimeError(f'FireCrawl batch scrape did not complete successfully. result: {result}') - - for data in result.data: - metadata = data.metadata or {} - yield Document( - page_content=data.markdown or '', - metadata={'source': metadata.url or metadata.source_url or ''}, + response = requests.post( + f'{self.api_url}/v1/scrape', + headers=headers, + json=payload, + timeout=self.timeout or 60, + verify=self.verify_ssl, ) + response.raise_for_status() + data = response.json().get('data', {}) + metadata = data.get('metadata', {}) + source = metadata.get('url') or metadata.get('sourceURL') or url + yield Document( + page_content=data.get('markdown', ''), + metadata={'source': source}, + ) except Exception as e: if self.continue_on_failure: log.exception(f'Error extracting content from URLs: {e}') @@ -275,38 +252,10 @@ class SafeFireCrawlLoader(BaseLoader, RateLimitMixin, URLProcessingMixin): raise e async def alazy_load(self): - """Async version of lazy_load.""" - log.debug( - 'Starting FireCrawl batch scrape for %d URLs, mode: %s, params: %s', - len(self.web_paths), - self.mode, - self.params, - ) try: - from firecrawl import FirecrawlApp - - firecrawl = FirecrawlApp(api_key=self.api_key, api_url=self.api_url) - result = firecrawl.batch_scrape( - self.web_paths, - formats=['markdown'], - skip_tls_verification=not self.verify_ssl, - ignore_invalid_urls=True, - remove_base64_images=True, - max_age=300000, # 5 minutes https://docs.firecrawl.dev/features/fast-scraping#common-maxage-values - wait_timeout=self.timeout if self.timeout else len(self.web_paths) * 3, - **self.params, - ) - - if result.status != 'completed': - raise RuntimeError(f'FireCrawl batch scrape did not complete successfully. result: {result}') - - for data in result.data: - metadata = data.metadata or {} - yield Document( - page_content=data.markdown or '', - metadata={'source': metadata.url or metadata.source_url or ''}, - ) - + docs = await run_in_threadpool(lambda: list(self.lazy_load())) + for doc in docs: + yield doc except Exception as e: if self.continue_on_failure: log.exception(f'Error extracting content from URLs: {e}') diff --git a/backend/open_webui/routers/analytics.py b/backend/open_webui/routers/analytics.py index 8636444a5d..fd045f79e7 100644 --- a/backend/open_webui/routers/analytics.py +++ b/backend/open_webui/routers/analytics.py @@ -62,7 +62,9 @@ async def get_model_analytics( db: AsyncSession = Depends(get_async_session), ): """Get message counts per model.""" - counts = await ChatMessages.get_message_count_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db) + counts = await ChatMessages.get_message_count_by_model( + start_date=start_date, end_date=end_date, group_id=group_id, db=db + ) models = [ ModelAnalyticsEntry(model_id=model_id, count=count) for model_id, count in sorted(counts.items(), key=lambda x: -x[1]) @@ -80,7 +82,9 @@ async def get_user_analytics( db: AsyncSession = Depends(get_async_session), ): """Get message counts and token usage per user with user info.""" - counts = await ChatMessages.get_message_count_by_user(start_date=start_date, end_date=end_date, group_id=group_id, db=db) + counts = await ChatMessages.get_message_count_by_user( + start_date=start_date, end_date=end_date, group_id=group_id, db=db + ) token_usage = await ChatMessages.get_token_usage_by_user( start_date=start_date, end_date=end_date, group_id=group_id, db=db ) @@ -227,7 +231,9 @@ async def get_token_usage( db: AsyncSession = Depends(get_async_session), ): """Get token usage aggregated by model.""" - usage = await ChatMessages.get_token_usage_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db) + usage = await ChatMessages.get_token_usage_by_model( + start_date=start_date, end_date=end_date, group_id=group_id, db=db + ) models = [ TokenUsageEntry(model_id=model_id, **data) diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py index 5a26d04e0f..499c876261 100644 --- a/backend/open_webui/routers/audio.py +++ b/backend/open_webui/routers/audio.py @@ -54,6 +54,7 @@ from open_webui.env import ( AIOHTTP_CLIENT_SESSION_SSL, AIOHTTP_CLIENT_TIMEOUT, AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST, + BYPASS_PYDUB_PREPROCESSING, DEVICE_TYPE, ENABLE_FORWARD_USER_INFO_HEADERS, ) @@ -330,7 +331,9 @@ async def speech(request: Request, user=Depends(get_verified_user)): detail=ERROR_MESSAGES.NOT_FOUND, ) - if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', request.app.state.config.USER_PERMISSIONS): + if user.role != 'admin' and not await has_permission( + user.id, 'chat.tts', request.app.state.config.USER_PERMISSIONS + ): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, @@ -630,6 +633,7 @@ async def speech(request: Request, user=Depends(get_verified_user)): detail=detail, ) + def transcription_handler(request, file_path, metadata, user=None): filename = os.path.basename(file_path) file_dir = os.path.dirname(file_path) @@ -1095,24 +1099,28 @@ def transcription_handler(request, file_path, metadata, user=None): def transcribe(request: Request, file_path: str, metadata: Optional[dict] = None, user=None): log.info(f'transcribe: {file_path} {metadata}') - if is_audio_conversion_required(file_path): - file_path = convert_audio_to_mp3(file_path) + if BYPASS_PYDUB_PREPROCESSING: + log.info('Bypassing pydub preprocessing (BYPASS_PYDUB_PREPROCESSING=true)') + chunk_paths = [file_path] + else: + if is_audio_conversion_required(file_path): + file_path = convert_audio_to_mp3(file_path) - try: - file_path = compress_audio(file_path) - except Exception as e: - log.exception(e) + try: + file_path = compress_audio(file_path) + except Exception as e: + log.exception(e) - # Always produce a list of chunk paths (could be one entry if small) - try: - chunk_paths = split_audio(file_path, MAX_FILE_SIZE) - print(f'Chunk paths: {chunk_paths}') - except Exception as e: - log.exception(e) - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=ERROR_MESSAGES.DEFAULT(e), - ) + # Always produce a list of chunk paths (could be one entry if small) + try: + chunk_paths = split_audio(file_path, MAX_FILE_SIZE) + print(f'Chunk paths: {chunk_paths}') + except Exception as e: + log.exception(e) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.DEFAULT(e), + ) results = [] try: @@ -1214,7 +1222,9 @@ async def transcription( language: Optional[str] = Form(None), user=Depends(get_verified_user), ): - if user.role != 'admin' and not await has_permission(user.id, 'chat.stt', request.app.state.config.USER_PERMISSIONS): + if user.role != 'admin' and not await has_permission( + user.id, 'chat.stt', request.app.state.config.USER_PERMISSIONS + ): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py index 484212a493..651e123b64 100644 --- a/backend/open_webui/routers/auths.py +++ b/backend/open_webui/routers/auths.py @@ -54,6 +54,7 @@ from open_webui.config import ( OAUTH_PROVIDERS, OAUTH_MERGE_ACCOUNTS_BY_EMAIL, ) +from open_webui.utils.oauth import auth_manager_config from pydantic import BaseModel from open_webui.utils.misc import parse_duration, validate_email_format @@ -96,7 +97,9 @@ log = logging.getLogger(__name__) signin_rate_limiter = RateLimiter(redis_client=get_redis_client(), limit=5 * 3, window=60 * 3) -async def create_session_response(request: Request, user, db, response: Response = None, set_cookie: bool = False) -> dict: +async def create_session_response( + request: Request, user, db, response: Response = None, set_cookie: bool = False +) -> dict: """ Create JWT token and build session response for a user. Shared helper for signin, signup, ldap_auth, add_user, and token_exchange endpoints. @@ -322,6 +325,14 @@ async def ldap_auth( detail=ERROR_MESSAGES.ACTION_PROHIBITED, ) + # Reject empty passwords before attempting the LDAP bind. + # Per RFC 4513 §5.1.2, a Simple Bind with a non-empty DN but empty + # password is "unauthenticated simple authentication" — many LDAP + # servers (OpenLDAP default, some AD configs) return success for these, + # which would grant access without valid credentials. + if not form_data.password or not form_data.password.strip(): + raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) + # NOW load LDAP config variables LDAP_SERVER_LABEL = request.app.state.config.LDAP_SERVER_LABEL LDAP_SERVER_HOST = request.app.state.config.LDAP_SERVER_HOST @@ -909,7 +920,9 @@ async def add_user( @router.get('/admin/details') -async def get_admin_details(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)): +async def get_admin_details( + request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session) +): if request.app.state.config.SHOW_ADMIN_DETAILS: admin_email = request.app.state.config.ADMIN_EMAIL admin_name = None @@ -1118,7 +1131,7 @@ async def update_ldap_server(request: Request, form_data: LdapServerConfig, user for key in required_fields: value = getattr(form_data, key) if not value: - raise HTTPException(400, detail=f'Required field {key} is empty') + raise HTTPException(400, detail=ERROR_MESSAGES.REQUIRED_FIELD_EMPTY(key)) request.app.state.config.LDAP_SERVER_LABEL = form_data.label request.app.state.config.LDAP_SERVER_HOST = form_data.host @@ -1173,7 +1186,9 @@ async def update_ldap_config(request: Request, form_data: LdapConfigForm, user=D # create api key @router.post('/api_key', response_model=ApiKey) -async def generate_api_key(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)): +async def generate_api_key( + request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session) +): if not request.app.state.config.ENABLE_API_KEYS or ( user.role != 'admin' and not await has_permission(user.id, 'features.api_keys', request.app.state.config.USER_PERMISSIONS) @@ -1245,7 +1260,7 @@ async def token_exchange( if provider not in OAUTH_PROVIDERS: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail=f"Provider '{provider}' is not configured", + detail=ERROR_MESSAGES.OAUTH_NOT_CONFIGURED(provider), ) # Get the OAuth client for this provider oauth_manager = request.app.state.oauth_manager @@ -1253,7 +1268,7 @@ async def token_exchange( if not client: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail=f"OAuth client for '{provider}' not found", + detail=ERROR_MESSAGES.OAUTH_NOT_CONFIGURED(provider), ) # Validate the token by calling the userinfo endpoint @@ -1295,6 +1310,17 @@ async def token_exchange( ) email = email.lower() + # Enforce domain allowlist — same check as the normal OAuth callback + if ( + '*' not in auth_manager_config.OAUTH_ALLOWED_DOMAINS + and email.split('@')[-1] not in auth_manager_config.OAUTH_ALLOWED_DOMAINS + ): + log.warning(f'Token exchange denied: email domain not in allowed domains list') + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + # Try to find the user by OAuth sub user = await Users.get_user_by_oauth_sub(provider, sub, db=db) diff --git a/backend/open_webui/routers/automations.py b/backend/open_webui/routers/automations.py index 504bc726d2..d68bd8e2c6 100644 --- a/backend/open_webui/routers/automations.py +++ b/backend/open_webui/routers/automations.py @@ -74,7 +74,7 @@ async def check_automation_limits(request, user, rrule_str: str, db, is_create: if max_count > 0 and await Automations.count_by_user(user.id, db=db) >= max_count: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, - detail=f'Automation limit reached ({max_count})', + detail=ERROR_MESSAGES.AUTOMATION_LIMIT_EXCEEDED(max_count), ) # Min interval (create + update) @@ -86,7 +86,7 @@ async def check_automation_limits(request, user, rrule_str: str, db, is_create: if interval is not None and interval < min_interval: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f'Schedule too frequent. Minimum interval is {min_interval} seconds.', + detail=ERROR_MESSAGES.AUTOMATION_TOO_FREQUENT(min_interval), ) @@ -167,15 +167,6 @@ async def create_new_automation( await check_automation_limits(request, user, form_data.data.rrule, db, is_create=True) - # Validate terminal server exists if linked - if form_data.data.terminal and form_data.data.terminal.server_id: - connections = request.app.state.config.TERMINAL_SERVER_CONNECTIONS or [] - if not any(c.get('id') == form_data.data.terminal.server_id for c in connections): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail='Terminal server not found', - ) - tz = user.timezone automation = await Automations.insert(user.id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db) return await enrich_automation(automation, db, tz=tz) @@ -226,15 +217,6 @@ async def update_automation_by_id( await check_automation_limits(request, user, form_data.data.rrule, db, is_create=False) - # Validate terminal server exists if linked - if form_data.data.terminal and form_data.data.terminal.server_id: - connections = request.app.state.config.TERMINAL_SERVER_CONNECTIONS or [] - if not any(c.get('id') == form_data.data.terminal.server_id for c in connections): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail='Terminal server not found', - ) - tz = user.timezone updated = await Automations.update_by_id(id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db) return await enrich_automation(updated, db, tz=tz) diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py index 6610ee2eca..a771b95920 100644 --- a/backend/open_webui/routers/channels.py +++ b/backend/open_webui/routers/channels.py @@ -61,7 +61,7 @@ from open_webui.utils.chat import generate_chat_completion from open_webui.utils.auth import get_admin_user, get_verified_user -from open_webui.utils.access_control import has_permission +from open_webui.utils.access_control import has_permission, filter_allowed_access_grants from open_webui.utils.webhook import post_webhook from open_webui.utils.channels import extract_mentions, replace_mentions from open_webui.internal.db import get_async_session @@ -94,7 +94,9 @@ async def channel_has_access( return False -async def get_channel_users_with_access(channel: ChannelModel, permission: str = 'read', db: Optional[AsyncSession] = None): +async def get_channel_users_with_access( + channel: ChannelModel, permission: str = 'read', db: Optional[AsyncSession] = None +): return await AccessGrants.get_users_with_access( resource_type='channel', resource_id=channel.id, @@ -138,7 +140,7 @@ async def check_channels_access(request: Request, user: Optional[UserModel] = No if not request.app.state.config.ENABLE_CHANNELS: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, - detail='Channels are not enabled', + detail=ERROR_MESSAGES.FEATURE_DISABLED('Channels'), ) if user: @@ -303,6 +305,14 @@ async def create_new_channel( detail=ERROR_MESSAGES.UNAUTHORIZED, ) + form_data.access_grants = filter_allowed_access_grants( + request.app.state.config.USER_PERMISSIONS, + user.id, + user.role, + form_data.access_grants, + 'sharing.public_channels', + ) + try: if form_data.type == 'dm': existing_channel = await Channels.get_dm_channel_by_user_ids([user.id, *form_data.user_ids], db=db) @@ -633,6 +643,14 @@ async def update_channel_by_id( if channel.user_id != user.id and user.role != 'admin': raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()) + form_data.access_grants = filter_allowed_access_grants( + request.app.state.config.USER_PERMISSIONS, + user.id, + user.role, + form_data.access_grants, + 'sharing.public_channels', + ) + try: channel = await Channels.update_channel_by_id(id, form_data, db=db) return ChannelModel(**channel.model_dump()) @@ -877,11 +895,13 @@ async def model_response_handler(request, channel, message, user, db=None): if model: try: # reverse to get in chronological order - thread_messages = (await Messages.get_messages_by_parent_id( - channel.id, - message.parent_id if message.parent_id else message.id, - db=db, - ))[::-1] + thread_messages = ( + await Messages.get_messages_by_parent_id( + channel.id, + message.parent_id if message.parent_id else message.id, + db=db, + ) + )[::-1] response_message, channel = await new_message_handler( request, @@ -1104,7 +1124,9 @@ async def post_new_message( try: if files := message.data.get('files', []): for file in files: - await Channels.set_file_message_id_in_channel_by_id(channel.id, file.get('id', ''), message.id, db=db) + await Channels.set_file_message_id_in_channel_by_id( + channel.id, file.get('id', ''), message.id, db=db + ) except Exception as e: log.debug(e) @@ -1769,7 +1791,7 @@ async def post_webhook_message( if not webhook: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, - detail='Invalid webhook URL', + detail=ERROR_MESSAGES.INVALID_URL, ) channel = await Channels.get_channel_by_id(webhook.channel_id, db=db) @@ -1787,7 +1809,7 @@ async def post_webhook_message( if not message: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail='Failed to create message', + detail=ERROR_MESSAGES.DEFAULT('Failed to create message'), ) # Update last_used_at diff --git a/backend/open_webui/routers/chats.py b/backend/open_webui/routers/chats.py index ba07937ed1..1980d22362 100644 --- a/backend/open_webui/routers/chats.py +++ b/backend/open_webui/routers/chats.py @@ -1,6 +1,7 @@ import json import logging from typing import Optional +from uuid import uuid4 from sqlalchemy.ext.asyncio import AsyncSession import asyncio from fastapi.responses import StreamingResponse @@ -493,7 +494,9 @@ async def delete_all_user_chats( user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session), ): - if user.role == 'user' and not await has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS): + if user.role == 'user' and not await has_permission( + user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS + ): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.ACCESS_PROHIBITED, @@ -538,7 +541,9 @@ async def get_user_chat_list_by_user_id( if direction: filter['direction'] = direction - return await Chats.get_chat_list_by_user_id(user_id, include_archived=True, filter=filter, skip=skip, limit=limit, db=db) + return await Chats.get_chat_list_by_user_id( + user_id, include_archived=True, filter=filter, skip=skip, limit=limit, db=db + ) ############################ @@ -553,7 +558,7 @@ async def create_new_chat( db: AsyncSession = Depends(get_async_session), ): try: - chat = await Chats.insert_new_chat(user.id, form_data, db=db) + chat = await Chats.insert_new_chat(str(uuid4()), user.id, form_data, db=db) return ChatResponse(**chat.model_dump()) except Exception as e: log.exception(e) @@ -620,7 +625,9 @@ async def search_user_chats( @router.get('/folder/{folder_id}', response_model=list[ChatResponse]) -async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_chats_by_folder_id( + folder_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): folder_ids = [folder_id] children_folders = await Folders.get_children_folders_by_id_and_user_id(folder_id, user.id, db=db) if children_folders: @@ -815,7 +822,9 @@ async def get_shared_session_user_chat_list( @router.get('/share/{share_id}', response_model=Optional[ChatResponse]) -async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_shared_chat_by_id( + share_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): if user.role == 'pending': raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND) @@ -851,7 +860,9 @@ async def get_user_chat_list_by_tag_name( user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session), ): - chats = await Chats.get_chat_list_by_user_id_and_tag_name(user.id, form_data.name, form_data.skip, form_data.limit, db=db) + chats = await Chats.get_chat_list_by_user_id_and_tag_name( + user.id, form_data.name, form_data.skip, form_data.limit, db=db + ) if len(chats) == 0: await Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db) @@ -1056,7 +1067,9 @@ async def delete_chat_by_id( @router.get('/{id}/pinned', response_model=Optional[bool]) -async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_pinned_status_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) if chat: return chat.pinned @@ -1137,7 +1150,9 @@ async def clone_chat_by_id( @router.post('/{id}/clone/shared', response_model=Optional[ChatResponse]) -async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def clone_shared_chat_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): if user.role == 'admin': chat = await Chats.get_chat_by_id(id, db=db) else: @@ -1250,7 +1265,9 @@ async def share_chat_by_id( @router.delete('/{id}/share', response_model=Optional[bool]) -async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def delete_shared_chat_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) if chat: if not chat.share_id: @@ -1371,7 +1388,9 @@ async def delete_tag_by_id_and_tag_name( @router.delete('/{id}/tags/all', response_model=Optional[bool]) -async def delete_all_tags_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def delete_all_tags_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db) if chat: old_tags = chat.meta.get('tags', []) diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index 041c4ad935..7c54c09039 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -8,7 +8,7 @@ from typing import Optional from open_webui.env import AIOHTTP_CLIENT_TIMEOUT from open_webui.utils.auth import get_admin_user, get_verified_user -from open_webui.config import get_config, save_config +from open_webui.config import get_config, save_config, async_save_config from open_webui.config import BannerModel from open_webui.utils.tools import ( @@ -27,6 +27,7 @@ from open_webui.utils.oauth import ( get_oauth_client_info_with_static_credentials, encrypt_data, decrypt_data, + resolve_oauth_client_info, OAuthClientInformationFull, ) from mcp.shared.auth import OAuthMetadata @@ -49,7 +50,7 @@ class ImportConfigForm(BaseModel): @router.post('/import', response_model=dict) async def import_config(form_data: ImportConfigForm, user=Depends(get_admin_user)): - save_config(form_data.config) + await async_save_config(form_data.config) return get_config() @@ -203,9 +204,7 @@ async def set_tool_servers_config( if auth_type in ('oauth_2.1', 'oauth_2.1_static') and server_id: try: - oauth_client_info = connection.get('info', {}).get('oauth_client_info', '') - oauth_client_info = decrypt_data(oauth_client_info) - + oauth_client_info = resolve_oauth_client_info(connection) request.app.state.oauth_client_manager.add_client( f'{server_type}:{server_id}', OAuthClientInformationFull(**oauth_client_info), diff --git a/backend/open_webui/routers/evaluations.py b/backend/open_webui/routers/evaluations.py index 6a847c22a5..072c7fa732 100644 --- a/backend/open_webui/routers/evaluations.py +++ b/backend/open_webui/routers/evaluations.py @@ -415,7 +415,9 @@ async def update_feedback_by_id( @router.delete('/feedback/{id}') -async def delete_feedback_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def delete_feedback_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): if user.role == 'admin': success = await Feedbacks.delete_feedback_by_id(id=id, db=db) else: diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py index 8f1ee13f7f..7ca1c2e73f 100644 --- a/backend/open_webui/routers/files.py +++ b/backend/open_webui/routers/files.py @@ -22,10 +22,10 @@ from fastapi import ( from fastapi.responses import FileResponse, StreamingResponse from sqlalchemy.ext.asyncio import AsyncSession -from open_webui.internal.db import get_async_session, SessionLocal +from open_webui.internal.db import get_async_session, get_async_db_context from open_webui.constants import ERROR_MESSAGES -from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT +from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT from open_webui.models.channels import Channels from open_webui.models.users import Users @@ -48,7 +48,7 @@ from open_webui.routers.audio import transcribe from open_webui.storage.provider import Storage -from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL +from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL, STORAGE_LOCAL_CACHE, STORAGE_PROVIDER, UPLOAD_DIR from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.misc import strict_match_mime_type from pydantic import BaseModel @@ -88,6 +88,20 @@ def _is_text_file(file_path: str, chunk_size: int = 8192) -> bool: return False +def _cleanup_local_cache(file_path: str) -> None: + """Remove the local cached copy of a cloud-stored file after processing.""" + if STORAGE_LOCAL_CACHE or STORAGE_PROVIDER == 'local': + return + try: + local_filename = os.path.basename(file_path) + local_path = os.path.join(UPLOAD_DIR, local_filename) + if os.path.isfile(local_path): + os.remove(local_path) + log.debug(f'Cleaned up local cache: {local_path}') + except OSError as e: + log.warning(f'Failed to clean up local cache for {file_path}: {e}') + + async def process_uploaded_file( request, file, @@ -110,10 +124,10 @@ async def process_uploaded_file( stt_supported_content_types = getattr(request.app.state.config, 'STT_SUPPORTED_CONTENT_TYPES', []) if strict_match_mime_type(stt_supported_content_types, content_type): - file_path_processed = Storage.get_file(file_path) + file_path_processed = await asyncio.to_thread(Storage.get_file, file_path) result = transcribe(request, file_path_processed, file_metadata, user) - process_file( + await process_file( request, ProcessFileForm(file_id=file_item.id, content=result.get('text', '')), user=user, @@ -122,7 +136,7 @@ async def process_uploaded_file( elif (not content_type.startswith(('image/', 'video/'))) or ( request.app.state.config.CONTENT_EXTRACTION_ENGINE == 'external' ): - process_file( + await process_file( request, ProcessFileForm(file_id=file_item.id), user=user, @@ -132,7 +146,7 @@ async def process_uploaded_file( raise Exception(f'File type {content_type} is not supported for processing') else: log.info(f'File type {file.content_type} is not provided, but trying to process anyway') - process_file( + await process_file( request, ProcessFileForm(file_id=file_item.id), user=user, @@ -150,11 +164,14 @@ async def process_uploaded_file( db=db_session, ) - if db: - _process_handler(db) - else: - with SessionLocal() as db_session: - _process_handler(db_session) + try: + if db: + await _process_handler(db) + else: + async with get_async_db_context() as db_session: + await _process_handler(db_session) + finally: + _cleanup_local_cache(file_path) @router.post('/', response_model=FileModelResponse) @@ -225,7 +242,8 @@ async def upload_file_handler( id = str(uuid.uuid4()) name = filename filename = f'{id}_{filename}' - contents, file_path = Storage.upload_file( + contents, file_path = await asyncio.to_thread( + Storage.upload_file, file.file, filename, { @@ -389,8 +407,8 @@ async def delete_all_files(user=Depends(get_admin_user), db: AsyncSession = Depe result = await Files.delete_all_files(db=db) if result: try: - Storage.delete_all_files() - VECTOR_DB_CLIENT.reset() + await asyncio.to_thread(Storage.delete_all_files) + await ASYNC_VECTOR_DB_CLIENT.reset() except Exception as e: log.exception(e) log.error('Error deleting files') @@ -495,7 +513,9 @@ async def get_file_process_status( @router.get('/{id}/data/content') -async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_file_data_content_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): file = await Files.get_file_by_id(id, db=db) if not file: @@ -540,7 +560,7 @@ async def update_file_data_content_by_id( if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'write', user, db=db): try: - process_file( + await process_file( request, ProcessFileForm(file_id=id, content=form_data.content), user=user, @@ -558,9 +578,9 @@ async def update_file_data_content_by_id( for knowledge in knowledges: try: # Remove old embeddings for this file from the KB collection - VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': id}) + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': id}) # Re-add from the now-updated file-{file_id} collection - process_file( + await process_file( request, ProcessFileForm(file_id=id, collection_name=knowledge.id), user=user, @@ -599,7 +619,7 @@ async def get_file_content_by_id( if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db): try: - file_path = Storage.get_file(file.path) + file_path = await asyncio.to_thread(Storage.get_file, file.path) file_path = Path(file_path) # Check if the file already exists in the cache @@ -646,7 +666,9 @@ async def get_file_content_by_id( @router.get('/{id}/content/html') -async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_html_file_content_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): file = await Files.get_file_by_id(id, db=db) if not file: @@ -664,7 +686,7 @@ async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user), if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db): try: - file_path = Storage.get_file(file.path) + file_path = await asyncio.to_thread(Storage.get_file, file.path) file_path = Path(file_path) # Check if the file already exists in the cache @@ -693,7 +715,9 @@ async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user), @router.get('/{id}/content/{file_name}') -async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_file_content_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): file = await Files.get_file_by_id(id, db=db) if not file: @@ -711,7 +735,7 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: A headers = {'Content-Disposition': f"attachment; filename*=UTF-8''{encoded_filename}"} if file_path: - file_path = Storage.get_file(file_path) + file_path = await asyncio.to_thread(Storage.get_file, file_path) file_path = Path(file_path) # Check if the file already exists in the cache @@ -766,17 +790,17 @@ async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: AsyncS await Knowledges.remove_file_from_knowledge_by_id(knowledge.id, id, db=db) # Clean KB embeddings (same logic as /knowledge/{id}/file/remove) try: - VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': id}) + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': id}) if file.hash: - VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'hash': file.hash}) + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'hash': file.hash}) except Exception as e: log.debug(f'KB embedding cleanup for {knowledge.id}: {e}') result = await Files.delete_file_by_id(id, db=db) if result: try: - Storage.delete_file(file.path) - VECTOR_DB_CLIENT.delete(collection_name=f'file-{id}') + await asyncio.to_thread(Storage.delete_file, file.path) + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=f'file-{id}') except Exception as e: log.exception(e) log.error('Error deleting files') diff --git a/backend/open_webui/routers/folders.py b/backend/open_webui/routers/folders.py index 9938d1eca1..ebd0c0cb17 100644 --- a/backend/open_webui/routers/folders.py +++ b/backend/open_webui/routers/folders.py @@ -89,7 +89,9 @@ async def get_folders( valid_files.append(file) folder.data['files'] = valid_files - await Folders.update_folder_by_id_and_user_id(folder.id, user.id, FolderUpdateForm(data=folder.data), db=db) + await Folders.update_folder_by_id_and_user_id( + folder.id, user.id, FolderUpdateForm(data=folder.data), db=db + ) folder_list.append(FolderNameIdResponse(**folder.model_dump())) @@ -107,7 +109,9 @@ async def create_folder( user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session), ): - folder = await Folders.get_folder_by_parent_id_and_user_id_and_name(form_data.parent_id, user.id, form_data.name, db=db) + folder = await Folders.get_folder_by_parent_id_and_user_id_and_name( + form_data.parent_id, user.id, form_data.name, db=db + ) if folder: raise HTTPException( @@ -250,7 +254,9 @@ async def update_folder_is_expanded_by_id( folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db) if folder: try: - folder = await Folders.update_folder_is_expanded_by_id_and_user_id(id, user.id, form_data.is_expanded, db=db) + folder = await Folders.update_folder_is_expanded_by_id_and_user_id( + id, user.id, form_data.is_expanded, db=db + ) return folder except Exception as e: log.exception(e) diff --git a/backend/open_webui/routers/functions.py b/backend/open_webui/routers/functions.py index 371079bed2..1d0f0342d2 100644 --- a/backend/open_webui/routers/functions.py +++ b/backend/open_webui/routers/functions.py @@ -128,7 +128,7 @@ async def load_function_from_url(request: Request, form_data: LoadUrlForm, user= 'content': data, } except Exception as e: - raise HTTPException(status_code=500, detail=f'Error importing function: {e}') + raise HTTPException(status_code=500, detail=ERROR_MESSAGES.DEFAULT(e)) ############################ @@ -373,7 +373,9 @@ async def delete_function_by_id( @router.get('/id/{id}/valves', response_model=Optional[dict]) -async def get_function_valves_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)): +async def get_function_valves_by_id( + id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session) +): function = await Functions.get_function_by_id(id, db=db) if function: try: @@ -473,7 +475,9 @@ async def update_function_valves_by_id( @router.get('/id/{id}/valves/user', response_model=Optional[dict]) -async def get_function_user_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_function_user_valves_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): function = await Functions.get_function_by_id(id, db=db) if function: try: diff --git a/backend/open_webui/routers/images.py b/backend/open_webui/routers/images.py index fb3bc1cec5..37dda2eb1b 100644 --- a/backend/open_webui/routers/images.py +++ b/backend/open_webui/routers/images.py @@ -10,6 +10,7 @@ from pathlib import Path from typing import Optional from urllib.parse import quote +import aiohttp import requests from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile from fastapi.responses import FileResponse @@ -21,7 +22,8 @@ from open_webui.config import ( ) from open_webui.constants import ERROR_MESSAGES from open_webui.retrieval.web.utils import validate_url -from open_webui.env import ENABLE_FORWARD_USER_INFO_HEADERS +from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL, ENABLE_FORWARD_USER_INFO_HEADERS +from open_webui.utils.session_pool import get_session from open_webui.models.chats import Chats from open_webui.routers.files import upload_file_handler, get_file_content_by_id @@ -313,12 +315,14 @@ def get_automatic1111_api_auth(request: Request): async def verify_url(request: Request, user=Depends(get_admin_user)): if request.app.state.config.IMAGE_GENERATION_ENGINE == 'automatic1111': try: - r = requests.get( + session = await get_session() + async with session.get( url=f'{request.app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/options', headers={'authorization': get_automatic1111_api_auth(request)}, - ) - r.raise_for_status() - return True + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return True except Exception: request.app.state.config.ENABLE_IMAGE_GENERATION = False raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL) @@ -327,12 +331,14 @@ async def verify_url(request: Request, user=Depends(get_admin_user)): if request.app.state.config.COMFYUI_API_KEY: headers = {'Authorization': f'Bearer {request.app.state.config.COMFYUI_API_KEY}'} try: - r = requests.get( + session = await get_session() + async with session.get( url=f'{request.app.state.config.COMFYUI_BASE_URL}/object_info', headers=headers, - ) - r.raise_for_status() - return True + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return True except Exception: request.app.state.config.ENABLE_IMAGE_GENERATION = False raise HTTPException(status_code=400, detail=ERROR_MESSAGES.INVALID_URL) @@ -357,11 +363,13 @@ async def get_models(request: Request, user=Depends(get_verified_user)): elif request.app.state.config.IMAGE_GENERATION_ENGINE == 'comfyui': # TODO - get models from comfyui headers = {'Authorization': f'Bearer {request.app.state.config.COMFYUI_API_KEY}'} - r = requests.get( + session = await get_session() + async with session.get( url=f'{request.app.state.config.COMFYUI_BASE_URL}/object_info', headers=headers, - ) - info = r.json() + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + info = await r.json() workflow = json.loads(request.app.state.config.COMFYUI_WORKFLOW) model_node_id = None @@ -399,11 +407,13 @@ async def get_models(request: Request, user=Depends(get_verified_user)): request.app.state.config.IMAGE_GENERATION_ENGINE == 'automatic1111' or request.app.state.config.IMAGE_GENERATION_ENGINE == '' ): - r = requests.get( + session = await get_session() + async with session.get( url=f'{request.app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/sd-models', headers={'authorization': get_automatic1111_api_auth(request)}, - ) - models = r.json() + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + models = await r.json() return list( map( lambda model: {'id': model['title'], 'name': model['model_name']}, @@ -533,7 +543,6 @@ async def image_generations( model = get_image_model(request) - r = None try: if request.app.state.config.IMAGE_GENERATION_ENGINE == 'openai': headers = { @@ -552,7 +561,11 @@ async def image_generations( 'model': model, 'prompt': form_data.prompt, 'n': form_data.n, - 'size': (form_data.size if form_data.size else request.app.state.config.IMAGE_SIZE), + **( + {'size': form_data.size or request.app.state.config.IMAGE_SIZE} + if (form_data.size or request.app.state.config.IMAGE_SIZE) + else {} + ), **( {} if re.match( @@ -568,16 +581,15 @@ async def image_generations( ), } - # Use asyncio.to_thread for the requests.post call - r = await asyncio.to_thread( - requests.post, + session = await get_session() + async with session.post( url=url, json=data, headers=headers, - ) - - r.raise_for_status() - res = r.json() + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + res = await r.json() images = [] @@ -619,16 +631,15 @@ async def image_generations( model = f'{model}:generateContent' data = {'contents': [{'parts': [{'text': form_data.prompt}]}]} - # Use asyncio.to_thread for the requests.post call - r = await asyncio.to_thread( - requests.post, + session = await get_session() + async with session.post( url=f'{request.app.state.config.IMAGES_GEMINI_API_BASE_URL}/models/{model}', json=data, headers=headers, - ) - - r.raise_for_status() - res = r.json() + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + res = await r.json() images = [] @@ -727,15 +738,14 @@ async def image_generations( if request.app.state.config.AUTOMATIC1111_PARAMS: data = {**data, **request.app.state.config.AUTOMATIC1111_PARAMS} - # Use asyncio.to_thread for the requests.post call - r = await asyncio.to_thread( - requests.post, + session = await get_session() + async with session.post( url=f'{request.app.state.config.AUTOMATIC1111_BASE_URL}/sdapi/v1/txt2img', json=data, headers={'authorization': get_automatic1111_api_auth(request)}, - ) - - res = r.json() + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + res = await r.json() log.debug(f'res: {res}') images = [] @@ -753,10 +763,8 @@ async def image_generations( return images except Exception as e: error = e - if r != None: - data = r.json() - if 'error' in data: - error = data['error']['message'] + if isinstance(e, aiohttp.ClientResponseError): + error = e.message raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(error)) @@ -798,11 +806,12 @@ async def image_edits( if data.startswith('http://') or data.startswith('https://'): # Validate URL to prevent SSRF attacks against local/private networks validate_url(data) - r = await asyncio.to_thread(requests.get, data) - r.raise_for_status() + session = await get_session() + async with session.get(data, ssl=AIOHTTP_CLIENT_SESSION_SSL) as r: + r.raise_for_status() - image_data = base64.b64encode(r.content).decode('utf-8') - return f'data:{r.headers["content-type"]};base64,{image_data}' + image_data = base64.b64encode(await r.read()).decode('utf-8') + return f'data:{r.headers["content-type"]};base64,{image_data}' else: file_id = None @@ -832,7 +841,7 @@ async def image_edits( except Exception as e: raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e)) - async def get_image_file_item(base64_string, param_name='image'): + def get_image_file_item(base64_string, param_name='image'): data = base64_string header, encoded = data.split(',', 1) mime_type = header.split(';')[0].lstrip('data:') @@ -846,7 +855,6 @@ async def image_edits( ), ) - r = None try: if request.app.state.config.IMAGE_EDIT_ENGINE == 'openai': headers = { @@ -883,17 +891,30 @@ async def image_edits( if request.app.state.config.IMAGES_EDIT_OPENAI_API_VERSION: url_search_params += f'?api-version={request.app.state.config.IMAGES_EDIT_OPENAI_API_VERSION}' - # Use asyncio.to_thread for the requests.post call - r = await asyncio.to_thread( - requests.post, + # Build multipart form data for aiohttp + form = aiohttp.FormData() + for key, value in data.items(): + if isinstance(value, dict): + form.add_field(key, json.dumps(value)) + else: + form.add_field(key, str(value)) + for param_name, (filename, file_obj, content_type_val) in files: + form.add_field( + param_name, + file_obj, + filename=filename, + content_type=content_type_val, + ) + + session = await get_session() + async with session.post( url=f'{request.app.state.config.IMAGES_EDIT_OPENAI_API_BASE_URL}/images/edits{url_search_params}', headers=headers, - files=files, - data=data, - ) - - r.raise_for_status() - res = r.json() + data=form, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + res = await r.json() images = [] for image in res['data']: @@ -940,16 +961,15 @@ async def image_edits( ] ) - # Use asyncio.to_thread for the requests.post call - r = await asyncio.to_thread( - requests.post, + session = await get_session() + async with session.post( url=f'{request.app.state.config.IMAGES_EDIT_GEMINI_API_BASE_URL}/models/{model}', json=data, headers=headers, - ) - - r.raise_for_status() - res = r.json() + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + res = await r.json() images = [] for image in res['candidates']: @@ -1048,13 +1068,7 @@ async def image_edits( return images except Exception as e: error = e - if r != None: - data = r.text - try: - data = json.loads(data) - if 'error' in data: - error = data['error']['message'] - except Exception: - error = data + if isinstance(e, aiohttp.ClientResponseError): + error = e.message raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(error)) diff --git a/backend/open_webui/routers/knowledge.py b/backend/open_webui/routers/knowledge.py index f6c3416c8d..77b72cacf0 100644 --- a/backend/open_webui/routers/knowledge.py +++ b/backend/open_webui/routers/knowledge.py @@ -2,7 +2,7 @@ from typing import List, Optional from pydantic import BaseModel from fastapi import APIRouter, Depends, HTTPException, status, Request, Query from fastapi.responses import StreamingResponse -from fastapi.concurrency import run_in_threadpool + import logging import io import zipfile @@ -19,7 +19,7 @@ from open_webui.models.knowledge import ( KnowledgeUserResponse, ) from open_webui.models.files import Files, FileModel, FileMetadataResponse -from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT +from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT from open_webui.routers.retrieval import ( process_file, ProcessFileForm, @@ -66,7 +66,7 @@ async def embed_knowledge_base_metadata( try: content = f'{name}\n\n{description}' if description else name embedding = await request.app.state.EMBEDDING_FUNCTION(content) - VECTOR_DB_CLIENT.upsert( + await ASYNC_VECTOR_DB_CLIENT.upsert( collection_name=KNOWLEDGE_BASES_COLLECTION, items=[ { @@ -85,10 +85,10 @@ async def embed_knowledge_base_metadata( return False -def remove_knowledge_base_metadata_embedding(knowledge_base_id: str) -> bool: +async def remove_knowledge_base_metadata_embedding(knowledge_base_id: str) -> bool: """Remove knowledge base embedding.""" try: - VECTOR_DB_CLIENT.delete( + await ASYNC_VECTOR_DB_CLIENT.delete( collection_name=KNOWLEDGE_BASES_COLLECTION, ids=[knowledge_base_id], ) @@ -310,8 +310,8 @@ async def reindex_knowledge_files( try: files = await Knowledges.get_files_by_id(knowledge_base.id, db=db) try: - if VECTOR_DB_CLIENT.has_collection(collection_name=knowledge_base.id): - VECTOR_DB_CLIENT.delete_collection(collection_name=knowledge_base.id) + if await ASYNC_VECTOR_DB_CLIENT.has_collection(collection_name=knowledge_base.id): + await ASYNC_VECTOR_DB_CLIENT.delete_collection(collection_name=knowledge_base.id) except Exception as e: log.error(f'Error deleting collection {knowledge_base.id}: {str(e)}') continue # Skip, don't raise @@ -319,8 +319,7 @@ async def reindex_knowledge_files( failed_files = [] for file in files: try: - await run_in_threadpool( - process_file, + await process_file( request, ProcessFileForm(file_id=file.id, collection_name=knowledge_base.id), user=user, @@ -543,7 +542,7 @@ async def update_knowledge_access_by_id( await AccessGrants.set_access_grants('knowledge', id, form_data.access_grants, db=db) return KnowledgeFilesResponse( - **await Knowledges.get_knowledge_by_id(id=id, db=db).model_dump(), + **(await Knowledges.get_knowledge_by_id(id=id, db=db)).model_dump(), files=await Knowledges.get_file_metadatas_by_id(id, db=db), ) @@ -659,7 +658,7 @@ async def add_file_to_knowledge_by_id( # Add content to the vector database try: - process_file( + await process_file( request, ProcessFileForm(file_id=form_data.file_id, collection_name=id), user=user, @@ -733,11 +732,11 @@ async def update_file_from_knowledge_by_id( ) # Remove content from the vector database - VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': form_data.file_id}) + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': form_data.file_id}) # Add content to the vector database try: - process_file( + await process_file( request, ProcessFileForm(file_id=form_data.file_id, collection_name=id), user=user, @@ -815,11 +814,11 @@ async def remove_file_from_knowledge_by_id( # Remove content from the vector database try: - VECTOR_DB_CLIENT.delete( + await ASYNC_VECTOR_DB_CLIENT.delete( collection_name=knowledge.id, filter={'file_id': form_data.file_id} ) # Remove by file_id first - VECTOR_DB_CLIENT.delete( + await ASYNC_VECTOR_DB_CLIENT.delete( collection_name=knowledge.id, filter={'hash': file.hash} ) # Remove by hash as well in case of duplicates except Exception as e: @@ -831,8 +830,8 @@ async def remove_file_from_knowledge_by_id( try: # Remove the file's collection from vector database file_collection = f'file-{form_data.file_id}' - if VECTOR_DB_CLIENT.has_collection(collection_name=file_collection): - VECTOR_DB_CLIENT.delete_collection(collection_name=file_collection) + if await ASYNC_VECTOR_DB_CLIENT.has_collection(collection_name=file_collection): + await ASYNC_VECTOR_DB_CLIENT.delete_collection(collection_name=file_collection) except Exception as e: log.debug('This was most likely caused by bypassing embedding processing') log.debug(e) @@ -859,7 +858,9 @@ async def remove_file_from_knowledge_by_id( @router.delete('/{id}/delete', response_model=bool) -async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def delete_knowledge_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db) if not knowledge: raise HTTPException( @@ -914,13 +915,13 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: A # Clean up vector DB try: - VECTOR_DB_CLIENT.delete_collection(collection_name=id) + await ASYNC_VECTOR_DB_CLIENT.delete_collection(collection_name=id) except Exception as e: log.debug(e) pass # Remove knowledge base embedding - remove_knowledge_base_metadata_embedding(id) + await remove_knowledge_base_metadata_embedding(id) result = await Knowledges.delete_knowledge_by_id(id=id, db=db) return result @@ -932,7 +933,9 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: A @router.post('/{id}/reset', response_model=Optional[KnowledgeResponse]) -async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def reset_knowledge_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db) if not knowledge: raise HTTPException( @@ -957,12 +960,12 @@ async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user), db: As ) try: - VECTOR_DB_CLIENT.delete_collection(collection_name=id) + await ASYNC_VECTOR_DB_CLIENT.delete_collection(collection_name=id) except Exception as e: log.debug(e) pass - knowledge = Knowledges.reset_knowledge_by_id(id=id, db=db) + knowledge = await Knowledges.reset_knowledge_by_id(id=id, db=db) return knowledge diff --git a/backend/open_webui/routers/memories.py b/backend/open_webui/routers/memories.py index 3f6c080ad6..3a42801d01 100644 --- a/backend/open_webui/routers/memories.py +++ b/backend/open_webui/routers/memories.py @@ -5,7 +5,7 @@ import asyncio from typing import Optional from open_webui.models.memories import Memories, MemoryModel -from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT +from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT from open_webui.utils.auth import get_verified_user from open_webui.internal.db import get_async_session from sqlalchemy.ext.asyncio import AsyncSession @@ -85,7 +85,7 @@ async def add_memory( vector = await request.app.state.EMBEDDING_FUNCTION(memory.content, user=user) - VECTOR_DB_CLIENT.upsert( + await ASYNC_VECTOR_DB_CLIENT.upsert( collection_name=f'user-memory-{user.id}', items=[ { @@ -138,7 +138,7 @@ async def query_memory( vector = await request.app.state.EMBEDDING_FUNCTION(form_data.content, user=user) - results = VECTOR_DB_CLIENT.search( + results = await ASYNC_VECTOR_DB_CLIENT.search( collection_name=f'user-memory-{user.id}', vectors=[vector], limit=form_data.k, @@ -175,7 +175,7 @@ async def reset_memory_from_vector_db( detail=ERROR_MESSAGES.ACCESS_PROHIBITED, ) - VECTOR_DB_CLIENT.delete_collection(f'user-memory-{user.id}') + await ASYNC_VECTOR_DB_CLIENT.delete_collection(f'user-memory-{user.id}') memories = await Memories.get_memories_by_user_id(user.id) @@ -184,7 +184,7 @@ async def reset_memory_from_vector_db( *[request.app.state.EMBEDDING_FUNCTION(memory.content, user=user) for memory in memories] ) - VECTOR_DB_CLIENT.upsert( + await ASYNC_VECTOR_DB_CLIENT.upsert( collection_name=f'user-memory-{user.id}', items=[ { @@ -230,7 +230,7 @@ async def delete_memory_by_user_id( if result: try: - VECTOR_DB_CLIENT.delete_collection(f'user-memory-{user.id}') + await ASYNC_VECTOR_DB_CLIENT.delete_collection(f'user-memory-{user.id}') except Exception as e: log.error(e) return True @@ -268,12 +268,12 @@ async def update_memory_by_id( memory = await Memories.update_memory_by_id_and_user_id(memory_id, user.id, form_data.content) if memory is None: - raise HTTPException(status_code=404, detail='Memory not found') + raise HTTPException(status_code=404, detail=ERROR_MESSAGES.NOT_FOUND) if form_data.content is not None: vector = await request.app.state.EMBEDDING_FUNCTION(memory.content, user=user) - VECTOR_DB_CLIENT.upsert( + await ASYNC_VECTOR_DB_CLIENT.upsert( collection_name=f'user-memory-{user.id}', items=[ { @@ -318,7 +318,7 @@ async def delete_memory_by_id( result = await Memories.delete_memory_by_id_and_user_id(memory_id, user.id, db=db) if result: - VECTOR_DB_CLIENT.delete(collection_name=f'user-memory-{user.id}', ids=[memory_id]) + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=f'user-memory-{user.id}', ids=[memory_id]) return True return False diff --git a/backend/open_webui/routers/notes.py b/backend/open_webui/routers/notes.py index 61c9fb7d95..4fbdd09993 100644 --- a/backend/open_webui/routers/notes.py +++ b/backend/open_webui/routers/notes.py @@ -58,6 +58,7 @@ class NoteItemResponse(BaseModel): id: str title: str data: Optional[dict] + is_pinned: Optional[bool] = False updated_at: int created_at: int user: Optional[UserResponse] = None @@ -104,6 +105,45 @@ async def get_notes( ] +############################ +# GetPinnedNotes +############################ + + +@router.get('/pinned', response_model=list[NoteItemResponse]) +async def get_pinned_notes( + request: Request, + user=Depends(get_verified_user), + db: AsyncSession = Depends(get_async_session), +): + if user.role != 'admin' and not await has_permission( + user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db + ): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.UNAUTHORIZED, + ) + + notes = await Notes.get_pinned_notes_by_user_id(user.id, 'read', db=db) + if not notes: + return [] + + user_ids = list(set(note.user_id for note in notes)) + users = {user.id: user for user in await Users.get_users_by_user_ids(user_ids, db=db)} + + return [ + NoteUserResponse( + **{ + **note.model_dump(), + 'data': _truncate_note_data(note.data), + 'user': UserResponse(**users[note.user_id].model_dump()), + } + ) + for note in notes + if note.user_id in users + ] + + @router.get('/search', response_model=NoteListResponse) async def search_notes( request: Request, @@ -364,6 +404,46 @@ async def update_note_access_by_id( return await Notes.get_note_by_id(id, db=db) +############################ +# PinNoteById +############################ + + +@router.post('/{id}/pin', response_model=Optional[NoteModel]) +async def pin_note_by_id( + request: Request, + id: str, + user=Depends(get_verified_user), + db: AsyncSession = Depends(get_async_session), +): + if user.role != 'admin' and not await has_permission( + user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db + ): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail=ERROR_MESSAGES.UNAUTHORIZED, + ) + + note = await Notes.get_note_by_id(id, db=db) + if not note: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND) + + if user.role != 'admin' and ( + user.id != note.user_id + and not await AccessGrants.has_access( + user_id=user.id, + resource_type='note', + resource_id=note.id, + permission='read', + db=db, + ) + ): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT()) + + note = await Notes.toggle_note_pinned_by_id(id, db=db) + return note + + ############################ # DeleteNoteById ############################ diff --git a/backend/open_webui/routers/ollama.py b/backend/open_webui/routers/ollama.py index 11c916846a..9272db1e6a 100644 --- a/backend/open_webui/routers/ollama.py +++ b/backend/open_webui/routers/ollama.py @@ -50,7 +50,10 @@ from open_webui.models.groups import Groups from open_webui.utils.access_control import check_model_access from open_webui.utils.misc import ( calculate_sha256, +) +from open_webui.utils.session_pool import ( cleanup_response, + get_session, stream_wrapper, ) from open_webui.utils.payload import ( @@ -122,10 +125,7 @@ async def send_request( r = None streaming = False try: - session = aiohttp.ClientSession( - trust_env=True, - timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), - ) + session = await get_session() headers = { 'Content-Type': 'application/json', @@ -138,8 +138,12 @@ async def send_request( headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = metadata.get('chat_id') r = await session.request( - method, url, data=payload, headers=headers, + method, + url, + data=payload, + headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL, + timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), ) if not r.ok: @@ -153,7 +157,7 @@ async def send_request( log.error(f'Failed to parse error response: {e}') raise HTTPException( status_code=r.status, - detail='Open WebUI: Server Connection Error', + detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR, ) r.raise_for_status() @@ -165,7 +169,7 @@ async def send_request( streaming = True return StreamingResponse( - stream_wrapper(r, session), + stream_wrapper(r), status_code=r.status, headers=response_headers, ) @@ -180,11 +184,11 @@ async def send_request( except Exception as e: raise HTTPException( status_code=r.status if r else 500, - detail=f'Ollama: {e}' if str(e) else 'Open WebUI: Server Connection Error', + detail=f'Ollama: {e}' if str(e) else ERROR_MESSAGES.SERVER_CONNECTION_ERROR, ) finally: if not streaming: - await cleanup_response(r, session) + await cleanup_response(r) def get_api_key(idx, url, configs): @@ -247,7 +251,7 @@ async def verify_connection(form_data: ConnectionVerificationForm, user=Depends( return data except aiohttp.ClientError as e: log.exception(f'Client error: {str(e)}') - raise HTTPException(status_code=500, detail='Open WebUI: Server Connection Error') + raise HTTPException(status_code=500, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR) except Exception as e: log.exception(f'Unexpected error: {e}') error_detail = f'Unexpected error: {str(e)}' @@ -424,7 +428,7 @@ async def get_filtered_models(models, user, db=None): @router.get('/api/tags/{url_idx}') async def get_ollama_tags(request: Request, url_idx: Optional[int] = None, user=Depends(get_verified_user)): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) models = [] @@ -617,7 +621,7 @@ async def pull_model( user=Depends(get_admin_user), ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) form_data = form_data.model_dump(exclude_none=True) form_data['model'] = form_data.get('model', form_data.get('name')) @@ -652,7 +656,7 @@ async def push_model( user=Depends(get_admin_user), ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) if url_idx is None: await get_all_models(request, user=user) @@ -695,7 +699,7 @@ async def create_model( user=Depends(get_admin_user), ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) log.debug(f'form_data: {form_data}') url = request.app.state.config.OLLAMA_BASE_URLS[url_idx] @@ -723,7 +727,7 @@ async def copy_model( user=Depends(get_admin_user), ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) if url_idx is None: await get_all_models(request, user=user) @@ -758,7 +762,7 @@ async def delete_model( user=Depends(get_admin_user), ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) form_data = form_data.model_dump(exclude_none=True) form_data['model'] = form_data.get('model', form_data.get('name')) @@ -781,7 +785,8 @@ async def delete_model( key = get_api_key(url_idx, url, request.app.state.config.OLLAMA_API_CONFIGS) await send_request( - f'{url}/api/delete', 'DELETE', + f'{url}/api/delete', + 'DELETE', payload=json.dumps(form_data), key=key, user=user, @@ -792,7 +797,7 @@ async def delete_model( @router.post('/api/show') async def show_model_info(request: Request, form_data: ModelNameForm, user=Depends(get_verified_user)): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) form_data = form_data.model_dump(exclude_none=True) form_data['model'] = form_data.get('model', form_data.get('name')) @@ -845,7 +850,7 @@ async def embed( user=Depends(get_verified_user), ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) log.info(f'generate_ollama_batch_embeddings {form_data}') @@ -904,7 +909,7 @@ async def embeddings( user=Depends(get_verified_user), ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) log.info(f'generate_ollama_embeddings {form_data}') @@ -971,7 +976,7 @@ async def generate_completion( user=Depends(get_verified_user), ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) # Enforce per-model access control await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL) @@ -1015,6 +1020,8 @@ class ChatMessage(BaseModel): tool_calls: Optional[list[dict]] = None images: Optional[list[str]] = None + model_config = ConfigDict(extra='allow') + @validator('content', pre=True) @classmethod def check_at_least_one_field(cls, field_value, values, **kwargs): @@ -1062,7 +1069,7 @@ async def generate_chat_completion( bypass_system_prompt: bool = False, ): if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) # NOTE: We intentionally do NOT use Depends(get_async_session) here. # Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions. @@ -1308,7 +1315,7 @@ async def generate_anthropic_messages( See https://docs.ollama.com/api/anthropic-compatibility """ if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) payload = {**form_data} model_id = payload.get('model', '') @@ -1366,7 +1373,7 @@ async def generate_responses( See https://ollama.com/blog/responses-api """ if not request.app.state.config.ENABLE_OLLAMA_API: - raise HTTPException(status_code=503, detail='Ollama API is disabled') + raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED) payload = form_data.model_dump() model_id = form_data.model @@ -1391,13 +1398,13 @@ async def generate_responses( ): raise HTTPException( status_code=403, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) else: if user.role != 'admin': raise HTTPException( status_code=403, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) url, url_idx = await get_ollama_url(request, payload['model'], url_idx) @@ -1649,9 +1656,7 @@ async def upload_model( url = f'{ollama_url}/api/blobs/sha256:{file_hash}' upload_timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT) async with aiohttp.ClientSession(timeout=upload_timeout, trust_env=True) as upload_session: - async with upload_session.post( - url, data=blob_data, ssl=AIOHTTP_CLIENT_SESSION_SSL - ) as response: + async with upload_session.post(url, data=blob_data, ssl=AIOHTTP_CLIENT_SESSION_SSL) as response: if not response.ok: raise Exception('Ollama: Could not create blob, Please try again.') diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py index 51d4267c38..82c844afba 100644 --- a/backend/open_webui/routers/openai.py +++ b/backend/open_webui/routers/openai.py @@ -8,7 +8,7 @@ from urllib.parse import quote, urlparse import aiohttp from aiocache import cached -import requests + from azure.identity import DefaultAzureCredential, get_bearer_token_provider @@ -52,9 +52,12 @@ from open_webui.utils.payload import ( apply_system_prompt_to_body, ) from open_webui.utils.misc import ( - cleanup_response, convert_logit_bias_input_to_json, stream_chunks_handler, +) +from open_webui.utils.session_pool import ( + cleanup_response, + get_session, stream_wrapper, ) @@ -309,19 +312,20 @@ async def speech(request: Request, user=Depends(get_verified_user)): r = None try: - r = requests.post( + session = await get_session() + r = await session.post( url=f'{url}/audio/speech', data=body, headers=headers, cookies=cookies, - stream=True, + ssl=AIOHTTP_CLIENT_SESSION_SSL, ) r.raise_for_status() # Save the streaming content to a file with open(file_path, 'wb') as f: - for chunk in r.iter_content(chunk_size=8192): + async for chunk in r.content.iter_chunked(8192): f.write(chunk) with open(file_body_path, 'w') as f: @@ -336,14 +340,14 @@ async def speech(request: Request, user=Depends(get_verified_user)): detail = None if r is not None: try: - res = r.json() + res = await r.json() if 'error' in res: detail = f'External: {res["error"]}' except Exception: detail = f'External: {e}' raise HTTPException( - status_code=r.status_code if r else 500, + status_code=r.status if r else 500, detail=detail if detail else 'Open WebUI: Server Connection Error', ) @@ -688,7 +692,7 @@ async def verify_connection( elif is_anthropic_url(url): result = await get_anthropic_models(url, key) if result is None: - raise HTTPException(status_code=500, detail='Failed to connect to Anthropic API') + raise HTTPException(status_code=500, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR) if 'error' in result: raise HTTPException(status_code=500, detail=result['error']) return result @@ -715,10 +719,10 @@ async def verify_connection( except aiohttp.ClientError as e: # ClientError covers all aiohttp requests issues log.exception(f'Client error: {str(e)}') - raise HTTPException(status_code=500, detail='Open WebUI: Server Connection Error') + raise HTTPException(status_code=500, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR) except Exception as e: log.exception(f'Unexpected error: {e}') - raise HTTPException(status_code=500, detail='Open WebUI: Server Connection Error') + raise HTTPException(status_code=500, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR) def get_azure_allowed_params(api_version: str) -> set[str]: @@ -1080,7 +1084,7 @@ async def generate_chat_completion( else: raise HTTPException( status_code=404, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) # Get the API config for the model @@ -1174,12 +1178,11 @@ async def generate_chat_completion( payload = json.dumps(payload) r = None - session = None streaming = False response = None try: - session = aiohttp.ClientSession(trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)) + session = await get_session() r = await session.request( method='POST', @@ -1188,13 +1191,33 @@ async def generate_chat_completion( headers=headers, cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, + timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), ) # Check if response is SSE if 'text/event-stream' in r.headers.get('Content-Type', ''): + # If the provider returned an error status with SSE content-type, + # read the body and return a proper error response instead of + # streaming the error back (which hides the error from logs). + if r.status >= 400: + error_body = await r.text() + log.error( + 'Provider returned HTTP %d with SSE content-type: %s', + r.status, + error_body[:1000], + ) + try: + error_json = json.loads(error_body) + return JSONResponse(status_code=r.status, content=error_json) + except json.JSONDecodeError: + return JSONResponse( + status_code=r.status, + content={'error': {'message': error_body, 'code': r.status}}, + ) + streaming = True return StreamingResponse( - stream_wrapper(r, session, stream_chunks_handler), + stream_wrapper(r, content_handler=stream_chunks_handler), status_code=r.status, headers=dict(r.headers), ) @@ -1221,11 +1244,11 @@ async def generate_chat_completion( raise HTTPException( status_code=r.status if r else 500, - detail='Open WebUI: Server Connection Error', + detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR, ) finally: if not streaming: - await cleanup_response(r, session) + await cleanup_response(r) async def embeddings(request: Request, form_data: dict, user): @@ -1261,27 +1284,24 @@ async def embeddings(request: Request, form_data: dict, user): ) r = None - session = None streaming = False headers, cookies = await get_headers_and_cookies(request, url, key, api_config, user=user) try: - session = aiohttp.ClientSession( - trust_env=True, - timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), - ) + session = await get_session() r = await session.request( method='POST', url=f'{url}/embeddings', data=body, headers=headers, cookies=cookies, + timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), ) if 'text/event-stream' in r.headers.get('Content-Type', ''): streaming = True return StreamingResponse( - stream_wrapper(r, session), + stream_wrapper(r), status_code=r.status, headers=dict(r.headers), ) @@ -1302,11 +1322,11 @@ async def embeddings(request: Request, form_data: dict, user): log.exception(e) raise HTTPException( status_code=r.status if r else 500, - detail='Open WebUI: Server Connection Error', + detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR, ) finally: if not streaming: - await cleanup_response(r, session) + await cleanup_response(r) class ResponsesForm(BaseModel): @@ -1365,7 +1385,6 @@ async def responses( ) r = None - session = None streaming = False try: @@ -1388,10 +1407,7 @@ async def responses( else: request_url = f'{url}/responses' - session = aiohttp.ClientSession( - trust_env=True, - timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), - ) + session = await get_session() r = await session.request( method='POST', url=request_url, @@ -1399,13 +1415,14 @@ async def responses( headers=headers, cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, + timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), ) # Check if response is SSE if 'text/event-stream' in r.headers.get('Content-Type', ''): streaming = True return StreamingResponse( - stream_wrapper(r, session), + stream_wrapper(r), status_code=r.status, headers=dict(r.headers), ) @@ -1429,11 +1446,11 @@ async def responses( log.exception(e) raise HTTPException( status_code=r.status if r else 500, - detail='Open WebUI: Server Connection Error', + detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR, ) finally: if not streaming: - await cleanup_response(r, session) + await cleanup_response(r) @router.api_route('/{path:path}', methods=['GET', 'POST', 'PUT', 'DELETE']) @@ -1479,7 +1496,6 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): ) r = None - session = None streaming = False try: @@ -1508,10 +1524,7 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): else: request_url = f'{url}/{path}' - session = aiohttp.ClientSession( - trust_env=True, - timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), - ) + session = await get_session() r = await session.request( method=request.method, url=request_url, @@ -1519,13 +1532,14 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): headers=headers, cookies=cookies, ssl=AIOHTTP_CLIENT_SESSION_SSL, + timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT), ) # Check if response is SSE if 'text/event-stream' in r.headers.get('Content-Type', ''): streaming = True return StreamingResponse( - stream_wrapper(r, session), + stream_wrapper(r), status_code=r.status, headers=dict(r.headers), ) @@ -1553,4 +1567,4 @@ async def proxy(path: str, request: Request, user=Depends(get_verified_user)): ) finally: if not streaming: - await cleanup_response(r, session) + await cleanup_response(r) diff --git a/backend/open_webui/routers/prompts.py b/backend/open_webui/routers/prompts.py index ed9b69af06..11901fc5a7 100644 --- a/backend/open_webui/routers/prompts.py +++ b/backend/open_webui/routers/prompts.py @@ -198,7 +198,9 @@ async def create_new_prompt( @router.get('/command/{command}', response_model=Optional[PromptAccessResponse]) -async def get_prompt_by_command(command: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_prompt_by_command( + command: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): prompt = await Prompts.get_prompt_by_command(command, db=db) if prompt: @@ -240,7 +242,9 @@ async def get_prompt_by_command(command: str, user=Depends(get_verified_user), d @router.get('/id/{prompt_id}', response_model=Optional[PromptAccessResponse]) -async def get_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_prompt_by_id( + prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): prompt = await Prompts.get_prompt_by_id(prompt_id, db=db) if prompt: @@ -320,7 +324,7 @@ async def update_prompt_by_id( if existing_prompt and existing_prompt.id != prompt.id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Command '/{form_data.command}' is already in use by another prompt", + detail=ERROR_MESSAGES.COMMAND_TAKEN, ) form_data.access_grants = await filter_allowed_access_grants( @@ -385,10 +389,12 @@ async def update_prompt_metadata( if existing_prompt and existing_prompt.id != prompt.id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Command '/{form_data.command}' is already in use", + detail=ERROR_MESSAGES.COMMAND_TAKEN, ) - updated_prompt = await Prompts.update_prompt_metadata(prompt.id, form_data.name, form_data.command, form_data.tags, db=db) + updated_prompt = await Prompts.update_prompt_metadata( + prompt.id, form_data.name, form_data.command, form_data.tags, db=db + ) if updated_prompt: return updated_prompt else: @@ -497,7 +503,9 @@ async def update_prompt_access_by_id( @router.post('/id/{prompt_id}/toggle', response_model=Optional[PromptModel]) -async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def toggle_prompt_active( + prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): prompt = await Prompts.get_prompt_by_id(prompt_id, db=db) if not prompt: @@ -537,7 +545,9 @@ async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user), @router.delete('/id/{prompt_id}/delete', response_model=bool) -async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def delete_prompt_by_id( + prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): prompt = await Prompts.get_prompt_by_id(prompt_id, db=db) if not prompt: @@ -741,7 +751,7 @@ async def get_prompt_diff( if not diff: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='One or both history entries not found', + detail=ERROR_MESSAGES.NOT_FOUND, ) return diff diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index 77261a6f2b..0c30122022 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -40,11 +40,12 @@ from open_webui.models.files import FileModel, FileUpdateForm, Files from open_webui.utils.access_control.files import has_access_to_file from open_webui.models.knowledge import Knowledges from open_webui.storage.provider import Storage -from open_webui.internal.db import get_async_session, get_db +from open_webui.internal.db import get_async_db, get_async_session from sqlalchemy.ext.asyncio import AsyncSession from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT +from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT # Document loaders from open_webui.retrieval.loaders.main import Loader @@ -151,7 +152,7 @@ def get_ef( model_kwargs=SENTENCE_TRANSFORMERS_MODEL_KWARGS, ) except Exception as e: - log.debug(f'Error loading SentenceTransformer: {e}') + log.error(f'Error loading SentenceTransformer: {e}') return ef @@ -1556,7 +1557,7 @@ async def process_file( try: # /files/{file_id}/data/content/update - VECTOR_DB_CLIENT.delete_collection(collection_name=f'file-{file.id}') + await ASYNC_VECTOR_DB_CLIENT.delete_collection(collection_name=f'file-{file.id}') except Exception: # Audio file upload pipeline pass @@ -1579,7 +1580,9 @@ async def process_file( # Check if the file has already been processed and save the content # Usage: /knowledge/{id}/file/add, /knowledge/{id}/file/update - result = VECTOR_DB_CLIENT.query(collection_name=f'file-{file.id}', filter={'file_id': file.id}) + result = await ASYNC_VECTOR_DB_CLIENT.query( + collection_name=f'file-{file.id}', filter={'file_id': file.id} + ) if result is not None and len(result.ids[0]) > 0: docs = [ @@ -1609,7 +1612,7 @@ async def process_file( # Usage: /files/ file_path = file.path if file_path: - file_path = Storage.get_file(file_path) + file_path = await asyncio.to_thread(Storage.get_file, file_path) loader = Loader( engine=request.app.state.config.CONTENT_EXTRACTION_ENGINE, user=user, @@ -1643,7 +1646,7 @@ async def process_file( MINERU_API_TIMEOUT=request.app.state.config.MINERU_API_TIMEOUT, MINERU_PARAMS=request.app.state.config.MINERU_PARAMS, ) - docs = loader.load(file.filename, file.meta.get('content_type'), file_path) + docs = await loader.aload(file.filename, file.meta.get('content_type'), file_path) docs = [ Document( @@ -1694,11 +1697,16 @@ async def process_file( try: # Commit any pending changes before the slow embedding step. # Note: file is already a Pydantic model (not ORM), so no expunge needed. - db.commit() + await db.commit() # External embedding API takes time (5-60s+). - # Subsequent updates use fresh sessions via get_db(). - result = save_docs_to_vector_db( + # Subsequent updates use fresh async sessions. + # NOTE: save_docs_to_vector_db is a sync function that + # calls asyncio.run_coroutine_threadsafe(..., main_loop).result() + # which blocks the calling thread. We MUST run it in a + # worker thread to avoid deadlocking the event loop. + result = await run_in_threadpool( + save_docs_to_vector_db, request, docs=docs, collection_name=collection_name, @@ -1714,7 +1722,7 @@ async def process_file( if result: # Fresh session for the final update. - with get_db() as session: + async with get_async_db() as session: await Files.update_file_metadata_by_id( file.id, { @@ -1744,7 +1752,7 @@ async def process_file( except Exception as e: log.exception(e) # Fresh session for error status update. - with get_db() as session: + async with get_async_db() as session: await Files.update_file_data_by_id( file.id, {'status': 'failed'}, @@ -2375,7 +2383,7 @@ async def query_doc_handler( try: if request.app.state.config.ENABLE_RAG_HYBRID_SEARCH and (form_data.hybrid is None or form_data.hybrid): collection_results = {} - collection_results[form_data.collection_name] = VECTOR_DB_CLIENT.get( + collection_results[form_data.collection_name] = await ASYNC_VECTOR_DB_CLIENT.get( collection_name=form_data.collection_name ) return await query_doc_with_hybrid_search( @@ -2404,7 +2412,10 @@ async def query_doc_handler( query_embedding = await request.app.state.EMBEDDING_FUNCTION( form_data.query, prefix=RAG_EMBEDDING_QUERY_PREFIX, user=user ) - return query_doc( + # query_doc wraps a blocking VECTOR_DB_CLIENT.search call; + # offload so the request's event loop stays responsive. + return await asyncio.to_thread( + query_doc, collection_name=form_data.collection_name, query_embedding=query_embedding, k=form_data.k if form_data.k else request.app.state.config.TOP_K, @@ -2502,7 +2513,7 @@ async def delete_entries_from_collection( db: AsyncSession = Depends(get_async_session), ): try: - if VECTOR_DB_CLIENT.has_collection(collection_name=form_data.collection_name): + if await ASYNC_VECTOR_DB_CLIENT.has_collection(collection_name=form_data.collection_name): file = await Files.get_file_by_id(form_data.file_id, db=db) if not file: raise HTTPException( @@ -2511,13 +2522,37 @@ async def delete_entries_from_collection( ) hash = file.hash - VECTOR_DB_CLIENT.delete( + # Refuse to issue a `filter={'hash': None}` query — the + # match semantics of a null filter value are + # backend-dependent (some backends ignore the key, some + # match every row whose metadata lacks `hash`) and risk + # deleting unrelated entries. Files without a hash are + # typically unprocessed / failed / legacy records that + # can't be targeted by hash anyway. + if hash is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=ERROR_MESSAGES.DEFAULT('File has no hash; cannot delete vector entries by hash.'), + ) + + # Pre-existing bug: this used `metadata=` which is not a + # parameter on `VectorDBBase.delete` nor on any backend + # implementation, so the call always raised TypeError that + # was silently swallowed by the surrounding `except + # Exception` and the endpoint reported `{'status': False}` + # for every request. Use `filter` to actually do what the + # endpoint name promises. + await ASYNC_VECTOR_DB_CLIENT.delete( collection_name=form_data.collection_name, - metadata={'hash': hash}, + filter={'hash': hash}, ) return {'status': True} else: return {'status': False} + except HTTPException: + # Caller-meaningful errors (404/400 above) must not be + # swallowed and re-shaped as `{'status': False}`. + raise except Exception as e: log.exception(e) return {'status': False} @@ -2525,7 +2560,7 @@ async def delete_entries_from_collection( @router.post('/reset/db') async def reset_vector_db(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)): - VECTOR_DB_CLIENT.reset() + await ASYNC_VECTOR_DB_CLIENT.reset() await Knowledges.delete_all_knowledge(db=db) diff --git a/backend/open_webui/routers/tasks.py b/backend/open_webui/routers/tasks.py index 0bb5813e6f..b921f7b3e6 100644 --- a/backend/open_webui/routers/tasks.py +++ b/backend/open_webui/routers/tasks.py @@ -18,7 +18,7 @@ from open_webui.utils.task import ( moa_response_generation_template, ) from open_webui.utils.auth import get_admin_user, get_verified_user -from open_webui.constants import TASKS +from open_webui.constants import ERROR_MESSAGES, TASKS from open_webui.routers.pipelines import process_pipeline_inlet_filter @@ -168,7 +168,7 @@ async def generate_title(request: Request, form_data: dict, user=Depends(get_ver if model_id not in models: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) # Check if the user has a custom task model @@ -245,7 +245,7 @@ async def generate_follow_ups(request: Request, form_data: dict, user=Depends(ge if model_id not in models: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) # Check if the user has a custom task model @@ -313,7 +313,7 @@ async def generate_chat_tags(request: Request, form_data: dict, user=Depends(get if model_id not in models: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) # Check if the user has a custom task model @@ -375,7 +375,7 @@ async def generate_image_prompt(request: Request, form_data: dict, user=Depends( if model_id not in models: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) # Check if the user has a custom task model @@ -431,13 +431,13 @@ async def generate_queries(request: Request, form_data: dict, user=Depends(get_v if not request.app.state.config.ENABLE_SEARCH_QUERY_GENERATION: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f'Search query generation is disabled', + detail=ERROR_MESSAGES.FEATURE_DISABLED('Search query generation'), ) elif type == 'retrieval': if not request.app.state.config.ENABLE_RETRIEVAL_QUERY_GENERATION: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f'Query generation is disabled', + detail=ERROR_MESSAGES.FEATURE_DISABLED('Query generation'), ) if getattr(request.state, 'cached_queries', None): @@ -455,7 +455,7 @@ async def generate_queries(request: Request, form_data: dict, user=Depends(get_v if model_id not in models: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) # Check if the user has a custom task model @@ -508,7 +508,7 @@ async def generate_autocompletion(request: Request, form_data: dict, user=Depend if not request.app.state.config.ENABLE_AUTOCOMPLETE_GENERATION: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f'Autocompletion generation is disabled', + detail=ERROR_MESSAGES.FEATURE_DISABLED('Autocompletion generation'), ) type = form_data.get('type') @@ -519,7 +519,7 @@ async def generate_autocompletion(request: Request, form_data: dict, user=Depend if len(prompt) > request.app.state.config.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, - detail=f'Input prompt exceeds maximum length of {request.app.state.config.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH}', + detail=ERROR_MESSAGES.INPUT_TOO_LONG(request.app.state.config.AUTOCOMPLETE_GENERATION_INPUT_MAX_LENGTH), ) if getattr(request.state, 'direct', False) and hasattr(request.state, 'model'): @@ -533,7 +533,7 @@ async def generate_autocompletion(request: Request, form_data: dict, user=Depend if model_id not in models: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) # Check if the user has a custom task model @@ -595,7 +595,7 @@ async def generate_emoji(request: Request, form_data: dict, user=Depends(get_ver if model_id not in models: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) # Check if the user has a custom task model @@ -661,7 +661,7 @@ async def generate_moa_response(request: Request, form_data: dict, user=Depends( if model_id not in models: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, - detail='Model not found', + detail=ERROR_MESSAGES.MODEL_NOT_FOUND(), ) template = DEFAULT_MOA_GENERATION_PROMPT_TEMPLATE diff --git a/backend/open_webui/routers/tools.py b/backend/open_webui/routers/tools.py index edf9c8b5ef..d70b4038fe 100644 --- a/backend/open_webui/routers/tools.py +++ b/backend/open_webui/routers/tools.py @@ -285,7 +285,7 @@ async def load_tool_from_url(request: Request, form_data: LoadUrlForm, user=Depe 'content': data, } except Exception as e: - raise HTTPException(status_code=500, detail=f'Error importing tool: {e}') + raise HTTPException(status_code=500, detail=ERROR_MESSAGES.DEFAULT(e)) ############################ @@ -622,7 +622,9 @@ async def delete_tools_by_id( @router.get('/id/{id}/valves', response_model=Optional[dict]) -async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_tools_valves_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): tools = await Tools.get_tool_by_id(id, db=db) if not tools: raise HTTPException( @@ -775,7 +777,9 @@ async def update_tools_valves_by_id( @router.get('/id/{id}/valves/user', response_model=Optional[dict]) -async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_tools_user_valves_by_id( + id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): tools = await Tools.get_tool_by_id(id, db=db) if not tools: raise HTTPException( diff --git a/backend/open_webui/routers/users.py b/backend/open_webui/routers/users.py index 091143a5d5..9fd2479ada 100644 --- a/backend/open_webui/routers/users.py +++ b/backend/open_webui/routers/users.py @@ -40,6 +40,7 @@ from open_webui.utils.auth import ( validate_password, ) from open_webui.utils.access_control import get_permissions, has_permission +from open_webui.socket.main import disconnect_user_sessions log = logging.getLogger(__name__) @@ -272,7 +273,9 @@ async def update_default_user_permissions(request: Request, form_data: UserPermi @router.get('/user/settings', response_model=Optional[UserSettings]) -async def get_user_settings_by_session_user(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_user_settings_by_session_user( + user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): user = await Users.get_user_by_id(user.id, db=db) if user: return user.settings @@ -467,7 +470,9 @@ async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSe @router.get('/{user_id}/info', response_model=UserInfoResponse) -async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)): +async def get_user_info_by_id( + user_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session) +): user = await Users.get_user_by_id(user_id, db=db) if user: groups = await Groups.get_groups_by_member_id(user_id, db=db) @@ -486,7 +491,9 @@ async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db: @router.get('/{user_id}/oauth/sessions') -async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)): +async def get_user_oauth_sessions_by_id( + user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session) +): sessions = await OAuthSessions.get_sessions_by_user_id(user_id, db=db) if sessions and len(sessions) > 0: return sessions @@ -573,7 +580,7 @@ async def update_user_by_id( detail=ERROR_MESSAGES.ACTION_PROHIBITED, ) - if form_data.role != 'admin': + if form_data.role is not None and form_data.role != 'admin': # If the primary admin is trying to change their own role, prevent it raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -590,7 +597,7 @@ async def update_user_by_id( user = await Users.get_user_by_id(user_id, db=db) if user: - if form_data.email.lower() != user.email: + if form_data.email is not None and form_data.email.lower() != user.email: email_user = await Users.get_user_by_email(form_data.email.lower(), db=db) if email_user: raise HTTPException( @@ -607,19 +614,32 @@ async def update_user_by_id( hashed = get_password_hash(form_data.password) await Auths.update_user_password_by_id(user_id, hashed, db=db) - await Auths.update_email_by_id(user_id, form_data.email.lower(), db=db) - updated_user = await Users.update_user_by_id( - user_id, - { - 'role': form_data.role, - 'name': form_data.name, - 'email': form_data.email.lower(), - 'profile_image_url': form_data.profile_image_url, - }, - db=db, - ) + # Build update dict from only the provided fields + update_data = {} + if form_data.role is not None: + update_data['role'] = form_data.role + if form_data.name is not None: + update_data['name'] = form_data.name + if form_data.email is not None: + update_data['email'] = form_data.email.lower() + await Auths.update_email_by_id(user_id, form_data.email.lower(), db=db) + if form_data.profile_image_url is not None: + update_data['profile_image_url'] = form_data.profile_image_url + + if update_data: + updated_user = await Users.update_user_by_id( + user_id, + update_data, + db=db, + ) + else: + updated_user = user if updated_user: + # If the role changed, disconnect all socket sessions so stale + # privileges cached in SESSION_POOL are invalidated. + if updated_user.role != user.role: + await disconnect_user_sessions(user_id) return updated_user raise HTTPException( @@ -659,6 +679,7 @@ async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Asyn result = await Auths.delete_auth_by_id(user_id, db=db) if result: + await disconnect_user_sessions(user_id) return True raise HTTPException( @@ -679,5 +700,7 @@ async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Asyn @router.get('/{user_id}/groups') -async def get_user_groups_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)): +async def get_user_groups_by_id( + user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session) +): return await Groups.get_groups_by_member_id(user_id, db=db) diff --git a/backend/open_webui/routers/utils.py b/backend/open_webui/routers/utils.py index c79d8fe5d8..20705c2c44 100644 --- a/backend/open_webui/routers/utils.py +++ b/backend/open_webui/routers/utils.py @@ -45,7 +45,7 @@ async def execute_code(request: Request, form_data: CodeForm, user=Depends(get_v if not request.app.state.config.ENABLE_CODE_EXECUTION: raise HTTPException( status_code=403, - detail='Code execution is disabled', + detail=ERROR_MESSAGES.FEATURE_DISABLED('Code execution'), ) if request.app.state.config.CODE_EXECUTION_ENGINE == 'jupyter': @@ -69,7 +69,7 @@ async def execute_code(request: Request, form_data: CodeForm, user=Depends(get_v else: raise HTTPException( status_code=400, - detail='Code execution engine not supported', + detail=ERROR_MESSAGES.DEFAULT('Code execution engine not supported'), ) diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index d2ddd90b19..2c44eb25c5 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -312,6 +312,24 @@ async def enter_room_for_users(room: str, user_ids: list[str]): log.debug(f'Failed to make users {user_ids} join room {room}: {e}') +async def disconnect_user_sessions(user_id: str): + """Disconnect all Socket.IO sessions belonging to a user. + + Call this when a user's role is changed or the user is deleted so that + stale role/permission data cached in SESSION_POOL is invalidated. + The client will automatically reconnect and re-authenticate with + fresh data from the database. + """ + try: + session_ids = get_session_ids_from_room(f'user:{user_id}') + for sid in session_ids: + await sio.disconnect(sid) + if session_ids: + log.info(f'Disconnected {len(session_ids)} session(s) for user {user_id}') + except Exception as e: + log.warning(f'Failed to disconnect sessions for user {user_id}: {e}') + + @sio.on('usage') async def usage(sid, data): if sid in SESSION_POOL: diff --git a/backend/open_webui/storage/provider.py b/backend/open_webui/storage/provider.py index 0886c851f0..f70f3e862b 100644 --- a/backend/open_webui/storage/provider.py +++ b/backend/open_webui/storage/provider.py @@ -140,7 +140,7 @@ class S3StorageProvider(StorageProvider): def upload_file(self, file: BinaryIO, filename: str, tags: Dict[str, str]) -> Tuple[bytes, str]: """Handles uploading of the file to S3 storage.""" - _, file_path = LocalStorageProvider.upload_file(file, filename, tags) + contents, file_path = LocalStorageProvider.upload_file(file, filename, tags) s3_key = os.path.join(self.key_prefix, filename) try: self.s3_client.upload_file(file_path, self.bucket_name, s3_key) @@ -153,7 +153,7 @@ class S3StorageProvider(StorageProvider): Tagging=tagging, ) return ( - open(file_path, 'rb').read(), + contents, f's3://{self.bucket_name}/{s3_key}', ) except ClientError as e: diff --git a/backend/open_webui/tools/builtin.py b/backend/open_webui/tools/builtin.py index 58af934372..8aa27520f3 100644 --- a/backend/open_webui/tools/builtin.py +++ b/backend/open_webui/tools/builtin.py @@ -37,7 +37,7 @@ from open_webui.models.channels import Channels, ChannelMember, Channel from open_webui.models.messages import Messages, Message from open_webui.models.groups import Groups from open_webui.models.memories import Memories -from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT +from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT from open_webui.utils.sanitize import sanitize_code log = logging.getLogger(__name__) @@ -653,7 +653,7 @@ async def delete_memory( result = await Memories.delete_memory_by_id_and_user_id(memory_id, user.id) if result: - VECTOR_DB_CLIENT.delete(collection_name=f'user-memory-{user.id}', ids=[memory_id]) + await ASYNC_VECTOR_DB_CLIENT.delete(collection_name=f'user-memory-{user.id}', ids=[memory_id]) return json.dumps( {'status': 'success', 'message': f'Memory {memory_id} deleted'}, ensure_ascii=False, @@ -760,14 +760,26 @@ async def search_notes( content_snippet = '' if note.data and note.data.get('content', {}).get('md'): md_content = note.data['content']['md'] - lower_content = md_content.lower() - lower_query = query.lower() - idx = lower_content.find(lower_query) - if idx != -1: - start = max(0, idx - 50) - end = min(len(md_content), idx + len(query) + 100) + content_lower = md_content.lower() + + # Find the first matching word to center the snippet around. + search_words = query.lower().split() + match_pos = -1 + match_len = len(query) + for word in search_words: + found_pos = content_lower.find(word) + if found_pos != -1: + match_pos = found_pos + match_len = len(word) + break + + if match_pos != -1: + snippet_start = max(0, match_pos - 50) + snippet_end = min(len(md_content), match_pos + match_len + 100) content_snippet = ( - ('...' if start > 0 else '') + md_content[start:end] + ('...' if end < len(md_content) else '') + ('...' if snippet_start > 0 else '') + + md_content[snippet_start:snippet_end] + + ('...' if snippet_end < len(md_content) else '') ) else: content_snippet = md_content[:150] + ('...' if len(md_content) > 150 else '') @@ -1213,7 +1225,7 @@ async def search_channel_messages( end_ts = end_timestamp * 1_000_000_000 if end_timestamp else None # Search messages using the model method - matching_messages = Messages.search_messages_by_channel_ids( + matching_messages = await Messages.search_messages_by_channel_ids( channel_ids=channel_ids, query=query, start_timestamp=start_ts, @@ -1274,7 +1286,7 @@ async def view_channel_message( try: user_id = __user__.get('id') - message = Messages.get_message_by_id(message_id) + message = await Messages.get_message_by_id(message_id) if not message: return json.dumps({'error': 'Message not found'}) @@ -1336,7 +1348,7 @@ async def view_channel_thread( user_id = __user__.get('id') # Get the parent message - parent_message = Messages.get_message_by_id(parent_message_id) + parent_message = await Messages.get_message_by_id(parent_message_id) if not parent_message: return json.dumps({'error': 'Message not found'}) @@ -1353,7 +1365,7 @@ async def view_channel_thread( return json.dumps({'error': 'Access denied'}) # Get all thread replies - thread_replies = Messages.get_thread_replies_by_message_id(parent_message_id) + thread_replies = await Messages.get_thread_replies_by_message_id(parent_message_id) # Build the response messages = [] @@ -2190,7 +2202,7 @@ async def query_knowledge_bases( import heapq from open_webui.models.knowledge import Knowledges from open_webui.routers.knowledge import KNOWLEDGE_BASES_COLLECTION - from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT + from open_webui.retrieval.vector.async_client import ASYNC_VECTOR_DB_CLIENT user_id = __user__.get('id') user_group_ids = [group.id for group in await Groups.get_groups_by_member_id(user_id)] @@ -2215,7 +2227,7 @@ async def query_knowledge_bases( accessible_ids = [kb.id for kb in accessible_knowledge_bases.items] - search_results = VECTOR_DB_CLIENT.search( + search_results = await ASYNC_VECTOR_DB_CLIENT.search( collection_name=KNOWLEDGE_BASES_COLLECTION, vectors=[query_embedding], filter={'knowledge_base_id': {'$in': accessible_ids}}, @@ -2337,13 +2349,40 @@ VALID_TASK_STATUSES = {'pending', 'in_progress', 'completed', 'cancelled'} class TaskItem(BaseModel): id: Optional[str] = Field(None, description='Unique identifier for the task. Auto-generated if omitted.') - content: Optional[str] = Field(None, description='Task description. Aliases: title, name, description.') + content: str = Field(..., description='Task description.') status: Literal['pending', 'in_progress', 'completed', 'cancelled'] = Field('pending', description='Task status.') -async def tasks( - tasks: Optional[list[TaskItem]] = None, - overwrite: bool = True, +def _task_summary(all_tasks: list[dict]) -> dict: + """Build summary counts for a task list.""" + pending = sum(1 for t in all_tasks if t['status'] == 'pending') + in_progress = sum(1 for t in all_tasks if t['status'] == 'in_progress') + completed = sum(1 for t in all_tasks if t['status'] == 'completed') + cancelled = sum(1 for t in all_tasks if t['status'] == 'cancelled') + return { + 'total': len(all_tasks), + 'pending': pending, + 'in_progress': in_progress, + 'completed': completed, + 'cancelled': cancelled, + } + + +async def _emit_tasks(event_emitter, all_tasks: list[dict]): + """Persist task state to the UI.""" + if event_emitter: + await event_emitter( + { + 'type': 'chat:message:tasks', + 'data': { + 'tasks': all_tasks, + }, + } + ) + + +async def create_tasks( + tasks: list[TaskItem], __chat_id__: str = None, __message_id__: str = None, __event_emitter__: callable = None, @@ -2351,144 +2390,42 @@ async def tasks( __user__: dict = None, ) -> str: """ - Track progress on multi-step work by maintaining a task checklist. - Use this whenever a request involves multiple steps or could take - significant effort. Call to set the full list, then call again - with overwrite=false after completing each task to mark it - completed. Do not leave tasks in_progress when the work is done. - Each task has an id, content, and status (pending, in_progress, - completed, cancelled). + Create a task checklist to track progress on multi-step work. + Call this once at the start to define all steps, then use + update_task to mark each task as you complete it. - :param tasks: Optional list of task items. Each item: id (string), content (string, required for new tasks), status (pending|in_progress|completed|cancelled). Leave empty to fetch without modifying. - :param overwrite: If true (default), replaces the entire task list. If false, updates/adds tasks by id while keeping existing ones. + :param tasks: List of task items. Each item: content (string, required), status (pending|in_progress|completed|cancelled, default pending), id (optional, auto-generated). :return: JSON with the full task list and summary counts """ if __chat_id__ is None: return json.dumps({'error': 'Chat context not available'}) try: - - def _to_dict(task) -> dict: - """Convert TaskItem or dict to plain dict.""" + all_tasks = [] + for idx, task in enumerate(tasks): if hasattr(task, 'model_dump'): d = task.model_dump(exclude_none=True) - # Include any extra fields the model sent - if hasattr(task, 'model_extra') and task.model_extra: - d.update(task.model_extra) - return d - return dict(task) if not isinstance(task, dict) else task + elif isinstance(task, dict): + d = task + else: + d = dict(task) - def _resolve_content(d: dict) -> str: - """Accept content, title, name, or description as the task text.""" - for key in ('content', 'title', 'name', 'description'): - val = str(d.get(key, '')).strip() - if val: - return val - return '' + content = str(d.get('content', '')).strip() + if not content: + continue - def _resolve_id(d: dict, idx: int) -> str: - """Use provided id, or auto-generate from index.""" - item_id = str(d.get('id', '') or '').strip() - return item_id if item_id else str(idx + 1) + item_id = str(d.get('id', '') or '').strip() or str(idx + 1) + status = str(d.get('status', 'pending')).strip().lower() + if status not in VALID_TASK_STATUSES: + status = 'pending' - if tasks is None: - # Read-only - return current list - all_tasks = await Chats.get_chat_tasks_by_id(__chat_id__) - elif overwrite: - # Full replacement - validate and write - all_tasks = [] - for idx, task in enumerate(tasks): - d = _to_dict(task) - item_id = _resolve_id(d, idx) - content = _resolve_content(d) - if not content: - continue + all_tasks.append({'id': item_id, 'content': content, 'status': status}) - status = str(d.get('status', 'pending')).strip().lower() - if status not in VALID_TASK_STATUSES: - status = 'pending' - - all_tasks.append( - { - 'id': item_id, - 'content': content, - 'status': status, - } - ) - else: - # Partial update - merge by id - existing_tasks = await Chats.get_chat_tasks_by_id(__chat_id__) - existing_by_id = {t['id']: t for t in existing_tasks} - - seen_ids = set() - for idx, task in enumerate(tasks): - d = _to_dict(task) - item_id = _resolve_id(d, len(existing_tasks) + idx) - - seen_ids.add(item_id) - - if item_id in existing_by_id: - resolved = _resolve_content(d) - if resolved: - existing_by_id[item_id]['content'] = resolved - status = str(d.get('status', '')).strip().lower() - if status and status in VALID_TASK_STATUSES: - existing_by_id[item_id]['status'] = status - else: - content = _resolve_content(d) - if not content: - continue - - status = str(d.get('status', 'pending')).strip().lower() - if status not in VALID_TASK_STATUSES: - status = 'pending' - - existing_by_id[item_id] = { - 'id': item_id, - 'content': content, - 'status': status, - } - - # Preserve order of existing, append new - all_tasks = [] - for t in existing_tasks: - if t['id'] in existing_by_id: - all_tasks.append(existing_by_id[t['id']]) - for item_id in seen_ids: - if not any(t['id'] == item_id for t in existing_tasks): - all_tasks.append(existing_by_id[item_id]) - - # Persist to DB and emit (skip for read-only) - if tasks is not None: - await Chats.update_chat_tasks_by_id(__chat_id__, all_tasks) - - if __event_emitter__: - await __event_emitter__( - { - 'type': 'chat:message:tasks', - 'data': { - 'tasks': all_tasks, - }, - } - ) - - # Build summary counts - pending = sum(1 for t in all_tasks if t['status'] == 'pending') - in_progress = sum(1 for t in all_tasks if t['status'] == 'in_progress') - completed = sum(1 for t in all_tasks if t['status'] == 'completed') - cancelled = sum(1 for t in all_tasks if t['status'] == 'cancelled') + await Chats.update_chat_tasks_by_id(__chat_id__, all_tasks) + await _emit_tasks(__event_emitter__, all_tasks) return json.dumps( - { - 'tasks': all_tasks, - 'summary': { - 'total': len(all_tasks), - 'pending': pending, - 'in_progress': in_progress, - 'completed': completed, - 'cancelled': cancelled, - }, - }, + {'tasks': all_tasks, 'summary': _task_summary(all_tasks)}, ensure_ascii=False, ) except Exception as e: @@ -2496,6 +2433,58 @@ async def tasks( return json.dumps({'error': str(e)}) +async def update_task( + id: str, + status: str = 'completed', + __chat_id__: str = None, + __message_id__: str = None, + __event_emitter__: callable = None, + __request__: Request = None, + __user__: dict = None, +) -> str: + """ + Mark a single task as completed, in_progress, pending, or cancelled. + Call this after finishing each step. You MUST call this for every + task, including the very last one. + + :param id: The task ID to update + :param status: New status: completed, in_progress, pending, or cancelled (default: completed) + :return: JSON with the updated task list and summary counts + """ + if __chat_id__ is None: + return json.dumps({'error': 'Chat context not available'}) + + try: + status = status.strip().lower() + if status not in VALID_TASK_STATUSES: + return json.dumps( + {'error': f'Invalid status: {status}. Must be one of: {", ".join(sorted(VALID_TASK_STATUSES))}'} + ) + + all_tasks = await Chats.get_chat_tasks_by_id(__chat_id__) + + found = False + for task in all_tasks: + if task['id'] == id: + task['status'] = status + found = True + break + + if not found: + return json.dumps({'error': f'Task with id "{id}" not found'}) + + await Chats.update_chat_tasks_by_id(__chat_id__, all_tasks) + await _emit_tasks(__event_emitter__, all_tasks) + + return json.dumps( + {'tasks': all_tasks, 'summary': _task_summary(all_tasks)}, + ensure_ascii=False, + ) + except Exception as e: + log.exception(f'update_task_status error: {e}') + return json.dumps({'error': str(e)}) + + # ============================================================================= # AUTOMATION TOOLS # ============================================================================= diff --git a/backend/open_webui/utils/anthropic.py b/backend/open_webui/utils/anthropic.py index 5ba4099fb4..a01184143f 100644 --- a/backend/open_webui/utils/anthropic.py +++ b/backend/open_webui/utils/anthropic.py @@ -181,17 +181,99 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict: ) elif block_type == 'tool_result': # Tool results become separate tool messages in OpenAI format - tool_content = block.get('content', '') - if isinstance(tool_content, list): - tool_text_parts = [] - for tc in tool_content: - if isinstance(tc, dict) and tc.get('type') == 'text': - tool_text_parts.append(tc.get('text', '')) - tool_content = '\n'.join(tool_text_parts) + tool_result_content = block.get('content', '') + tool_content: str | list = '' + + if isinstance(tool_result_content, str): + tool_content = tool_result_content + elif isinstance(tool_result_content, list): + # Build a multimodal content array to preserve + # images and other non-text content types. + converted_parts = [] + for content_block in tool_result_content: + if not isinstance(content_block, dict): + continue + content_type = content_block.get('type', 'text') + + if content_type == 'text': + converted_parts.append( + { + 'type': 'text', + 'text': content_block.get('text', ''), + } + ) + elif content_type == 'image': + source = content_block.get('source', {}) + if source.get('type') == 'base64': + media_type = source.get('media_type', 'image/png') + data = source.get('data', '') + converted_parts.append( + { + 'type': 'image_url', + 'image_url': { + 'url': f'data:{media_type};base64,{data}', + }, + } + ) + elif source.get('type') == 'url': + converted_parts.append( + { + 'type': 'image_url', + 'image_url': { + 'url': source.get('url', ''), + }, + } + ) + elif content_type == 'document': + # Documents have no direct OpenAI equivalent; + # convert to a text representation. + document_source = content_block.get('source', {}) + document_title = content_block.get('title', 'Document') + document_context = content_block.get('context', '') + document_text = f'[Document: {document_title}]' + if document_context: + document_text += f'\n{document_context}' + if document_source.get('type') == 'text' and document_source.get('data'): + document_text += f'\n{document_source["data"]}' + converted_parts.append({'type': 'text', 'text': document_text}) + elif content_type == 'search_result': + # Convert search results to a text + # representation with source attribution. + search_title = content_block.get('title', '') + search_url = content_block.get('source', '') + search_content_blocks = content_block.get('content', []) + search_texts = [] + for search_block in search_content_blocks: + if isinstance(search_block, dict) and search_block.get('type') == 'text': + search_texts.append(search_block.get('text', '')) + search_body = '\n'.join(search_texts) + search_text = f'[Search Result: {search_title}]' + if search_url: + search_text += f'\nSource: {search_url}' + if search_body: + search_text += f'\n{search_body}' + converted_parts.append({'type': 'text', 'text': search_text}) + + # Flatten to string when only text parts are present + if all(part.get('type') == 'text' for part in converted_parts): + tool_content = '\n'.join(part.get('text', '') for part in converted_parts) + elif converted_parts: + tool_content = converted_parts + else: + tool_content = '' # Propagate error status if present if block.get('is_error'): - tool_content = f'Error: {tool_content}' + if isinstance(tool_content, str): + tool_content = f'Error: {tool_content}' + elif isinstance(tool_content, list): + tool_content.insert( + 0, + { + 'type': 'text', + 'text': 'Error: ', + }, + ) messages.append( { diff --git a/backend/open_webui/utils/asgi_middleware.py b/backend/open_webui/utils/asgi_middleware.py new file mode 100644 index 0000000000..05389d8f94 --- /dev/null +++ b/backend/open_webui/utils/asgi_middleware.py @@ -0,0 +1,268 @@ +""" +Pure-ASGI replacements for the project's previous +`@app.middleware('http')` / `BaseHTTPMiddleware` middlewares. + +Why this matters +---------------- +Starlette's `BaseHTTPMiddleware` (which `@app.middleware('http')` is +sugar for) runs the downstream app inside an `anyio` task group. When +the wrapper exits — for any reason: response complete, client +disconnect, an outer middleware bailing out — the task group cancels +the inner task. That `CancelledError` then propagates into whatever +the inner task was doing, including in-flight DB queries, embedding +calls and disk I/O. + +In Open WebUI this surfaces as: + +* SQLAlchemy logging multi-page `NotImplementedError: + terminate_force_close()` tracebacks at ERROR every time a request is + cancelled mid-DB-call (the aiosqlite connector cleanup path). +* Spurious cancellations cascading through the four stacked + `@app.middleware('http')` wrappers. + +Pure ASGI middleware does not introduce a cancel scope around the +downstream app, so client disconnects propagate the way ASGI was +designed to (via `receive()` returning `http.disconnect`) instead of +being injected as `CancelledError` into arbitrary `await` points. + +Reference: https://www.starlette.io/middleware/#limitations +""" + +from __future__ import annotations + +import logging +import re +import time +from urllib.parse import parse_qs, urlencode + +from fastapi.responses import JSONResponse, RedirectResponse +from fastapi.security import HTTPAuthorizationCredentials +from starlette.datastructures import MutableHeaders +from starlette.requests import Request +from starlette.types import ASGIApp, Message, Receive, Scope, Send + +from open_webui.internal.db import ScopedSession +from open_webui.utils.auth import get_http_authorization_cred + +log = logging.getLogger(__name__) + + +class CommitSessionMiddleware: + """Commit and release the thread-local sync `ScopedSession` after each + HTTP request. + + Most requests now use the async session; the sync ScopedSession is + only touched by startup, healthchecks, and a handful of legacy + helpers (notably the pgvector / opengauss vector-DB clients). The + middleware exists so that PostgreSQL connections do not accumulate + as "idle in transaction" and so that any pending sync work made + inside the request is durably persisted. + + Failure semantics + ----------------- + * Downstream raised → roll back any pending sync work, release the + connection, and re-raise so the outer exception middleware can + turn it into an error response. We never commit work on a + request that did not complete successfully. + * Downstream returned → commit pending sync work; on commit + failure, log loudly, roll back, and re-raise. Note that in pure + ASGI the response messages have already been emitted by the + time `await self.app(...)` returns, so a commit failure cannot + retroactively change what the client sees on the wire — but + re-raising still surfaces the error in logs and to ASGI servers + that expose it. We deliberately do not buffer the response to + gate it on commit success, because that would defeat streaming + responses (chat completions, SSE) which are core to the app. + + For request paths where commit-before-send is required, manage the + sync session explicitly inside the handler instead of relying on + this middleware. + """ + + def __init__(self, app: ASGIApp) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope['type'] != 'http': + await self.app(scope, receive, send) + return + + try: + await self.app(scope, receive, send) + except BaseException: + # Downstream did not complete successfully. Roll back any + # pending sync writes, release the connection, and let the + # exception propagate. + try: + ScopedSession.rollback() + except Exception: + log.exception('CommitSessionMiddleware: rollback failed after downstream error') + finally: + ScopedSession.remove() + raise + + # Downstream completed. Commit pending sync work. + try: + ScopedSession.commit() + except Exception: + log.exception('CommitSessionMiddleware: post-request commit failed; response was already sent to client') + try: + ScopedSession.rollback() + except Exception: + log.exception('CommitSessionMiddleware: rollback failed after commit failure') + raise + finally: + # CRITICAL: remove() returns the connection to the pool. + # Without this, connections remain "checked out" and + # accumulate as "idle in transaction" in PostgreSQL. + ScopedSession.remove() + + +class AuthTokenMiddleware: + """Extract the bearer/cookie/x-api-key credential and stash it on + `request.state.token`. + + Routes that depend on `get_verified_user` etc. read this state. + Also exposes `request.state.enable_api_keys` (snapshotted at request + entry from runtime config) and stamps an `X-Process-Time` response + header. + """ + + def __init__(self, app: ASGIApp, *, fastapi_app) -> None: + self.app = app + self._fastapi_app = fastapi_app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope['type'] != 'http': + await self.app(scope, receive, send) + return + + start_time = time.monotonic() + request = Request(scope) + + token = get_http_authorization_cred(request.headers.get('Authorization')) + if token is None: + cookie_token = request.cookies.get('token') + if cookie_token: + token = HTTPAuthorizationCredentials(scheme='Bearer', credentials=cookie_token) + if token is None: + api_key = request.headers.get('x-api-key') + if api_key: + token = HTTPAuthorizationCredentials(scheme='Bearer', credentials=api_key) + + request.state.token = token + request.state.enable_api_keys = self._fastapi_app.state.config.ENABLE_API_KEYS + + async def send_with_timing(message: Message) -> None: + if message['type'] == 'http.response.start': + process_time = int(time.monotonic() - start_time) + headers = MutableHeaders(scope=message) + headers['X-Process-Time'] = str(process_time) + await send(message) + + await self.app(scope, receive, send_with_timing) + + +class WebsocketUpgradeGuardMiddleware: + """Reject HTTP requests to `/ws/socket.io` that claim + `transport=websocket` but lack the proper `Upgrade`/`Connection` + headers. + + Works around https://github.com/miguelgrinberg/python-engineio/issues/367 + where engineio mishandles such requests. + """ + + def __init__(self, app: ASGIApp) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope['type'] != 'http': + await self.app(scope, receive, send) + return + + path = scope.get('path', '') + if '/ws/socket.io' in path: + query_string = scope.get('query_string', b'').decode('latin-1', errors='replace') + query_params = parse_qs(query_string) + if query_params.get('transport', [''])[0] == 'websocket': + headers = _scope_headers(scope) + upgrade = headers.get('upgrade', '').lower() + connection_tokens = [token.strip() for token in headers.get('connection', '').lower().split(',')] + if upgrade != 'websocket' or 'upgrade' not in connection_tokens: + response = JSONResponse( + status_code=400, + content={'detail': 'Invalid WebSocket upgrade request'}, + ) + await response(scope, receive, send) + return + + await self.app(scope, receive, send) + + +class RedirectMiddleware: + """Rewrites a couple of legacy entry-points to the SPA's own routes: + + * ``GET /watch?v=ID`` (YouTube) → ``/?youtube=ID`` + * ``GET /?shared=…`` (PWA share-target) → ``/?youtube=…`` / + ``/?load-url=…`` / ``/?q=…`` + """ + + def __init__(self, app: ASGIApp) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope['type'] != 'http' or scope.get('method', '').upper() != 'GET': + await self.app(scope, receive, send) + return + + path = scope.get('path', '') + query_string = scope.get('query_string', b'').decode('latin-1', errors='replace') + query_params = parse_qs(query_string) + + redirect_params: dict[str, str] = {} + if path.endswith('/watch') and 'v' in query_params and query_params['v']: + redirect_params['youtube'] = query_params['v'][0] + + if 'shared' in query_params and query_params['shared']: + text = query_params['shared'][0] + if text: + url_match = re.match(r'https://\S+', text) + if url_match: + # Local import: youtube loader pulls heavy deps and is + # only needed when a share-target actually contains a + # YouTube URL. + from open_webui.retrieval.loaders.youtube import _parse_video_id + + youtube_video_id = _parse_video_id(url_match[0]) + if youtube_video_id: + redirect_params['youtube'] = youtube_video_id + else: + redirect_params['load-url'] = url_match[0] + else: + redirect_params['q'] = text + + if redirect_params: + redirect_url = f'/?{urlencode(redirect_params)}' + response = RedirectResponse(url=redirect_url) + await response(scope, receive, send) + return + + await self.app(scope, receive, send) + + +def _scope_headers(scope: Scope) -> dict[str, str]: + """Return ASGI scope headers as a lower-cased str→str dict. + + ASGI delivers headers as a list of (bytes, bytes) pairs. For + convenience, fold duplicate keys with comma-joining (matching + HTTP/1.1 semantics). + """ + decoded: dict[str, str] = {} + for raw_key, raw_value in scope.get('headers', []): + key = raw_key.decode('latin-1').lower() + value = raw_value.decode('latin-1') + if key in decoded: + decoded[key] = f'{decoded[key]}, {value}' + else: + decoded[key] = value + return decoded diff --git a/backend/open_webui/utils/audit.py b/backend/open_webui/utils/audit.py index 1200d813af..5686c88d5d 100644 --- a/backend/open_webui/utils/audit.py +++ b/backend/open_webui/utils/audit.py @@ -24,7 +24,7 @@ from asgiref.typing import ( from loguru import logger from starlette.requests import Request -from open_webui.env import AUDIT_LOG_LEVEL, AUDIT_INCLUDED_PATHS, MAX_BODY_LOG_SIZE +from open_webui.env import AUDIT_LOG_LEVEL, ENABLE_AUDIT_GET_REQUESTS, AUDIT_INCLUDED_PATHS, MAX_BODY_LOG_SIZE from open_webui.utils.auth import get_current_user, get_http_authorization_cred from open_webui.models.users import UserModel @@ -117,7 +117,7 @@ class AuditLoggingMiddleware: ASGI middleware that intercepts HTTP requests and responses to perform audit logging. It captures request/response bodies (depending on audit level), headers, HTTP methods, and user information, then logs a structured audit entry at the end of the request cycle. """ - AUDITED_METHODS = {'PUT', 'PATCH', 'DELETE', 'POST'} + DEFAULT_AUDITED_METHODS = {'PUT', 'PATCH', 'DELETE', 'POST'} def __init__( self, @@ -127,12 +127,16 @@ class AuditLoggingMiddleware: included_paths: Optional[list[str]] = None, max_body_size: int = MAX_BODY_LOG_SIZE, audit_level: AuditLevel = AuditLevel.NONE, + audit_get_requests: bool = False, ) -> None: self.app = app self.audit_logger = AuditLogger(logger) self.excluded_paths = excluded_paths or [] self.included_paths = included_paths or [] self.max_body_size = max_body_size + self.audited_methods = set(self.DEFAULT_AUDITED_METHODS) + if audit_get_requests: + self.audited_methods.add('GET') self.audit_level = audit_level if self.included_paths and self.excluded_paths: @@ -202,7 +206,10 @@ class AuditLoggingMiddleware: return None def _should_skip_auditing(self, request: Request) -> bool: - if request.method not in {'POST', 'PUT', 'PATCH', 'DELETE'} or AUDIT_LOG_LEVEL == 'NONE': + if AUDIT_LOG_LEVEL == 'NONE': + return True + + if request.method not in self.audited_methods: return True ALWAYS_LOG_ENDPOINTS = { diff --git a/backend/open_webui/utils/auth.py b/backend/open_webui/utils/auth.py index 32e7db3423..e0f331a9df 100644 --- a/backend/open_webui/utils/auth.py +++ b/backend/open_webui/utils/auth.py @@ -20,7 +20,6 @@ from pytz import UTC from typing import Optional, Union, List, Dict - from open_webui.utils.access_control import has_permission from open_webui.models.users import Users from open_webui.models.auths import Auths @@ -238,9 +237,7 @@ async def is_valid_token(request, decoded) -> bool: # Per-user revocation (OIDC back-channel logout) user_id = decoded.get('id') if user_id: - revoked_at = await request.app.state.redis.get( - f'{REDIS_KEY_PREFIX}:auth:user:{user_id}:revoked_at' - ) + revoked_at = await request.app.state.redis.get(f'{REDIS_KEY_PREFIX}:auth:user:{user_id}:revoked_at') if revoked_at: try: revoked_at_ts = int(revoked_at) @@ -385,6 +382,7 @@ async def get_current_user( # Refresh the user's last active timestamp # Fire-and-forget via asyncio.create_task to avoid blocking import asyncio + asyncio.create_task(Users.update_last_active_by_id(user.id)) return user else: @@ -427,6 +425,21 @@ async def get_current_user_by_api_key(request, api_key: str): ): raise HTTPException(status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.API_KEY_NOT_ALLOWED) + # Enforce endpoint restrictions — checked here (not in middleware) + # so it applies regardless of how the API key was transported + # (Authorization header, cookie, x-api-key header, etc.). + if request.app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS: + allowed_paths = [ + path.strip() for path in str(request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS).split(',') if path.strip() + ] + request_path = request.url.path + is_allowed = any(request_path == allowed or request_path.startswith(allowed + '/') for allowed in allowed_paths) + if not is_allowed: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.ACCESS_PROHIBITED, + ) + # Add user info to current span if ENABLE_OTEL: from opentelemetry import trace diff --git a/backend/open_webui/utils/automations.py b/backend/open_webui/utils/automations.py index 262430dcf0..3866eb865a 100644 --- a/backend/open_webui/utils/automations.py +++ b/backend/open_webui/utils/automations.py @@ -22,11 +22,12 @@ from dateutil.rrule import rrulestr from fastapi import Request from starlette.datastructures import Headers +from open_webui.constants import ERROR_MESSAGES from open_webui.models.automations import Automations, AutomationRuns, AutomationModel from open_webui.models.chats import ChatForm, Chats from open_webui.models.users import Users from open_webui.utils.task import prompt_template -from open_webui.internal.db import get_db +from open_webui.internal.db import get_async_db log = logging.getLogger(__name__) @@ -59,9 +60,9 @@ def validate_rrule(s: str) -> None: try: rule = _parse_rule(s) except Exception as e: - raise ValueError(f'Invalid RRULE: {e}') + raise ValueError(ERROR_MESSAGES.AUTOMATION_INVALID_RRULE(e)) if rule.after(datetime.now()) is None: - raise ValueError('RRULE has no future occurrences') + raise ValueError(ERROR_MESSAGES.AUTOMATION_NO_FUTURE_RUNS) def next_run_ns(s: str, tz: str = None) -> Optional[int]: @@ -125,7 +126,7 @@ async def automation_worker_loop(app) -> None: log.info(f'Automation worker started (poll interval: {AUTOMATION_POLL_INTERVAL}s)') while True: try: - with get_db() as db: + async with get_async_db() as db: batch = await Automations.claim_due(int(time.time_ns()), limit=10, db=db) if batch: log.info(f'Claimed {len(batch)} due automation(s)') @@ -224,6 +225,16 @@ def _resolve_model_filter_ids(app, model_id: str) -> list[str]: return list(filter_ids) if filter_ids else [] +def _resolve_model_terminal_id(app, model_id: str) -> Optional[str]: + """Read model default terminal_id from model config. + + The frontend does this in Chat.svelte (model.info.meta.terminalId). + """ + models = getattr(app.state, 'MODELS', {}) + model = models.get(model_id, {}) + return model.get('info', {}).get('meta', {}).get('terminalId') or None + + async def _set_terminal_cwd(app, server_id: str, user, cwd: str, chat_id: str) -> None: """Set the working directory on a terminal server via the proxy. @@ -296,8 +307,9 @@ async def execute_automation(app, automation: AutomationModel) -> None: user_msg_id = str(uuid4()) assistant_msg_id = str(uuid4()) - # Create the chat with user message (same structure as frontend) + chat_id = str(uuid4()) chat = await Chats.insert_new_chat( + chat_id, automation.user_id, ChatForm( chat={ @@ -357,13 +369,8 @@ async def execute_automation(app, automation: AutomationModel) -> None: features = _resolve_model_features(app, model_id) filter_ids = _resolve_model_filter_ids(app, model_id) - # If a terminal is linked, set the CWD before building the payload - terminal_id = None - if terminal_config and terminal_config.get('server_id'): - terminal_id = terminal_config['server_id'] - cwd = terminal_config.get('cwd') - if cwd: - await _set_terminal_cwd(app, terminal_id, user, cwd, chat.id) + # Resolve terminal from model config + terminal_id = _resolve_model_terminal_id(app, model_id) # Build the same payload the frontend sends to /api/chat/completions form_data = { @@ -372,7 +379,13 @@ async def execute_automation(app, automation: AutomationModel) -> None: 'stream': True, 'chat_id': chat.id, 'id': assistant_msg_id, - 'parent_id': user_msg_id, + 'parent_id': None, # Root message (chat already created above) + 'user_message': { + 'id': user_msg_id, + 'parentId': None, + 'role': 'user', + 'content': prompt, + }, 'session_id': f'automation:{automation.id}', 'background_tasks': {}, } @@ -423,5 +436,5 @@ async def _record_run( error: str = None, ): """Insert a run record into automation_run.""" - with get_db() as db: + async with get_async_db() as db: await AutomationRuns.insert(automation_id, status, chat_id=chat_id, error=error, db=db) diff --git a/backend/open_webui/utils/files.py b/backend/open_webui/utils/files.py index ef7900a1ce..eea3a8b486 100644 --- a/backend/open_webui/utils/files.py +++ b/backend/open_webui/utils/files.py @@ -20,12 +20,14 @@ from open_webui.models.files import Files from open_webui.routers.files import upload_file_handler from open_webui.retrieval.web.utils import validate_url +import asyncio import mimetypes import base64 import io import re -import requests +from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL +from open_webui.utils.session_pool import get_session BASE64_IMAGE_URL_PREFIX = re.compile(r'data:image/\w+;base64,', re.IGNORECASE) MARKDOWN_IMAGE_URL_PATTERN = re.compile(r'!\[(.*?)\]\((.+?)\)', re.IGNORECASE) @@ -37,19 +39,20 @@ async def get_image_base64_from_url(url: str) -> Optional[str]: # Validate URL to prevent SSRF attacks against local/private networks validate_url(url) # Download the image from the URL - response = requests.get(url) - response.raise_for_status() - image_data = response.content - encoded_string = base64.b64encode(image_data).decode('utf-8') - content_type = response.headers.get('Content-Type', 'image/png') - return f'data:{content_type};base64,{encoded_string}' + session = await get_session() + async with session.get(url, ssl=AIOHTTP_CLIENT_SESSION_SSL) as response: + response.raise_for_status() + image_data = await response.read() + encoded_string = base64.b64encode(image_data).decode('utf-8') + content_type = response.headers.get('Content-Type', 'image/png') + return f'data:{content_type};base64,{encoded_string}' else: file = await Files.get_file_by_id(url) if not file: return None - file_path = Storage.get_file(file.path) + file_path = await asyncio.to_thread(Storage.get_file, file.path) file_path = Path(file_path) if file_path.is_file(): @@ -88,7 +91,7 @@ async def convert_markdown_base64_images(request, content: str, metadata, user): last_end = 0 for match in MARKDOWN_IMAGE_URL_PATTERN.finditer(content): - result_parts.append(content[last_end:match.start()]) + result_parts.append(content[last_end : match.start()]) base64_string = match.group(2) if len(base64_string) > MIN_REPLACEMENT_URL_LENGTH: url = await get_image_url_from_base64(request, base64_string, metadata, user) @@ -170,7 +173,7 @@ async def get_image_base64_from_file_id(id: str) -> Optional[str]: return None try: - file_path = Storage.get_file(file.path) + file_path = await asyncio.to_thread(Storage.get_file, file.path) file_path = Path(file_path) # Check if the file already exists in the cache diff --git a/backend/open_webui/utils/images/comfyui.py b/backend/open_webui/utils/images/comfyui.py index 497808c22d..9172f1c325 100644 --- a/backend/open_webui/utils/images/comfyui.py +++ b/backend/open_webui/utils/images/comfyui.py @@ -1,49 +1,51 @@ -import asyncio import json import logging import random -import requests -import aiohttp import urllib.parse -import urllib.request from typing import Optional -import websocket # NOTE: websocket-client (https://github.com/websocket-client/websocket-client) +import aiohttp from pydantic import BaseModel +from open_webui.env import AIOHTTP_CLIENT_SESSION_SSL +from open_webui.utils.session_pool import get_session + log = logging.getLogger(__name__) default_headers = {'User-Agent': 'Mozilla/5.0'} -def queue_prompt(prompt, client_id, base_url, api_key): +async def queue_prompt(prompt, client_id, base_url, api_key): log.info('queue_prompt') p = {'prompt': prompt, 'client_id': client_id} - data = json.dumps(p).encode('utf-8') - log.debug(f'queue_prompt data: {data}') + log.debug(f'queue_prompt data: {p}') try: - req = urllib.request.Request( + session = await get_session() + async with session.post( f'{base_url}/prompt', - data=data, + json=p, headers={**default_headers, 'Authorization': f'Bearer {api_key}'}, - ) - response = urllib.request.urlopen(req).read() - return json.loads(response) + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return await r.json() except Exception as e: log.exception(f'Error while queuing prompt: {e}') - raise e + raise -def get_image(filename, subfolder, folder_type, base_url, api_key): +async def get_image(filename, subfolder, folder_type, base_url, api_key): log.info('get_image') data = {'filename': filename, 'subfolder': subfolder, 'type': folder_type} url_values = urllib.parse.urlencode(data) - req = urllib.request.Request( + session = await get_session() + async with session.get( f'{base_url}/view?{url_values}', headers={**default_headers, 'Authorization': f'Bearer {api_key}'}, - ) - with urllib.request.urlopen(req) as response: - return response.read() + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return await r.read() def get_image_url(filename, subfolder, folder_type, base_url): @@ -53,32 +55,39 @@ def get_image_url(filename, subfolder, folder_type, base_url): return f'{base_url}/view?{url_values}' -def get_history(prompt_id, base_url, api_key): +async def get_history(prompt_id, base_url, api_key): log.info('get_history') - - req = urllib.request.Request( + session = await get_session() + async with session.get( f'{base_url}/history/{prompt_id}', headers={**default_headers, 'Authorization': f'Bearer {api_key}'}, - ) - with urllib.request.urlopen(req) as response: - return json.loads(response.read()) + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as r: + r.raise_for_status() + return await r.json() -def get_images(ws, workflow, client_id, base_url, api_key): - prompt_id = queue_prompt(workflow, client_id, base_url, api_key)['prompt_id'] +async def _ws_get_images(ws, workflow, client_id, base_url, api_key): + """Queue a prompt and wait on *ws* for ComfyUI to finish executing it. + + Returns a dict of ``{'data': [{'url': ...}, ...]}``. + """ + prompt_id = (await queue_prompt(workflow, client_id, base_url, api_key))['prompt_id'] output_images = [] - while True: - out = ws.recv() - if isinstance(out, str): - message = json.loads(out) + + async for msg in ws: + if msg.type == aiohttp.WSMsgType.TEXT: + message = json.loads(msg.data) if message['type'] == 'executing': data = message['data'] if data['node'] is None and data['prompt_id'] == prompt_id: break # Execution is done - else: - continue # previews are binary data + elif msg.type in (aiohttp.WSMsgType.CLOSED, aiohttp.WSMsgType.ERROR): + log.error(f'WebSocket closed unexpectedly: {msg.type}') + break + # binary messages (previews) are silently skipped - history = get_history(prompt_id, base_url, api_key)[prompt_id] + history = (await get_history(prompt_id, base_url, api_key))[prompt_id] for node_id in history['outputs']: node_output = history['outputs'][node_id] if node_id in workflow and workflow[node_id].get('class_type') in [ @@ -105,10 +114,10 @@ async def comfyui_upload_image(image_file_item, base_url, api_key): form.add_field('image', file_bytes, filename=filename, content_type=mime_type) form.add_field('type', 'input') # required by ComfyUI - async with aiohttp.ClientSession() as session: - async with session.post(url, data=form, headers=headers) as resp: - resp.raise_for_status() - return await resp.json() + session = await get_session() + async with session.post(url, data=form, headers=headers, ssl=AIOHTTP_CLIENT_SESSION_SSL) as resp: + resp.raise_for_status() + return await resp.json() class ComfyUINodeInput(BaseModel): @@ -136,11 +145,9 @@ class ComfyUICreateImageForm(BaseModel): seed: Optional[int] = None -async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, client_id, base_url, api_key): - ws_url = base_url.replace('http://', 'ws://').replace('https://', 'wss://') - workflow = json.loads(payload.workflow.workflow) - - for node in payload.workflow.nodes: +def _apply_workflow_nodes(workflow, nodes, model, payload): + """Mutate *workflow* dict in-place based on typed node definitions.""" + for node in nodes: if node.type: if node.type == 'model': for node_id in node.node_ids: @@ -151,6 +158,14 @@ async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, clie elif node.type == 'negative_prompt': for node_id in node.node_ids: workflow[node_id]['inputs'][node.key if node.key else 'text'] = payload.negative_prompt + elif node.type == 'image': + if isinstance(payload.image, list): + for idx, node_id in enumerate(node.node_ids): + if idx < len(payload.image): + workflow[node_id]['inputs'][node.key] = payload.image[idx] + else: + for node_id in node.node_ids: + workflow[node_id]['inputs'][node.key] = payload.image elif node.type == 'width': for node_id in node.node_ids: workflow[node_id]['inputs'][node.key if node.key else 'width'] = payload.width @@ -171,24 +186,31 @@ async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, clie for node_id in node.node_ids: workflow[node_id]['inputs'][node.key] = node.value + +async def comfyui_create_image(model: str, payload: ComfyUICreateImageForm, client_id, base_url, api_key): + ws_url = base_url.replace('http://', 'ws://').replace('https://', 'wss://') + workflow = json.loads(payload.workflow.workflow) + _apply_workflow_nodes(workflow, payload.workflow.nodes, model, payload) + + headers = {'Authorization': f'Bearer {api_key}'} + session = await get_session() + try: - ws = websocket.WebSocket() - headers = {'Authorization': f'Bearer {api_key}'} - ws.connect(f'{ws_url}/ws?clientId={client_id}', header=headers) - log.info('WebSocket connection established.') - except Exception as e: + async with session.ws_connect( + f'{ws_url}/ws?clientId={client_id}', + headers=headers, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as ws: + log.info('WebSocket connection established.') + log.info('Sending workflow to WebSocket server.') + log.info(f'Workflow: {workflow}') + images = await _ws_get_images(ws, workflow, client_id, base_url, api_key) + except aiohttp.WSServerHandshakeError as e: log.exception(f'Failed to connect to WebSocket server: {e}') return None - - try: - log.info('Sending workflow to WebSocket server.') - log.info(f'Workflow: {workflow}') - images = await asyncio.to_thread(get_images, ws, workflow, client_id, base_url, api_key) except Exception as e: - log.exception(f'Error while receiving images: {e}') - images = None - - ws.close() + log.exception(f'Error during image generation: {e}') + return None return images @@ -209,64 +231,26 @@ class ComfyUIEditImageForm(BaseModel): async def comfyui_edit_image(model: str, payload: ComfyUIEditImageForm, client_id, base_url, api_key): ws_url = base_url.replace('http://', 'ws://').replace('https://', 'wss://') workflow = json.loads(payload.workflow.workflow) + _apply_workflow_nodes(workflow, payload.workflow.nodes, model, payload) - for node in payload.workflow.nodes: - if node.type: - if node.type == 'model': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key] = model - elif node.type == 'image': - if isinstance(payload.image, list): - # check if multiple images are provided - for idx, node_id in enumerate(node.node_ids): - if idx < len(payload.image): - workflow[node_id]['inputs'][node.key] = payload.image[idx] - else: - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key] = payload.image - elif node.type == 'prompt': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'text'] = payload.prompt - elif node.type == 'negative_prompt': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'text'] = payload.negative_prompt - elif node.type == 'width': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'width'] = payload.width - elif node.type == 'height': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'height'] = payload.height - elif node.type == 'n': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'batch_size'] = payload.n - elif node.type == 'steps': - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key if node.key else 'steps'] = payload.steps - elif node.type == 'seed': - seed = payload.seed if payload.seed else random.randint(0, 1125899906842624) - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key] = seed - else: - for node_id in node.node_ids: - workflow[node_id]['inputs'][node.key] = node.value + headers = {'Authorization': f'Bearer {api_key}'} + session = await get_session() try: - ws = websocket.WebSocket() - headers = {'Authorization': f'Bearer {api_key}'} - ws.connect(f'{ws_url}/ws?clientId={client_id}', header=headers) - log.info('WebSocket connection established.') - except Exception as e: + async with session.ws_connect( + f'{ws_url}/ws?clientId={client_id}', + headers=headers, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as ws: + log.info('WebSocket connection established.') + log.info('Sending workflow to WebSocket server.') + log.info(f'Workflow: {workflow}') + images = await _ws_get_images(ws, workflow, client_id, base_url, api_key) + except aiohttp.WSServerHandshakeError as e: log.exception(f'Failed to connect to WebSocket server: {e}') return None - - try: - log.info('Sending workflow to WebSocket server.') - log.info(f'Workflow: {workflow}') - images = await asyncio.to_thread(get_images, ws, workflow, client_id, base_url, api_key) except Exception as e: - log.exception(f'Error while receiving images: {e}') - images = None - - ws.close() + log.exception(f'Error during image editing: {e}') + return None return images diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 9ab5c18b74..2b3971148f 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -450,7 +450,8 @@ def serialize_output(output: list) -> str: files = result_item.get('files') embeds = result_item.get('embeds', '') - append_part(f'
\nTool Executed\n
\n') + append_part(f'
\nTool Executed\n{html.escape(json.dumps(result_text, ensure_ascii=False))}\n
\n') + else: append_part(f'
\nExecuting...\n
\n') @@ -907,10 +908,14 @@ def get_source_context(sources: list, source_ids: dict = None, include_content: if source_id not in source_ids: source_ids[source_id] = len(source_ids) + 1 src_name = source.get('source', {}).get('name') + src_type = source.get('source', {}).get('type') + src_rid = source.get('source', {}).get('id') body = doc if include_content else '' context_string += ( f'{body}\n' ) return context_string @@ -2068,13 +2073,16 @@ async def convert_url_images_to_base64(form_data): continue try: - base64_data = await asyncio.to_thread(get_image_base64_from_url, image_url) - new_content.append( - { - 'type': 'image_url', - 'image_url': {'url': base64_data}, - } - ) + base64_data = await get_image_base64_from_url(image_url) + if base64_data: + new_content.append( + { + 'type': 'image_url', + 'image_url': {'url': base64_data}, + } + ) + else: + new_content.append(item) except Exception as e: log.debug(f'Error converting image URL to base64: {e}') new_content.append(item) @@ -2164,10 +2172,10 @@ async def process_chat_payload(request, form_data, user, metadata, model): # Load messages from DB when available — DB preserves structured 'output' items # which the frontend strips, causing tool calls to be merged into content. chat_id = metadata.get('chat_id') - parent_message_id = metadata.get('parent_message_id') + user_message_id = metadata.get('user_message_id') - if chat_id and parent_message_id and not chat_id.startswith('local:'): - db_messages = await load_messages_from_db(chat_id, parent_message_id) + if chat_id and user_message_id and not chat_id.startswith('local:'): + db_messages = await load_messages_from_db(chat_id, user_message_id) if db_messages: system_message = get_system_message(form_data.get('messages', [])) form_data['messages'] = [system_message, *db_messages] if system_message else db_messages @@ -3072,6 +3080,145 @@ async def background_tasks_handler(ctx): pass +async def outlet_filter_handler(ctx): + """Run outlet filters inline after chat completion. + + Replaces the separate POST /api/chat/completed round-trip. + Persists outlet-modified content to DB and emits a chat:outlet event + so the frontend can sync its in-memory state. + + For temp chats (local: prefix), messages are built from form_data + plus the assistant response message stored in ctx['assistant_message'], + since temp chats have no DB-persisted history. + """ + request = ctx['request'] + user = ctx['user'] + model = ctx['model'] + metadata = ctx['metadata'] + event_emitter = ctx.get('event_emitter') + event_caller = ctx.get('event_caller') + + chat_id = metadata.get('chat_id', '') + message_id = metadata.get('message_id') + + if not chat_id or not message_id: + return + + is_temp_chat = chat_id.startswith('local:') + + try: + messages_map = None + + if is_temp_chat: + # Temp chats have no DB record — build message list from + # the in-memory form_data plus the assistant response. + form_messages = ctx.get('form_data', {}).get('messages', []) + assistant_message = ctx.get('assistant_message', {}) + + message_list = [ + { + 'role': m.get('role'), + 'content': m.get('content', ''), + } + for m in form_messages + ] + + # Append the full assistant message (content, output, usage, etc.) + if assistant_message: + message_list.append({ + 'id': message_id, + 'role': 'assistant', + **assistant_message, + }) + else: + messages_map = await Chats.get_messages_map_by_chat_id(chat_id) + if not messages_map: + return + + message_list = get_message_list(messages_map, message_id) + if not message_list: + return + + model_id = model.get('id') if isinstance(model, dict) else model + + outlet_data = { + 'model': model_id, + 'messages': [ + { + 'id': m.get('id'), + 'role': m.get('role'), + 'content': m.get('content', ''), + 'info': m.get('info'), + 'timestamp': m.get('timestamp'), + **({'output': m['output']} if m.get('output') else {}), + **({'usage': m['usage']} if m.get('usage') else {}), + **({'sources': m['sources']} if m.get('sources') else {}), + } + for m in message_list + ], + 'filter_ids': metadata.get('filter_ids', []), + 'chat_id': chat_id, + 'session_id': metadata.get('session_id'), + 'id': message_id, + } + + # Pipeline outlet filters + models = request.app.state.MODELS + try: + outlet_data = await process_pipeline_outlet_filter(request, outlet_data, user, models) + except Exception as e: + log.debug(f'Pipeline outlet filter error: {e}') + + # Function outlet filters + extra_params = { + '__event_emitter__': event_emitter, + '__event_call__': event_caller, + '__user__': user.model_dump() if isinstance(user, UserModel) else {}, + '__metadata__': metadata, + '__request__': request, + '__model__': model, + } + + filter_ids = await get_sorted_filter_ids(request, model, metadata.get('filter_ids', [])) + filter_functions = await Functions.get_functions_by_ids(filter_ids) + + outlet_result, _ = await process_filter_functions( + request=request, + filter_functions=filter_functions, + filter_type='outlet', + form_data=outlet_data, + extra_params=extra_params, + ) + + # Persist outlet-modified content and notify frontend + # (skip DB persistence for temp chats — they have no DB record) + if outlet_result and outlet_result.get('messages'): + if not is_temp_chat and messages_map: + for message in outlet_result['messages']: + outlet_message_id = message.get('id') + if outlet_message_id and outlet_message_id in messages_map: + original_message = messages_map[outlet_message_id] + if original_message.get('content') != message.get('content'): + await Chats.upsert_message_to_chat_by_id_and_message_id( + chat_id, + outlet_message_id, + { + 'content': message['content'], + 'originalContent': original_message.get('content'), + }, + ) + + if event_emitter: + await event_emitter( + { + 'type': 'chat:outlet', + 'data': {'messages': outlet_result['messages']}, + } + ) + except Exception as e: + log.debug(f'Error running outlet filters: {e}') + + async def non_streaming_chat_response_handler(response, ctx): request = ctx['request'] @@ -3095,6 +3242,8 @@ async def non_streaming_chat_response_handler(response, ctx): else: error = str(error) + log.error('Provider returned error (non-streaming): %s', error) + await Chats.upsert_message_to_chat_by_id_and_message_id( metadata['chat_id'], metadata['message_id'], @@ -3191,6 +3340,12 @@ async def non_streaming_chat_response_handler(response, ctx): ) await background_tasks_handler(ctx) + ctx['assistant_message'] = { + 'content': content, + 'output': response_output, + **({'usage': usage} if usage else {}), + } + await outlet_filter_handler(ctx) response = build_response_object(response, merge_events_into_response(response_data, events)) except Exception as e: @@ -3663,6 +3818,7 @@ async def streaming_chat_response_handler(response, ctx): if not choices: error = data.get('error', {}) if error: + log.error('Provider returned error (streaming): %s', error) try: await Chats.upsert_message_to_chat_by_id_and_message_id( metadata['chat_id'], @@ -4078,11 +4234,12 @@ async def streaming_chat_response_handler(response, ctx): if responses_api_tool_calls: tool_calls.append(_split_tool_calls(responses_api_tool_calls)) + try: + await stream_body_handler(response, form_data) + finally: if response.background: await response.background() - await stream_body_handler(response, form_data) - tool_call_retries = 0 tool_call_sources = [] # Track citation sources from tool results all_tool_call_sources = [] # Accumulated sources across all iterations @@ -4700,27 +4857,41 @@ async def streaming_chat_response_handler(response, ctx): ) await background_tasks_handler(ctx) + ctx['assistant_message'] = { + 'content': serialize_output(output), + 'output': output, + **({'usage': usage} if usage else {}), + } + await outlet_filter_handler(ctx) except asyncio.CancelledError: log.warning('Task was cancelled!') - await event_emitter({'type': 'chat:tasks:cancel'}) + try: + await asyncio.shield(event_emitter({'type': 'chat:tasks:cancel'})) - if not ENABLE_REALTIME_CHAT_SAVE: - # Save message in the database - await Chats.upsert_message_to_chat_by_id_and_message_id( - metadata['chat_id'], - metadata['message_id'], - { - 'done': True, - 'content': serialize_output(output), - 'output': output, - }, - ) - else: - await Chats.upsert_message_to_chat_by_id_and_message_id( - metadata['chat_id'], - metadata['message_id'], - {'done': True}, - ) + if not ENABLE_REALTIME_CHAT_SAVE: + # Save message in the database + await asyncio.shield( + Chats.upsert_message_to_chat_by_id_and_message_id( + metadata['chat_id'], + metadata['message_id'], + { + 'done': True, + 'content': serialize_output(output), + 'output': output, + }, + ) + ) + else: + await asyncio.shield( + Chats.upsert_message_to_chat_by_id_and_message_id( + metadata['chat_id'], + metadata['message_id'], + {'done': True}, + ) + ) + except Exception: + pass + raise # re-raise CancelledError for proper propagation if response.background is not None: await response.background() diff --git a/backend/open_webui/utils/misc.py b/backend/open_webui/utils/misc.py index 787ef4d6e8..345165db28 100644 --- a/backend/open_webui/utils/misc.py +++ b/backend/open_webui/utils/misc.py @@ -905,9 +905,17 @@ async def cleanup_response( session: Optional[aiohttp.ClientSession], ): if response: - response.close() + if not response.closed: + # aiohttp 3.9+ made ClientResponse.close() synchronous (returns None). + # Older versions returned a coroutine. Handle both gracefully. + result = response.close() + if result is not None: + await result if session: - await session.close() + if not session.closed: + result = session.close() + if result is not None: + await result async def stream_wrapper(response, session, content_handler=None): diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 535adca5ec..e8527fce4b 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -18,6 +18,7 @@ from typing import Literal import aiohttp from authlib.integrations.starlette_client import OAuth +from authlib.jose.errors import BadSignatureError from authlib.oidc.core import UserInfo from fastapi import ( HTTPException, @@ -139,6 +140,41 @@ auth_manager_config.OAUTH_UPDATE_EMAIL_ON_LOGIN = OAUTH_UPDATE_EMAIL_ON_LOGIN auth_manager_config.OAUTH_AUDIENCE = OAUTH_AUDIENCE +# Conservative default when the provider omits both expires_in and expires_at. +# Matches the value recommended by Authlib's compliance_fix documentation. +DEFAULT_TOKEN_EXPIRY_SECONDS = 3600 + + +def _normalize_token_expiry(token: dict) -> dict: + """Ensure a token dict always has a numeric ``expires_at``. + + Resolution order: + 1. If *expires_at* is already present and non-None, trust it. + 2. Else if *expires_in* is present and non-None, compute *expires_at*. + 3. Otherwise fall back to ``DEFAULT_TOKEN_EXPIRY_SECONDS`` and log a + warning so operators can identify providers that omit expiration. + + Also stamps *issued_at* for auditing. + """ + token['issued_at'] = datetime.now().timestamp() + + if token.get('expires_at') is not None: + token['expires_at'] = int(token['expires_at']) + return token + + if token.get('expires_in') is not None: + token['expires_at'] = int(datetime.now().timestamp() + token['expires_in']) + return token + + # Neither field present — conservative fallback + log.warning( + "OAuth token response missing both 'expires_in' and 'expires_at'; " + f'defaulting to {DEFAULT_TOKEN_EXPIRY_SECONDS}s from now' + ) + token['expires_at'] = int(datetime.now().timestamp() + DEFAULT_TOKEN_EXPIRY_SECONDS) + return token + + FERNET = None if len(OAUTH_CLIENT_INFO_ENCRYPTION_KEY) != 44: @@ -512,6 +548,24 @@ async def get_oauth_client_info_with_static_credentials( raise e +def resolve_oauth_client_info(connection: dict) -> dict: + """ + Decrypt OAuth client info from a tool server connection config. + + For oauth_2.1_static, overlays admin-provided credentials from + info.oauth_client_id and info.oauth_client_secret onto the blob. + """ + info = connection.get('info', {}) + data = decrypt_data(info.get('oauth_client_info', '')) + + if connection.get('auth_type') == 'oauth_2.1_static': + if info.get('oauth_client_id') and info.get('oauth_client_secret'): + data['client_id'] = info['oauth_client_id'] + data['client_secret'] = info['oauth_client_secret'] + + return data + + class OAuthClientManager: def __init__(self, app): self.oauth = OAuth() @@ -524,6 +578,7 @@ class OAuthClientManager: 'client_id': oauth_client_info.client_id, 'client_secret': oauth_client_info.client_secret, 'client_kwargs': { + 'follow_redirects': True, **({'scope': oauth_client_info.scope} if oauth_client_info.scope else {}), **( {'token_endpoint_auth_method': oauth_client_info.token_endpoint_auth_method} @@ -534,15 +589,20 @@ class OAuthClientManager: 'server_metadata_url': (oauth_client_info.issuer if oauth_client_info.issuer else None), } - if oauth_client_info.server_metadata and oauth_client_info.server_metadata.code_challenge_methods_supported: - if ( - isinstance( - oauth_client_info.server_metadata.code_challenge_methods_supported, - list, - ) - and 'S256' in oauth_client_info.server_metadata.code_challenge_methods_supported - ): - kwargs['code_challenge_method'] = 'S256' + # Default to S256 for OAuth 2.1 (PKCE is mandatory per RFC 9700) + kwargs['code_challenge_method'] = 'S256' + + # Only remove PKCE if metadata explicitly excludes S256 + if ( + oauth_client_info.server_metadata + and oauth_client_info.server_metadata.code_challenge_methods_supported + and isinstance( + oauth_client_info.server_metadata.code_challenge_methods_supported, + list, + ) + and 'S256' not in oauth_client_info.server_metadata.code_challenge_methods_supported + ): + del kwargs['code_challenge_method'] self.clients[client_id] = { 'client': self.oauth.register(**kwargs), @@ -582,7 +642,7 @@ class OAuthClientManager: continue try: - oauth_client_info = decrypt_data(oauth_client_info) + oauth_client_info = resolve_oauth_client_info(connection) return self.add_client(expected_client_id, OAuthClientInformationFull(**oauth_client_info))['client'] except Exception as e: log.error(f'Failed to lazily add OAuth client {expected_client_id} from config: {e}') @@ -705,7 +765,11 @@ class OAuthClientManager: log.warning(f'No OAuth session found for user {user_id}, client_id {client_id}') return None - if force_refresh or datetime.now() + timedelta(minutes=5) >= datetime.fromtimestamp(session.expires_at): + if ( + force_refresh + or session.expires_at is None + or datetime.now() + timedelta(minutes=5) >= datetime.fromtimestamp(session.expires_at) + ): log.debug(f'Token refresh needed for user {user_id}, client_id {session.provider}') refreshed_token = await self._refresh_token(session) if refreshed_token: @@ -816,14 +880,7 @@ class OAuthClientManager: if 'refresh_token' not in new_token_data: new_token_data['refresh_token'] = token_data['refresh_token'] - # Add timestamp for tracking - new_token_data['issued_at'] = datetime.now().timestamp() - - # Calculate expires_at if we have expires_in - if 'expires_in' in new_token_data and 'expires_at' not in new_token_data: - new_token_data['expires_at'] = int( - datetime.now().timestamp() + new_token_data['expires_in'] - ) + _normalize_token_expiry(new_token_data) log.debug(f'Token refresh successful for client_id {client_id}') return new_token_data @@ -876,12 +933,7 @@ class OAuthClientManager: if token: try: - # Add timestamp for tracking - token['issued_at'] = datetime.now().timestamp() - - # Calculate expires_at if we have expires_in - if 'expires_in' in token and 'expires_at' not in token: - token['expires_at'] = datetime.now().timestamp() + token['expires_in'] + _normalize_token_expiry(token) # Clean up any existing sessions for this user/client_id first sessions = await OAuthSessions.get_sessions_by_user_id(user_id) @@ -968,7 +1020,11 @@ class OAuthManager: log.warning(f'No OAuth session found for user {user_id}, session {session_id}') return None - if force_refresh or datetime.now() + timedelta(minutes=5) >= datetime.fromtimestamp(session.expires_at): + if ( + force_refresh + or session.expires_at is None + or datetime.now() + timedelta(minutes=5) >= datetime.fromtimestamp(session.expires_at) + ): log.debug(f'Token refresh needed for user {user_id}, provider {session.provider}') refreshed_token = await self._refresh_token(session) if refreshed_token: @@ -1082,14 +1138,7 @@ class OAuthManager: if 'refresh_token' not in new_token_data: new_token_data['refresh_token'] = token_data['refresh_token'] - # Add timestamp for tracking - new_token_data['issued_at'] = datetime.now().timestamp() - - # Calculate expires_at if we have expires_in - if 'expires_in' in new_token_data and 'expires_at' not in new_token_data: - new_token_data['expires_at'] = int( - datetime.now().timestamp() + new_token_data['expires_in'] - ) + _normalize_token_expiry(new_token_data) log.debug(f'Token refresh successful for provider {provider}') return new_token_data @@ -1392,6 +1441,27 @@ class OAuthManager: try: token = await client.authorize_access_token(request, **auth_params) + except BadSignatureError: + # The IdP likely rotated its signing keys and the cached JWKS + # is stale. Evict the cached key set so the next attempt + # fetches fresh keys from the jwks_uri. + log.warning( + 'OIDC bad_signature for provider %s — evicting cached JWKS and retrying', + provider, + ) + if hasattr(client, 'server_metadata') and isinstance(client.server_metadata, dict): + client.server_metadata.pop('jwks', None) + try: + token = await client.authorize_access_token(request, **auth_params) + except Exception as retry_exc: + detailed_error = _build_oauth_callback_error_message(retry_exc) + log.warning( + 'OAuth callback error during authorize_access_token retry for provider %s: %s', + provider, + detailed_error, + exc_info=True, + ) + raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) except Exception as e: detailed_error = _build_oauth_callback_error_message(e) log.warning( @@ -1666,12 +1736,7 @@ class OAuthManager: ) try: - # Add timestamp for tracking - token['issued_at'] = datetime.now().timestamp() - - # Calculate expires_at if we have expires_in - if 'expires_in' in token and 'expires_at' not in token: - token['expires_at'] = datetime.now().timestamp() + token['expires_in'] + _normalize_token_expiry(token) # Enforce max concurrent sessions per user/provider to prevent # unbounded growth while allowing multi-device usage @@ -1785,7 +1850,10 @@ class OAuthManager: log.warning(f'Back-channel logout: no configured provider matches issuer {token_issuer}') return JSONResponse( status_code=400, - content={'error': 'invalid_request', 'error_description': 'No configured provider matches token issuer'}, + content={ + 'error': 'invalid_request', + 'error_description': 'No configured provider matches token issuer', + }, ) # 4. Validate the logout_token signature and claims @@ -1886,5 +1954,7 @@ class OAuthManager: f'(email={user.email}, provider={matched_provider}, sessions_deleted={len(sessions)})' ) - log.info(f'Back-channel logout: completed for {len(users_to_logout)} user(s), {revoked_count} revocation(s) set') + log.info( + f'Back-channel logout: completed for {len(users_to_logout)} user(s), {revoked_count} revocation(s) set' + ) return JSONResponse(status_code=200, content={}) diff --git a/backend/open_webui/utils/payload.py b/backend/open_webui/utils/payload.py index 440927caf1..7cf4fe4de3 100644 --- a/backend/open_webui/utils/payload.py +++ b/backend/open_webui/utils/payload.py @@ -204,6 +204,11 @@ def convert_messages_openai_to_ollama(messages: list[dict]) -> list[dict]: # Initialize the new message structure with the role new_message = {'role': message['role']} + # Preserve Ollama-native 'thinking' field (used by reasoning models, + # may be injected by filter inlet functions). + if 'thinking' in message: + new_message['thinking'] = message['thinking'] + content = message.get('content', []) tool_calls = message.get('tool_calls', None) tool_call_id = message.get('tool_call_id', None) diff --git a/backend/open_webui/utils/redis.py b/backend/open_webui/utils/redis.py index 7a114393b0..cb570cb45a 100644 --- a/backend/open_webui/utils/redis.py +++ b/backend/open_webui/utils/redis.py @@ -194,20 +194,12 @@ def get_redis_connection( connection = None connect_timeout_kwargs = ( - {'socket_connect_timeout': REDIS_SOCKET_CONNECT_TIMEOUT} - if REDIS_SOCKET_CONNECT_TIMEOUT is not None - else {} + {'socket_connect_timeout': REDIS_SOCKET_CONNECT_TIMEOUT} if REDIS_SOCKET_CONNECT_TIMEOUT is not None else {} ) - keepalive_kwargs = ( - {'socket_keepalive': True} if REDIS_SOCKET_KEEPALIVE else {} - ) + keepalive_kwargs = {'socket_keepalive': True} if REDIS_SOCKET_KEEPALIVE else {} - health_check_kwargs = ( - {'health_check_interval': REDIS_HEALTH_CHECK_INTERVAL} - if REDIS_HEALTH_CHECK_INTERVAL - else {} - ) + health_check_kwargs = {'health_check_interval': REDIS_HEALTH_CHECK_INTERVAL} if REDIS_HEALTH_CHECK_INTERVAL else {} if async_mode: import redis.asyncio as redis diff --git a/backend/open_webui/utils/session_pool.py b/backend/open_webui/utils/session_pool.py new file mode 100644 index 0000000000..d74eae4f04 --- /dev/null +++ b/backend/open_webui/utils/session_pool.py @@ -0,0 +1,119 @@ +"""Shared aiohttp ClientSession pool. + +Instead of creating a new ClientSession (and TCPConnector) per request, +callers acquire a long-lived session from this module. The pool manages +a single TCPConnector with configurable limits, enabling TCP/SSL connection +reuse, shared DNS cache, and bounded concurrency. + +All pool parameters are configurable via environment variables: + - AIOHTTP_POOL_CONNECTIONS (default 100) — max total connections + - AIOHTTP_POOL_CONNECTIONS_PER_HOST (default 30) — per-host limit + - AIOHTTP_POOL_DNS_TTL (default 300) — DNS cache TTL in seconds + +Usage: + from open_webui.utils.session_pool import get_session, cleanup_response + + session = await get_session() + r = await session.request(...) + # When done with the *response* (not the session): + await cleanup_response(r) + +IMPORTANT: Callers must NOT close the shared session. Only the response +needs cleanup. The session is closed once during application shutdown +via ``close_session()``. +""" + +import logging +from typing import Optional + +import aiohttp + +from open_webui.env import ( + AIOHTTP_CLIENT_TIMEOUT, + AIOHTTP_POOL_CONNECTIONS, + AIOHTTP_POOL_CONNECTIONS_PER_HOST, + AIOHTTP_POOL_DNS_TTL, +) + +log = logging.getLogger(__name__) + +_session: Optional[aiohttp.ClientSession] = None + + +async def get_session() -> aiohttp.ClientSession: + """Return the shared aiohttp ClientSession, creating it lazily.""" + global _session + if _session is None or _session.closed: + connector_kwargs = { + 'ttl_dns_cache': AIOHTTP_POOL_DNS_TTL, + 'enable_cleanup_closed': True, + } + if AIOHTTP_POOL_CONNECTIONS is not None: + connector_kwargs['limit'] = AIOHTTP_POOL_CONNECTIONS + else: + connector_kwargs['limit'] = 0 # aiohttp: 0 = unlimited + if AIOHTTP_POOL_CONNECTIONS_PER_HOST is not None: + connector_kwargs['limit_per_host'] = AIOHTTP_POOL_CONNECTIONS_PER_HOST + else: + connector_kwargs['limit_per_host'] = 0 # aiohttp: 0 = unlimited + connector = aiohttp.TCPConnector(**connector_kwargs) + timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT) + _session = aiohttp.ClientSession( + connector=connector, + timeout=timeout, + trust_env=True, + ) + log.info( + 'Created shared aiohttp session pool (limit=%s, per_host=%s, dns_ttl=%d)', + AIOHTTP_POOL_CONNECTIONS or 'unlimited', + AIOHTTP_POOL_CONNECTIONS_PER_HOST or 'unlimited', + AIOHTTP_POOL_DNS_TTL, + ) + return _session + + +async def close_session(): + """Close the shared session. Called during application shutdown.""" + global _session + if _session and not _session.closed: + await _session.close() + log.info('Closed shared aiohttp session pool') + _session = None + + +async def cleanup_response( + response: Optional[aiohttp.ClientResponse], + session: Optional[aiohttp.ClientSession] = None, +): + """Release and close an aiohttp response, optionally closing the session. + + When using the shared pool, ``session`` should be ``None`` (the pool + session is never closed per-request). When a caller creates its own + one-off session, pass it here to close it after the response. + """ + if response: + if not response.closed: + # aiohttp 3.9+ made ClientResponse.close() synchronous (returns None). + # Older versions returned a coroutine. Handle both gracefully. + result = response.close() + if result is not None: + await result + if session: + if not session.closed: + result = session.close() + if result is not None: + await result + + +async def stream_wrapper(response, session=None, content_handler=None): + """Wrap a stream to ensure cleanup happens even if streaming is interrupted. + + This is more reliable than BackgroundTask which may not run if the client + disconnects. When using the shared pool, ``session`` should be ``None``. + """ + try: + stream = content_handler(response.content) if content_handler else response.content + async for chunk in stream: + yield chunk + finally: + await cleanup_response(response, session) diff --git a/backend/open_webui/utils/tools.py b/backend/open_webui/utils/tools.py index a44fe69ab8..da817c4741 100644 --- a/backend/open_webui/utils/tools.py +++ b/backend/open_webui/utils/tools.py @@ -51,6 +51,7 @@ from open_webui.env import ( ENABLE_FORWARD_USER_INFO_HEADERS, FORWARD_SESSION_INFO_HEADER_CHAT_ID, FORWARD_SESSION_INFO_HEADER_MESSAGE_ID, + REDIS_KEY_PREFIX, ) from open_webui.utils.headers import include_user_info_headers from open_webui.tools.builtin import ( @@ -85,7 +86,8 @@ from open_webui.tools.builtin import ( view_file, view_knowledge_file, view_skill, - tasks, + create_tasks, + update_task, create_automation, update_automation, list_automations, @@ -101,7 +103,9 @@ log = logging.getLogger(__name__) # Let no function be called without need, and let what # it yields justify the cost of running it. -async def get_async_tool_function_and_apply_extra_params(function: Callable, extra_params: dict) -> Callable[..., Awaitable]: +async def get_async_tool_function_and_apply_extra_params( + function: Callable, extra_params: dict +) -> Callable[..., Awaitable]: sig = inspect.signature(function) extra_params = {k: v for k, v in extra_params.items() if k in sig.parameters} partial_func = partial(function, **extra_params) @@ -540,11 +544,13 @@ async def get_builtin_tools( # Task management - break down complex work into trackable steps if is_builtin_tool_enabled('tasks'): - builtin_functions.append(tasks) + builtin_functions.extend([create_tasks, update_task]) # Automation tools - create and manage scheduled automations from chat if is_builtin_tool_enabled('automations') and await has_user_permission('automations'): - builtin_functions.extend([create_automation, update_automation, list_automations, toggle_automation, delete_automation]) + builtin_functions.extend( + [create_automation, update_automation, list_automations, toggle_automation, delete_automation] + ) for func in builtin_functions: callable = await get_async_tool_function_and_apply_extra_params( @@ -806,7 +812,7 @@ def convert_openapi_to_tool_payload(openapi_spec): if not description: description = param.get('description') or '' if param_schema.get('enum') and isinstance(param_schema.get('enum'), list): - description += f'. Possible values: {", ".join(param_schema.get("enum"))}' + description += f'. Possible values: {", ".join(str(v) for v in param_schema.get("enum"))}' param_property = { 'type': param_schema.get('type') or 'string', 'description': description, @@ -849,7 +855,9 @@ async def set_tool_servers(request: Request): request.app.state.TOOL_SERVERS = await get_tool_servers_data(request.app.state.config.TOOL_SERVER_CONNECTIONS) if request.app.state.redis is not None: - await request.app.state.redis.set('tool_servers', json.dumps(request.app.state.TOOL_SERVERS)) + await request.app.state.redis.set( + f'{REDIS_KEY_PREFIX}:tool_servers', json.dumps(request.app.state.TOOL_SERVERS) + ) return request.app.state.TOOL_SERVERS @@ -858,7 +866,7 @@ async def get_tool_servers(request: Request): tool_servers = [] if request.app.state.redis is not None: try: - tool_servers = json.loads(await request.app.state.redis.get('tool_servers')) + tool_servers = json.loads(await request.app.state.redis.get(f'{REDIS_KEY_PREFIX}:tool_servers')) request.app.state.TOOL_SERVERS = tool_servers except Exception as e: log.error(f'Error fetching tool_servers from Redis: {e}') @@ -984,7 +992,9 @@ async def set_terminal_servers(request: Request): ) if request.app.state.redis is not None: - await request.app.state.redis.set('terminal_servers', json.dumps(request.app.state.TERMINAL_SERVERS)) + await request.app.state.redis.set( + f'{REDIS_KEY_PREFIX}:terminal_servers', json.dumps(request.app.state.TERMINAL_SERVERS) + ) return request.app.state.TERMINAL_SERVERS @@ -994,7 +1004,7 @@ async def get_terminal_servers(request: Request): terminal_servers = [] if request.app.state.redis is not None: try: - terminal_servers = json.loads(await request.app.state.redis.get('terminal_servers')) + terminal_servers = json.loads(await request.app.state.redis.get(f'{REDIS_KEY_PREFIX}:terminal_servers')) request.app.state.TERMINAL_SERVERS = terminal_servers except Exception as e: log.error(f'Error fetching terminal_servers from Redis: {e}') diff --git a/backend/open_webui/utils/validate.py b/backend/open_webui/utils/validate.py index eec53317f5..1e98b41105 100644 --- a/backend/open_webui/utils/validate.py +++ b/backend/open_webui/utils/validate.py @@ -1,13 +1,31 @@ """Validation utilities for user-supplied input.""" -# Known static asset paths used as default profile images -_ALLOWED_STATIC_PATHS = ( - '/user.png', - '/static/favicon.png', -) +import re +from urllib.parse import urlparse -# External URL prefixes that are explicitly trusted for profile images -_ALLOWED_URL_PREFIXES = ('https://www.gravatar.com/avatar/',) +# Matches the OWUI-generated profile image route. ``[^/?#]+`` accepts +# any user-ID without allowing path-traversal or query/fragment injection, +# and the ``$`` anchor rejects trailing path components. +_USER_PROFILE_IMAGE_RE = re.compile(r'^/api/v1/users/[^/?#]+/profile/image$') + +# Validates MIME type and structure of base64 data URIs. Only the prefix +# is checked — validating the full base64 payload would mean running a +# regex across megabytes of data on every Pydantic instantiation for zero +# security benefit (corrupt base64 simply renders a broken image, same as +# a 404 URL). SVG is intentionally excluded: it can carry embedded scripts. +_SAFE_DATA_URI_RE = re.compile(r'^data:image/(png|jpeg|gif|webp);base64,', re.IGNORECASE) + +# Exact relative paths accepted as profile images. These are the only +# static-asset paths OWUI itself assigns; no prefix/wildcard matching is +# used so that arbitrary relative paths cannot trigger authenticated GETs +# against internal endpoints when rendered as ```` sources. +_SAFE_STATIC_PATHS = frozenset( + { + '/user.png', + '/favicon.png', + '/static/favicon.png', + } +) def validate_profile_image_url(url: str) -> str: @@ -16,28 +34,49 @@ def validate_profile_image_url(url: str) -> str: Allowed formats: - Empty string (falls back to default avatar) - - data:image/* URIs (base64-encoded uploads from the frontend) - - Known static asset paths (/user.png, /static/favicon.png) - - Trusted external URLs (e.g. Gravatar) + - Known static-asset paths assigned by OWUI (exact match) + - The OWUI profile-image API route ``/api/v1/users/{id}/profile/image`` + - ``http://`` and ``https://`` URLs with a valid hostname + - ``data:image/{png,jpeg,gif,webp};base64,...`` URIs - Returns the url unchanged if valid, raises ValueError otherwise. + Everything else is rejected, including: + - Dangerous schemes (javascript:, file:, ftp:, …) + - SVG data URIs (can contain embedded scripts) + - Arbitrary relative paths (prevents authenticated GET triggers) + - Scheme-relative URLs (``//host/path``) """ if not url: return url - _ALLOWED_DATA_PREFIXES = ( - 'data:image/png', - 'data:image/jpeg', - 'data:image/gif', - 'data:image/webp', + # --- Relative paths (exact match + anchored regex only) ----------- + + if url in _SAFE_STATIC_PATHS: + return url + + if _USER_PROFILE_IMAGE_RE.match(url): + return url + + # --- Absolute URLs ------------------------------------------------- + + # urlparse normalises the scheme to lowercase, giving us + # case-insensitive scheme matching for free. + parsed = urlparse(url) + + # External images served over HTTP(S), e.g. OAuth provider avatars. + # Require a non-empty hostname (not just netloc, which can be ":80" + # for a URL like http://:80/path with no actual host). + if parsed.scheme in ('http', 'https'): + if not parsed.hostname: + raise ValueError('Invalid profile image URL: HTTP(S) URLs must include a host.') + return url + + # Base64-encoded raster images uploaded via the frontend. + # The regex enforces the ;base64, boundary and is case-insensitive + # per the data-URI / MIME-type specs. + if _SAFE_DATA_URI_RE.match(url): + return url + + raise ValueError( + 'Invalid profile image URL: must be a known internal path, ' + 'an HTTP(S) URL with a host, or a data:image URI (png/jpeg/gif/webp).' ) - if any(url.startswith(prefix) for prefix in _ALLOWED_DATA_PREFIXES): - return url - - if url in _ALLOWED_STATIC_PATHS: - return url - - if any(url.startswith(prefix) for prefix in _ALLOWED_URL_PREFIXES): - return url - - raise ValueError('Invalid profile image URL: only data URIs and default avatars are allowed.') diff --git a/backend/requirements-min.txt b/backend/requirements-min.txt index b48006db20..b7dfd69ffd 100644 --- a/backend/requirements-min.txt +++ b/backend/requirements-min.txt @@ -13,15 +13,15 @@ cryptography bcrypt==5.0.0 argon2-cffi==25.1.0 PyJWT[crypto]==2.11.0 -authlib==1.6.9 +authlib==1.6.10 -requests==2.32.5 -aiohttp==3.13.2 # do not update to 3.13.3 - broken +requests==2.33.1 +aiohttp==3.13.5 # do not update to 3.13.3 - broken async-timeout aiocache aiofiles starlette-compress==1.7.0 -Brotli==1.1.0 +Brotli==1.2.0 httpx[socks,http2,zstd,cli,brotli]==0.28.1 starsessions[redis]==2.2.1 @@ -52,7 +52,7 @@ langchain-text-splitters==1.1.1 fake-useragent==2.2.0 chromadb==1.5.2 -black==26.1.0 +black==26.3.1 pydub chardet==5.2.0 beautifulsoup4 diff --git a/backend/requirements.txt b/backend/requirements.txt index 25265d0631..9aaa3aad5d 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -10,15 +10,15 @@ cryptography==46.0.5 bcrypt==5.0.0 argon2-cffi==25.1.0 PyJWT[crypto]==2.11.0 -authlib==1.6.9 +authlib==1.6.10 -requests==2.32.5 -aiohttp==3.13.2 # do not update to 3.13.3 - broken +requests==2.33.1 +aiohttp==3.13.5 # do not update to 3.13.3 - broken async-timeout==5.0.1 aiocache==0.12.3 aiofiles==25.1.0 starlette-compress==1.7.0 -Brotli==1.1.0 +Brotli==1.2.0 httpx[socks,http2,zstd,cli,brotli]==0.28.1 starsessions[redis]==2.2.1 python-mimeparse==2.0.0 @@ -58,8 +58,8 @@ chromadb==1.5.2 weaviate-client==4.20.3 opensearch-py==3.1.0 -transformers==5.3.0 -sentence-transformers==5.2.3 +transformers==5.5.4 +sentence-transformers==5.4.0 accelerate==1.13.0 pyarrow==20.0.0 # fix: pin pyarrow version to 20 for rpi compatibility #15897 einops==0.8.2 @@ -95,7 +95,7 @@ rank-bm25==0.2.2 onnxruntime==1.24.3 faster-whisper==1.2.1 -black==26.1.0 +black==26.3.1 youtube-transcript-api==1.2.4 pytube==15.0.0 diff --git a/pyproject.toml b/pyproject.toml index 7a546e935f..27e6faeddf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,10 +18,10 @@ dependencies = [ "bcrypt==5.0.0", "argon2-cffi==25.1.0", "PyJWT[crypto]==2.11.0", - "authlib==1.6.9", + "authlib==1.6.10", - "requests==2.32.5", - "aiohttp==3.13.2", # do not update to 3.13.3 - broken + "requests==2.33.1", + "aiohttp==3.13.5", # do not update to 3.13.3 - broken "async-timeout==5.0.1", "aiocache==0.12.3", "aiofiles==25.1.0", @@ -64,8 +64,8 @@ dependencies = [ "PyMySQL==1.1.2", "boto3==1.42.62", - "transformers==5.3.0", - "sentence-transformers==5.2.3", + "transformers==5.5.4", + "sentence-transformers==5.4.0", "accelerate==1.13.0", "pyarrow==20.0.0", # fix: pin pyarrow version to 20 for rpi compatibility #15897 "einops==0.8.2", @@ -100,7 +100,7 @@ dependencies = [ "onnxruntime==1.24.3", "faster-whisper==1.2.1", - "black==26.1.0", + "black==26.3.1", "youtube-transcript-api==1.2.4", "pytube==15.0.0", diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 05475b4b37..5faf56d4d4 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -273,10 +273,42 @@ export const stopTask = async (token: string, id: string) => { return res; }; +export const stopTasksByChatId = async (token: string, chat_id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_BASE_URL}/api/tasks/chat/${encodeURIComponent(chat_id)}/stop`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.error(err); + if ('detail' in err) { + error = err.detail; + } else { + error = err; + } + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const getTaskIdsByChatId = async (token: string, chat_id: string) => { let error = null; - const res = await fetch(`${WEBUI_BASE_URL}/api/tasks/chat/${chat_id}`, { + const res = await fetch(`${WEBUI_BASE_URL}/api/tasks/chat/${encodeURIComponent(chat_id)}`, { method: 'GET', headers: { Accept: 'application/json', diff --git a/src/lib/apis/notes/index.ts b/src/lib/apis/notes/index.ts index 07e249a889..80f0413bbc 100644 --- a/src/lib/apis/notes/index.ts +++ b/src/lib/apis/notes/index.ts @@ -313,3 +313,65 @@ export const deleteNoteById = async (token: string, id: string) => { return res; }; + +export const getPinnedNoteList = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/notes/pinned`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res ?? []; +}; + +export const toggleNotePinnedStatusById = async (token: string, id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/notes/${id}/pin`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + authorization: `Bearer ${token}` + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err.detail; + console.error(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; diff --git a/src/lib/components/AddToolServerModal.svelte b/src/lib/components/AddToolServerModal.svelte index d146aeca0c..74571c3086 100644 --- a/src/lib/components/AddToolServerModal.svelte +++ b/src/lib/components/AddToolServerModal.svelte @@ -78,21 +78,20 @@ return; } + if (auth_type === 'oauth_2.1_static' && (!oauthClientId || !oauthClientSecret)) { + toast.error($i18n.t('Please enter Client ID and Client Secret')); + return; + } + + // client_id is the tool server ID (used as the internal lookup key for both flows). + // For static, client_secret signals the backend to use the static credential path. + // The actual OAuth client_id/secret come from the connection info at save time. const formData: { url: string; client_id: string; client_secret?: string } = { url: url, - client_id: id + client_id: id, + ...(auth_type === 'oauth_2.1_static' ? { client_secret: oauthClientSecret } : {}) }; - // For static OAuth, include client credentials - if (auth_type === 'oauth_2.1_static') { - if (!oauthClientId || !oauthClientSecret) { - toast.error($i18n.t('Please enter Client ID and Client Secret')); - return; - } - formData.client_id = id; - formData.client_secret = oauthClientSecret; - } - const res = await registerOAuthClient(localStorage.token, formData, 'mcp').catch((err) => { toast.error($i18n.t('Registration failed')); return null; diff --git a/src/lib/components/AutomationModal.svelte b/src/lib/components/AutomationModal.svelte index c1843f20e7..c16265515e 100644 --- a/src/lib/components/AutomationModal.svelte +++ b/src/lib/components/AutomationModal.svelte @@ -8,7 +8,6 @@ import ScheduleDropdown from '$lib/components/automations/ScheduleDropdown.svelte'; import ModelDropdown from '$lib/components/automations/ModelDropdown.svelte'; - import TerminalDropdown from '$lib/components/automations/TerminalDropdown.svelte'; import { createAutomation, @@ -16,7 +15,6 @@ type AutomationForm, type AutomationResponse } from '$lib/apis/automations'; - import { getTerminalServers, type TerminalServer } from '$lib/apis/terminal/index'; const i18n = getContext('i18n'); const dispatch = createEventDispatcher(); @@ -31,11 +29,6 @@ let loading = false; - // Terminal state - let terminalServers: TerminalServer[] = []; - let terminalServerId = ''; - let terminalCwd = ''; - // Schedule dropdown ref let scheduleDropdown: ScheduleDropdown; @@ -58,15 +51,7 @@ data: { prompt: prompt.trim(), model_id: model_id.trim(), - rrule: scheduleDropdown.buildRrule(), - ...(terminalServerId - ? { - terminal: { - server_id: terminalServerId, - ...(terminalCwd.trim() ? { cwd: terminalCwd.trim() } : {}) - } - } - : {}) + rrule: scheduleDropdown.buildRrule() }, is_active }; @@ -90,20 +75,11 @@ }; const init = async () => { - // Load terminal servers - try { - terminalServers = await getTerminalServers(localStorage.token); - } catch { - terminalServers = []; - } - if (automation) { name = automation.name; prompt = automation.data.prompt; model_id = automation.data.model_id; is_active = automation.is_active; - terminalServerId = automation.data.terminal?.server_id || ''; - terminalCwd = automation.data.terminal?.cwd || ''; if (scheduleDropdown) { scheduleDropdown.parseRrule(automation.data.rrule); } @@ -112,8 +88,6 @@ prompt = ''; model_id = ''; is_active = true; - terminalServerId = ''; - terminalCwd = ''; } }; @@ -158,14 +132,6 @@ - -
diff --git a/src/lib/components/automations/AutomationEditor.svelte b/src/lib/components/automations/AutomationEditor.svelte index fbdf448179..463cb0fa70 100644 --- a/src/lib/components/automations/AutomationEditor.svelte +++ b/src/lib/components/automations/AutomationEditor.svelte @@ -19,7 +19,6 @@ type AutomationResponse, type AutomationRunModel } from '$lib/apis/automations'; - import { getTerminalServers, type TerminalServer } from '$lib/apis/terminal/index'; import Spinner from '$lib/components/common/Spinner.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; @@ -29,7 +28,6 @@ import ScheduleDropdown from '$lib/components/automations/ScheduleDropdown.svelte'; import ModelDropdown from '$lib/components/automations/ModelDropdown.svelte'; - import TerminalDropdown from '$lib/components/automations/TerminalDropdown.svelte'; dayjs.extend(relativeTime); dayjs.extend(localizedFormat); @@ -43,10 +41,6 @@ let model_id = ''; let is_active = true; - let terminalServers: TerminalServer[] = []; - let terminalServerId = ''; - let terminalCwd = ''; - let loading = false; let saving = false; let showDeleteConfirm = false; @@ -97,15 +91,7 @@ data: { prompt: prompt.trim(), model_id: model_id.trim(), - rrule: scheduleDropdown.buildRrule(), - ...(terminalServerId - ? { - terminal: { - server_id: terminalServerId, - ...(terminalCwd.trim() ? { cwd: terminalCwd.trim() } : {}) - } - } - : {}) + rrule: scheduleDropdown.buildRrule() }, is_active }; @@ -204,18 +190,11 @@ prompt = automation.data.prompt; model_id = automation.data.model_id; is_active = automation.is_active; - terminalServerId = automation.data.terminal?.server_id || ''; - terminalCwd = automation.data.terminal?.cwd || ''; if (scheduleDropdown) { scheduleDropdown.parseRrule(automation.data.rrule); } - try { - terminalServers = await getTerminalServers(localStorage.token); - } catch { - terminalServers = []; - } await loadRuns(); }); @@ -354,21 +333,6 @@ {$i18n.t('Model')}
- - - {#if terminalServers.length > 0} -
- {$i18n.t('Terminal')} - -
- {/if} diff --git a/src/lib/components/automations/ScheduleDropdown.svelte b/src/lib/components/automations/ScheduleDropdown.svelte index 67f4fbce69..4f7b63a541 100644 --- a/src/lib/components/automations/ScheduleDropdown.svelte +++ b/src/lib/components/automations/ScheduleDropdown.svelte @@ -1,9 +1,10 @@ + +{#if terminals.length > 0} +
+
{$i18n.t('Terminal')}
+
+ + +{/if} diff --git a/src/lib/i18n/locales/ar-BH/translation.json b/src/lib/i18n/locales/ar-BH/translation.json index 34647cd1f6..c390eff0fd 100644 --- a/src/lib/i18n/locales/ar-BH/translation.json +++ b/src/lib/i18n/locales/ar-BH/translation.json @@ -458,6 +458,7 @@ "Create a new note": "", "Create Account": "إنشاء حساب", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -483,6 +484,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "مظلم", @@ -973,6 +975,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1047,6 +1050,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "كيف استطيع مساعدتك اليوم؟", "How would you rate this response?": "", @@ -1269,10 +1273,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "تم تحميل النموذج '{{modelName}}' بنجاح", "Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل", - "Model {{modelId}} not found": "لم يتم العثور على النموذج {{modelId}}.", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "نموذج {{modelName}} غير قادر على الرؤية", "Model {{name}} is now {{status}}": "نموذج {{name}} هو الآن {{status}}", @@ -1313,6 +1317,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "المزيد", "More Concise": "", "More options": "", @@ -1433,6 +1438,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama الاصدار", "On": "تشغيل", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1517,6 +1523,7 @@ "Persistent": "", "Personalization": "التخصيص", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1681,6 +1688,7 @@ "Running": "", "Running...": "جارٍ التنفيذ...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "حفظ", "Save & Create": "حفظ وإنشاء", "Save & Update": "حفظ وتحديث", @@ -1897,6 +1905,7 @@ "STT Model": "", "STT Settings": "STT اعدادات", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1942,6 +1951,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "محرك تحويل النص إلى كلام", + "Th_day_of_week": "", "Thanks for your feedback!": "شكرا لملاحظاتك!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2055,6 +2065,7 @@ "TTS Model": "", "TTS Settings": "TTS اعدادات", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "نوع", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "اكتب عنوان URL لحل مشكلة الوجه (تنزيل).", @@ -2157,6 +2168,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "", "Web Loader Engine": "", @@ -2173,6 +2185,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ar/translation.json b/src/lib/i18n/locales/ar/translation.json index 9e64b36427..50efd0c748 100644 --- a/src/lib/i18n/locales/ar/translation.json +++ b/src/lib/i18n/locales/ar/translation.json @@ -458,6 +458,7 @@ "Create a new note": "", "Create Account": "إنشاء حساب", "Create Admin Account": "إنشاء حساب مسؤول", + "Create and manage scheduled automations": "", "Create Channel": "إنشاء قناة", "Create Folder": "", "Create Image": "", @@ -483,6 +484,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "منطقة الخطر", "Dark": "داكن", @@ -973,6 +975,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "وضع السياق الكامل", "Function": "وظيفة", "Function Calling": "استدعاء الوظائف", @@ -1047,6 +1050,7 @@ "History": "", "Home": "الصفحة الرئيسية", "Host": "المضيف", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "كيف استطيع مساعدتك اليوم؟", "How would you rate this response?": "كيف تقيّم هذا الرد؟", @@ -1269,10 +1273,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "النموذج", "Model '{{modelName}}' has been successfully downloaded.": "تم تحميل النموذج '{{modelName}}' بنجاح", "Model '{{modelTag}}' is already in queue for downloading.": "النموذج '{{modelTag}}' موجود بالفعل في قائمة الانتظار للتحميل", - "Model {{modelId}} not found": "لم يتم العثور على النموذج {{modelId}}.", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "نموذج {{modelName}} غير قادر على الرؤية", "Model {{name}} is now {{status}}": "نموذج {{name}} هو الآن {{status}}", @@ -1313,6 +1317,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "مفتاح API لـ Mojeek Search", + "Monthly": "", "More": "المزيد", "More Concise": "", "More options": "", @@ -1433,6 +1438,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama الاصدار", "On": "تشغيل", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1517,6 +1523,7 @@ "Persistent": "", "Personalization": "التخصيص", "Pin": "تثبيت", + "Pin to Sidebar": "", "Pinned": "مثبت", "Pinned Messages": "", "Pinned Models": "", @@ -1681,6 +1688,7 @@ "Running": "جارٍ التنفيذ", "Running...": "جارٍ التنفيذ...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "حفظ", "Save & Create": "حفظ وإنشاء", "Save & Update": "حفظ وتحديث", @@ -1897,6 +1905,7 @@ "STT Model": "نموذج تحويل الصوت إلى نص (STT)", "STT Settings": "STT اعدادات", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1942,6 +1951,7 @@ "Text Splitter": "تقسيم النص", "Text-to-Speech": "", "Text-to-Speech Engine": "محرك تحويل النص إلى كلام", + "Th_day_of_week": "", "Thanks for your feedback!": "شكرا لملاحظاتك!", "The Application Account DN you bind with for search": "DN لحساب التطبيق الذي تستخدمه للبحث", "The base to search for users": "الأساس الذي يُستخدم للبحث عن المستخدمين", @@ -2055,6 +2065,7 @@ "TTS Model": "نموذج تحويل النص إلى كلام (TTS)", "TTS Settings": "TTS اعدادات", "TTS Voice": "صوت TTS", + "Tu_day_of_week": "", "Type": "نوع", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "اكتب عنوان URL لحل مشكلة الوجه (تنزيل).", @@ -2157,6 +2168,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "تحذير: تفعيل هذا الخيار سيسمح للمستخدمين برفع كود عشوائي على الخادم.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "تحذير: تنفيذ كود Jupyter يتيح تنفيذ كود عشوائي مما يشكل مخاطر أمنية جسيمة—تابع بحذر شديد.", + "We_day_of_week": "", "Web": "Web", "Web API": "واجهة برمجة التطبيقات (API)", "Web Loader Engine": "", @@ -2173,6 +2185,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "ستقوم WebUI بإرسال الطلبات إلى \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "ستقوم WebUI بإرسال الطلبات إلى \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "ما الذي تحاول تحقيقه؟", "What are you working on?": "على ماذا تعمل؟", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/az-AZ/translation.json b/src/lib/i18n/locales/az-AZ/translation.json index e9c75e8671..5aa624f6d3 100644 --- a/src/lib/i18n/locales/az-AZ/translation.json +++ b/src/lib/i18n/locales/az-AZ/translation.json @@ -454,6 +454,7 @@ "Create a new note": "Yeni qeyd yarat", "Create Account": "Hesab yarat", "Create Admin Account": "Admin hesabı yarat", + "Create and manage scheduled automations": "", "Create Channel": "Kanal yarat", "Create Folder": "Qovluq yarat", "Create Image": "Şəkil yarat", @@ -479,6 +480,7 @@ "Custom Gender": "Fərdi cins", "Custom Parameter Name": "Fərdi parametr adı", "Custom Parameter Value": "Fərdi parametr dəyəri", + "Daily": "", "Daily Messages": "Gündəlik mesajlar", "Danger Zone": "Təhlükəli zona", "Dark": "Tünd", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Autentifikasiya üçün sistem istifadəçisinin OAuth giriş tokenini yönləndirir", "Forwards system user session credentials to authenticate": "Autentifikasiya üçün sistem istifadəçisinin sessiya məlumatlarını yönləndirir", + "Fr_day_of_week": "", "Full Context Mode": "Tam kontekst rejimi", "Function": "Funksiya", "Function Calling": "Funksiya çağırışı", @@ -1043,6 +1046,7 @@ "History": "Tarixçə", "Home": "Ana səhifə", "Host": "Host", + "Hourly": "", "Hourly Messages": "Saatlıq mesajlar", "How can I help you today?": "Bu gün sizə necə kömək edə bilərəm?", "How would you rate this response?": "Bu cavabı necə qiymətləndirərdiniz?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR API açarı tələb olunur.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' modeli uğurla yükləndi.", "Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' modeli artıq yükləmə növbəsindədir.", - "Model {{modelId}} not found": "{{modelId}} modeli tapılmadı", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "{{modelName}} modeli görüntünü tanıma (vision) qabiliyyətinə malik deyil", "Model {{name}} is now {{status}}": "{{name}} modeli indi {{status}} statusundadır", @@ -1309,6 +1313,7 @@ "Models Sharing": "Modellərin paylaşılması", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek Axtarış API Açarı", + "Monthly": "", "More": "Daha çox", "More Concise": "Daha yığcam", "More options": "Daha çox seçim", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Ollama Cloud API Açarı", "Ollama Version": "Ollama Versiyası", "On": "Açıq", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Yalnız \"Böyük mətni fayl kimi yapışdır\" ayarı aktiv olduqda işləyir.", "Only active when the chat input is in focus and an LLM is generating a response.": "Yalnız çat girişi fokusda olduqda və LLM cavab yaratdıqda aktiv olur.", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Fərdiləşdirmə", "Pin": "Bərkit", + "Pin to Sidebar": "", "Pinned": "Bərkidilib", "Pinned Messages": "Bərkidilmiş mesajlar", "Pinned Models": "Bərkidilmiş modellər", @@ -1673,6 +1680,7 @@ "Running": "İcra edilir", "Running...": "İcra edilir...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Emalı sürətləndirmək üçün yerləşdirmə (embedding) tapşırıqlarını eyni vaxtda icra edir. Əgər sorğu limiti problemi yaranarsa, bunu söndürün.", + "Sa_day_of_week": "", "Save": "Yadda saxla", "Save & Create": "Saxla və Yarat", "Save & Update": "Saxla və Yenilə", @@ -1889,6 +1897,7 @@ "STT Model": "STT (Səsdən mətnə) Modeli", "STT Settings": "STT Ayarları", "Stylized PDF Export": "Stil verilmiş PDF ixracı", + "Su_day_of_week": "", "Submit question": "Sualı göndər", "Submit suggestion": "Təklifi göndər", "Subtitle": "Altyazı", @@ -1934,6 +1943,7 @@ "Text Splitter": "Mətn bölücü", "Text-to-Speech": "Mətndən səsə (TTS)", "Text-to-Speech Engine": "Mətndən səsə mühərriki", + "Th_day_of_week": "", "Thanks for your feedback!": "Rəyiniz üçün təşəkkürlər!", "The Application Account DN you bind with for search": "Axtarış üçün bağladığınız Tətbiq Hesabı DN (Application Account DN)", "The base to search for users": "İstifadəçilərin axtarışı üçün baza", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS Modeli", "TTS Settings": "TTS Ayarları", "TTS Voice": "TTS Səsi", + "Tu_day_of_week": "", "Type": "Növ", "Type here...": "Bura yazın...", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (Yükləmə) URL-ni yazın", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Xəbərdarlıq: Bunun aktiv edilməsi istifadəçilərə serverə ixtiyari kod yükləməyə icazə verəcək.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Xəbərdarlıq: Jupyter icrası ixtiyari kodun işlədilməsinə imkan verir və ciddi təhlükəsizlik riskləri yaradır — son dərəcə ehtiyatlı olun.", + "We_day_of_week": "", "Web": "Veb", "Web API": "Veb API", "Web Loader Engine": "Veb yükləyici mühərrik", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI \"{{url}}\" ünvanına sorğular göndərəcək", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI \"{{url}}/api/chat\" ünvanına sorğular göndərəcək", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI \"{{url}}/chat/completions\" ünvanına sorğular göndərəcək", + "Weekly": "", "What are you trying to achieve?": "Nəyə nail olmaq istəyirsiniz?", "What are you working on?": "Nəyin üzərində işləyirsiniz?", "What is NOT shared:": "Nələr paylaşılmır:", diff --git a/src/lib/i18n/locales/bg-BG/translation.json b/src/lib/i18n/locales/bg-BG/translation.json index e425f2063d..07ca792153 100644 --- a/src/lib/i18n/locales/bg-BG/translation.json +++ b/src/lib/i18n/locales/bg-BG/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Създаване на Акаунт", "Create Admin Account": "Създаване на администраторски акаунт", + "Create and manage scheduled automations": "", "Create Channel": "Създаване на канал", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Тъмен", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "Режим на пълен контекст", "Function": "Функция", "Function Calling": "Извикване на функция", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Начало", "Host": "Хост", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Как мога да ви помогна днес?", "How would you rate this response?": "Как бихте оценили този отговор?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Модел", "Model '{{modelName}}' has been successfully downloaded.": "Моделът '{{modelName}}' беше успешно свален.", "Model '{{modelTag}}' is already in queue for downloading.": "Моделът '{{modelTag}}' е вече в очакване за сваляне.", - "Model {{modelId}} not found": "Моделът {{modelId}} не е намерен", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Моделът {{modelName}} не поддържа визуални възможности", "Model {{name}} is now {{status}}": "Моделът {{name}} сега е {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "API ключ за Mojeek Search", + "Monthly": "", "More": "Повече", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama Версия", "On": "Вкл.", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Персонализация", "Pin": "Закачи", + "Pin to Sidebar": "", "Pinned": "Закачено", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Изпълнява се", "Running...": "Изпълнява се...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Запис", "Save & Create": "Запис & Създаване", "Save & Update": "Запис & Актуализиране", @@ -1889,6 +1897,7 @@ "STT Model": "STT Модел", "STT Settings": "STT Настройки", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Разделител на текст", "Text-to-Speech": "", "Text-to-Speech Engine": "Двигател за преобразуване на текст в реч", + "Th_day_of_week": "", "Thanks for your feedback!": "Благодарим ви за вашия отзив!", "The Application Account DN you bind with for search": "DN на акаунта на приложението, с който се свързвате за търсене", "The base to search for users": "Базата за търсене на потребители", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS Модел", "TTS Settings": "TTS Настройки", "TTS Voice": "TTS Глас", + "Tu_day_of_week": "", "Type": "Вид", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Въведете Hugging Face Resolve (Изтегляне) URL", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Предупреждение: Активирането на това ще позволи на потребителите да качват произволен код на сървъра.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Предупреждение: Изпълнението на Jupyter позволява произволно изпълнение на код, което представлява сериозни рискове за сигурността-продължете с изключително внимание.", + "We_day_of_week": "", "Web": "Уеб", "Web API": "Уеб API", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI ще прави заявки към \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI ще прави заявки към \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Какво се опитвате да постигнете?", "What are you working on?": "Върху какво работите?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/bn-BD/translation.json b/src/lib/i18n/locales/bn-BD/translation.json index fecb6f5ffe..a20abb0d9a 100644 --- a/src/lib/i18n/locales/bn-BD/translation.json +++ b/src/lib/i18n/locales/bn-BD/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "একাউন্ট তৈরি করুন", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "ডার্ক", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "আপনাকে আজ কিভাবে সাহায্য করতে পারি?", "How would you rate this response?": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' মডেল সফলভাবে ডাউনলোড হয়েছে।", "Model '{{modelTag}}' is already in queue for downloading.": "{{modelTag}} ডাউনলোডের জন্য আগে থেকেই অপেক্ষমান আছে।", - "Model {{modelId}} not found": "{{modelId}} মডেল পাওয়া যায়নি", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "মডেল {{modelName}} দৃষ্টি সক্ষম নয়", "Model {{name}} is now {{status}}": "মডেল {{name}} এখন {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "আরো", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama ভার্সন", "On": "চালু", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "ডিজিটাল বাংলা", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "", "Running...": "চলমান...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "সংরক্ষণ", "Save & Create": "সংরক্ষণ এবং তৈরি করুন", "Save & Update": "সংরক্ষণ এবং আপডেট করুন", @@ -1889,6 +1897,7 @@ "STT Model": "", "STT Settings": "STT সেটিংস", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "টেক্সট-টু-স্পিচ ইঞ্জিন", + "Th_day_of_week": "", "Thanks for your feedback!": "আপনার মতামত ধন্যবাদ!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "", "TTS Settings": "TTS সেটিংসমূহ", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "টাইপ", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Hugging Face থেকে ডাউনলোড করার ইউআরএল টাইপ করুন", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "ওয়েব", "Web API": "", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/bo-TB/translation.json b/src/lib/i18n/locales/bo-TB/translation.json index 30ca5f031a..fef2cb1847 100644 --- a/src/lib/i18n/locales/bo-TB/translation.json +++ b/src/lib/i18n/locales/bo-TB/translation.json @@ -453,6 +453,7 @@ "Create a new note": "", "Create Account": "རྩིས་ཁྲ་གསར་བཟོ།", "Create Admin Account": "དོ་དམ་པའི་རྩིས་ཁྲ་གསར་བཟོ།", + "Create and manage scheduled automations": "", "Create Channel": "བགྲོ་གླེང་གསར་བཟོ།", "Create Folder": "", "Create Image": "", @@ -478,6 +479,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "ཉེན་ཁའི་ས་ཁུལ།", "Dark": "ནག་པོ།", @@ -968,6 +970,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "ནང་དོན་ཆ་ཚང་མ་དཔེ།", "Function": "ལས་འགན།", "Function Calling": "ལས་འགན་འབོད་པ།", @@ -1042,6 +1045,7 @@ "History": "", "Home": "གཙོ་ངོས།", "Host": "Host", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "དེ་རིང་ངས་ཁྱེད་ལ་རོགས་པ་ཅི་ཞིག་བྱེད་ཐུབ་བམ།", "How would you rate this response?": "ལན་འདི་ལ་ཁྱེད་ཀྱིས་སྐར་མ་ག་ཚོད་སྤྲོད་འདོད་དམ།", @@ -1264,10 +1268,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "དཔེ་དབྱིབས།", "Model '{{modelName}}' has been successfully downloaded.": "དཔེ་དབྱིབས། '{{modelName}}' ལེགས་པར་ཕབ་ལེན་བྱས་ཟིན།", "Model '{{modelTag}}' is already in queue for downloading.": "དཔེ་དབྱིབས། '{{modelTag}}' ཕབ་ལེན་གྱི་སྒུག་ཐོ་ནང་ཡོད་ཟིན།", - "Model {{modelId}} not found": "དཔེ་དབྱིབས། {{modelId}} མ་རྙེད།", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "དཔེ་དབྱིབས། {{modelName}} ལ་མཐོང་ནུས་མེད།", "Model {{name}} is now {{status}}": "དཔེ་དབྱིབས། {{name}} ད་ལྟ་ {{status}} ཡིན།", @@ -1308,6 +1312,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Mojeek Search API ལྡེ་མིག", + "Monthly": "", "More": "མང་བ།", "More Concise": "", "More options": "", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama པར་གཞི།", "On": "ཁ་ཕྱེ་བ།", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1512,6 +1518,7 @@ "Persistent": "", "Personalization": "སྒེར་སྤྱོད་ཅན།", "Pin": "གདབ་པ།", + "Pin to Sidebar": "", "Pinned": "གདབ་ཟིན།", "Pinned Messages": "", "Pinned Models": "", @@ -1671,6 +1678,7 @@ "Running": "ལག་བསྟར་བྱེད་བཞིན་པ།", "Running...": "ལག་བསྟར་བྱེད་བཞིན་པ།...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "ཉར་ཚགས།", "Save & Create": "ཉར་ཚགས་ & གསར་བཟོ།", "Save & Update": "ཉར་ཚགས་ & གསར་སྒྱུར།", @@ -1887,6 +1895,7 @@ "STT Model": "STT དཔེ་དབྱིབས།", "STT Settings": "STT སྒྲིག་འགོད།", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1932,6 +1941,7 @@ "Text Splitter": "ཡིག་རྐྱང་བགོ་བྱེད།", "Text-to-Speech": "", "Text-to-Speech Engine": "ཡིག་རྐྱང་ནས་གཏམ་བཤད་ཀྱི་འཕྲུལ་འཁོར།", + "Th_day_of_week": "", "Thanks for your feedback!": "ཁྱེད་ཀྱི་བསམ་འཆར་ལ་ཐུགས་རྗེ་ཆེ།", "The Application Account DN you bind with for search": "ཁྱེད་ཀྱིས་འཚོལ་བཤེར་གྱི་ཆེད་དུ་སྦྲེལ་བའི་ Application Account DN", "The base to search for users": "བེད་སྤྱོད་མཁན་འཚོལ་བའི་གཞི་རྩ།", @@ -2045,6 +2055,7 @@ "TTS Model": "TTS དཔེ་དབྱིབས།", "TTS Settings": "TTS སྒྲིག་འགོད།", "TTS Voice": "TTS སྐད།", + "Tu_day_of_week": "", "Type": "རིགས།", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (ཕབ་ལེན།) URL མནན་པ།", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "ཉེན་བརྡ།: འདི་སྒུལ་བསྐྱོད་བྱས་ན་བེད་སྤྱོད་མཁན་ཚོས་སར་བར་སྟེང་གང་འདོད་ཀྱི་ཀོཌ་སྤར་བར་གནང་བ་སྤྲོད་ངེས།", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "ཉེན་བརྡ།: Jupyter ལག་བསྟར་གྱིས་གང་འདོད་ཀྱི་ཀོཌ་ལག་བསྟར་སྒུལ་བསྐྱོད་བྱས་ནས། བདེ་འཇགས་ཀྱི་ཉེན་ཁ་ཚབས་ཆེན་བཟོ་གི་ཡོད།—ཧ་ཅང་གཟབ་ནན་གྱིས་སྔོན་སྐྱོད་བྱེད་རོགས།", + "We_day_of_week": "", "Web": "དྲ་བ།", "Web API": "Web API", "Web Loader Engine": "", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI ཡིས་ \"{{url}}/api/chat\" ལ་རེ་ཞུ་གཏོང་ངེས།", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI ཡིས་ \"{{url}}/chat/completions\" ལ་རེ་ཞུ་གཏོང་ངེས།", + "Weekly": "", "What are you trying to achieve?": "ཁྱེད་ཀྱིས་ཅི་ཞིག་འགྲུབ་ཐབས་བྱེད་བཞིན་ཡོད།", "What are you working on?": "ཁྱེད་ཀྱིས་ཅི་ཞིག་ལས་ཀ་བྱེད་བཞིན་ཡོད།", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/bs-BA/translation.json b/src/lib/i18n/locales/bs-BA/translation.json index e539d89c1c..223a0ff884 100644 --- a/src/lib/i18n/locales/bs-BA/translation.json +++ b/src/lib/i18n/locales/bs-BA/translation.json @@ -455,6 +455,7 @@ "Create a new note": "", "Create Account": "Stvori račun", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -480,6 +481,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Tamno", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1044,6 +1047,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Kako vam mogu pomoći danas?", "How would you rate this response?": "", @@ -1266,10 +1270,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' je uspješno preuzet.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je već u redu za preuzimanje.", - "Model {{modelId}} not found": "Model {{modelId}} nije pronađen", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} ne čita vizualne impute", "Model {{name}} is now {{status}}": "Model {{name}} sada je {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "Više", "More Concise": "", "More options": "", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama verzija", "On": "Uključeno", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Prilagodba", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1675,6 +1682,7 @@ "Running": "Pokrenuto", "Running...": "Pokrenuto...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Spremi", "Save & Create": "Spremi i stvori", "Save & Update": "Spremi i ažuriraj", @@ -1891,6 +1899,7 @@ "STT Model": "STT model", "STT Settings": "STT postavke", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1936,6 +1945,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "Stroj za pretvorbu teksta u govor", + "Th_day_of_week": "", "Thanks for your feedback!": "Hvala na povratnim informacijama!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2049,6 +2059,7 @@ "TTS Model": "TTS model", "TTS Settings": "TTS postavke", "TTS Voice": "TTS glas", + "Tu_day_of_week": "", "Type": "Tip", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Upišite Hugging Face Resolve (Download) URL", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ca-ES/translation.json b/src/lib/i18n/locales/ca-ES/translation.json index fad81172aa..588e88a9ff 100644 --- a/src/lib/i18n/locales/ca-ES/translation.json +++ b/src/lib/i18n/locales/ca-ES/translation.json @@ -455,6 +455,7 @@ "Create a new note": "Crear una nova nota", "Create Account": "Crear un compte", "Create Admin Account": "Crear un compte d'Administrador", + "Create and manage scheduled automations": "Crear i gestionar les automatitzacions programades", "Create Channel": "Crear un canal", "Create Folder": "Crear carpeta", "Create Image": "Crear imatge", @@ -480,6 +481,7 @@ "Custom Gender": "Gènere personalitzat", "Custom Parameter Name": "Nom del paràmetre personalitzat", "Custom Parameter Value": "Valor del paràmetre personalitzat", + "Daily": "Cada dia", "Daily Messages": "Missatges diaris", "Danger Zone": "Zona de perill", "Dark": "Fosc", @@ -970,6 +972,7 @@ "Forward": "Endavant", "Forwards system user OAuth access token to authenticate": "Reenvia el testimoni d'accés OAuth de l'usuari del sistema per autenticar-se.", "Forwards system user session credentials to authenticate": "Envia les credencials de l'usuari del sistema per autenticar", + "Fr_day_of_week": "Divendres", "Full Context Mode": "Mode de context complert", "Function": "Funció", "Function Calling": "Crida a funcions", @@ -1044,6 +1047,7 @@ "History": "Historial", "Home": "Inici", "Host": "Servidor", + "Hourly": "Cada hora", "Hourly Messages": "Missatges horaris", "How can I help you today?": "Com et puc ajudar avui?", "How would you rate this response?": "Com avaluaries aquesta resposta?", @@ -1266,10 +1270,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "És necessària la clau API de Mistral OCR", "MistralAI": "MistralAI", + "Mo_day_of_week": "Dilluns", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "El model '{{modelName}}' s'ha descarregat correctament.", "Model '{{modelTag}}' is already in queue for downloading.": "El model '{{modelTag}}' ja està en cua per ser descarregat.", - "Model {{modelId}} not found": "No s'ha trobat el model {{modelId}}", "Model {{modelName}} deleted successfully": "El model {{modelName}} s'ha eliminat correctament", "Model {{modelName}} is not vision capable": "El model {{modelName}} no és capaç de visió", "Model {{name}} is now {{status}}": "El model {{name}} ara és {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "Compartir els models", "Mojeek": "Mojeek", "Mojeek Search API Key": "Clau API de Mojeek Search", + "Monthly": "Cada mes", "More": "Més", "More Concise": "Més precís", "More options": "Més opcions", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "Clau API d'Ollama Cloud", "Ollama Version": "Versió d'Ollama", "On": "Activat", + "Once": "Una vegada", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Només està actiu quan l'opció \"Enganxa text gran com a fitxer\" està activada.", "Only active when the chat input is in focus and an LLM is generating a response.": "Només s'activa quan l'entrada del xat està en focus i un LLM està generant una resposta.", @@ -1514,6 +1520,7 @@ "Persistent": "Persistent", "Personalization": "Personalització", "Pin": "Fixar", + "Pin to Sidebar": "Fixar a la barra lateral", "Pinned": "Fixat", "Pinned Messages": "Missatges fixats", "Pinned Models": "Models fixats", @@ -1675,6 +1682,7 @@ "Running": "S'està executant", "Running...": "S'està executant...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Executa tasques d'incrustació simultàniament per accelerar el processament. Desactiva-ho si els límits de velocitat es converteixen en un problema.", + "Sa_day_of_week": "Dissabte", "Save": "Desar", "Save & Create": "Desar i crear", "Save & Update": "Desar i actualitzar", @@ -1891,6 +1899,7 @@ "STT Model": "Model SST", "STT Settings": "Preferències de STT", "Stylized PDF Export": "Exportació en PDF estilitzat", + "Su_day_of_week": "Diumenge", "Submit question": "Enviar la pregunta", "Submit suggestion": "Enviar un suggeriment", "Subtitle": "Subtítol", @@ -1936,6 +1945,7 @@ "Text Splitter": "Separador de text", "Text-to-Speech": "Text-a-veu", "Text-to-Speech Engine": "Motor de text a veu", + "Th_day_of_week": "Dijous", "Thanks for your feedback!": "Gràcies pel teu comentari!", "The Application Account DN you bind with for search": "El DN del compte d'aplicació per realitzar la cerca", "The base to search for users": "La base per cercar usuaris", @@ -2049,6 +2059,7 @@ "TTS Model": "Model TTS", "TTS Settings": "Preferències de TTS", "TTS Voice": "Veu TTS", + "Tu_day_of_week": "Dimarts", "Type": "Tipus", "Type here...": "Escriu aquí...", "Type Hugging Face Resolve (Download) URL": "Escriu la URL de Resolució (Descàrrega) de Hugging Face", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Avís: Si actives aquesta opció, els usuaris podran executar sol·licituds programades automàticament.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Avís: Habilitar això permetrà als usuaris penjar codi arbitrari al servidor.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Avís: l'execució de Jupyter permet l'execució de codi arbitrari, la qual cosa comporta greus riscos de seguretat; procediu amb extrema precaució.", + "We_day_of_week": "Dimecres", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "Motor de càrrega Web", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI farà peticions a \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI farà peticions a \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI farà peticions a \"{{url}}/chat/completions\"", + "Weekly": "Cada setmana", "What are you trying to achieve?": "Què intentes aconseguir?", "What are you working on?": "En què estàs treballant?", "What is NOT shared:": "Què no es comparteix", diff --git a/src/lib/i18n/locales/ceb-PH/translation.json b/src/lib/i18n/locales/ceb-PH/translation.json index 138fa01b1e..34a49f5a86 100644 --- a/src/lib/i18n/locales/ceb-PH/translation.json +++ b/src/lib/i18n/locales/ceb-PH/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Paghimo og account", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Ngitngit", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Unsaon nako pagtabang kanimo karon?", "How would you rate this response?": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "Ang modelo'{{modelName}}' malampuson nga na-download.", "Model '{{modelTag}}' is already in queue for downloading.": "Ang modelo'{{modelTag}}' naa na sa pila para ma-download.", - "Model {{modelId}} not found": "Modelo {{modelId}} wala makit-an", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "", "Model {{name}} is now {{status}}": "", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama nga bersyon", "On": "Gipaandar", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "", "Running...": "Nagdagan...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Tipigi", "Save & Create": "I-save ug Paghimo", "Save & Update": "I-save ug I-update", @@ -1889,6 +1897,7 @@ "STT Model": "", "STT Settings": "Mga setting sa STT", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "Text-to-speech nga makina", + "Th_day_of_week": "", "Thanks for your feedback!": "", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "", "TTS Settings": "Mga Setting sa TTS", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Pagsulod sa resolusyon (pag-download) URL Hugging Face", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/cs-CZ/translation.json b/src/lib/i18n/locales/cs-CZ/translation.json index 6bb5fca5b3..227d6b8a61 100644 --- a/src/lib/i18n/locales/cs-CZ/translation.json +++ b/src/lib/i18n/locales/cs-CZ/translation.json @@ -456,6 +456,7 @@ "Create a new note": "", "Create Account": "Vytvořit účet", "Create Admin Account": "Vytvořit účet administrátora", + "Create and manage scheduled automations": "", "Create Channel": "Vytvořit kanál", "Create Folder": "Vytvořit složku", "Create Image": "", @@ -481,6 +482,7 @@ "Custom Gender": "", "Custom Parameter Name": "Název vlastního parametru", "Custom Parameter Value": "Hodnota vlastního parametru", + "Daily": "", "Daily Messages": "", "Danger Zone": "Nebezpečná zóna", "Dark": "Tmavý", @@ -971,6 +973,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Přeposílá přihlašovací údaje relace systémového uživatele pro ověření", + "Fr_day_of_week": "", "Full Context Mode": "Režim plného kontextu", "Function": "Funkce", "Function Calling": "function calling", @@ -1045,6 +1048,7 @@ "History": "", "Home": "Domů", "Host": "Hostitel", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Jak vám dnes mohu pomoci?", "How would you rate this response?": "Jak byste ohodnotili tuto odpověď?", @@ -1267,10 +1271,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Je vyžadován API klíč pro Mistral OCR.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' byl úspěšně stažen.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je již ve frontě na stažení.", - "Model {{modelId}} not found": "Model {{modelId}} nenalezen", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} nemá schopnost zpracování obrazu.", "Model {{name}} is now {{status}}": "Model {{name}} je nyní {{status}}.", @@ -1311,6 +1315,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "API klíč pro Mojeek Search", + "Monthly": "", "More": "Více", "More Concise": "Stručnější", "More options": "", @@ -1431,6 +1436,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Verze Ollama", "On": "Zapnuto", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1515,6 +1521,7 @@ "Persistent": "", "Personalization": "Personalizace", "Pin": "Připnout", + "Pin to Sidebar": "", "Pinned": "Připnuto", "Pinned Messages": "", "Pinned Models": "", @@ -1677,6 +1684,7 @@ "Running": "Běží", "Running...": "Běží...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Uložit", "Save & Create": "Uložit a vytvořit", "Save & Update": "Uložit a aktualizovat", @@ -1893,6 +1901,7 @@ "STT Model": "Model STT", "STT Settings": "Nastavení STT", "Stylized PDF Export": "Stylizovaný export do PDF", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1938,6 +1947,7 @@ "Text Splitter": "Rozdělovač textu", "Text-to-Speech": "Převod textu na řeč", "Text-to-Speech Engine": "Jádro pro převod textu na řeč", + "Th_day_of_week": "", "Thanks for your feedback!": "Děkujeme za vaši zpětnou vazbu!", "The Application Account DN you bind with for search": "DN aplikačního účtu, se kterým se vážete pro vyhledávání", "The base to search for users": "Základ pro vyhledávání uživatelů", @@ -2051,6 +2061,7 @@ "TTS Model": "Model TTS", "TTS Settings": "Nastavení TTS", "TTS Voice": "Hlas TTS", + "Tu_day_of_week": "", "Type": "Typ", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Zadejte URL pro stažení z Hugging Face.", @@ -2153,6 +2164,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Varování: Povolení této volby umožní uživatelům nahrávat na server libovolný kód.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Varování: Spouštění Jupyteru umožňuje provádění libovolného kódu, což představuje vážná bezpečnostní rizika – postupujte s maximální opatrností.", + "We_day_of_week": "", "Web": "Web", "Web API": "Webové API", "Web Loader Engine": "Jádro webového zavaděče", @@ -2169,6 +2181,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI bude odesílat požadavky na \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI bude odesílat požadavky na \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI bude odesílat požadavky na \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Čeho se snažíte dosáhnout?", "What are you working on?": "Na čem pracujete?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/da-DK/translation.json b/src/lib/i18n/locales/da-DK/translation.json index 58c6d6adb1..5e0e4af930 100644 --- a/src/lib/i18n/locales/da-DK/translation.json +++ b/src/lib/i18n/locales/da-DK/translation.json @@ -454,6 +454,7 @@ "Create a new note": "Opret ny note", "Create Account": "Opret profil", "Create Admin Account": "Opret administrator profil", + "Create and manage scheduled automations": "", "Create Channel": "Opret kanal", "Create Folder": "Opret mappe", "Create Image": "Opret billede", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "Brugerdefineret parameternavn", "Custom Parameter Value": "Brugerdefineret parameterværdi", + "Daily": "", "Daily Messages": "", "Danger Zone": "Danger Zone", "Dark": "Mørk", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Videresender system bruger OAuth access token til autentificering", "Forwards system user session credentials to authenticate": "Videresender system bruger session credentials til autentificering", + "Fr_day_of_week": "", "Full Context Mode": "Fuld kontekst tilstand", "Function": "Funktion", "Function Calling": "Funktionskald", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Hjem", "Host": "Vært", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Hvordan kan jeg hjælpe dig i dag?", "How would you rate this response?": "Hvordan vurderer du dette svar?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR API nøgle påkrævet.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' er blevet downloadet.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' er allerede i kø til download.", - "Model {{modelId}} not found": "Model {{modelId}} ikke fundet", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} understøtter ikke billeder", "Model {{name}} is now {{status}}": "Model {{name}} er nu {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "Modeldeling", "Mojeek": "", "Mojeek Search API Key": "Mojeek Search API nøgle", + "Monthly": "", "More": "Mere", "More Concise": "Mere kortfattet", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Ollama Cloud API nøgle", "Ollama Version": "Ollama-version", "On": "Til", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Kun aktiv når \"Indsæt store tekster som fil\" indstillingen er slået til.", "Only active when the chat input is in focus and an LLM is generating a response.": "Kun aktiv når chat-input er fokuseret og en LLM er ved at generere et svar.", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Personalisering", "Pin": "Fastgør", + "Pin to Sidebar": "", "Pinned": "Fastgjort", "Pinned Messages": "Fastgjorte beskeder", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Kører", "Running...": "Kører...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Kører embedding-opgaver sideløbende for at fremskynde behandlingen. Slå fra hvis hastighedsbegrænsninger bliver et problem.", + "Sa_day_of_week": "", "Save": "Gem", "Save & Create": "Gem og opret", "Save & Update": "Gem og opdater", @@ -1889,6 +1897,7 @@ "STT Model": "STT-model", "STT Settings": "STT-indstillinger", "Stylized PDF Export": "Stiliseret PDF eksport", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "Undertekst", @@ -1934,6 +1943,7 @@ "Text Splitter": "Tekstopdeler", "Text-to-Speech": "Tekst-til-tale", "Text-to-Speech Engine": "Tekst-til-tale-engine", + "Th_day_of_week": "", "Thanks for your feedback!": "Tak for din feedback!", "The Application Account DN you bind with for search": "Application Account DN du binder med til søgning", "The base to search for users": "Basen til at søge efter brugere", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS-model", "TTS Settings": "TTS-indstillinger", "TTS Voice": "TTS-stemme", + "Tu_day_of_week": "", "Type": "Type", "Type here...": "Skriv her...", "Type Hugging Face Resolve (Download) URL": "Indtast Hugging Face Resolve (Download) URL", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Advarsel: Hvis du aktiverer dette, vil brugerne kunne uploade vilkårlig kode på serveren.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Advarsel: Jupyter-udførelse gør det muligt at udføre vilkårlig kode, hvilket udfordrer alvorlige sikkerhedsrisici - fortsæt med ekstremt omhyggelighed.", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "Web indlæser motor", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI vil lave forespørgsler til \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI vil lave forespørgsler til \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI vil lave forespørgsler til \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Hvad prøver du at opnå?", "What are you working on?": "Hvad arbejder du på?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/de-DE/translation.json b/src/lib/i18n/locales/de-DE/translation.json index 4fe9f1a2cb..1fbfdba8ce 100644 --- a/src/lib/i18n/locales/de-DE/translation.json +++ b/src/lib/i18n/locales/de-DE/translation.json @@ -454,6 +454,7 @@ "Create a new note": "Neue Notiz erstellen", "Create Account": "Konto erstellen", "Create Admin Account": "Admin-Konto erstellen", + "Create and manage scheduled automations": "Erstelle und manage geplante Automatisierungen", "Create Channel": "Kanal erstellen", "Create Folder": "Ordner erstellen", "Create Image": "Bild erstellen", @@ -479,6 +480,7 @@ "Custom Gender": "Benutzerdefiniertes Geschlecht", "Custom Parameter Name": "Name des benutzerdef. Parameters", "Custom Parameter Value": "Wert des benutzerdef. Parameters", + "Daily": "Täglich", "Daily Messages": "Tägliche Nachrichten", "Danger Zone": "Gefahrenzone", "Dark": "Dunkel", @@ -969,6 +971,7 @@ "Forward": "Weiterleiten", "Forwards system user OAuth access token to authenticate": "Leitet OAuth-Zugriffstoken des Systembenutzers zur Authentifizierung weiter", "Forwards system user session credentials to authenticate": "Leitet Sitzungsdaten des Systembenutzers zur Authentifizierung weiter", + "Fr_day_of_week": "Fr", "Full Context Mode": "Vollkontext-Modus", "Function": "Funktion", "Function Calling": "Funktionsaufruf", @@ -1043,6 +1046,7 @@ "History": "History", "Home": "Startseite", "Host": "Host", + "Hourly": "Stündlich", "Hourly Messages": "Stündliche Nachrichten", "How can I help you today?": "Wie kann ich Ihnen heute helfen?", "How would you rate this response?": "Wie bewerten Sie diese Antwort?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral-OCR-API-Schlüssel erforderlich.", "MistralAI": "MistralAI", + "Mo_day_of_week": "Mo", "Model": "Modell", "Model '{{modelName}}' has been successfully downloaded.": "Modell '{{modelName}}' wurde erfolgreich heruntergeladen.", "Model '{{modelTag}}' is already in queue for downloading.": "Modell '{{modelTag}}' befindet sich bereits in der Warteschlange.", - "Model {{modelId}} not found": "Modell {{modelId}} nicht gefunden", "Model {{modelName}} deleted successfully": "Modell {{modelName}} erfolgreich gelöscht", "Model {{modelName}} is not vision capable": "Das Modell {{modelName}} unterstützt keine Bilderkennung", "Model {{name}} is now {{status}}": "Modell {{name}} ist jetzt {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "Modelle teilen", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek Search API-Schlüssel", + "Monthly": "Monatlich", "More": "Mehr", "More Concise": "Kürzer", "More options": "Mehr Optionen", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Ollama Cloud API-Schlüssel", "Ollama Version": "Ollama-Version", "On": "Ein", + "Once": "Einmalig", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Nur aktiv, wenn die Einstellung „Großen Text als Datei einfügen“ aktiviert ist.", "Only active when the chat input is in focus and an LLM is generating a response.": "Nur aktiv, wenn das Chat-Eingabefeld fokussiert ist und ein LLM eine Antwort generiert.", @@ -1513,6 +1519,7 @@ "Persistent": "Persistent", "Personalization": "Personalisierung", "Pin": "Anheften", + "Pin to Sidebar": "An Seitenleiste anheften", "Pinned": "Angeheftet", "Pinned Messages": "Angeheftete Nachrichten", "Pinned Models": "Angepinnte Modelle", @@ -1673,6 +1680,7 @@ "Running": "Läuft", "Running...": "Läuft...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Führt Embedding-Aufgaben parallel aus, um die Verarbeitung zu beschleunigen. Deaktivieren Sie dies, falls Rate-Limits oder Ressourcenprobleme auftreten.", + "Sa_day_of_week": "Sa", "Save": "Speichern", "Save & Create": "Speichern & Erstellen", "Save & Update": "Speichern & Aktualisieren", @@ -1889,6 +1897,7 @@ "STT Model": "STT-Modell", "STT Settings": "STT-Einstellungen", "Stylized PDF Export": "Stilisierter PDF-Export", + "Su_day_of_week": "So", "Submit question": "Frage absenden", "Submit suggestion": "Vorschlag absenden", "Subtitle": "Untertitel", @@ -1934,6 +1943,7 @@ "Text Splitter": "Text-Splitter", "Text-to-Speech": "Text-zu-Sprache", "Text-to-Speech Engine": "Text-zu-Sprache-Engine", + "Th_day_of_week": "Do", "Thanks for your feedback!": "Danke für Ihr Feedback!", "The Application Account DN you bind with for search": "Der Anwendungs-Konto-DN für die Suche", "The base to search for users": "Die Basis, in der nach Benutzern gesucht wird", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS-Modell", "TTS Settings": "TTS-Einstellungen", "TTS Voice": "TTS-Stimme", + "Tu_day_of_week": "Di", "Type": "Typ", "Type here...": "Hier eingeben...", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (Download) URL eingeben", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Warnung: Wenn Sie dies aktivieren, können Nutzer geplante Prompts automatisch ausführen.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Warnung: Wenn Sie dies aktivieren, können Benutzer beliebigen Code auf den Server hochladen.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Warnung: Die Jupyter-Ausführung ermöglicht beliebige Codeausführung und birgt erhebliche Sicherheitsrisiken – gehen Sie mit äußerster Vorsicht vor.", + "We_day_of_week": "Mi", "Web": "Web", "Web API": "Web-API", "Web Loader Engine": "Web-Loader-Engine", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI wird Anfragen an \"{{url}}\" senden", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI wird Anfragen an \"{{url}}/api/chat\" senden", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI wird Anfragen an \"{{url}}/chat/completions\" senden", + "Weekly": "Wöchentlich", "What are you trying to achieve?": "Was möchten Sie erreichen?", "What are you working on?": "Woran arbeiten Sie?", "What is NOT shared:": "Was NICHT geteilt wird:", diff --git a/src/lib/i18n/locales/dg-DG/translation.json b/src/lib/i18n/locales/dg-DG/translation.json index c4a87b0687..9f2ad4240f 100644 --- a/src/lib/i18n/locales/dg-DG/translation.json +++ b/src/lib/i18n/locales/dg-DG/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Create Account", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Dark", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "How can I halp u today?", "How would you rate this response?": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' has been successfully downloaded.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' is already in queue for downloading.", - "Model {{modelId}} not found": "Model {{modelId}} not found", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "", "Model {{name}} is now {{status}}": "", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama Version", "On": "On", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Personalization", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "", "Running...": "Running... wow", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Save much wow", "Save & Create": "Save & Create much create", "Save & Update": "Save & Update much update", @@ -1889,6 +1897,7 @@ "STT Model": "", "STT Settings": "STT Settings very settings", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "Text-to-Speech Engine much speak", + "Th_day_of_week": "", "Thanks for your feedback!": "", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "", "TTS Settings": "TTS Settings much settings", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL much download", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web very web", "Web API": "", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/el-GR/translation.json b/src/lib/i18n/locales/el-GR/translation.json index fe4da3a8d4..6e347a9e94 100644 --- a/src/lib/i18n/locales/el-GR/translation.json +++ b/src/lib/i18n/locales/el-GR/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Δημιουργία Λογαριασμού", "Create Admin Account": "Δημιουργία Λογαριασμού Διαχειριστή", + "Create and manage scheduled automations": "", "Create Channel": "Δημιουργία Καναλιού", "Create Folder": "Δημιουργία Φακέλου", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "Περιοχή Κινδύνου", "Dark": "Σκούρο", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "Λειτουργία χρήσης όλων των συμφραζομένων", "Function": "Λειτουργία", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Αρχική", "Host": "Διακομιστής", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Πώς μπορώ να σας βοηθήσω σήμερα;", "How would you rate this response?": "Πώς θα βαθμολογούσατε αυτή την απάντηση;", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "Απαιτείται το API κλειδί του Mistral OCR.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Μοντέλο", "Model '{{modelName}}' has been successfully downloaded.": "Το μοντέλο '{{modelName}}' κατεβάστηκε με επιτυχία.", "Model '{{modelTag}}' is already in queue for downloading.": "Το μοντέλο '{{modelTag}}' βρίσκεται ήδη στην ουρά για λήψη.", - "Model {{modelId}} not found": "Το μοντέλο {{modelId}} δεν βρέθηκε", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Το μοντέλο {{modelName}} δεν έχει δυνατότητα όρασης", "Model {{name}} is now {{status}}": "Το μοντέλο {{name}} είναι τώρα {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Κλειδί API Mojeek Search", + "Monthly": "", "More": "Περισσότερα", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Έκδοση Ollama", "On": "Ενεργό", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Προσωποποίηση", "Pin": "Καρφίτσωμα", + "Pin to Sidebar": "", "Pinned": "Καρφιτσωμένο", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Εκτέλεση", "Running...": "Εκτέλεση...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Αποθήκευση", "Save & Create": "Αποθήκευση & Δημιουργία", "Save & Update": "Αποθήκευση & Ενημέρωση", @@ -1889,6 +1897,7 @@ "STT Model": "Μοντέλο Μετατροπής Ομιλίας σε Κείμενο", "STT Settings": "Ρυθμίσεις Μετατροπής Ομιλίας σε Κείμενο", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Διαχωριστής Κειμένου", "Text-to-Speech": "Κείμενο σε Ομιλία", "Text-to-Speech Engine": "Μηχανή Ομιλίας σε Κείμενο", + "Th_day_of_week": "", "Thanks for your feedback!": "Ευχαριστούμε για τα σχόλιά σας!", "The Application Account DN you bind with for search": "Το DN του Λογαριασμού Εφαρμογής που συνδέετε για αναζήτηση", "The base to search for users": "Η βάση για αναζήτηση χρηστών", @@ -2047,6 +2057,7 @@ "TTS Model": "Μοντέλο μετατροπής Κειμένου σε Ομιλία", "TTS Settings": "Ρυθμίσεις μετατροπής Κειμένου σε Ομιλία", "TTS Voice": "Φωνή Κειμένου σε Ομιλία", + "Tu_day_of_week": "", "Type": "Τύπος", "Type here...": "Πληκτρολογήστε εδώ...", "Type Hugging Face Resolve (Download) URL": "Τύπος URL Ανάλυσης Hugging Face Resolve (Λήψη)", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Προειδοποίηση: Η ενεργοποίηση αυτού θα επιτρέψει στους χρήστες να ανεβάσουν αυθαίρετο κώδικα στον διακομιστή.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Προειδοποίηση: Η εκτέλεση του Jupyter επιτρέπει την εκτέλεση αυθαίρετου κώδικα, γεγονός που θέτει σοβαρούς κινδύνους ασφαλεία - προχωρήστε με εξαιρετική προσοχή.", + "We_day_of_week": "", "Web": "Διαδίκτυο", "Web API": "", "Web Loader Engine": "Μηχανή Φόρτωσης Διαδικτύου", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "Το WebUI θα κάνει αιτήματα στο \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Τι προσπαθείτε να πετύχετε?", "What are you working on?": "Τι εργάζεστε;", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/en-GB/translation.json b/src/lib/i18n/locales/en-GB/translation.json index e688ca17b1..28a735ae18 100644 --- a/src/lib/i18n/locales/en-GB/translation.json +++ b/src/lib/i18n/locales/en-GB/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "", "How would you rate this response?": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "", "Model '{{modelTag}}' is already in queue for downloading.": "", - "Model {{modelId}} not found": "", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "", "Model {{name}} is now {{status}}": "", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "", "On": "", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Personalisation", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "", "Running...": "", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "", "Save & Create": "", "Save & Update": "", @@ -1889,6 +1897,7 @@ "STT Model": "", "STT Settings": "", "Stylized PDF Export": "Stylised PDF Export", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "", + "Th_day_of_week": "", "Thanks for your feedback!": "", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "", "TTS Settings": "", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "", "Web API": "", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/en-US/translation.json b/src/lib/i18n/locales/en-US/translation.json index 7ac7cb3e5b..b9f462c6a9 100644 --- a/src/lib/i18n/locales/en-US/translation.json +++ b/src/lib/i18n/locales/en-US/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "", "How would you rate this response?": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "", "Model '{{modelTag}}' is already in queue for downloading.": "", - "Model {{modelId}} not found": "", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "", "Model {{name}} is now {{status}}": "", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "", "On": "", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "", "Running...": "", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "", "Save & Create": "", "Save & Update": "", @@ -1889,6 +1897,7 @@ "STT Model": "", "STT Settings": "", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "", + "Th_day_of_week": "", "Thanks for your feedback!": "", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "", "TTS Settings": "", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "", "Web API": "", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/es-ES/translation.json b/src/lib/i18n/locales/es-ES/translation.json index b1339c0456..63e98ee49d 100644 --- a/src/lib/i18n/locales/es-ES/translation.json +++ b/src/lib/i18n/locales/es-ES/translation.json @@ -455,6 +455,7 @@ "Create a new note": "Crea una nueva nota", "Create Account": "Crear Cuenta", "Create Admin Account": "Crear Cuenta Administrativa", + "Create and manage scheduled automations": "", "Create Channel": "Crear Canal", "Create Folder": "Crear Carpeta", "Create Image": "Crear Imagen", @@ -480,6 +481,7 @@ "Custom Gender": "Género Personalizado", "Custom Parameter Name": "Nombre del Parámetro Personalizado", "Custom Parameter Value": "Valor del Parámetro Personalizado", + "Daily": "", "Daily Messages": "Mensajes Diarios", "Danger Zone": "Zona Peligrosa", "Dark": "Oscuro", @@ -970,6 +972,7 @@ "Forward": "Reenviar", "Forwards system user OAuth access token to authenticate": "Reenvía el token de acceso OAuth del usuario del sistema para autenticarse", "Forwards system user session credentials to authenticate": "Reenvío de las credenciales de la sesión del usuario del sistema para autenticación", + "Fr_day_of_week": "", "Full Context Mode": "Modo Contexto Completo", "Function": "Función", "Function Calling": "Modo de Llamada a Funciones (Herramientas)", @@ -1044,6 +1047,7 @@ "History": "Historial", "Home": "Inicio", "Host": "Host", + "Hourly": "", "Hourly Messages": "Mensajes por Hora", "How can I help you today?": "¿Cómo puedo ayudarte hoy?", "How would you rate this response?": "¿Cómo calificarías esta respuesta?", @@ -1266,10 +1270,10 @@ "Mistral OCR": "OCR Mistral", "Mistral OCR API Key required.": "Clave API de Mistral OCR requerida", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Modelo", "Model '{{modelName}}' has been successfully downloaded.": "Modelo '{{modelName}}' se ha descargado correctamente.", "Model '{{modelTag}}' is already in queue for downloading.": "Modelo '{{modelTag}}' ya está en cola para descargar.", - "Model {{modelId}} not found": "Modelo {{modelId}} no encontrado", "Model {{modelName}} deleted successfully": "Modelo {{modelName}} borrado correctamente", "Model {{modelName}} is not vision capable": "Modelo {{modelName}} no esta capacitado para visión", "Model {{name}} is now {{status}}": "Modelo {{name}} está ahora {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "Compartir Modelos", "Mojeek": "Mojeek", "Mojeek Search API Key": "Clave API de Mojeek Search", + "Monthly": "", "More": "Más", "More Concise": "Más Conciso", "More options": "Más Opciones", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "Clave API de Ollama Cloud", "Ollama Version": "Versión de Ollama", "On": "Activado", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Solo activo cuando \"Pegar el Texto Largo como Archivo\" está activado", "Only active when the chat input is in focus and an LLM is generating a response.": "Solo activo con el foco en la entrada del chat y se está generando una respuesta", @@ -1514,6 +1520,7 @@ "Persistent": "Persistente", "Personalization": "Personalización", "Pin": "Fijar", + "Pin to Sidebar": "", "Pinned": "Fijado", "Pinned Messages": "Mensajes Fijados", "Pinned Models": "Modelos Fijados", @@ -1675,6 +1682,7 @@ "Running": "Ejecutando", "Running...": "Ejecutando...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Ejecuta tareas de incrustración concurrentes para acelerar el procesado. Desactivar si se generan problemas (por limitaciones de los motores de incrustracción en uso)", + "Sa_day_of_week": "", "Save": "Guardar", "Save & Create": "Guardar y Crear", "Save & Update": "Guardar y Actualizar", @@ -1891,6 +1899,7 @@ "STT Model": "Modelo STT", "STT Settings": "Ajustes Voz a Texto (STT)", "Stylized PDF Export": "Exportar PDF Estilizado", + "Su_day_of_week": "", "Submit question": "Enviar pregunta", "Submit suggestion": "Enviar sugerencia", "Subtitle": "Subtítulo", @@ -1936,6 +1945,7 @@ "Text Splitter": "Divisor de Texto", "Text-to-Speech": "Texto a Voz", "Text-to-Speech Engine": "Motor Texto a Voz(TTS)", + "Th_day_of_week": "", "Thanks for your feedback!": "¡Gracias por tu comentario!", "The Application Account DN you bind with for search": "Cuenta DN de la aplicación vinculada para búsqueda", "The base to search for users": "Base para buscar usuarios", @@ -2049,6 +2059,7 @@ "TTS Model": "Modelo TTS", "TTS Settings": "Ajustes Texto a Voz (TTS)", "TTS Voice": "Voz TTS", + "Tu_day_of_week": "", "Type": "Tipo", "Type here...": "Teclea aquí...", "Type Hugging Face Resolve (Download) URL": "Escribir la URL de Hugging Face Resolve (Descarga)", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Aviso: Habilitar esto permitirá a los usuarios ejecutar automáticamente indicadores programados.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Aviso: Habilitar esto permitirá a los usuarios subir código arbitrario al servidor.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Aviso: La ejecución Jupyter habilita la ejecución de código arbitrario, planteando graves riesgos de seguridad; Proceder con extrema precaución.", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "Motor Cargador Web", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI hará solicitudes a \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI hará solicitudes a \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI hará solicitudes a \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "¿Qué estás tratando de conseguir?", "What are you working on?": "¿En qué estás trabajando?", "What is NOT shared:": "Que NO es compartido:", diff --git a/src/lib/i18n/locales/et-EE/translation.json b/src/lib/i18n/locales/et-EE/translation.json index 4f7bc75de7..1d061c333b 100644 --- a/src/lib/i18n/locales/et-EE/translation.json +++ b/src/lib/i18n/locales/et-EE/translation.json @@ -454,6 +454,7 @@ "Create a new note": "Loo uus märge", "Create Account": "Loo konto", "Create Admin Account": "Loo administraatori konto", + "Create and manage scheduled automations": "", "Create Channel": "Loo kanal", "Create Folder": "Loo kaust", "Create Image": "Loo pilt", @@ -479,6 +480,7 @@ "Custom Gender": "Kohandatud sugu", "Custom Parameter Name": "Kohandatud parameetri nimi", "Custom Parameter Value": "Kohandatud parameetri väärtus", + "Daily": "", "Daily Messages": "Päevased sõnumid", "Danger Zone": "Ohutsoon", "Dark": "Tume", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Edastab süsteemikasutaja OAuthi juurdepääsumärgi autentimiseks", "Forwards system user session credentials to authenticate": "Edastab süsteemikasutaja seansi mandaadid autentimiseks", + "Fr_day_of_week": "", "Full Context Mode": "Täiskonteksti režiim", "Function": "Funktsioon", "Function Calling": "Funktsiooni kutsumine", @@ -1043,6 +1046,7 @@ "History": "Ajalugu", "Home": "Avaleht", "Host": "Host", + "Hourly": "", "Hourly Messages": "Tunni sõnumid", "How can I help you today?": "Kuidas saan teid täna aidata?", "How would you rate this response?": "Kuidas hindaksite seda vastust?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR API võti on nõutav.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Mudel", "Model '{{modelName}}' has been successfully downloaded.": "Mudel '{{modelName}}' on edukalt alla laaditud.", "Model '{{modelTag}}' is already in queue for downloading.": "Mudel '{{modelTag}}' on juba allalaadimise järjekorras.", - "Model {{modelId}} not found": "Mudelit {{modelId}} ei leitud", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Mudel {{modelName}} ei ole võimeline visuaalseid sisendeid töötlema", "Model {{name}} is now {{status}}": "Mudel {{name}} on nüüd {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "Mudelite jagamine", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek Search API võti", + "Monthly": "", "More": "Rohkem", "More Concise": "Kokkuvõtlikum", "More options": "Rohkem valikuid", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Ollama Cloud API Võti", "Ollama Version": "Ollama versioon", "On": "Sees", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Aktiivne ainult siis, kui seade \"Kleebi suur tekst failina\" on sisse lülitatud.", "Only active when the chat input is in focus and an LLM is generating a response.": "Aktiivne ainult siis, kui vestluse sisend on fookuses ja LLM genereerib vastust.", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Isikupärastamine", "Pin": "Kinnita", + "Pin to Sidebar": "", "Pinned": "Kinnitatud", "Pinned Messages": "Kinnitatud sõnumid", "Pinned Models": "Kinnitatud mudelid", @@ -1673,6 +1680,7 @@ "Running": "Töötab", "Running...": "Töötab...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Käitab manustamisülesandeid samaaegselt töötlemise kiirendamiseks. Lülitage välja, kui piirangud muutuvad probleemiks.", + "Sa_day_of_week": "", "Save": "Salvesta", "Save & Create": "Salvesta ja loo", "Save & Update": "Salvesta ja uuenda", @@ -1889,6 +1897,7 @@ "STT Model": "STT mudel", "STT Settings": "STT seaded", "Stylized PDF Export": "Stiliseeritud PDF eksport", + "Su_day_of_week": "", "Submit question": "Esita küsimus", "Submit suggestion": "Esita soovitus", "Subtitle": "Alampealkiri", @@ -1934,6 +1943,7 @@ "Text Splitter": "Teksti tükeldaja", "Text-to-Speech": "Text-to-Speech", "Text-to-Speech Engine": "Tekst-kõneks mootor", + "Th_day_of_week": "", "Thanks for your feedback!": "Täname tagasiside eest!", "The Application Account DN you bind with for search": "Rakenduse konto DN, millega seote otsingu jaoks", "The base to search for users": "Baas kasutajate otsimiseks", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS mudel", "TTS Settings": "TTS seaded", "TTS Voice": "TTS hääl", + "Tu_day_of_week": "", "Type": "Tüüp", "Type here...": "Sisestage siia...", "Type Hugging Face Resolve (Download) URL": "Sisestage Hugging Face Resolve (Allalaadimise) URL", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Hoiatus: Selle lubamine võimaldab kasutajatel üles laadida suvalist koodi serverisse.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Hoiatus: Jupyter täitmine võimaldab suvalise koodi käivitamist, mis kujutab endast tõsist turvariski - jätkake äärmise ettevaatusega.", + "We_day_of_week": "", "Web": "Veeb", "Web API": "Veebi API", "Web Loader Engine": "Veebilaadija mootor", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI teeb päringuid aadressile \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI teeb päringuid aadressile \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI teeb päringuid aadressile \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Mida te püüate saavutada?", "What are you working on?": "Millega te tegelete?", "What is NOT shared:": "Mida EI jagata:", diff --git a/src/lib/i18n/locales/eu-ES/translation.json b/src/lib/i18n/locales/eu-ES/translation.json index ab7ee3117b..af50fb0122 100644 --- a/src/lib/i18n/locales/eu-ES/translation.json +++ b/src/lib/i18n/locales/eu-ES/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Sortu Kontua", "Create Admin Account": "Sortu Administratzaile Kontua", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Iluna", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "Funtzioa", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "Ostalaria", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Zertan lagun zaitzaket gaur?", "How would you rate this response?": "Nola baloratuko zenuke erantzun hau?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Modeloa", "Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' modeloa ongi deskargatu da.", "Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' modeloa dagoeneko deskarga ilaran dago.", - "Model {{modelId}} not found": "{{modelId}} modeloa ez da aurkitu", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "{{modelName}} modeloak ez du ikusmen gaitasunik", "Model {{name}} is now {{status}}": "{{name}} modeloa orain {{status}} dago", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Mojeek bilaketa API gakoa", + "Monthly": "", "More": "Gehiago", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama bertsioa", "On": "Piztuta", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Pertsonalizazioa", "Pin": "Ainguratu", + "Pin to Sidebar": "", "Pinned": "Ainguratuta", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Exekutatzen", "Running...": "Exekutatzen...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Gorde", "Save & Create": "Gorde eta sortu", "Save & Update": "Gorde eta eguneratu", @@ -1889,6 +1897,7 @@ "STT Model": "STT modeloa", "STT Settings": "STT ezarpenak", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Testu banatzailea", "Text-to-Speech": "", "Text-to-Speech Engine": "Testutik-ahotsera motorra", + "Th_day_of_week": "", "Thanks for your feedback!": "Eskerrik asko zure iritzia emateagatik!", "The Application Account DN you bind with for search": "Bilaketarako lotzen duzun aplikazio kontuaren DN-a", "The base to search for users": "Erabiltzaileak bilatzeko oinarria", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS modeloa", "TTS Settings": "TTS ezarpenak", "TTS Voice": "TTS ahotsa", + "Tu_day_of_week": "", "Type": "Mota", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Idatzi Hugging Face Resolve (Deskarga) URLa", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Abisua: Hau gaitzeak erabiltzaileei zerbitzarian kode arbitrarioa kargatzea ahalbidetuko die.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Weba", "Web API": "Web APIa", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI-k eskaerak egingo ditu \"{{url}}/api/chat\"-era", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI-k eskaerak egingo ditu \"{{url}}/chat/completions\"-era", + "Weekly": "", "What are you trying to achieve?": "Zer lortu nahi duzu?", "What are you working on?": "Zertan ari zara lanean?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/fa-IR/translation.json b/src/lib/i18n/locales/fa-IR/translation.json index 725f340932..cd2f7839a9 100644 --- a/src/lib/i18n/locales/fa-IR/translation.json +++ b/src/lib/i18n/locales/fa-IR/translation.json @@ -454,6 +454,7 @@ "Create a new note": "ایجاد یک یادداشت جدید", "Create Account": "ساخت حساب کاربری", "Create Admin Account": "ایجاد حساب مدیر", + "Create and manage scheduled automations": "", "Create Channel": "ایجاد کانال", "Create Folder": "ایجاد پوشه", "Create Image": "ایجاد تصویر", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "نام پارامتر سفارشی", "Custom Parameter Value": "مقدار پارامتر سفارشی", + "Daily": "", "Daily Messages": "", "Danger Zone": "منطقه خطر", "Dark": "تیره", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "ارسال توکن دسترسی OAuth کاربر سیستم برای احراز هویت", "Forwards system user session credentials to authenticate": "اعتبارنامه\u200cهای نشست کاربر سیستم را برای احراز هویت ارسال می\u200cکند", + "Fr_day_of_week": "", "Full Context Mode": "حالت متن کامل", "Function": "تابع", "Function Calling": "فراخوانی تابع", @@ -1043,6 +1046,7 @@ "History": "", "Home": "خانه", "Host": "میزبان", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "امروز چطور می توانم کمک تان کنم؟", "How would you rate this response?": "این پاسخ را چگونه ارزیابی می\u200cکنید؟", @@ -1265,10 +1269,10 @@ "Mistral OCR": "تشخیص متن میسترال", "Mistral OCR API Key required.": "کلید API تشخیص متن میسترال مورد نیاز است.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "مدل", "Model '{{modelName}}' has been successfully downloaded.": "مدل '{{modelName}}' با موفقیت دانلود شد.", "Model '{{modelTag}}' is already in queue for downloading.": "مدل '{{modelTag}}' در حال حاضر در صف برای دانلود است.", - "Model {{modelId}} not found": "مدل {{modelId}} یافت نشد", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "مدل {{modelName}} قادر به بینایی نیست", "Model {{name}} is now {{status}}": "مدل {{name}} در حال حاضر {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "کلید API جستجوی موجیک", + "Monthly": "", "More": "بیشتر", "More Concise": "خلاصه\u200cتر", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "کلید API ابری اُلاما", "Ollama Version": "نسخه ollama", "On": "روشن", + "Once": "", "OneDrive": "وان\u200cدرایو", "Only active when \"Paste Large Text as File\" setting is toggled on.": "فقط زمانی فعال است که تنظیم «چسباندن متن بزرگ به عنوان فایل» روشن باشد.", "Only active when the chat input is in focus and an LLM is generating a response.": "فقط زمانی فعال است که ورودی چت در فوکوس باشد و یک LLM در حال تولید پاسخ باشد.", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "شخصی سازی", "Pin": "پین کردن", + "Pin to Sidebar": "", "Pinned": "پین شده", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "در حال اجرا", "Running...": "در حال اجرا...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "ذخیره", "Save & Create": "ذخیره و ایجاد", "Save & Update": "ذخیره و به\u200cروزرسانی", @@ -1889,6 +1897,7 @@ "STT Model": "مدل تبدیل صدا به متن", "STT Settings": "تنظیمات تبدیل صدا به متن", "Stylized PDF Export": "خروجی گرفتن از PDF با استایل", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "تقسیم\u200cکننده متن", "Text-to-Speech": "متن به گفتار", "Text-to-Speech Engine": "موتور تبدیل متن به گفتار", + "Th_day_of_week": "", "Thanks for your feedback!": "با تشکر از بازخورد شما!", "The Application Account DN you bind with for search": "DN حساب برنامه که برای جستجو به آن متصل می\u200cشوید", "The base to search for users": "پایه برای جستجوی کاربران", @@ -2047,6 +2057,7 @@ "TTS Model": "مدل TTS", "TTS Settings": "تنظیمات TTS", "TTS Voice": "صدای TTS", + "Tu_day_of_week": "", "Type": "نوع", "Type here...": "اینجا تایپ کنید...", "Type Hugging Face Resolve (Download) URL": "مقدار URL دانلود (Resolve) Hugging Face را وارد کنید", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "هشدار: فعال کردن این گزینه به کاربران اجازه می\u200cدهد کد دلخواه را روی سرور آپلود کنند.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "هشدار: اجرای ژوپیتر امکان اجرای کد دلخواه را فراهم می\u200cکند که خطرات امنیتی جدی به همراه دارد - با احتیاط زیاد ادامه دهید.", + "We_day_of_week": "", "Web": "وب", "Web API": "API وب", "Web Loader Engine": "موتور بارگذاری وب", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI به \"{{url}}\" درخواست خواهد داد", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI به \"{{url}}/api/chat\" درخواست خواهد داد", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI به \"{{url}}/chat/completions\" درخواست خواهد داد", + "Weekly": "", "What are you trying to achieve?": "به دنبال دستیابی به چه هدفی هستید؟", "What are you working on?": "روی چه چیزی کار می\u200cکنید؟", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/fi-FI/translation.json b/src/lib/i18n/locales/fi-FI/translation.json index 94a728a77f..10d63179d3 100644 --- a/src/lib/i18n/locales/fi-FI/translation.json +++ b/src/lib/i18n/locales/fi-FI/translation.json @@ -454,6 +454,7 @@ "Create a new note": "Luo uusi muistiinpano", "Create Account": "Luo tili", "Create Admin Account": "Luo ylläpitäjätili", + "Create and manage scheduled automations": "", "Create Channel": "Luo kanava", "Create Folder": "Luo kansio", "Create Image": "Luo kuva", @@ -479,6 +480,7 @@ "Custom Gender": "Muu sukupuoli", "Custom Parameter Name": "Mukautetun parametrin nimi", "Custom Parameter Value": "Mukautetun parametrin arvo", + "Daily": "", "Daily Messages": "Päivittäiset viestit", "Danger Zone": "Vaara-alue", "Dark": "Tumma", @@ -969,6 +971,7 @@ "Forward": "Eteenpäin", "Forwards system user OAuth access token to authenticate": "Välittää järjestelmä käyttäjän OAuth tunniste todennuksessa", "Forwards system user session credentials to authenticate": "Välittää järjestelmän käyttäjän istunnon tunnistetiedot todennusta varten", + "Fr_day_of_week": "", "Full Context Mode": "Koko kontekstitila", "Function": "Toiminto", "Function Calling": "Toiminto kutsu", @@ -1043,6 +1046,7 @@ "History": "Historia", "Home": "Koti", "Host": "Palvelin", + "Hourly": "", "Hourly Messages": "Tuntikohtaiset viestit", "How can I help you today?": "Miten voin auttaa sinua tänään?", "How would you rate this response?": "Kuinka arvioisit tätä vastausta?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR api-avain vaaditaan", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Malli", "Model '{{modelName}}' has been successfully downloaded.": "Malli '{{modelName}}' ladattiin onnistuneesti.", "Model '{{modelTag}}' is already in queue for downloading.": "Malli '{{modelTag}}' on jo jonossa ladattavaksi.", - "Model {{modelId}} not found": "Mallia {{modelId}} ei löytynyt", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Malli {{modelName}} ei kykene näkökykyyn", "Model {{name}} is now {{status}}": "Malli {{name}} on nyt {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "Mallien jako", "Mojeek": "", "Mojeek Search API Key": "Mojeek Search API -avain", + "Monthly": "", "More": "Lisää", "More Concise": "Lyhyemmin", "More options": "Lisää vaihtoehtoja", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Ollama Cloud API avain", "Ollama Version": "Ollama-versio", "On": "Päällä", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Aktiivinen vain, kun \"Liitä suuri teksti tiedostona\" -asetus on käytössä.", "Only active when the chat input is in focus and an LLM is generating a response.": "Aktiivinen vain, kun tekstikenttä on kohdistettuna ja LLM luo vastausta.", @@ -1513,6 +1519,7 @@ "Persistent": "Pysyvä", "Personalization": "Personointi", "Pin": "Kiinnitä", + "Pin to Sidebar": "", "Pinned": "Kiinnitetty", "Pinned Messages": "Kiinnitetyt viestit", "Pinned Models": "Kiinnitetyt mallit", @@ -1673,6 +1680,7 @@ "Running": "Käynnissä", "Running...": "Käynnissä...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Suorittaa upotustehtäviä samanaikaisesti käsittelyn nopeuttamiseksi. Poista käytöstä, jos kutsurajoituksesta tulee ongelma.", + "Sa_day_of_week": "", "Save": "Tallenna", "Save & Create": "Tallenna ja luo", "Save & Update": "Tallenna ja päivitä", @@ -1889,6 +1897,7 @@ "STT Model": "Puheentunnistusmalli", "STT Settings": "Puheentunnistuksen asetukset", "Stylized PDF Export": "Muotoiltun PDF-vienti", + "Su_day_of_week": "", "Submit question": "Lähetä kysymys", "Submit suggestion": "Lähetä ehdotus", "Subtitle": "Alaotsikko", @@ -1934,6 +1943,7 @@ "Text Splitter": "Tekstin jakaja", "Text-to-Speech": "Puhesynteesi", "Text-to-Speech Engine": "Puhesynteesimoottori", + "Th_day_of_week": "", "Thanks for your feedback!": "Kiitos palautteestasi!", "The Application Account DN you bind with for search": "Hakua varten sidottu sovelluksen käyttäjätilin DN", "The base to search for users": "Käyttäjien haun perusta", @@ -2047,6 +2057,7 @@ "TTS Model": "Puhesynteesimalli", "TTS Settings": "Puhesynteesiasetukset", "TTS Voice": "Puhesynteesiääni", + "Tu_day_of_week": "", "Type": "Tyyppi", "Type here...": "Kirjoita tähän...", "Type Hugging Face Resolve (Download) URL": "Kirjoita Hugging Face -resolve-latausosoite", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Varoitus: Tämän käyttöönotto sallii käyttäjien ladata mielivaltaista koodia palvelimelle.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Varoitus: Jupyter käyttö voi mahdollistaa mielivaltaiseen koodin suorittamiseen, mikä voi aiheuttaa tietoturvariskejä - käytä äärimmäisen varoen.", + "We_day_of_week": "", "Web": "Web", "Web API": "Web-API", "Web Loader Engine": "Verkkolataaja moottori", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI lähettää pyyntöjä osoitteeseen \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI lähettää pyyntöjä osoitteeseen \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI lähettää pyyntöjä osoitteeseen \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Mitä yrität saavuttaa?", "What are you working on?": "Mitä olet työskentelemässä?", "What is NOT shared:": "Mitä EI jaeta:", diff --git a/src/lib/i18n/locales/fr-CA/translation.json b/src/lib/i18n/locales/fr-CA/translation.json index de0021689b..b0f58be103 100644 --- a/src/lib/i18n/locales/fr-CA/translation.json +++ b/src/lib/i18n/locales/fr-CA/translation.json @@ -455,6 +455,7 @@ "Create a new note": "", "Create Account": "Créer un compte", "Create Admin Account": "Créer un compte administrateur", + "Create and manage scheduled automations": "", "Create Channel": "Créer un canal", "Create Folder": "", "Create Image": "", @@ -480,6 +481,7 @@ "Custom Gender": "", "Custom Parameter Name": "Nom du réglage personnalisé", "Custom Parameter Value": "Valeur du réglage personnalisé", + "Daily": "", "Daily Messages": "", "Danger Zone": "Zone de danger", "Dark": "Sombre", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Transmet les identifiants de session de l'utilisateur pour l'authentification", + "Fr_day_of_week": "", "Full Context Mode": "Mode avec injection complète dans le Context", "Function": "Fonction", "Function Calling": "Appel de fonction", @@ -1044,6 +1047,7 @@ "History": "", "Home": "Home", "Host": "Hôte", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Comment puis-je vous aider aujourd'hui ?", "How would you rate this response?": "Comment évalueriez-vous cette réponse ?", @@ -1266,10 +1270,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Clé d'API pour Mistral OCR requise", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Modèle", "Model '{{modelName}}' has been successfully downloaded.": "Le modèle '{{modelName}}' a été téléchargé avec succès.", "Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.", - "Model {{modelId}} not found": "Modèle {{modelId}} introuvable", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Le modèle {{modelName}} n'a pas de capacités visuelles", "Model {{name}} is now {{status}}": "Le modèle {{name}} est désormais {{status}}.", @@ -1310,6 +1314,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Clé API Mojeek", + "Monthly": "", "More": "Plus", "More Concise": "", "More options": "", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Version d'Ollama", "On": "Activé", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Personnalisation", "Pin": "Épingler", + "Pin to Sidebar": "", "Pinned": "Épinglé", "Pinned Messages": "", "Pinned Models": "", @@ -1675,6 +1682,7 @@ "Running": "Exécution", "Running...": "Exécution...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Enregistrer", "Save & Create": "Enregistrer & Créer", "Save & Update": "Enregistrer & Mettre à jour", @@ -1891,6 +1899,7 @@ "STT Model": "Modèle de Speech-to-Text", "STT Settings": "Réglages de Speech-to-Text", "Stylized PDF Export": "Export de PDF stylisés", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1936,6 +1945,7 @@ "Text Splitter": "Text Splitter", "Text-to-Speech": "Text-to-Speech", "Text-to-Speech Engine": "Moteur de Text-to-Speech", + "Th_day_of_week": "", "Thanks for your feedback!": "Merci pour vos commentaires !", "The Application Account DN you bind with for search": "Le DN du compte de l'application avec lequel vous vous liez pour la recherche", "The base to search for users": "La base pour rechercher des utilisateurs", @@ -2049,6 +2059,7 @@ "TTS Model": "Modèle de Text-to-Speech", "TTS Settings": "Réglages de Text-to-Speech", "TTS Voice": "Voix de Text-to-Speech", + "Tu_day_of_week": "", "Type": "Type", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Entrez l'URL de Téléchargement Hugging Face Resolve", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Avertissement : Activer cette option permettra aux utilisateurs de télécharger du code arbitraire sur le serveur.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Avertissement : L'exécution Jupyter permet l'exécution de code arbitraire, ce qui présente des risques de sécurité importants. Procédez avec une extrême prudence.", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "Moteur de chargement Web", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI effectuera des requêtes vers \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI fera des requêtes à \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI fera des requêtes à \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Que cherchez-vous à accomplir ?", "What are you working on?": "Sur quoi travaillez-vous ?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/fr-FR/translation.json b/src/lib/i18n/locales/fr-FR/translation.json index a8a8a8dd52..60c85440d3 100644 --- a/src/lib/i18n/locales/fr-FR/translation.json +++ b/src/lib/i18n/locales/fr-FR/translation.json @@ -455,6 +455,7 @@ "Create a new note": "Créer une nouvelle note", "Create Account": "Créer un compte", "Create Admin Account": "Créer un compte administrateur", + "Create and manage scheduled automations": "", "Create Channel": "Créer un canal", "Create Folder": "Créer un dossier", "Create Image": "Création d'image", @@ -480,6 +481,7 @@ "Custom Gender": "Genre personnalisé", "Custom Parameter Name": "Nom du réglage personnalisé", "Custom Parameter Value": "Valeur du réglage personnalisé", + "Daily": "", "Daily Messages": "Messages par jour", "Danger Zone": "Zone de danger", "Dark": "Sombre", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Transfère le jeton d'accès OAuth de l'utilisateur système pour l'authentification", "Forwards system user session credentials to authenticate": "Transmet les identifiants de session de l'utilisateur pour l'authentification", + "Fr_day_of_week": "", "Full Context Mode": "Mode avec injection complète dans le contexte", "Function": "Fonction", "Function Calling": "Appel de fonction", @@ -1044,6 +1047,7 @@ "History": "Historique", "Home": "Home", "Host": "Hôte", + "Hourly": "", "Hourly Messages": "Messages par heure", "How can I help you today?": "Comment puis-je vous aider aujourd'hui ?", "How would you rate this response?": "Comment évalueriez-vous cette réponse ?", @@ -1266,10 +1270,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Clé d'API pour Mistral OCR requise", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Modèle", "Model '{{modelName}}' has been successfully downloaded.": "Le modèle '{{modelName}}' a été téléchargé avec succès.", "Model '{{modelTag}}' is already in queue for downloading.": "Le modèle '{{modelTag}}' est déjà dans la file d'attente pour le téléchargement.", - "Model {{modelId}} not found": "Modèle {{modelId}} introuvable", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Le modèle {{modelName}} n'a pas de fonctionnalité de vision", "Model {{name}} is now {{status}}": "Le modèle {{name}} est désormais {{status}}.", @@ -1310,6 +1314,7 @@ "Models Sharing": "Partage des modèles", "Mojeek": "Mojeek", "Mojeek Search API Key": "Clé API Mojeek", + "Monthly": "", "More": "Plus", "More Concise": "Plus concis", "More options": "Plus d'options", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "Clé API Ollama Cloud", "Ollama Version": "Version d'Ollama", "On": "Activé", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Uniquement lorsque le paramètre \"Coller un texte volumineux comme fichier\" est activé.", "Only active when the chat input is in focus and an LLM is generating a response.": "Uniquement lorsque la zone de saisie de la conversation est active et qu'un LLM génère une réponse.", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Personnalisation", "Pin": "Épingler", + "Pin to Sidebar": "", "Pinned": "Épinglé", "Pinned Messages": "Messages épinglés", "Pinned Models": "Modèles épinglés", @@ -1675,6 +1682,7 @@ "Running": "Exécution", "Running...": "Exécution...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Exécute les tâches d'embedding en parallèle pour accélérer le traitement. Désactivez si les limites de débit posent problème.", + "Sa_day_of_week": "", "Save": "Enregistrer", "Save & Create": "Enregistrer & Créer", "Save & Update": "Enregistrer & Mettre à jour", @@ -1891,6 +1899,7 @@ "STT Model": "Modèle de Speech-to-Text", "STT Settings": "Réglages de Speech-to-Text", "Stylized PDF Export": "Export de PDF stylisés", + "Su_day_of_week": "", "Submit question": "Envoyer la question", "Submit suggestion": "Soumettre la suggestion", "Subtitle": "Sous-titre", @@ -1936,6 +1945,7 @@ "Text Splitter": "Découpage du texte", "Text-to-Speech": "Text-to-Speech", "Text-to-Speech Engine": "Moteur de Text-to-Speech", + "Th_day_of_week": "", "Thanks for your feedback!": "Merci pour vos commentaires !", "The Application Account DN you bind with for search": "Le DN du compte de l'application avec lequel vous vous liez pour la recherche", "The base to search for users": "La base pour rechercher des utilisateurs", @@ -2049,6 +2059,7 @@ "TTS Model": "Modèle de Text-to-Speech", "TTS Settings": "Réglages de Text-to-Speech", "TTS Voice": "Voix de Text-to-Speech", + "Tu_day_of_week": "", "Type": "Type", "Type here...": "Entrez votre message ici...", "Type Hugging Face Resolve (Download) URL": "Entrez l'URL de Téléchargement Hugging Face Resolve", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Avertissement : Activer cette option permettra aux utilisateurs de télécharger du code arbitraire sur le serveur.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Avertissement : L'exécution Jupyter permet l'exécution de code arbitraire, ce qui présente des risques de sécurité importants. Procédez avec une extrême prudence.", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "Moteur de chargement Web", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI effectuera des requêtes vers \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI fera des requêtes à \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI fera des requêtes à \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Que cherchez-vous à accomplir ?", "What are you working on?": "Sur quoi travaillez-vous ?", "What is NOT shared:": "Ce qui n'est PAS partagé :", diff --git a/src/lib/i18n/locales/gl-ES/translation.json b/src/lib/i18n/locales/gl-ES/translation.json index d831953ddf..19fad395d1 100644 --- a/src/lib/i18n/locales/gl-ES/translation.json +++ b/src/lib/i18n/locales/gl-ES/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Xerar unha conta", "Create Admin Account": "Xerar conta administrativa", + "Create and manage scheduled automations": "", "Create Channel": "Xerar Canal", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Oscuro", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "Función", "Function Calling": "chamada de función", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "Host", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "¿Cómo podo axudarche hoxe?", "How would you rate this response?": "¿Cómo calificarías esta resposta?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Modelo", "Model '{{modelName}}' has been successfully downloaded.": "0 modelo '{{modelName}}' se ha descargado correctamente.", "Model '{{modelTag}}' is already in queue for downloading.": "0 modelo '{{modelTag}}' ya está en cola para descargar.", - "Model {{modelId}} not found": "0 modelo {{modelId}} no fue encontrado", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "O modelo {{modelName}} no es capaz de ver", "Model {{name}} is now {{status}}": "O modelo {{name}} ahora es {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "chave API de Mojeek Search", + "Monthly": "", "More": "mais", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Versión de Ollama", "On": "Activado", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Personalización", "Pin": "Fijar", + "Pin to Sidebar": "", "Pinned": "Fijado", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Executando", "Running...": "Executando...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Gardar", "Save & Create": "Gardar y xerar", "Save & Update": "Gardar y Actualizar", @@ -1889,6 +1897,7 @@ "STT Model": "Modelo STT", "STT Settings": "Configuracions de STT", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Divisor de texto", "Text-to-Speech": "", "Text-to-Speech Engine": "Motor de texto a voz", + "Th_day_of_week": "", "Thanks for your feedback!": "¡Gracias pola tua retroalimentación!", "The Application Account DN you bind with for search": "A conta de aplicación DN que vincula para a búsqueda", "The base to search for users": "A base para buscar usuarios", @@ -2047,6 +2057,7 @@ "TTS Model": "Modelo TTS", "TTS Settings": "Configuración de TTS", "TTS Voice": "Voz do TTS", + "Tu_day_of_week": "", "Type": "Tipo", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Escriba la URL (Descarga) de Hugging Face Resolve", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Advertencia: Habilitar esto permitirá a os usuarios subir código arbitrario no servidor.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Aviso: A execución de Jupyter permite a execución de código arbitrario, o que supón riscos de seguridade graves - procede con extrema precaución.", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI hará solicitudes a \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI hará solicitudes a \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "¿Qué estás tratando de lograr?", "What are you working on?": "¿En qué estás trabajando?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/he-IL/translation.json b/src/lib/i18n/locales/he-IL/translation.json index 99feaf7600..a273c87c71 100644 --- a/src/lib/i18n/locales/he-IL/translation.json +++ b/src/lib/i18n/locales/he-IL/translation.json @@ -455,6 +455,7 @@ "Create a new note": "", "Create Account": "צור חשבון", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -480,6 +481,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "כהה", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1044,6 +1047,7 @@ "History": "", "Home": "בית", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "כיצד אוכל לעזור לך היום?", "How would you rate this response?": "", @@ -1266,10 +1270,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "המודל '{{modelName}}' הורד בהצלחה.", "Model '{{modelTag}}' is already in queue for downloading.": "המודל '{{modelTag}}' כבר בתור להורדה.", - "Model {{modelId}} not found": "המודל {{modelId}} לא נמצא", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "דגם {{modelName}} אינו בעל יכולת ראייה", "Model {{name}} is now {{status}}": "דגם {{name}} הוא כעת {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "עוד", "More Concise": "", "More options": "", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "גרסת Ollama", "On": "פועל", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "תאור", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1675,6 +1682,7 @@ "Running": "", "Running...": "פועל...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "שמור", "Save & Create": "שמור וצור", "Save & Update": "שמור ועדכן", @@ -1891,6 +1899,7 @@ "STT Model": "", "STT Settings": "הגדרות חקירה של TTS", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1936,6 +1945,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "מנוע טקסט לדיבור", + "Th_day_of_week": "", "Thanks for your feedback!": "תודה על המשוב שלך!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2049,6 +2059,7 @@ "TTS Model": "", "TTS Settings": "הגדרות TTS", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "סוג", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "הקלד כתובת URL של פתרון פנים מחבק (הורד)", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "רשת", "Web API": "", "Web Loader Engine": "", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/hi-IN/translation.json b/src/lib/i18n/locales/hi-IN/translation.json index 71edceb884..7b172c8adf 100644 --- a/src/lib/i18n/locales/hi-IN/translation.json +++ b/src/lib/i18n/locales/hi-IN/translation.json @@ -454,13 +454,14 @@ "Create a new note": "", "Create Account": "खाता बनाएं", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", "Create Knowledge": "", "Create Model": "", - "Create new key": "नया क्रिप्टोग्राफिक क्षेत्र बनाएं", - "Create new secret key": "नया क्रिप्टोग्राफिक क्षेत्र बनाएं", + "Create new key": "नई कुंजी बनाएं", + "Create new secret key": "नई गुप्त कुंजी बनाएं", "Create note": "", "Create Note": "", "Create scheduled prompts that run automatically on a recurring basis.": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "डार्क", @@ -813,7 +815,7 @@ "Entra ID": "", "Environment Variables": "", "Ephemeral": "", - "Error": "चूक", + "Error": "त्रुटि", "ERROR": "", "Error accessing directory": "", "Error accessing Google Drive: {{error}}": "", @@ -843,7 +845,7 @@ "Explore the cosmos": "", "Explored": "", "Exploring": "", - "Export": "निर्यातित माल", + "Export": "निर्यात करें", "Export All Archived Chats": "", "Export All Chats (All Users)": "सभी चैट निर्यात करें (सभी उपयोगकर्ताओं की)", "Export as CSV": "", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "आज मैं आपकी कैसे मदद कर सकता हूँ?", "How would you rate this response?": "", @@ -1089,7 +1093,7 @@ "Includes SharePoint": "SharePoint शामिल है", "Increase UI Scale": "", "Influences how quickly the algorithm responds to feedback from the generated text. A lower learning rate will result in slower adjustments, while a higher learning rate will make the algorithm more responsive.": "", - "Info": "सूचना-विषयक", + "Info": "जानकारी", "Initials": "", "Inject file content into conversation context": "", "Inject the entire content as context for comprehensive processing, this is recommended for complex queries.": "", @@ -1122,7 +1126,7 @@ "Jina API Base URL": "", "Jina API Key": "", "join our Discord for help.": "मदद के लिए हमारे डिस्कोर्ड में शामिल हों।", - "JSON": "ज्ञान प्रकार", + "JSON": "JSON", "JSON Preview": "JSON पूर्वावलोकन", "JSON Spec": "", "July": "जुलाई", @@ -1188,7 +1192,7 @@ "lexical": "", "License": "", "Lift List": "", - "Light": "सुन", + "Light": "हल्का", "Limit concurrent search queries. 0 = unlimited (default). Set to 1 for sequential execution (recommended for APIs with strict rate limits like Brave free tier).": "", "Limits the number of concurrent embedding requests. Set to 0 for unlimited.": "", "List": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "मॉडल '{{modelName}}' सफलतापूर्वक डाउनलोड हो गया है।", "Model '{{modelTag}}' is already in queue for downloading.": "मॉडल '{{modelTag}}' पहले से ही डाउनलोड करने के लिए कतार में है।", - "Model {{modelId}} not found": "मॉडल {{modelId}} नहीं मिला", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "मॉडल {{modelName}} दृष्टि सक्षम नहीं है", "Model {{name}} is now {{status}}": "मॉडल {{name}} अब {{status}} है", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "और..", "More Concise": "", "More options": "", @@ -1422,13 +1427,14 @@ "October": "अक्टूबर", "Off": "बंद", "Okay, Let's Go!": "ठीक है, चलिए चलते हैं!", - "OLED Dark": "OLEDescuro", + "OLED Dark": "OLED डार्क", "Ollama": "Ollama", "Ollama API": "ओलामा एपीआई", "Ollama API settings updated": "", "Ollama Cloud API Key": "", "Ollama Version": "Ollama Version", "On": "चालू", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "पेरसनलाइज़मेंट", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1526,7 +1533,7 @@ "Pipelines Valves": "पाइपलाइन वाल्व", "Plain text (.md)": "", "Plain text (.txt)": "सादा पाठ (.txt)", - "Playground": "कार्यक्षेत्र", + "Playground": "प्रयोगशाला", "Playwright Timeout (ms)": "", "Playwright WebSocket URL": "", "Please carefully review the following warnings:": "", @@ -1673,6 +1680,7 @@ "Running": "", "Running...": "चल रहा है...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "सहेजें", "Save & Create": "सहेजें और बनाएं", "Save & Update": "सहेजें और अपडेट करें", @@ -1889,6 +1897,7 @@ "STT Model": "", "STT Settings": "STT सेटिंग्स ", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "टेक्स्ट-टू-स्पीच इंजन", + "Th_day_of_week": "", "Thanks for your feedback!": "आपकी प्रतिक्रिया के लिए धन्यवाद!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "", "TTS Settings": "TTS सेटिंग्स", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "प्रकार", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "हगिंग फेस रिज़ॉल्व (डाउनलोड) यूआरएल टाइप करें", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "वेब", "Web API": "", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", @@ -2218,7 +2231,7 @@ "You have no shared conversations.": "", "You have shared this chat": "आपने इस चैट को शेयर किया है", "You.com API Key": "", - "You're a helpful assistant.": "आप एक सहायक सहायक हैं", + "You're a helpful assistant.": "आप एक मददगार सहायक हैं।", "You're now logged in.": "अब आप लॉग इन हो गए हैं", "Your Account": "", "Your account status is currently pending activation.": "", diff --git a/src/lib/i18n/locales/hr-HR/translation.json b/src/lib/i18n/locales/hr-HR/translation.json index 33ee5f734a..5bf1a7e65c 100644 --- a/src/lib/i18n/locales/hr-HR/translation.json +++ b/src/lib/i18n/locales/hr-HR/translation.json @@ -455,6 +455,7 @@ "Create a new note": "", "Create Account": "Stvori račun", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -480,6 +481,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Tamno", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1044,6 +1047,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Kako vam mogu pomoći danas?", "How would you rate this response?": "", @@ -1266,10 +1270,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' je uspješno preuzet.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je već u redu za preuzimanje.", - "Model {{modelId}} not found": "Model {{modelId}} nije pronađen", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} ne čita vizualne impute", "Model {{name}} is now {{status}}": "Model {{name}} sada je {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "Više", "More Concise": "", "More options": "", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama verzija", "On": "Uključeno", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Prilagodba", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1675,6 +1682,7 @@ "Running": "Pokrenuto", "Running...": "Pokrenuto...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Spremi", "Save & Create": "Spremi i stvori", "Save & Update": "Spremi i ažuriraj", @@ -1891,6 +1899,7 @@ "STT Model": "STT model", "STT Settings": "STT postavke", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1936,6 +1945,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "Stroj za pretvorbu teksta u govor", + "Th_day_of_week": "", "Thanks for your feedback!": "Hvala na povratnim informacijama!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2049,6 +2059,7 @@ "TTS Model": "TTS model", "TTS Settings": "TTS postavke", "TTS Voice": "TTS glas", + "Tu_day_of_week": "", "Type": "Tip", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Upišite Hugging Face Resolve (Download) URL", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/hu-HU/translation.json b/src/lib/i18n/locales/hu-HU/translation.json index f411786290..362977a7f0 100644 --- a/src/lib/i18n/locales/hu-HU/translation.json +++ b/src/lib/i18n/locales/hu-HU/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Fiók létrehozása", "Create Admin Account": "Admin fiók létrehozása", + "Create and manage scheduled automations": "", "Create Channel": "Csatorna létrehozása", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "Veszélyzóna", "Dark": "Sötét", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Továbbítja a rendszer felhasználói munkamenet hitelesítő adatait a hitelesítéshez", + "Fr_day_of_week": "", "Full Context Mode": "Teljes kontextus mód", "Function": "Funkció", "Function Calling": "Funkcióhívás", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Kezdőlap", "Host": "Hoszt", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Hogyan segíthetek ma?", "How would you rate this response?": "Hogyan értékelnéd ezt a választ?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR API kulcs szükséges.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Modell", "Model '{{modelName}}' has been successfully downloaded.": "A '{{modelName}}' modell sikeresen letöltve.", "Model '{{modelTag}}' is already in queue for downloading.": "A '{{modelTag}}' modell már a letöltési sorban van.", - "Model {{modelId}} not found": "A {{modelId}} modell nem található", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "A {{modelName}} modell nem képes képfeldolgozásra", "Model {{name}} is now {{status}}": "A {{name}} modell most {{status}} állapotban van", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Mojeek Search API kulcs", + "Monthly": "", "More": "Több", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama verzió", "On": "Be", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Személyre szabás", "Pin": "Rögzítés", + "Pin to Sidebar": "", "Pinned": "Rögzítve", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Fut", "Running...": "Fut...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Mentés", "Save & Create": "Mentés és létrehozás", "Save & Update": "Mentés és frissítés", @@ -1889,6 +1897,7 @@ "STT Model": "STT modell", "STT Settings": "STT beállítások", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Szöveg felosztó", "Text-to-Speech": "", "Text-to-Speech Engine": "Szöveg-beszéd motor", + "Th_day_of_week": "", "Thanks for your feedback!": "Köszönjük a visszajelzést!", "The Application Account DN you bind with for search": "Az alkalmazás fiók DN, amellyel kereséshez kötsz", "The base to search for users": "A felhasználók keresésének alapja", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS modell", "TTS Settings": "TTS beállítások", "TTS Voice": "TTS hang", + "Tu_day_of_week": "", "Type": "Típus", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Adja meg a Hugging Face Resolve (Letöltési) URL-t", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Figyelmeztetés: Ennek engedélyezése lehetővé teszi a felhasználók számára, hogy tetszőleges kódot töltsenek fel a szerverre.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Figyelmeztetés: A Jupyter végrehajtás lehetővé teszi a tetszőleges kód végrehajtását, ami súlyos biztonsági kockázatot jelent – óvatosan folytassa.", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "A WebUI kéréseket küld a \"{{url}}\" címre", "WebUI will make requests to \"{{url}}/api/chat\"": "A WebUI kéréseket küld a \"{{url}}/api/chat\" címre", "WebUI will make requests to \"{{url}}/chat/completions\"": "A WebUI kéréseket küld a \"{{url}}/chat/completions\" címre", + "Weekly": "", "What are you trying to achieve?": "Mit próbálsz elérni?", "What are you working on?": "Min dolgozol?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/id-ID/translation.json b/src/lib/i18n/locales/id-ID/translation.json index 0d36c77484..80317e4361 100644 --- a/src/lib/i18n/locales/id-ID/translation.json +++ b/src/lib/i18n/locales/id-ID/translation.json @@ -453,6 +453,7 @@ "Create a new note": "", "Create Account": "Buat Akun", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -478,6 +479,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Gelap", @@ -968,6 +970,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1042,6 +1045,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Ada yang bisa saya bantu hari ini?", "How would you rate this response?": "", @@ -1264,10 +1268,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' telah berhasil diunduh.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' sudah berada dalam antrean untuk diunduh.", - "Model {{modelId}} not found": "Model {{modelId}} tidak ditemukan", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} tidak dapat dilihat", "Model {{name}} is now {{status}}": "Model {{name}} sekarang menjadi {{status}}", @@ -1308,6 +1312,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "Lainnya", "More Concise": "", "More options": "", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Versi Ollama", "On": "Aktif", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1512,6 +1518,7 @@ "Persistent": "", "Personalization": "Personalisasi", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1671,6 +1678,7 @@ "Running": "Berjalan", "Running...": "Berjalan...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Simpan", "Save & Create": "Simpan & Buat", "Save & Update": "Simpan & Perbarui", @@ -1887,6 +1895,7 @@ "STT Model": "Model STT", "STT Settings": "Pengaturan STT", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1932,6 +1941,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "Mesin Teks-ke-Suara", + "Th_day_of_week": "", "Thanks for your feedback!": "Terima kasih atas umpan balik Anda!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2045,6 +2055,7 @@ "TTS Model": "Model TTS", "TTS Settings": "Pengaturan TTS", "TTS Voice": "Suara TTS", + "Tu_day_of_week": "", "Type": "Ketik", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Ketik Hugging Face Resolve (Unduh) URL", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ie-GA/translation.json b/src/lib/i18n/locales/ie-GA/translation.json index ba6fdbc60d..58788f792f 100644 --- a/src/lib/i18n/locales/ie-GA/translation.json +++ b/src/lib/i18n/locales/ie-GA/translation.json @@ -17,8 +17,8 @@ "{{COUNT}} members": "{{COUNT}} ball", "{{COUNT}} Replies": "{{COUNT}} Freagra", "{{COUNT}} Rows": "{{COUNT}} Sraitheanna", - "{{count}} selected_one": "", - "{{count}} selected_other": "", + "{{count}} selected_one": "{{count}} mir roghnaithe", + "{{count}} selected_other": "{{count}} míreanna roghnaithe", "{{COUNT}} Sources": "{{COUNT}} Foinsí", "{{COUNT}} words": "{{COUNT}} focail", "{{COUNT}}d_time_ago": "l", @@ -32,7 +32,7 @@ "{{NAMES}} reacted with {{REACTION}}": "D’fhreagair {{NAMES}} le {{REACTION}}", "{{user}}'s Chats": "Comhráite {{user}}", "{{webUIName}} Backend Required": "{{webUIName}} Ceoldeireadh Riachtanach", - "*Prompt node ID(s) are required for image generation": "* Tá ID nód leid ag teastáil chun íomhá a ghiniúint", + "*Prompt node ID(s) are required for image generation": "* Tá ID(anna) nód treorach ag teastáil chun íomhá a ghiniúint", "1 Source": "1 Foinse", "1m_time_ago": "1 nóiméad ó shin", "A collaboration channel where people join as members": "Cainéal comhoibrithe ina mbíonn daoine ag glacadh páirte mar bhaill", @@ -42,7 +42,7 @@ "A task model is used when performing tasks such as generating titles for chats and web search queries": "Úsáidtear samhail tascanna agus tascanna á ndéanamh amhail teidil a ghiniúint le haghaidh comhráite agus fiosrúcháin chuardaigh ghréasáin", "a user": "úsáideoir", "About": "Maidir", - "Accept Autocomplete Generation\nJump to Prompt Variable": "Glac le Giniúint Uathchríochnaithe\nLéim go dtí an Athróg Pras", + "Accept Autocomplete Generation\nJump to Prompt Variable": "Glac le Giniúint Uathchríochnaithe\nLéim go dtí Athróg na Treorach", "Access": "Rochtain", "Access Control": "Rialaithe Rochtana", "Access Grants": "Deontais Rochtana", @@ -71,7 +71,7 @@ "Add Content": "Cuir Ábhar leis", "Add content here": "Cuir ábhar anseo", "Add Custom Parameter": "Cuir Paraiméadar Saincheaptha leis", - "Add Custom Prompt": "Cuir Leid Saincheaptha leis", + "Add Custom Prompt": "Cuir Treoir Shaincheaptha leis", "Add Details": "Cuir Sonraí leis", "Add Files": "Cuir Comhaid", "Add Image": "Cuir Íomhá leis", @@ -122,14 +122,14 @@ "Allow Chat Export": "Ceadaigh Easpórtáil Comhrá", "Allow Chat Params": "Ceadaigh Paraiméadair Comhrá", "Allow Chat Share": "Ceadaigh Comhroinnt Comhrá", - "Allow Chat System Prompt": "Ceadaigh Pras Córais Comhrá", + "Allow Chat System Prompt": "Ceadaigh Treoir Chórais Comhrá", "Allow Chat Valves": "Ceadaigh Comhlaí Comhrá", "Allow Continue Response": "Ceadaigh Leanúint ar aghaidh leis an bhFreagra", "Allow Delete Messages": "Ceadaigh Teachtaireachtaí a Scriosadh", "Allow File Upload": "Ceadaigh Uaslódáil Comhad", "Allow Multiple Models in Chat": "Ceadaigh Il-Samhlacha i gComhrá", - "Allow non-local voices": "Lig guthanna neamh-áitiúla", - "Allow public write access": "", + "Allow non-local voices": "Ceadaigh guthanna neamh-áitiúla", + "Allow public write access": "Ceadaigh rochtain scríbhneoireachta phoiblí", "Allow Rate Response": "Ceadaigh Freagairt Ráta", "Allow Regenerate Response": "Ceadaigh Freagra Athghiniúint", "Allow Sharing With Users": "Ceadaigh Comhroinnt le hÚsáideoirí", @@ -182,14 +182,14 @@ "Are you sure you want to archive all chats? This action cannot be undone.": "An bhfuil tú cinnte gur mian leat na comhráite go léir a chartlannú? Ní féidir an gníomh seo a chealú.", "Are you sure you want to clear all memories? This action cannot be undone.": "An bhfuil tú cinnte gur mhaith leat na cuimhní go léir a ghlanadh? Ní féidir an gníomh seo a chealú.", "Are you sure you want to delete \"{{NAME}}\"?": "An bhfuil tú cinnte gur mian leat \"{{NAME}}\" a scriosadh?", - "Are you sure you want to delete **{{modelName}}**?": "", + "Are you sure you want to delete **{{modelName}}**?": "An bhfuil tú cinnte gur mian leat **{{modelName}}** a scriosadh?", "Are you sure you want to delete all chats? This action cannot be undone.": "An bhfuil tú cinnte gur mian leat na comhráite go léir a scriosadh? Ní féidir an gníomh seo a chealú.", "Are you sure you want to delete this channel?": "An bhfuil tú cinnte gur mhaith leat an cainéal seo a scriosadh?", - "Are you sure you want to delete this connection? This action cannot be undone.": "", - "Are you sure you want to delete this memory? This action cannot be undone.": "", + "Are you sure you want to delete this connection? This action cannot be undone.": "An bhfuil tú cinnte gur mian leat an nasc seo a scriosadh? Ní féidir an gníomh seo a chealú.", + "Are you sure you want to delete this memory? This action cannot be undone.": "An bhfuil tú cinnte gur mian leat an chuimhne seo a scriosadh? Ní féidir an gníomh seo a chealú.", "Are you sure you want to delete this message?": "An bhfuil tú cinnte gur mhaith leat an teachtaireacht seo a scriosadh?", "Are you sure you want to delete this version? Child versions will be relinked to this version's parent.": "An bhfuil tú cinnte gur mian leat an leagan seo a scriosadh? Déanfar leaganacha linbh a athnascadh le tuismitheoir an leagain seo.", - "Are you sure you want to delete this?": "", + "Are you sure you want to delete this?": "An bhfuil tú cinnte gur mian leat é seo a scriosadh?", "Are you sure you want to unarchive all archived chats?": "An bhfuil tú cinnte gur mhaith leat gach comhrá cartlainne a dhíchartlannú?", "Arena Models": "Samhlacha Réimse", "Artifacts": "Déantáin", @@ -199,7 +199,7 @@ "Assistant": "Cúntóir", "Async Embedding Processing": "Próiseáil Leabaithe Asyncrónach", "Attach File From Knowledge": "Ceangail Comhad ó Eolas", - "Attach Files": "", + "Attach Files": "Ceangail Comhaid", "Attach Knowledge": "Ceangail Eolas", "Attach Notes": "Ceangail Nótaí", "Attach Webpage": "Ceangail Leathanach Gréasáin", @@ -222,13 +222,13 @@ "AUTOMATIC1111 Base URL": "UATHOIBRÍOCH1111 Bun URL", "AUTOMATIC1111 Base URL is required.": "Tá URL bonn UATHOIBRÍOCH1111 ag teastáil.", "Automatically inject system tools in native function calling mode (e.g., timestamps, memory, chat history, notes, etc.)": "Uirlisí córais a instealladh go huathoibríoch i mód glaonna feidhme dúchais (m.sh., stampaí ama, cuimhne, stair comhrá, nótaí, srl.)", - "Automation": "", - "Automation created": "", - "Automation Name": "", - "Automation title": "", - "Automation triggered": "", - "Automation updated": "", - "Automations": "", + "Automation": "Uathoibriú", + "Automation created": "Uathoibriú cruthaithe", + "Automation Name": "Ainm uathoibrithe", + "Automation title": "Teideal uathoibrithe", + "Automation triggered": "Uathoibriú spreagtha", + "Automation updated": "Uathoibriú nuashonraithe", + "Automations": "Uathoibrithe", "Available list": "Liosta atá ar fáil", "Available models": "Samhlacha atá ar fáil", "Available Tools": "Uirlisí ar Fáil", @@ -259,13 +259,13 @@ "Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "Treisiú nó pionós a ghearradh ar chomharthaí sonracha as freagraí srianta. Déanfar luachanna laofachta a chlampáil idir -100 agus 100 (san áireamh). (Réamhshocrú: ceann ar bith)", "Brave": "Brave", "Brave Search API Key": "Eochair API Cuardaigh Brave", - "Break down complex requests into trackable steps": "", + "Break down complex requests into trackable steps": "Bris síos iarratais chasta i gcéimeanna inrianaithe", "Browse and query knowledge bases": "Brabhsáil agus fiosraigh bunachair eolais", "Builtin Tools": "Uirlisí Tógtha", "Bullet List": "Liosta Urchair", "Button ID": "Aitheantas an Chnaipe", "Button Label": "Lipéad Cnaipe", - "Button Prompt": "Leid Cnaipe", + "Button Prompt": "Treoir Cnaipe", "by {{name}}": "le {{name}}", "By {{name}}": "Le {{name}}", "Bypass Embedding and Retrieval": "Seachbhóthar Leabú agus Aisghabháil", @@ -298,7 +298,7 @@ "Character limit for autocomplete generation input": "Teorainn charachtair le haghaidh ionchur giniúna uathchríochnaithe", "Chart new frontiers": "Cairt teorainneacha nua", "Chat": "Comhrá", - "Chat archived.": "", + "Chat archived.": "Comhrá cartlannaithe.", "Chat Background Image": "Íomhá Cúlra Comhrá", "Chat Bubble UI": "Comhrá Bubble UI", "Chat Completions": "Críochnuithe Comhrá", @@ -341,10 +341,10 @@ "Click here to upload a workflow.json file.": "Cliceáil anseo chun comhad workflow.json a uaslódáil.", "click here.": "cliceáil anseo.", "Click on the user role button to change a user's role.": "Cliceáil ar an gcnaipe ról úsáideora chun ról úsáideora a athrú.", - "Click to connect": "", + "Click to connect": "Cliceáil chun ceangal", "Click to copy ID": "Cliceáil chun an t-aitheantas a chóipeáil", - "Client ID": "", - "Client Secret": "", + "Client ID": "Aitheantas Cliant", + "Client Secret": "Rún an Chliaint", "Clipboard write permission denied. Please check your browser settings to grant the necessary access.": "Diúltaíodh cead scríofa an ghearrthaisce. Seiceáil socruithe do bhrabhsálaí chun an rochtain riachtanach a dheonú.", "Clone": "Clón", "Clone Chat": "Comhrá Clón", @@ -370,7 +370,7 @@ "Code formatted successfully": "Cód formáidithe go rathúil", "Code Interpreter": "Ateangaire Cód", "Code Interpreter Engine": "Inneall Ateangaire Cóid", - "Code Interpreter Prompt Template": "Teimpléad Pras Ateangaire Cód", + "Code Interpreter Prompt Template": "Teimpléad Treorach Ateangaire Cód", "Collaboration channel where people join as members": "Cainéal comhoibrithe ina mbíonn daoine páirteach mar bhaill", "Collapse": "Laghdaigh", "Collection": "Bailiúchán", @@ -393,11 +393,11 @@ "Concurrent Requests": "Iarrataí Comhthéime", "Config": "Cumraíocht", "Config imported successfully": "Cumraíocht allmhairithe go rathúil", - "Configuration": "", + "Configuration": "Cumraíocht", "Configure": "Cumraigh", "Confirm": "Deimhnigh", "Confirm Password": "Deimhnigh Pasfhocal", - "Confirm Prompt from Embed": "", + "Confirm Prompt from Embed": "Deimhnigh Treoir ón Leabú", "Confirm your action": "Deimhnigh do ghníomh", "Confirm your new password": "Deimhnigh do phasfhocal nua", "Confirm Your Password": "Deimhnigh Do Phasfhocal", @@ -406,7 +406,7 @@ "Connect to Open Terminal instances. All users will have access to file browsing and terminal tools through these servers.": "Ceangail le cásanna Open Terminal. Beidh rochtain ag gach úsáideoir ar bhrabhsáil comhad agus uirlisí críochfoirt trí na freastalaithe seo.", "Connect to your own OpenAI compatible API endpoints.": "Ceangail le do chríochphointí API atá comhoiriúnach le OpenAI.", "Connect to your own OpenAPI compatible external tool servers.": "Ceangail le do fhreastalaithe uirlisí seachtracha atá comhoiriúnach le OpenAPI.", - "Connected ({{type}})": "", + "Connected ({{type}})": "Ceangailte ({{type}})", "Connection failed": "Theip ar an gceangal", "Connection successful": "Ceangal rathúil", "Connection Type": "Cineál Ceangail", @@ -439,7 +439,7 @@ "Copy Last Response": "Cóipeáil an Fhreagra Deiridh", "Copy link": "Cóipeáil nasc", "Copy Link": "Cóipeáil Nasc", - "Copy Prompt": "CCóipeáil an Treoir", + "Copy Prompt": "Cóipeáil an Treoir", "Copy Share Link": "Cóipeáil Nasc Comhroinnte", "Copy to clipboard": "Cóipeáil chuig an ngearrthaisce", "Copy Token": "Cóipeáil Comhartha", @@ -447,13 +447,14 @@ "Copying to clipboard was successful!": "D'éirigh le cóipeáil chuig an ngearrthaisce!", "CORS must be properly configured by the provider to allow requests from Open WebUI.": "Ní mór don soláthraí CORS a chumrú i gceart chun iarratais ó Open WebUI a cheadú.", "Could not read file.": "Níorbh fhéidir an comhad a léamh.", - "CPU": "", + "CPU": "LAP", "Create": "Cruthaigh", "Create a knowledge base": "Cruthaigh bonn eolais", "Create a model": "Cruthaigh samhail", "Create a new note": "Cruthaigh nóta nua", "Create Account": "Cruthaigh Cuntas", "Create Admin Account": "Cruthaigh Cuntas Riaracháin", + "Create and manage scheduled automations": "Cruthaigh agus bainistigh uathoibrithe sceidealaithe", "Create Channel": "Cruthaigh Cainéal", "Create Folder": "Cruthaigh Fillteán", "Create Image": "Cruthaigh Íomhá", @@ -463,7 +464,7 @@ "Create new secret key": "Cruthaigh eochair rúnda nua", "Create note": "Cruthaigh nóta", "Create Note": "Cruthaigh Nóta", - "Create scheduled prompts that run automatically on a recurring basis.": "", + "Create scheduled prompts that run automatically on a recurring basis.": "Cruthaigh leideanna sceidealaithe a ritheann go huathoibríoch ar bhonn athfhillteach.", "Create your first note by clicking on the plus button below.": "Cruthaigh do chéad nóta trí chliceáil ar an gcnaipe móide thíos.", "Created at": "Cruthaithe ag", "Created At": "Cruthaithe Ag", @@ -479,13 +480,14 @@ "Custom Gender": "Inscne Saincheaptha", "Custom Parameter Name": "Ainm Paraiméadair Saincheaptha", "Custom Parameter Value": "Luach Paraiméadair Saincheaptha", + "Daily": "Laethúil", "Daily Messages": "Teachtaireachtaí Laethúla", "Danger Zone": "Crios Contúirte", "Dark": "Dorcha", "Data Controls": "Rialuithe Sonraí", "Database": "Bunachar Sonraí", "Datalab Marker API": "API Marcóra Datalab", - "Day": "", + "Day": "Lá", "DD/MM/YYYY": "DD/MM/YYYY", "DDGS Backend": "Cúltaca DDGS", "December": "Nollaig", @@ -504,30 +506,30 @@ "Default model updated": "Nuashonraithe samhail réamhshocraithe", "Default permissions": "Ceadanna réamhshocraithe", "Default permissions updated successfully": "D'éirigh le ceadanna réamhshocraithe a nuashonrú", - "Default Prompt Suggestions": "Moltaí Leid Réamhshocraithe", + "Default Prompt Suggestions": "Moltaí Treoracha Réamhshocraithe", "Default to 389 or 636 if TLS is enabled": "Réamhshocrú go 389 nó 636 má tá TLS cumasaithe", "Default to ALL": "Réamhshocrú do GACH", "Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.": "Réamhshocrú maidir le haisghabháil deighilte d'eastóscadh ábhar dírithe agus ábhartha, moltar é seo i bhformhór na gcásanna.", "Default User Role": "Ról Úsáideora Réamhshocraithe", "Defaults": "Réamhshocruithe", "Delete": "Scrios", - "Delete {{name}}": "", + "Delete {{name}}": "Scrios {{name}}", "Delete a model": "Scrios samhail", "Delete All": "Scrios Gach Rud", "Delete All Chats": "Scrios Gach Comhrá", "Delete all contents inside this folder": "Scrios an t-ábhar go léir atá sa fhillteán seo", - "Delete automation?": "", + "Delete automation?": "Scrios an t-uathoibriú?", "Delete Chat": "Scrios Comhrá", "Delete chat?": "Scrios comhrá?", "Delete File": "Scrios Comhad", "Delete folder?": "Scrios fillteán?", "Delete function?": "Scrios feidhm?", - "Delete Memory?": "", + "Delete Memory?": "Scrios Cuimhne?", "Delete Message": "Scrios Teachtaireacht", "Delete message?": "Scrios teachtaireacht?", "Delete Model": "Scrios an tSamhail", "Delete note?": "Scrios an nóta?", - "Delete prompt?": "Scrios leid?", + "Delete prompt?": "Scrios an treoir?", "Delete skill?": "Scrios an scil?", "delete this link": "scrios an nasc seo", "Delete tool?": "Uirlis a scriosadh?", @@ -536,7 +538,7 @@ "Deleted": "Scriosta", "Deleted {{deleteModelTag}}": "Scriosta {{deleteModelTag}}", "Deleted {{name}}": "Scriosta {{name}}", - "Deleted {{ok}} of {{total}} items": "", + "Deleted {{ok}} of {{total}} items": "Scriosadh {{ok}} de {{total}} míreanna", "Deleted User": "Úsáideoir Scriosta", "Deployment names are required for Azure OpenAI": "Tá ainmneacha imscartha ag teastáil le haghaidh Azure OpenAI", "Desc": "Cur Síos", @@ -545,7 +547,7 @@ "Describe what changed...": "Déan cur síos ar a bhfuil athraithe...", "Describe your knowledge base and objectives": "Déan cur síos ar do bhunachar eolais agus do chuspóirí", "Description": "Cur síos", - "Deselect": "", + "Deselect": "Díroghnaigh", "Detect Artifacts Automatically": "Déan Déantáin a bhrath go huathoibríoch", "Dictate": "Deachtaigh", "Didn't fully follow instructions": "Níor lean sé treoracha go hiomlán", @@ -562,14 +564,14 @@ "Disabled": "Díchumasaithe", "Discover a function": "Faigh amach feidhm", "Discover a model": "Faigh amach samhail", - "Discover a prompt": "Faigh amach leid", + "Discover a prompt": "Faigh amach treoir", "Discover a tool": "Faigh amach uirlis", "Discover how to use Open WebUI and seek support from the community.": "Faigh amach conas Open WebUI a úsáid agus lorg tacaíocht ón bpobal.", "Discover wonders": "Faigh amach iontais", "Discover, download, and explore custom functions": "Faigh amach, íoslódáil agus iniúchadh feidhmeanna saincheaptha", - "Discover, download, and explore custom prompts": "Leideanna saincheaptha a fháil amach, a íoslódáil agus a iniúchadh", - "Discover, download, and explore custom tools": "Uirlisí saincheaptha a fháil amach, íoslódáil agus iniúchadh", - "Discover, download, and explore model presets": "Réamhshocruithe samhail a fháil amach, a íoslódáil agus a iniúchadh", + "Discover, download, and explore custom prompts": "Faigh amach, íoslódáil agus iniúch treoracha saincheaptha", + "Discover, download, and explore custom tools": "Faigh amach, íoslódáil agus taiscéal uirlisí saincheaptha", + "Discover, download, and explore model presets": "Faigh amach, íoslódáil agus réamhshocruithe samhail a iniúchadh", "Discussion channel where access is based on groups and permissions": "Cainéal plé ina bhfuil rochtain bunaithe ar ghrúpaí agus ceadanna", "Display": "Taispeáin", "Display chat title in tab": "Taispeáin teideal an chomhrá sa chluaisín", @@ -608,7 +610,7 @@ "Downloading stats...": "Ag íoslódáil staitisticí...", "Draw": "Tarraing", "Drop any files here to upload": "Scaoil aon chomhaid anseo le huaslódáil", - "Drop files here": "", + "Drop files here": "Scaoil comhaid anseo", "Drop files here to upload": "Scaoil comhaid anseo le huaslódáil", "DuckDuckGo": "DuckDuckGo", "e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "m.sh. '30s', '10m'. Is iad aonaid ama bailí ná 's', 'm', 'h'.", @@ -661,8 +663,8 @@ "Embedding Concurrent Requests": "Iarratais Chomhuaineacha a Leabú", "Embedding Model": "Samhail Leabháilte", "Embedding Model Engine": "Inneall Samhail Leabaithe", - "Emojis": "", - "Empty message": "", + "Emojis": "Emoji", + "Empty message": "Teachtaireacht folamh", "Enable All": "Cumasaigh Gach Rud", "Enable API Keys": "Cumasaigh Eochracha API", "Enable autocomplete generation for chat messages": "Cumasaigh giniúint uathchríochnaithe le haghaidh teachtaireachtaí comhrá", @@ -753,7 +755,7 @@ "Enter Perplexity Search API URL": "Cuir isteach URL API Cuardaigh na Measctha", "Enter Playwright Timeout": "Iontráil Teorainn Ama na nDrámadóir", "Enter Playwright WebSocket URL": "Cuir isteach URL WebSocket Seinmeora", - "Enter prompt here.": "", + "Enter prompt here.": "Cuir isteach an treoir anseo.", "Enter proxy URL (e.g. https://user:password@host:port)": "Cuir isteach URL seachfhreastalaí (m.sh. https://user:password@host:port)", "Enter reasoning effort": "Cuir isteach iarracht réasúnaíochta", "Enter Score": "Iontráil Scór", @@ -774,11 +776,11 @@ "Enter Sougou Search API sID": "Cuir isteach sID Sougou Search API", "Enter Sougou Search API SK": "Cuir isteach Sougou Search API SK", "Enter stop sequence": "Cuir isteach seicheamh stad", - "Enter system prompt": "Cuir isteach an chóras leid", - "Enter system prompt here": "Cuir leid córais isteach anseo", + "Enter system prompt": "Cuir isteach treoir chórais", + "Enter system prompt here": "Cuir isteach treoir chórais anseo", "Enter Tavily API Key": "Cuir isteach eochair API Tavily", "Enter Tavily Extract Depth": "Cuir isteach Doimhneacht Sliocht Tavily", - "Enter the prompt instructions for this automation...": "", + "Enter the prompt instructions for this automation...": "Cuir isteach treoracha na treorach don uathoibriú seo...", "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 the URL of the function to import": "Cuir isteach URL na feidhme atá le hallmhairiú", "Enter the URL to import": "Cuir isteach an URL le hallmhairiú", @@ -811,14 +813,14 @@ "Enter Your Username": "Cuir isteach D'Ainm Úsáideora", "Enter your webhook URL": "Cuir isteach URL do webhook", "Entra ID": "Aitheantas Entra", - "Environment Variables": "", - "Ephemeral": "", + "Environment Variables": "Athróga Timpeallachta", + "Ephemeral": "Gearrshaolach", "Error": "Earráid", "ERROR": "EARRÁID", "Error accessing directory": "Earráid ag rochtain eolaire", "Error accessing Google Drive: {{error}}": "Earráid agus tú ag rochtain Google Drive: {{error}}", "Error accessing media devices.": "Earráid ag rochtain gléasanna meán.", - "Error deleting model: {{error}}": "", + "Error deleting model: {{error}}": "Earráid ag scriosadh samhail: {{error}}", "Error starting recording.": "Earráid ag tosú taifeadta.", "Error unloading model: {{error}}": "Earráid ag díluchtú samhail: {{error}}", "Error uploading file: {{error}}": "Earráid agus comhad á uaslódáil: {{error}}", @@ -836,25 +838,25 @@ "Execute code": "Cód a fhorghníomhú", "Execute code for analysis": "Íosluchtaigh cód le haghaidh anailíse", "Executing **{{NAME}}**...": "**{{NAME}}** á rith...", - "Execution Logs": "", + "Execution Logs": "Logaí Forghníomhaithe", "Expand": "Leathnaigh", "Experimental": "Turgnamhach", "Explain": "Mínigh", "Explore the cosmos": "Déan iniúchadh ar an cosmos", - "Explored": "", - "Exploring": "", + "Explored": "Iniúchadh", + "Exploring": "Ag iniúchadh", "Export": "Easpórtáil", "Export All Archived Chats": "Easpórtáil Gach Comhrá Cartlainne", "Export All Chats (All Users)": "Easpórtáil gach comhrá (Gach Úsáideoir)", - "Export as CSV": "", - "Export as JSON": "", + "Export as CSV": "Easpórtáil mar CSV", + "Export as JSON": "Easpórtáil mar JSON", "Export chat (.json)": "Easpórtáil comhrá (.json)", - "Export Chats": "Comhráite Easpórtá", - "Export Config": "Cumraíocht Easpórtála", - "Export Models": "Samhlacha Easpórtála", - "Export Prompts": "Leideanna Easpórtála", + "Export Chats": "Easpórtáil Comhráite", + "Export Config": "Easpórtáil Cumraíocht", + "Export Models": "Easpórtáil Samhlacha", + "Export Prompts": "Easpórtáil Treoracha", "Export to CSV": "Easpórtáil go CSV", - "Export Tools": "Uirlisí Easpórtála", + "Export Tools": "Easpórtáil Uirlisí", "Export Users": "Easpórtáil Úsáideoirí", "External": "Seachtrach", "External Document Loader URL required.": "URL Luchtaitheora Doiciméad Seachtrach ag teastáil.", @@ -866,7 +868,7 @@ "Fade Effect for Streaming Text": "Éifeacht Céimnithe le haghaidh Sruthú Téacs", "Failed to add file.": "Theip ar an gcomhad a chur leis.", "Failed to add members": "Theip ar bhaill a chur leis", - "Failed to archive chat.": "", + "Failed to archive chat.": "Theip ar an gcomhrá a chartlannú.", "Failed to attach file": "Theip ar an gcomhad a cheangal", "Failed to clear status": "Theip ar an stádas a ghlanadh", "Failed to connect to {{URL}} OpenAPI tool server": "Theip ar nascadh le {{URL}} freastalaí uirlisí OpenAPI", @@ -881,11 +883,11 @@ "Failed to generate title": "Theip ar an teideal a ghiniúint", "Failed to import models": "Theip ar samhail a iompórtáil", "Failed to load chat preview": "Theip ar réamhamharc comhrá a lódáil", - "Failed to load DOCX file. Please try downloading it instead.": "", + "Failed to load DOCX file. Please try downloading it instead.": "Theip ar an gcomhad DOCX a luchtú. Déan iarracht é a íoslódáil ina ionad.", "Failed to load Excel/CSV file. Please try downloading it instead.": "Theip ar an gcomhad Excel/CSV a lódáil. Déan iarracht é a íoslódáil ina ionad.", "Failed to load file content.": "Theip ar lódáil ábhar an chomhaid.", "Failed to load Interface settings": "Theip ar shocruithe an Chomhéadain a lódáil", - "Failed to load PPTX file. Please try downloading it instead.": "", + "Failed to load PPTX file. Please try downloading it instead.": "Theip ar an gcomhad PPTX a luchtú. Déan iarracht é a íoslódáil ina ionad.", "Failed to move chat": "Theip ar an gcomhrá a bhogadh", "Failed to process URL: {{url}}": "Theip ar phróiseáil an URL: {{url}}", "Failed to read clipboard contents": "Theip ar ábhar gearrthaisce a lé", @@ -895,7 +897,7 @@ "Failed to save connections": "Theip ar na naisc a shábháil", "Failed to save conversation": "Theip ar an gcomhrá a shábháil", "Failed to save models configuration": "Theip ar chumraíocht na samhlacha a shábháil", - "Failed to save policy: {{error}}": "", + "Failed to save policy: {{error}}": "Theip ar an mbeartas a shábháil: {{error}}", "Failed to save terminal servers": "Theip ar fhreastalaithe críochfoirt a shábháil", "Failed to unshare chat.": "Theip ar an gcomhrá a dhíroinnt.", "Failed to update settings": "Theip ar shocruithe a nuashonrú", @@ -911,7 +913,7 @@ "Feedback History": "Stair Aiseolais", "Feel free to add specific details": "Ná bíodh leisce ort sonraí ar leith a chur leis", "Female": "Baineann", - "Fetch URL Content Length Limit": "", + "Fetch URL Content Length Limit": "Teorainn Fad Ábhar URL a Aisghabháil", "File": "Comhad", "File added successfully.": "D'éirigh leis an gcomhad a chur leis.", "File attached to chat": "Comhad ceangailte leis an gcomhrá", @@ -942,7 +944,7 @@ "Focus Chat Input": "Dírigh ar Ionchur Comhrá", "Folder": "Fillteán", "Folder Background Image": "Íomhá Chúlra Fillteáin", - "Folder created successfully": "", + "Folder created successfully": "Cruthaíodh fillteán go rathúil", "Folder deleted successfully": "Scriosadh an fillteán go rathúil", "Folder Max File Count": "Uasmhéid Líon Comhad Fillteán", "Folder name": "Ainm fillteáin", @@ -954,7 +956,7 @@ "Folders": "Fillteáin", "Follow up": "Leanúint suas", "Follow Up Generation": "Giniúint Leantach", - "Follow Up Generation Prompt": "Leid Ghiniúna Leanúnach", + "Follow Up Generation Prompt": "Treoir Giniúna Leantach", "Follow up: {{question}}": "Leanúint suas: {{question}}", "Follow-Up Auto-Generation": "Uathghiniúint Leantach", "Followed instructions perfectly": "Lean treoracha go foirfe", @@ -966,9 +968,10 @@ "Format Lines": "Formáid Línte", "Format the lines in the output. Defaults to False. If set to True, the lines will be formatted to detect inline math and styles.": "Formáidigh na línte san aschur. Is é Bréag an réamhshocrú. Má shocraítear é go Fíor, déanfar na línte a fhormáidiú chun matamaitic agus stíleanna inlíne a bhrath.", "Formatting may be inconsistent from source.": "B’fhéidir nach bhfuil an fhormáidiú comhsheasmhach ón bhfoinse.", - "Forward": "", + "Forward": "Ar Aghaidh", "Forwards system user OAuth access token to authenticate": "Seolann sé comhartha rochtana OAuth úsáideora an chórais ar aghaidh chun fíordheimhniú a dhéanamh", "Forwards system user session credentials to authenticate": "Cuir dintiúir seisiúin úsáideora córais ar aghaidh lena bhfíordheimhniú", + "Fr_day_of_week": "Aoine", "Full Context Mode": "Mód Comhthéacs Iomlán", "Function": "Feidhm", "Function Calling": "Glaonna Feidhme", @@ -1043,7 +1046,8 @@ "History": "Stair", "Home": "Baile", "Host": "Óstach", - "Hourly Messages": "Teachtaireachtaí Uaireanta", + "Hourly": "Gach uair an chloig", + "Hourly Messages": "Teachtaireachtaí gach uair an chloig", "How can I help you today?": "Conas is féidir liom cabhrú leat inniu?", "How would you rate this response?": "Cad é mar a mheasfá an freagra seo?", "HTML": "HTML", @@ -1054,7 +1058,7 @@ "ID": "ID", "ID cannot contain \":\" or \"|\" characters": "Ní féidir carachtair \":\" nó \"|\" a bheith san ID", "ID copied to clipboard": "Aitheantas cóipeáilte chuig an ghearrthaisce", - "Idle Timeout": "", + "Idle Timeout": "Am Teorann Díomhaoin", "iframe Sandbox Allow Forms": "iframe Bosca Gainimh Foirmeacha Ceadaithe", "iframe Sandbox Allow Same Origin": "ceadaigh Bosca Gainimh iframe an Bunús Céanna", "Ignite curiosity": "Las fiosracht", @@ -1069,8 +1073,8 @@ "Image Max Compression Size": "Íomhá Méid Comhbhrú Max", "Image Max Compression Size height": "Airde Uasmhéid Comhbhrúite Íomhá", "Image Max Compression Size width": "Leithead Uasmhéid Comhbhrúite Íomhá", - "Image Prompt Generation": "Giniúint Leid Íomhá", - "Image Prompt Generation Prompt": "Leid Giniúint Leide Íomhá", + "Image Prompt Generation": "Giniúint Treoracha Íomhá", + "Image Prompt Generation Prompt": "Treoir Giniúna Treoracha Íomhá", "Image Size": "Méid na hÍomhá", "Images": "Íomhánna", "Import": "Iompórtáil", @@ -1078,7 +1082,7 @@ "Import Config": "Cumraíocht Iompórtála", "Import From Link": "Iompórtáil Ó Nasc", "Import Models": "Iompórtáil Samhlacha", - "Import Prompts": "Leideanna Iompórtála", + "Import Prompts": "Iompórtáil Treoracha", "Import successful": "D'éirigh leis an allmhairiú", "Import Tools": "Uirlisí Iompórtála", "Important Update": "Nuashonrú tábhachtach", @@ -1097,12 +1101,12 @@ "Input Key (e.g. text, unet_name, steps)": "Eochair Ionchuir (m.sh. téacs, unet_name, céimeanna)", "Input Variables": "Athróga Ionchuir", "Insert": "Cuir isteach", - "Insert Follow-Up Prompt to Input": "Cuir isteach leid leantach le hionchur", - "Insert Prompt as Rich Text": "Cuir isteach an leid mar théacs saibhir", - "Insert Suggestion Prompt to Input": "Cuir isteach Moladh Leid chun Ionchur", + "Insert Follow-Up Prompt to Input": "Cuir Treoir Leantach leis an Ionchur", + "Insert Prompt as Rich Text": "Cuir an Treoir isteach mar Théacs Saibhir", + "Insert Suggestion Prompt to Input": "Cuir Treoir Mholta leis an Ionchur", "Install from Github URL": "Suiteáil ó Github URL", "Instant Auto-Send After Voice Transcription": "Seoladh Uathoibríoch Láithreach Tar éis", - "Instructions": "", + "Instructions": "Treoracha", "Integration": "Comhtháthú", "Integrations": "Comhtháthúcháin", "Interface": "Comhéadan", @@ -1132,7 +1136,7 @@ "JWT Expiration": "Éag JWT", "JWT Token": "Comhartha JWT", "Kagi Search API Key": "Eochair API Chuardaigh Kagi", - "Keep Follow-Up Prompts in Chat": "Coinnigh Leideanna Leanúnacha i gComhrá", + "Keep Follow-Up Prompts in Chat": "Coinnigh Treoracha Leantacha sa Chomhrá", "Keep in Sidebar": "Coinnigh sa Bharra Taobh", "Key": "Eochair", "Key is required": "Tá eochair ag teastáil", @@ -1162,7 +1166,7 @@ "Last 90 days": "90 lá seo caite", "Last Active": "Gníomhach Deiridh", "Last Modified": "Athraithe Deiridh", - "Last ran": "", + "Last ran": "Rith dheireanach", "Last reply": "Freagra deiridh", "LDAP": "LDAP", "LDAP server updated": "Nuashonraíodh freastalaí LDAP", @@ -1182,7 +1186,7 @@ "Leave empty to use first admin user": "Fág folamh chun an chéad úsáideoir riarthóra a úsáid", "Leave empty to use the default config, or enter a valid json (see https://yandex.cloud/en/docs/search-api/api-ref/WebSearch/search#yandex.cloud.searchapi.v2.WebSearchRequest)": "Fág folamh chun an chumraíocht réamhshocraithe a úsáid, nó cuir isteach json bailí (féach https://yandex.cloud/en/docs/search-api/api-ref/WebSearch/search#yandex.cloud.searchapi.v2.WebSearchRequest)", "Leave empty to use the default model (voxtral-mini-latest).": "Fág folamh chun an tsamhail réamhshocraithe (voxtral-mini-latest) a úsáid.", - "Leave empty to use the default prompt, or enter a custom prompt": "Fág folamh chun an leid réamhshocraithe a úsáid, nó cuir isteach leid saincheaptha", + "Leave empty to use the default prompt, or enter a custom prompt": "Fág folamh chun an treoir réamhshocraithe a úsáid, nó cuir isteach treoir shaincheaptha", "Leave model field empty to use the default model.": "Fág an réimse samhail folamh chun an tsamhail réamhshocraithe a úsáid.", "Legacy": "Oidhreacht", "lexical": "leicseach", @@ -1227,7 +1231,7 @@ "Max Speakers": "Uasmhéid Cainteoirí", "Max Upload Count": "Líon Uaslódála Max", "Max Upload Size": "Méid Uaslódála Max", - "Maximum characters to return from fetched URLs. Leave empty for no limit.": "", + "Maximum characters to return from fetched URLs. Leave empty for no limit.": "Uasmhéid carachtair le tabhairt ar ais ó URLanna a fuarthas. Fág folamh le haghaidh gan teorainn.", "Maximum number of files allowed per folder.": "Uasmhéid na gcomhad a cheadaítear in aghaidh an fhillteáin.", "Maximum number of files per folder is {{max}}.": "Is é {{max}} an líon uasta comhad in aghaidh an fhillteáin.", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Is féidir uasmhéid de 3 samhail a íoslódáil ag an am Bain triail as arís níos déanaí.", @@ -1259,17 +1263,17 @@ "Microsoft OneDrive": "Microsoft OneDrive", "Microsoft OneDrive (personal)": "Microsoft OneDrive (pearsanta)", "Microsoft OneDrive (work/school)": "Microsoft OneDrive (obair/scoil)", - "min": "", + "min": "nóim", "MinerU": "MinerU", "MinerU API Key required for Cloud API mode.": "Eochair API MinerU ag teastáil le haghaidh mód Cloud API.", "Mistral OCR": "OCR Mistral", "Mistral OCR API Key required.": "Mistral OCR API Eochair ag teastáil.", "MistralAI": "MistralAI", + "Mo_day_of_week": "Luan", "Model": "Samhail", "Model '{{modelName}}' has been successfully downloaded.": "Rinneadh an tsamhail '{{modelName}}' a íoslódáil go rathúil.", "Model '{{modelTag}}' is already in queue for downloading.": "Tá samhail '{{modelTag}}' sa scuaine cheana féin le híoslódáil.", - "Model {{modelId}} not found": "Níor aimsíodh an tsamhail {{modelId}}", - "Model {{modelName}} deleted successfully": "", + "Model {{modelName}} deleted successfully": "Scriosadh an tsamhail {{modelName}} go rathúil", "Model {{modelName}} is not vision capable": "Níl samhail {{modelName}} in ann amharc", "Model {{name}} is now {{status}}": "Tá samhail {{name}} {{status}} anois", "Model {{name}} is now hidden": "Tá an tsamhail {{name}} i bhfolach anois", @@ -1277,7 +1281,7 @@ "Model accepts file inputs": "Glacann an tsamhail le hionchuir chomhaid", "Model accepts image inputs": "Glacann an tsamhail le hionchuir íomhá", "Model can execute code and perform calculations": "Is féidir leis an tsamhail cód a fhorghníomhú agus ríomhaireachtaí a dhéanamh", - "Model can generate images based on text prompts": "Is féidir leis an tsamhail íomhánna a ghiniúint bunaithe ar leideanna téacs", + "Model can generate images based on text prompts": "Is féidir leis an tsamhail íomhánna a ghiniúint bunaithe ar threoracha téacs", "Model can search the web for information": "Is féidir leis an tsamhail cuardach a dhéanamh ar an ngréasán le haghaidh faisnéise", "Model Capabilities": "Cumais Samhail", "Model created successfully!": "Cruthaíodh an tsamhail go rathúil!", @@ -1309,21 +1313,22 @@ "Models Sharing": "Roinnt Samhlacha", "Mojeek": "Mojeek", "Mojeek Search API Key": "Eochair API Cuardach Mojeek", + "Monthly": "Míosúil", "More": "Tuilleadh", "More Concise": "Níos Gonta", "More options": "Tuilleadh roghanna", "More Options": "Tuilleadh Roghanna", "Move": "Bog", - "Moved {{name}}": "", + "Moved {{name}}": "Bogadh {{name}}", "My Terminal": "Mo Teirminéal", "Name": "Ainm", "Name and ID are required, please fill them out": "Tá ainm agus aitheantas ag teastáil, líon isteach iad le do thoil", "Name your knowledge base": "Cuir ainm ar do bhunachar eolais", - "Name, prompt, and model are required": "", + "Name, prompt, and model are required": "Tá ainm, treoir agus samhail riachtanach", "Native": "Dúchasach", - "Never": "", + "Never": "Choíche", "New": "Nua", - "New Automation": "", + "New Automation": "Uathoibriú Nua", "New Button": "Cnaipe Nua", "New Chat": "Comhrá Nua", "New File": "Comhad Nua", @@ -1334,7 +1339,7 @@ "New Model": "Samhail Nua", "New Note": "Nóta Nua", "New Password": "Pasfhocal Nua", - "New Prompt": "Leid Nua", + "New Prompt": "Treoir Nua", "New Skill": "Scil Nua", "New Temporary Chat": "Comhrá Sealadach Nua", "New Terminal": "Teirminéal Nua", @@ -1342,11 +1347,11 @@ "New Webhook": "Gréasáin Nua", "new-channel": "nua-chainéil", "Next message": "An chéad teachtaireacht eile", - "Next run": "", + "Next run": "An chéad rith eile", "No access grants. Private to you.": "Gan aon deontais rochtana. Príobháideach duitse.", "No activity data": "Gan aon sonraí gníomhaíochta", "No authentication": "Gan fíordheimhniú", - "No automations found": "", + "No automations found": "Níor aimsíodh aon uathoibrithe", "No chats found": "Ní bhfuarthas aon chomhráite", "No chats found for this user.": "Ní bhfuarthas aon chomhráite don úsáideoir seo.", "No chats found.": "Ní bhfuarthas aon chomhráite.", @@ -1357,22 +1362,22 @@ "No data": "Gan aon sonraí", "No data found": "Níor aimsíodh aon sonraí", "No distance available": "Níl achar ar fáil", - "No execution logs available yet": "", + "No execution logs available yet": "Níl aon logaí forghníomhaithe ar fáil go fóill", "No expiration can pose security risks.": "Ní féidir le haon dáta éaga rioscaí slándála a chruthú.", "No feedback found": "Níor aimsíodh aon aiseolas", "No file selected": "Níl aon chomhad roghnaithe", "No files found": "Níor aimsíodh aon chomhaid", "No files in this knowledge base.": "Níl aon chomhaid sa bhunachar eolais seo.", - "No files yet. Upload files or run Python code to create them.": "", + "No files yet. Upload files or run Python code to create them.": "Gan aon chomhaid fós. Uaslódáil comhaid nó rith cód Python chun iad a chruthú.", "No functions found": "Níor aimsíodh aon fheidhmeanna", "No groups found": "Níor aimsíodh aon ghrúpaí", "No history available": "Níl aon stair ar fáil", "No HTML, CSS, or JavaScript content found.": "Níor aimsíodh aon ábhar HTML, CSS nó JavaScript.", "No inference engine with management support found": "Níor aimsíodh aon inneall tátail le tacaíocht bhainistíochta", - "No kernel": "", + "No kernel": "Gan aon eithne", "No knowledge bases found.": "Níor aimsíodh aon bhunachair eolais.", "No knowledge found": "Níor aimsíodh aon eolas", - "No limit": "", + "No limit": "Gan teorainn", "No memories to clear": "Gan cuimhní cinn a ghlanadh", "No model IDs": "Gan aon aitheantóirí samhail", "No models available": "Níl aon samhlacha ar fáil", @@ -1382,15 +1387,15 @@ "No notes found": "Níor aimsíodh aon nótaí", "No one": "Níl aon duine", "No pinned messages": "Gan aon teachtaireachtaí bioráilte", - "No prompts found": "Níor aimsíodh aon leideanna", + "No prompts found": "Níor aimsíodh aon treoracha", "No results": "Níl aon torthaí le fáil", "No results found": "Níl aon torthaí le fáil", "No search query generated": "Ní ghintear aon cheist cuardaigh", - "No servers detected": "", + "No servers detected": "Níor braitheadh aon fhreastalaithe", "No skills found": "Níor aimsíodh aon scileanna", "No source available": "Níl aon fhoinse ar fáil", "No sources found": "Níor aimsíodh aon fhoinsí", - "No suggestion prompts": "Gan leideanna molta", + "No suggestion prompts": "Gan aon treoracha molta", "No Terminal connection configured.": "Gan nasc teirminéal cumraithe.", "No terminal connections configured.": "Gan aon naisc teirminéal cumraithe.", "No tool server connections configured.": "Níl aon naisc freastalaí uirlisí cumraithe.", @@ -1404,7 +1409,7 @@ "Not factually correct": "Níl sé ceart go fírineach", "Not helpful": "Gan a bheith cabhrach", "Not Registered": "Gan Clárú", - "Not scheduled": "", + "Not scheduled": "Gan sceidealú", "Note": "Nóta", "Note deleted successfully": "Scriosadh an nóta go rathúil", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nóta: Má shocraíonn tú íosscór, ní thabharfaidh an cuardach ach doiciméid a bhfuil scór níos mó ná nó cothrom leis an scór íosta ar ais.", @@ -1417,7 +1422,7 @@ "November": "Samhain", "OAuth": "OAuth", "OAuth 2.1": "OAuth 2.1", - "OAuth 2.1 (Static)": "", + "OAuth 2.1 (Static)": "OAuth 2.1 (Statach)", "OAuth ID": "Aitheantas OAuth", "October": "Deireadh Fómhair", "Off": "As", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Eochair API Ollama Cloud", "Ollama Version": "Leagan Ollama", "On": "Ar", + "Once": "Uair amháin", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Gníomhach amháin nuair a bhíonn an socrú \"Greamaigh Téacs Mór mar Chomhad\" casta air.", "Only active when the chat input is in focus and an LLM is generating a response.": "Gníomhach ach amháin nuair a bhíonn an t-ionchur comhrá i bhfócas agus nuair a bhíonn LLM ag giniúint freagra.", @@ -1447,7 +1453,7 @@ "Oops! You're using an unsupported method (frontend only). Please serve the WebUI from the backend.": "Ups! Tá modh gan tacaíocht á úsáid agat (tosaigh amháin). Freastal ar an WebUI ón gcúltaca le do thoil.", "Open file": "Oscail comhad", "Open in full screen": "Oscail i scáileán iomlán", - "Open in new tab": "", + "Open in new tab": "Oscail i gcluaisín nua", "Open link": "Oscail nasc", "Open modal to configure connection": "Oscail an modal chun an nasc a chumrú", "Open Modal To Manage Floating Quick Actions": "Oscail Modúl Chun Gníomhartha Tapa Snámhacha a Bhainistiú", @@ -1478,7 +1484,7 @@ "or": "nó", "Ordered List": "Liosta Ordaithe", "Other": "Eile", - "out of": "", + "out of": "as", "Output": "Aschur", "OUTPUT": "ASCHUR", "Output format": "Formáid aschuir", @@ -1494,7 +1500,7 @@ "Password": "Pasfhocal", "Passwords do not match.": "Ní hionann na pasfhocail.", "Paste Large Text as File": "Greamaigh Téacs Mór mar Chomhad", - "Paused": "", + "Paused": "Sosaithe", "PDF document (.pdf)": "Doiciméad PDF (.pdf)", "PDF Extract Images (OCR)": "Íomhánna Sliocht PDF (OCR)", "PDF Loader Mode": "Mód Luchtaithe PDF", @@ -1510,12 +1516,12 @@ "Perplexity Model": "Samhail Perplexity", "Perplexity Search API URL": "URL API Cuardaigh Measctha", "Perplexity Search Context Usage": "Úsáid Chomhthéacs Cuardaigh Mearbhall", - "Persistent": "", + "Persistent": "Dianseasmhach", "Personalization": "Pearsantú", "Pin": "Bioráin", - "Pinned": "Pinneáilte", - "Pinned Messages": "Teachtaireachtaí Pionáilte", - "Pinned Models": "Samhlacha bioráilte", + "Pinned": "Bioránaithe", + "Pinned Messages": "Teachtaireachtaí Bioránaithe", + "Pinned Models": "Samhlacha Bioránaithe", "Pioneer insights": "Léargais ceannródaí", "Pipe": "Píopa", "Pipeline deleted successfully": "Scriosta píblíne go rathúil", @@ -1530,16 +1536,16 @@ "Playwright Timeout (ms)": "Teorainn Ama drámadóra (ms)", "Playwright WebSocket URL": "URL drámadóir WebSocket", "Please carefully review the following warnings:": "Déan athbhreithniú cúramach ar na rabhaidh seo a leanas le do thoil:", - "Please connect all required integrations before sending a message": "", + "Please connect all required integrations before sending a message": "Ceangail na comhtháthúcháin riachtanacha go léir sula seoltar teachtaireacht", "Please do not close the settings page while loading the model.": "Ná dún leathanach na socruithe agus an tsamhail á luchtú.", "Please enter a message or attach a file.": "Cuir isteach teachtaireacht nó ceangail comhad le do thoil.", - "Please enter a prompt": "Cuir isteach leid", + "Please enter a prompt": "Cuir isteach treoir", "Please enter a valid ID": "Cuir isteach aitheantas bailí le do thoil", "Please enter a valid JSON spec": "Cuir isteach sonraíocht JSON bhailí le do thoil", "Please enter a valid path": "Cuir isteach cosán bailí", "Please enter a valid URL": "Cuir isteach URL bailí", "Please enter a valid URL.": "Cuir isteach URL bailí le do thoil.", - "Please enter Client ID and Client Secret": "", + "Please enter Client ID and Client Secret": "Cuir isteach ID Cliant agus Rún Cliant le do thoil", "Please fill in all fields.": "Líon isteach gach réimse le do thoil.", "Please register the OAuth client": "Cláraigh an cliant OAuth le do thoil", "Please save the connection to persist the OAuth client information and do not change the ID": "Sábháil an nasc le go gcoimeádfar faisnéis an chliaint OAuth agus ná hathraigh an ID.", @@ -1549,9 +1555,9 @@ "Please select a valid JSON file": "Roghnaigh comhad JSON bailí le do thoil", "Please select at least one user for Direct Message channel.": "Roghnaigh úsáideoir amháin ar a laghad don chainéal Teachtaireachtaí Díreacha.", "Please wait until all files are uploaded.": "Fan go dtí go mbeidh na comhaid go léir uaslódáilte.", - "Policy ID": "", + "Policy ID": "Aitheantas Polasaí", "Port": "Port", - "Ports": "", + "Ports": "Poirt", "Positive attitude": "Dearcadh dearfach", "Prefer not to say": "Is fearr liom gan a rá", "Prefix ID": "Aitheantas Réimír", @@ -1565,29 +1571,29 @@ "Private conversation between selected users": "Comhrá príobháideach idir úsáideoirí roghnaithe", "Production version updated": "Leagan táirgeachta nuashonraithe", "Profile": "Próifíl", - "Prompt": "Leid", - "Prompt Autocompletion": "Uathchríochnú Pras", - "Prompt Content": "Ábhar Leid", - "Prompt created successfully": "Leid cruthaithe go rathúil", - "Prompt Name": "Ainm an Phraghas", - "Prompt Suggestions": "Moltaí Treoir", - "Prompt updated successfully": "D'éirigh leis an leid a nuashonrú", - "Prompts": "Leabhair", - "Prompts Access": "Rochtain ar Chuirí", - "Prompts Public Sharing": "Spreagann Roinnt Phoiblí", - "Prompts Sharing": "Comhroinnt Leideanna", + "Prompt": "Treoir", + "Prompt Autocompletion": "Uathchríochnú Treoracha", + "Prompt Content": "Ábhar na Treorach", + "Prompt created successfully": "Cruthaíodh an treoir go rathúil", + "Prompt Name": "Ainm na Treorach", + "Prompt Suggestions": "Moltaí Treoracha", + "Prompt updated successfully": "Nuashonraíodh an treoir go rathúil", + "Prompts": "Treoracha", + "Prompts Access": "Rochtain ar Threoracha", + "Prompts Public Sharing": "Comhroinnt Phoiblí Treoracha", + "Prompts Sharing": "Comhroinnt Treoracha", "Provider Type": "Cineál Soláthraí", "Public": "Poiblí", "Pull \"{{searchValue}}\" from Ollama.com": "Tarraing \"{{searchValue}}\" ó Ollama.com", "Pull a model from Ollama.com": "Tarraing samhail ó Ollama.com", "Pull Model": "Samhail Tarraingthe", - "Pyodide file browser": "", - "Query Generation Prompt": "Cuirí Ginearáil Ceisteanna", + "Pyodide file browser": "Brabhsálaí comhad Pyodide", + "Query Generation Prompt": "Treoir Giniúna Ceisteanna", "Querying": "Ag fiosrú", "Quick Actions": "Gníomhartha Tapa", "RAG Template": "Teimpléad RAG", - "Ran {{COUNT}} analyses": "", - "Ran {{COUNT}} analysis": "", + "Ran {{COUNT}} analyses": "Rinne {{COUNT}} anailísí", + "Ran {{COUNT}} analysis": "Rinneadh anailís ar {{COUNT}}", "Rate {{rating}} out of 10": "Rátáil {{rating}} as 10", "Rating": "Rátáil", "Re-rank models by topic similarity": "Athrangú samhlacha de réir cosúlachta topaice", @@ -1599,7 +1605,7 @@ "Reason": "Cúis", "Reasoning Effort": "Iarracht Réasúnúcháin", "Reasoning Tags": "Clibeanna Réasúnaíochta", - "Recently Used": "", + "Recently Used": "Úsáidte le Déanaí", "Record": "Taifead", "Record voice": "Taifead guth", "Redirecting you to Open WebUI Community": "Tú a atreorú chuig OpenWebUI Community", @@ -1632,10 +1638,10 @@ "Remove image": "Bain íomhá", "Remove Model": "Bain an tSamhail", "Rename": "Athainmnigh", - "Renamed to {{name}}": "", + "Renamed to {{name}}": "Athainmnithe go {{name}}", "Render Markdown in Previews": "Rindreáil Markdown i Réamhamhairc", "Reorder Models": "Athordú na Samhlacha", - "Repeats": "", + "Repeats": "Athdhéantar", "Reply": "Freagra", "Reply in Thread": "Freagra i Snáithe", "Reply to thread...": "Freagra ar an snáithe...", @@ -1654,9 +1660,8 @@ "Response splitting": "Scoilt freagartha", "Response Watermark": "Comhartha Uisce Freagartha", "Responses": "Freagraí", - "Restart": "", "Result": "Toradh", - "RESULT": "Toradh", + "RESULT": "TORADH", "Retrieval": "Aisghabháil", "Retrieval Query Generation": "Aisghabháil Giniúint Ceist", "Retrieved {{count}} sources": "Aisghafa {{count}} foinsí", @@ -1667,12 +1672,13 @@ "Role": "Ról", "RTL": "RTL", "Run": "Rith", - "Run All": "", - "Run now": "", - "Run Now": "", + "Run All": "Rith Gach Rud", + "Run now": "Rith anois", + "Run Now": "Rith Anois", "Running": "Ag rith", "Running...": "Ag rith...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Ritheann sé tascanna leabaithe ag an am céanna chun luas a chur leis an bpróiseáil. Múch é má bhíonn teorainneacha ráta ina bhfadhb.", + "Sa_day_of_week": "Satharn", "Save": "Sábháil", "Save & Create": "Sábháil & Cruthaigh", "Save & Update": "Sábháil & Nuashonraigh", @@ -1680,15 +1686,15 @@ "Save Chat": "Sábháil Comhrá", "Saved": "Shábháil", "Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Ní thacaítear le logaí comhrá a shábháil go díreach chuig stóráil do bhrabhsálaí Tóg nóiméad chun do logaí comhrá a íoslódáil agus a scriosadh trí chliceáil an cnaipe thíos. Ná bíodh imní ort, is féidir leat do logaí comhrá a athiompórtáil go héasca chuig an gcúltaca trí", - "Schedule": "", - "Scheduled time must be in the future": "", + "Schedule": "Sceideal", + "Scheduled time must be in the future": "Ní mór don am sceidealaithe a bheith sa todhchaí", "Scroll On Branch Change": "Scrollaigh ar Athrú Brainse", "Search": "Cuardaigh", "Search a model": "Cuardaigh samhail", "Search all emojis": "Cuardaigh gach emoji", "Search and manage user memories": "Cuardaigh agus bainistigh cuimhní úsáideora", "Search and view user chat history": "Cuardaigh agus féach ar stair comhrá úsáideora", - "Search Automations": "", + "Search Automations": "Uathoibriúcháin Cuardaigh", "Search Base": "Bonn Cuardaigh", "Search channels and channel messages": "Cuardaigh bealaí agus teachtaireachtaí bealaí", "Search Chats": "Cuardaigh Comhráite", @@ -1704,11 +1710,11 @@ "Search Groups": "Cuardaigh Grúpaí", "Search In Models": "Cuardaigh i Samhlacha", "Search Knowledge": "Cuardaigh Eolais", - "Search Memories": "", + "Search Memories": "Cuardaigh Cuimhní", "Search Models": "Cuardaigh Samhlacha", "Search Notes": "Cuardaigh Nótaí", "Search options": "Roghanna cuardaigh", - "Search Prompts": "Leideanna Cuardaigh", + "Search Prompts": "Treoracha Cuardaigh", "Search Result Count": "Líon Torthaí Cuardaigh", "Search Skills": "Scileanna Cuardaigh", "Search the internet": "Cuardaigh an tIdirlíon", @@ -1746,7 +1752,7 @@ "Select a theme": "Roghnaigh téama", "Select a tool": "Roghnaigh uirlis", "Select a voice": "Roghnaigh guth", - "Select All": "", + "Select All": "Roghnaigh Uile", "Select an auth method": "Roghnaigh modh an údair", "Select an embedding model engine": "Roghnaigh inneall samhail leabaithe", "Select an engine": "Roghnaigh inneall", @@ -1758,7 +1764,7 @@ "Select how to split message text for TTS requests": "Roghnaigh conas téacs teachtaireachta a roinnt le haghaidh iarratais TTS", "Select Knowledge": "Roghnaigh Eolais", "Select Method": "Roghnaigh Modh", - "Select model": "", + "Select model": "Roghnaigh samhail", "Select only one model to call": "Roghnaigh samhail amháin le glaoch", "Select view": "Roghnaigh radharc", "Selected model: {{modelName}}": "Samhail roghnaithe: {{modelName}}", @@ -1776,7 +1782,7 @@ "Serper API Key": "Serper API Eochair", "Serply API Key": "Eochair API Serply", "Serpstack API Key": "Eochair API Serpstack", - "Server connection failed": "", + "Server connection failed": "Theip ar cheangal leis an bhfreastalaí", "Server connection verified": "Ceangal freastalaí fíoraithe", "Session": "Seisiún", "Set as default": "Socraigh mar réamhshocraithe", @@ -1794,7 +1800,7 @@ "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.": "Socraíonn sé claonadh cothrom i gcoinne comharthaí a tháinig chun solais uair amháin ar a laghad. Cuirfidh luach níos airde (m.sh., 1.5) pionós níos láidre ar athrá, agus beidh luach níos ísle (m.sh., 0.9) níos boige. Ag 0, tá sé díchumasaithe.", "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.": "Socraíonn sé laofacht scálaithe i gcoinne comharthaí chun pionós a ghearradh ar athrá, bunaithe ar cé mhéad uair a tháinig siad chun solais. Cuirfidh luach níos airde (m.sh., 1.5) pionós níos láidre ar athrá, agus beidh luach níos ísle (m.sh., 0.9) níos boige. Ag 0, tá sé díchumasaithe.", "Sets how far back for the model to look back to prevent repetition.": "Socraíonn sé cé chomh fada siar is atá an tsamhail le breathnú siar chun athrá a chosc.", - "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.": "Socraíonn sé an síol uimhir randamach a úsáid le haghaidh giniúna. Má shocraítear é seo ar uimhir shainiúil, ginfidh an tsamhail an téacs céanna don leid céanna.", + "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.": "Socraíonn sé an síol uimhir randamach a úsáid le haghaidh giniúna. Má shocraítear é seo ar uimhir shainiúil, ginfidh an tsamhail an téacs céanna don treoir céanna.", "Sets the size of the context window used to generate the next token.": "Socraíonn sé méid na fuinneoige comhthéacs a úsáidtear chun an chéad chomhartha eile a ghiniúint.", "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.": "Socraíonn sé na stadanna le húsáid. Nuair a thagtar ar an bpatrún seo, stopfaidh an LLM ag giniúint téacs agus ag filleadh. Is féidir patrúin stad iolracha a shocrú trí pharaiméadair stadanna iolracha a shonrú i gcomhad samhail.", "Setting": "Socrú", @@ -1867,8 +1873,8 @@ "Start a new conversation": "Tosaigh comhrá nua", "Start of the channel": "Tús an chainéil", "Start Tag": "Clib Tosaigh", - "Starting kernel...": "", - "State": "", + "Starting kernel...": "Ag tosú an eithne...", + "State": "Stát", "Status": "Stádas", "Status cleared successfully": "Glanadh an stádais go rathúil", "Status updated successfully": "Nuashonraíodh an stádas go rathúil", @@ -1879,7 +1885,7 @@ "Stop Download": "Stop an Íoslódáil", "Stop Generating": "Stop a Ghiniúint", "Stop Sequence": "Stop Seicheamh", - "Storage": "", + "Storage": "Stóráil", "Stream Chat Response": "Freagra Comhrá Sruth", "Stream Delta Chunk Size": "Sruth Méid Leadhb Delta", "Streamable HTTP": "HTTP sruthaithe", @@ -1889,6 +1895,7 @@ "STT Model": "Samhail STT", "STT Settings": "Socruithe STT", "Stylized PDF Export": "Easpórtáil PDF Stílithe", + "Su_day_of_week": "Domhnaigh", "Submit question": "Cuir ceist isteach", "Submit suggestion": "Cuir moladh isteach", "Subtitle": "Fotheideal", @@ -1910,19 +1917,19 @@ "Syncs only chats with updates after your last sync timestamp. Disable to re-sync all chats.": "Ní shioncrónaíonn sé ach comhráite le nuashonruithe tar éis do stampa ama sioncrónaithe deireanach. Díchumasaigh chun gach comhrá a athshioncrónú.", "System": "Córas", "System Instructions": "Treoracha Córas", - "System Prompt": "Córas Leid", + "System Prompt": "Treoir Chóras", "Tag": "Clib", "Tags": "Clibeanna", "Tags Generation": "Giniúint Clibeanna", - "Tags Generation Prompt": "Clibeanna Giniúint Leid", + "Tags Generation Prompt": "Treoir Giniúna Clibeanna", "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.": "Úsáidtear sampláil saor ó eireabaill chun tionchar na n-chomharthaí ón aschur nach bhfuil chomh dóchúil céanna a laghdú. Laghdóidh luach níos airde (m.sh., 2.0) an tionchar níos mó, agus díchumasaíonn luach 1.0 an socrú seo. (réamhshocraithe: 1)", "Talk to Model": "Labhair leis an tSamhail", "Tap to interrupt": "Tapáil chun cur isteach", "Task List": "Liosta Tascanna", - "Task Management": "", - "Task Model": "Samhail Thasc", + "Task Management": "Bainistíocht Tascanna", + "Task Model": "Samhail Tasc", "Tasks": "Tascanna", - "tasks completed": "", + "tasks completed": "tascanna críochnaithe", "Tavily API Key": "Eochair API Tavily", "Tavily Extract Depth": "Doimhneacht Sliocht Tavily", "Tell us more:": "Inis dúinn níos mó:", @@ -1934,6 +1941,7 @@ "Text Splitter": "Scoilteoir Téacs", "Text-to-Speech": "Téacs-go-Caint", "Text-to-Speech Engine": "Inneall téacs-go-labhra", + "Th_day_of_week": "Déardaoin", "Thanks for your feedback!": "Go raibh maith agat as do chuid aiseolas!", "The Application Account DN you bind with for search": "An Cuntas Feidhmchláir DN a nascann tú leis le haghaidh cuardaigh", "The base to search for users": "An bonn chun cuardach a dhéanamh ar úsáideoirí", @@ -1980,7 +1988,7 @@ "This will delete all models including custom models and cannot be undone.": "Scriosfaidh sé seo gach samhail, lena n-áirítear samhlacha saincheaptha, agus ní féidir é a chealú.", "This will reset the knowledge base and sync all files. Do you wish to continue?": "Déanfaidh sé seo an bonn eolais a athshocrú agus gach comhad a shioncronú. Ar mhaith leat leanúint ar aghaidh?", "Thorough explanation": "Míniú críochnúil", - "Thought": "", + "Thought": "Smaoineamh", "Thought for {{DURATION}}": "Smaoineamh ar {{DURATION}}", "Thought for {{DURATION}} seconds": "Smaoineamh ar feadh {{DURATION}} soicind", "Thought for less than a second": "Smaoinigh mé ar feadh níos lú ná soicind", @@ -1989,14 +1997,14 @@ "Tika": "Tika", "Tika Server URL required.": "Teastaíonn URL Freastalaí Tika.", "Tiktoken": "Tiktoken", - "Time": "", + "Time": "Am", "Time & Calculation": "Am & Ríomh", "Timeout": "Am istigh", "Title": "Teideal", "Title Auto-Generation": "Teideal Auto-Generation", "Title cannot be an empty string.": "Ní féidir leis an teideal a bheith ina teaghrán folamh.", "Title Generation": "Giniúint Teidil", - "Title Generation Prompt": "Leid Giniúint Teideal", + "Title Generation Prompt": "Treoir Giniúna Teidil", "TLS": "TLS", "To access the available model names for downloading,": "Chun rochtain a fháil ar ainmneacha na samhlacha atá ar fáil lena n-íoslódáil,", "To access the GGUF models available for downloading,": "Chun rochtain a fháil ar na samhlacha GGUF atá ar fáil lena n-íoslódáil,", @@ -2007,11 +2015,11 @@ "To select toolkits here, add them to the \"Tools\" workspace first.": "Chun trealamh uirlisí a roghnú anseo, cuir iad leis an spás oibre \"Uirlisí\" ar dtús.", "Toast notifications for new updates": "Fógraí tósta le haghaidh nuashonruithe nua", "Today": "Inniu", - "Today at": "", + "Today at": "Inniu ag", "Today at {{LOCALIZED_TIME}}": "Inniu ag {{LOCALIZED_TIME}}", "Toggle {{COUNT}} sources": "Athraigh {{COUNT}} foinsí", "Toggle 1 source": "Athraigh foinse amháin", - "Toggle details": "", + "Toggle details": "Athraigh sonraí", "Toggle Dictation": "Athraigh Deachtú", "Toggle Sidebar": "Barra Taobh a Athraigh", "Toggle status history": "Athraigh stair stádais", @@ -2032,7 +2040,7 @@ "Tools": "Uirlisí", "Tools Access": "Rochtain Uirlisí", "Tools are a function calling system with arbitrary code execution": "Is córas glaonna feidhme iad uirlisí le forghníomhú cód treallach", - "Tools Function Calling Prompt": "Leid Glaonna Feidhm Uirlisí", + "Tools Function Calling Prompt": "Treoir Ghlao Feidhme Uirlisí", "Tools have a function calling system that allows arbitrary code execution.": "Tá córas glaonna feidhme ag uirlisí a cheadaíonn forghníomhú cód treallach.", "Tools Public Sharing": "Uirlisí Roinnte Poiblí", "Tools Sharing": "Comhroinnt Uirlisí", @@ -2047,6 +2055,7 @@ "TTS Model": "Samhail TTS", "TTS Settings": "Socruithe TTS", "TTS Voice": "Guth TTS", + "Tu_day_of_week": "Máirt", "Type": "Cineál", "Type here...": "Clóscríobh anseo...", "Type Hugging Face Resolve (Download) URL": "Cineál Hugging Face Resolve (Íoslódáil) URL", @@ -2095,7 +2104,7 @@ "URL Mode": "Mód URL", "Usage": "Úsáid", "Use": "Úsáid", - "Use '#' in the prompt input to load and include your knowledge.": "Úsáid '#' san ionchur leid chun do chuid eolais a lódáil agus a chur san áireamh.", + "Use '#' in the prompt input to load and include your knowledge.": "Úsáid '#' san ionchur treoir chun do chuid eolais a lódáil agus a chur san áireamh.", "Use /v1/chat/completions endpoint instead of /v1/audio/transcriptions for potentially better accuracy.": "Bain úsáid as an gcríochphointe /v1/chat/completions in ionad /v1/audio/transcriptions le haghaidh cruinneas níos fearr b’fhéidir.", "Use Chat Completions API": "Úsáid API Comhlánuithe Comhrá", "Use groups to organize your users and assign permissions.": "Bain úsáid as grúpaí chun d’úsáideoirí a eagrú agus ceadanna a shannadh.", @@ -2141,14 +2150,15 @@ "Voice": "Guth", "Voice Input": "Ionchur Gutha", "Voice mode": "Mod Gutha", - "Voice Mode Custom Prompt": "Leid Saincheaptha Mód Gutha", - "Voice Mode Prompt": "Leid Mód Gutha", + "Voice Mode Custom Prompt": "Treoir Saincheaptha Mód Gutha", + "Voice Mode Prompt": "Treoir Mód Gutha", "Waiting for upload...": "Ag fanacht le huaslódáil...", "Warning": "Rabhadh", "Warning:": "Rabhadh:", - "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", + "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Rabhadh: Trí seo a chumasú, beidh úsáideoirí in ann leideanna sceidealaithe a rith go huathoibríoch.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Rabhadh: Cuirfidh sé seo ar chumas úsáideoirí cód treallach a uaslódáil ar an bhfreastalaí.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Rabhadh: Trí fhorghníomhú Jupyter is féidir cód a fhorghníomhú go treallach, rud a chruthaíonn mór-rioscaí slándála - bí fíorchúramach.", + "We_day_of_week": "Céadaoin", "Web": "Gréasán", "Web API": "API Gréasáin", "Web Loader Engine": "Inneall Luchtaithe Gréasáin", @@ -2165,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "Déanfaidh WebUI iarratais ar \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "Déanfaidh WebUI iarratais ar \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "Déanfaidh WebUI iarratais ar \"{{url}}/chat/completions\"", + "Weekly": "Seachtainiúil", "What are you trying to achieve?": "Cad atá tú ag iarraidh a bhaint amach?", "What are you working on?": "Cad air a bhfuil tú ag obair?", "What is NOT shared:": "Cad NACH roinntear", @@ -2181,14 +2192,14 @@ "Width": "Leithead", "Wikipedia": "Vicipéid", "Won": "Bhuaigh", - "Working Directory": "", + "Working Directory": "Eolaire Oibre", "Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text.": "Oibríonn sé le barr-k. Beidh téacs níos éagsúla mar thoradh ar luach níos airde (m.sh., 0.95), agus ginfidh luach níos ísle (m.sh., 0.5) téacs níos dírithe agus níos coimeádaí.", "Workspace": "Spás oibre", "Workspace Permissions": "Ceadanna Spás Oibre", "Write": "Scríobh", "Write a summary in 50 words that summarizes {{topic}}.": "Scríobh achoimre i 50 focal a dhéanann achoimre ar [ábhar nó eochairfhocal].", "Write something...": "Scríobh rud...", - "Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.": "Scríobh inneachar system prompt do mhúnla anseo\nm.sh.: Is é Mario ó Super Mario Bros tú agus tá tú ag gníomhú mar chúntóir.", + "Write your model system prompt content here\ne.g.) You are Mario from Super Mario Bros, acting as an assistant.": "Scríobh inneachar threoir chórais do mhúnla anseo\nm.sh.: Is é Mario ó Super Mario Bros tú, ag gníomhú mar chúntóir.", "Yacy Instance URL": "URL Cás Yacy", "Yacy Password": "Pasfhocal Yacy", "Yacy Username": "Ainm úsáideora Yacy", @@ -2205,7 +2216,7 @@ "You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you.": "Is féidir leat do chuid idirghníomhaíochtaí le LLManna a phearsantú ach cuimhní cinn a chur leis tríd an gcnaipe 'Bainistigh' thíos, rud a fhágann go mbeidh siad níos cabhrach agus níos oiriúnaí duit.", "You cannot upload an empty file.": "Ní féidir leat comhad folamh a uaslódáil.", "You do not have permission to edit this model": "Níl cead agat an tsamhail seo a chur in eagar", - "You do not have permission to edit this prompt.": "Níl cead agat an leid seo a chur in eagar.", + "You do not have permission to edit this prompt.": "Níl cead agat an treoir seo a chur in eagar.", "You do not have permission to edit this skill.": "Níl cead agat an scil seo a chur in eagar.", "You do not have permission to edit this tool": "Níl cead agat an uirlis seo a chur in eagar", "You do not have permission to make this public": "Níl cead agat é seo a chur ar fáil don phobal", @@ -2222,8 +2233,8 @@ "You're now logged in.": "Tá tú logáilte isteach anois.", "Your Account": "Do Chuntas", "Your account status is currently pending activation.": "Tá stádas do chuntais ar feitheamh faoi ghníomhachtú.", - "Your browser does not support the audio tag.": "", - "Your browser does not support the video tag.": "", + "Your browser does not support the audio tag.": "Ní thacaíonn do bhrabhsálaí leis an gclib fuaime.", + "Your browser does not support the video tag.": "Ní thacaíonn do bhrabhsálaí leis an gclib físeáin.", "Your entire contribution will go directly to the plugin developer; Open WebUI does not take any percentage. However, the chosen funding platform might have its own fees.": "Rachaidh do ranníocaíocht iomlán go díreach chuig an bhforbróir breiseán; Ní ghlacann Open WebUI aon chéatadán. Mar sin féin, d'fhéadfadh a tháillí féin a bheith ag an ardán maoinithe roghnaithe.", "Your message text or inputs": "Téacs nó ionchur do theachtaireachta", "Your usage stats have been successfully synced.": "Tá do staitisticí úsáide sioncronaithe go rathúil.", diff --git a/src/lib/i18n/locales/it-IT/translation.json b/src/lib/i18n/locales/it-IT/translation.json index eb3e42aa68..8cb9822986 100644 --- a/src/lib/i18n/locales/it-IT/translation.json +++ b/src/lib/i18n/locales/it-IT/translation.json @@ -455,6 +455,7 @@ "Create a new note": "", "Create Account": "Crea account", "Create Admin Account": "Crea account amministratore", + "Create and manage scheduled automations": "", "Create Channel": "Crea canale", "Create Folder": "", "Create Image": "", @@ -480,6 +481,7 @@ "Custom Gender": "", "Custom Parameter Name": "Nome parametro personalizzato", "Custom Parameter Value": "Valore parametro personalizzato", + "Daily": "", "Daily Messages": "", "Danger Zone": "Zona di pericolo", "Dark": "Scuro", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Inoltra le credenziali della sessione utente di sistema per autenticare", + "Fr_day_of_week": "", "Full Context Mode": "Modalità Contesto Completo", "Function": "Funzione", "Function Calling": "Chiamata Funzione", @@ -1044,6 +1047,7 @@ "History": "", "Home": "Home", "Host": "Host", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Come posso aiutarti oggi?", "How would you rate this response?": "Come valuteresti questa risposta?", @@ -1266,10 +1270,10 @@ "Mistral OCR": "OCR Mistral", "Mistral OCR API Key required.": "La Chiave API OCR Mistral è richiesta.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Modello", "Model '{{modelName}}' has been successfully downloaded.": "Il modello '{{modelName}}' è stato scaricato con successo.", "Model '{{modelTag}}' is already in queue for downloading.": "Il modello '{{modelTag}}' è già in coda per il download.", - "Model {{modelId}} not found": "Modello {{modelId}} non trovato", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Il modello {{modelName}} non è in grado di vedere", "Model {{name}} is now {{status}}": "Il modello {{name}} è ora {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Chiave API di Mojeek Search", + "Monthly": "", "More": "Altro", "More Concise": "", "More options": "", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Versione Ollama", "On": "Attivato", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Personalizzazione", "Pin": "Appunta", + "Pin to Sidebar": "", "Pinned": "Appuntato", "Pinned Messages": "", "Pinned Models": "", @@ -1675,6 +1682,7 @@ "Running": "In esecuzione", "Running...": "In esecuzione...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Salva", "Save & Create": "Salva e crea", "Save & Update": "Salva e aggiorna", @@ -1891,6 +1899,7 @@ "STT Model": "Modello STT", "STT Settings": "Impostazioni STT", "Stylized PDF Export": "Esportazione PDF Stilizzata", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1936,6 +1945,7 @@ "Text Splitter": "Divisore di testo", "Text-to-Speech": "", "Text-to-Speech Engine": "Motore da testo a voce", + "Th_day_of_week": "", "Thanks for your feedback!": "Grazie per il tuo feedback!", "The Application Account DN you bind with for search": "L'account dell'applicazione DN con cui ti colleghi per la ricerca", "The base to search for users": "La base da cercare per gli utenti", @@ -2049,6 +2059,7 @@ "TTS Model": "Modello TTS", "TTS Settings": "Impostazioni TTS", "TTS Voice": "Voce TTS", + "Tu_day_of_week": "", "Type": "Digitare", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Digita URL di Risoluzione (Download) di Hugging Face", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Attenzione: abilitando questo, gli utenti potranno caricare codice arbitrario sul server.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Attenzione: l'esecuzione di Jupyter consente l'esecuzione di codice arbitrario, comportando gravi rischi per la sicurezza: procedere con estrema cautela.", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "Motore di Caricamento Web", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI farà richieste a \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI farà richieste a \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI farà richieste a \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Cosa stai cercando di ottenere?", "What are you working on?": "Su cosa stai lavorando?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ja-JP/translation.json b/src/lib/i18n/locales/ja-JP/translation.json index b559404dc9..be5c8d6a98 100644 --- a/src/lib/i18n/locales/ja-JP/translation.json +++ b/src/lib/i18n/locales/ja-JP/translation.json @@ -453,6 +453,7 @@ "Create a new note": "新しいノートを作成する", "Create Account": "アカウントを作成", "Create Admin Account": "管理者アカウントを作成", + "Create and manage scheduled automations": "", "Create Channel": "チャンネルを作成", "Create Folder": "フォルダを作成", "Create Image": "", @@ -478,6 +479,7 @@ "Custom Gender": "", "Custom Parameter Name": "カスタムパラメータ名", "Custom Parameter Value": "カスタムパラメータ値", + "Daily": "", "Daily Messages": "", "Danger Zone": "危険地帯", "Dark": "ダーク", @@ -968,6 +970,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "システムユーザーセッションの資格情報を転送して認証する", + "Fr_day_of_week": "", "Full Context Mode": "フルコンテキストモード", "Function": "", "Function Calling": "Function呼び出し", @@ -1042,6 +1045,7 @@ "History": "", "Home": "ホーム", "Host": "ホスト", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "今日はどのようにお手伝いしましょうか?", "How would you rate this response?": "この応答をどのように評価しますか?", @@ -1264,10 +1268,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "Mistral OCR APIキーが必要です。", "MistralAI": "", + "Mo_day_of_week": "", "Model": "モデル", "Model '{{modelName}}' has been successfully downloaded.": "モデル '{{modelName}}' が正常にダウンロードされました。", "Model '{{modelTag}}' is already in queue for downloading.": "モデル '{{modelTag}}' はすでにダウンロード待機中です。", - "Model {{modelId}} not found": "モデル {{modelId}} が見つかりません", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "モデル {{modelName}} は視覚に対応していません", "Model {{name}} is now {{status}}": "モデル {{name}} は {{status}} になりました。", @@ -1308,6 +1312,7 @@ "Models Sharing": "モデルの共有", "Mojeek": "", "Mojeek Search API Key": "Mojeek Search APIキー", + "Monthly": "", "More": "もっと見る", "More Concise": "より簡潔に", "More options": "", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama バージョン", "On": "オン", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "\"大きなテキストをファイルとして貼り付ける\"がオンの場合にのみ有効です。", "Only active when the chat input is in focus and an LLM is generating a response.": "チャット入力欄にフォーカスがあり、LLMが応答を生成しているときのみ有効です。", @@ -1512,6 +1518,7 @@ "Persistent": "", "Personalization": "パーソナライズ", "Pin": "ピン留め", + "Pin to Sidebar": "", "Pinned": "ピン留めされています", "Pinned Messages": "", "Pinned Models": "", @@ -1671,6 +1678,7 @@ "Running": "実行中", "Running...": "実行中...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "保存", "Save & Create": "保存して作成", "Save & Update": "保存して更新", @@ -1887,6 +1895,7 @@ "STT Model": "STTモデル", "STT Settings": "STT設定", "Stylized PDF Export": "スタイル付きPDFエクスポート", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "サブタイトル", @@ -1932,6 +1941,7 @@ "Text Splitter": "テキスト分割", "Text-to-Speech": "テキスト音声変換", "Text-to-Speech Engine": "テキスト音声変換エンジン", + "Th_day_of_week": "", "Thanks for your feedback!": "ご意見ありがとうございます!", "The Application Account DN you bind with for search": "LDAP属性を使用してユーザーを検索するためにバインドするアプリケーションアカウントのDN。", "The base to search for users": "ユーザーを検索するためのベース。", @@ -2045,6 +2055,7 @@ "TTS Model": "TTSモデル", "TTS Settings": "TTS 設定", "TTS Voice": "TTSボイス", + "Tu_day_of_week": "", "Type": "種類", "Type here...": "ここに入力してください...", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (ダウンロード) URL を入力してください", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "警告: これを有効にすると、ユーザーがサーバー上で任意のコードをアップロードできるようになります。", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "警告: Jupyter 実行は任意のコード実行を可能にし、重大なセキュリティリスクを伴います。極めて慎重に進めてください。", + "We_day_of_week": "", "Web": "ウェブ", "Web API": "ウェブAPI", "Web Loader Engine": "ウェブローダーエンジン", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUIは\"{{url}}\"にリクエストを行います", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUIは\"{{url}}/api/chat\"にリクエストを行います", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUIは\"{{url}}/chat/completions\"にリクエストを行います", + "Weekly": "", "What are you trying to achieve?": "何を達成したいですか?", "What are you working on?": "何に取り組んでいますか?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ka-GE/translation.json b/src/lib/i18n/locales/ka-GE/translation.json index 67c99ba548..cdbc392315 100644 --- a/src/lib/i18n/locales/ka-GE/translation.json +++ b/src/lib/i18n/locales/ka-GE/translation.json @@ -454,6 +454,7 @@ "Create a new note": "ახალი შენიშვნის შექმნა", "Create Account": "ანგარიშის შექმნა", "Create Admin Account": "ადმინისტრატორის ანგარიშის შექმნა", + "Create and manage scheduled automations": "", "Create Channel": "არხის შექმნა", "Create Folder": "საქაღალდის შექმნა", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "მორგებული პარამეტრის სახელი", "Custom Parameter Value": "მორგებული პარამეტრის მნიშვნელობა", + "Daily": "", "Daily Messages": "", "Danger Zone": "საშიში ზონა", "Dark": "მუქი", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "სრული კონტექსტის რეჟიმი", "Function": "ფუნქცია", "Function Calling": "ფუნქციის გამოძახება", @@ -1043,6 +1046,7 @@ "History": "", "Home": "მთავარი", "Host": "ჰოსტი", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "რით შემიძლია დაგეხმაროთ დღეს?", "How would you rate this response?": "როგორ შეაფასებდით ამ პასუხს?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "მოდელი", "Model '{{modelName}}' has been successfully downloaded.": "მოდელის „{{modelName}}“ გადმოწერა წარმატებით დასრულდა.", "Model '{{modelTag}}' is already in queue for downloading.": "მოდელი „{{modelTag}}“ უკვე გადმოწერის რიგშია.", - "Model {{modelId}} not found": "მოდელი {{modelId}} აღმოჩენილი არაა", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} is not vision capable", "Model {{name}} is now {{status}}": "Model {{name}} is now {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "მეტი", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Ollama Cloud API-ის გასაღები", "Ollama Version": "Ollama ვერსია", "On": "ჩართული", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "პერსონალიზაცია", "Pin": "მიმაგრება", + "Pin to Sidebar": "", "Pinned": "მიმაგრებულია", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "გაშვებულია", "Running...": "გაშვებულია...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "შენახვა", "Save & Create": "შენახვა და შექმნა", "Save & Update": "შენახვა და განახლება", @@ -1889,6 +1897,7 @@ "STT Model": "STT მოდელი", "STT Settings": "STT-ის მორგება", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "ტექსტის გამყოფი", "Text-to-Speech": "ტექსტის-ხმამაღლა-წაკითხვა", "Text-to-Speech Engine": "ტექსტურ-ხმოვანი ძრავი", + "Th_day_of_week": "", "Thanks for your feedback!": "მადლობა გამოხმაურებისთვის!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS მოდელი", "TTS Settings": "TTS პარამეტრები", "TTS Voice": "TTS ხმა", + "Tu_day_of_week": "", "Type": "ტიპი", "Type here...": "აკრიფეთ აქ...", "Type Hugging Face Resolve (Download) URL": "აკრიფეთ HuggingFace-ის ამოხსნის (გადმოწერის) URL", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "ვები", "Web API": "Web API", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "რას ცდილობთ, მიაღწიოთ?", "What are you working on?": "რაზე მუშაობთ?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/kab-DZ/translation.json b/src/lib/i18n/locales/kab-DZ/translation.json index b38b37e73c..82dcd4c833 100644 --- a/src/lib/i18n/locales/kab-DZ/translation.json +++ b/src/lib/i18n/locales/kab-DZ/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Snulfu-d amiḍan", "Create Admin Account": "Snulfu-d amiḍan n unedbal", + "Create and manage scheduled automations": "", "Create Channel": "Snulfu-d abadu", "Create Folder": "Snulfu-d akaram", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "Isem n uɣewwar udmawan", "Custom Parameter Value": "Azal n uɣewwar udmawan", + "Daily": "", "Daily Messages": "", "Danger Zone": "Tamnaḍt i iweɛren", "Dark": "Aberkan", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Welleh inekcam n tɣimit n useqdac i usesteb", + "Fr_day_of_week": "", "Full Context Mode": "Askar n usatal aččuran", "Function": "Tasɣent", "Function Calling": "Asiwel n twuriwin", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Agejdan", "Host": "Asneftaɣ", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Amek i zemreɣ ad k·kem-ɛiwneɣ ass-a?", "How would you rate this response?": "Amek ara d-teskefleḍ tiririt-a?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "API Mistral OCR Tesri tasarut.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Tamudemt", "Model '{{modelName}}' has been successfully downloaded.": "Tettwasider-d tmudemt '{{modelName}}' akken iwata.", "Model '{{modelTag}}' is already in queue for downloading.": "Tamudemt '{{modelTag}}' ha-t-an yakan deg tebdart n usader.", - "Model {{modelId}} not found": "Tamudemt {{modelId}} ulac-itt", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Mudel Isem}} mačči d tamuɣli izemren ad tili", "Model {{name}} is now {{status}}": "Tamudemt {{name}} tura {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Tasarut API n Mojeek", + "Monthly": "", "More": "Ugar", "More Concise": "Awezlan ugar", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Tasarut API n Ollama Cloud", "Ollama Version": "Lqem n Ollama", "On": "Irmed", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Asagen", "Pin": "Senteḍ", + "Pin to Sidebar": "", "Pinned": "Yettwasenteḍ", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Aselkem", "Running...": "Aselkem...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Sekles", "Save & Create": "Sekles rnu snulfu-d", "Save & Update": "Sekles rnu leqqem", @@ -1889,6 +1897,7 @@ "STT Model": "Tamudemt n uɛqal n taɣect", "STT Settings": "Iɣewwaren n uɛqal n tavect", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Amebḍay n uḍris", "Text-to-Speech": "Aḍris-ɣer-taɣect", "Text-to-Speech Engine": "Amsadday n TTS", + "Th_day_of_week": "", "Thanks for your feedback!": "Tanemmirt ɣef tikti-inek·inem!", "The Application Account DN you bind with for search": "", "The base to search for users": "Taffa n unadi ɣef yiseqdacen", @@ -2047,6 +2057,7 @@ "TTS Model": "Tamudemt TTS", "TTS Settings": "Iɣewwaṛen n TTS", "TTS Voice": "Taɣect n TTS", + "Tu_day_of_week": "", "Type": "Anaw", "Type here...": "Aru da…", "Type Hugging Face Resolve (Download) URL": "Anaw n usefres n wudem amezwer (Download) URL", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Ɣur-k: Asewḥel n waya ad yeǧǧ iseqdacen ad d-salin tangalt tazurant ɣef uqeddac.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Ɣur-k: Asselkem n Jupyter yettaǧǧa asselkem n tengalt tazurant, d tukksa n tmijwin n tɣellist qessiḥen — s leḥder meqqren.", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "Amsedday n uzdam Web", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI ad ssutreɣ \"{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI ad ssutreɣ i \"{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI ad ssutreɣ i \"{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Sanda ay tettarmed ad tessiwḍed?", "What are you working on?": "Ɣef wacu ay la tettmahaled?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ko-KR/translation.json b/src/lib/i18n/locales/ko-KR/translation.json index c4492175ca..96a52e359d 100644 --- a/src/lib/i18n/locales/ko-KR/translation.json +++ b/src/lib/i18n/locales/ko-KR/translation.json @@ -453,6 +453,7 @@ "Create a new note": "새 노트 생성", "Create Account": "계정 생성", "Create Admin Account": "관리자 계정 생성", + "Create and manage scheduled automations": "", "Create Channel": "채널 생성", "Create Folder": "폴더 생성", "Create Image": "이미지 생성", @@ -478,6 +479,7 @@ "Custom Gender": "", "Custom Parameter Name": "사용자 정의 매개변수 이름", "Custom Parameter Value": "사용자 정의 매개변수 값", + "Daily": "", "Daily Messages": "", "Danger Zone": "위험 기능", "Dark": "다크", @@ -968,6 +970,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "인증을 위해 시스템 사용자 OAuth 액세스 토큰을 전달합니다.", "Forwards system user session credentials to authenticate": "인증을 위해 시스템 사용자 세션 자격 증명 전달", + "Fr_day_of_week": "", "Full Context Mode": "전체 컨텍스트 모드", "Function": "함수", "Function Calling": "함수 호출", @@ -1042,6 +1045,7 @@ "History": "", "Home": "홈", "Host": "호스트", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "무엇을 도와드릴까요?", "How would you rate this response?": "이 응답을 어떻게 평가하시겠어요?", @@ -1264,10 +1268,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "Mistral OCR API Key가 필요합니다.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "모델", "Model '{{modelName}}' has been successfully downloaded.": "모델 '{{modelName}}'이/가 성공적으로 다운로드되었습니다.", "Model '{{modelTag}}' is already in queue for downloading.": "모델 '{{modelTag}}'은/는 이미 다운로드 대기열에 있습니다.", - "Model {{modelId}} not found": "모델 {{modelId}}을/를 찾을 수 없습니다.", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "모델 {{modelName}}은/는 비전을 사용할 수 없습니다.", "Model {{name}} is now {{status}}": "모델 {{name}}은/는 이제 {{status}} 상태입니다.", @@ -1308,6 +1312,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Mojeek Search API 키", + "Monthly": "", "More": "더보기", "More Concise": "더 간결하게", "More options": "", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama 버전", "On": "켜기", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "\"긴 텍스트를 파일로 붙여넣기\" 설정이 켜져 있을 때만 작동합니다.", "Only active when the chat input is in focus and an LLM is generating a response.": "채팅 입력창이 선택되어 있고 LLM이 응답을 생성 중일 때만 작동합니다.", @@ -1512,6 +1518,7 @@ "Persistent": "", "Personalization": "개인화", "Pin": "고정", + "Pin to Sidebar": "", "Pinned": "고정됨", "Pinned Messages": "고정된 메시지", "Pinned Models": "", @@ -1671,6 +1678,7 @@ "Running": "실행 중", "Running...": "실행 중...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "저장", "Save & Create": "저장 및 생성", "Save & Update": "저장 및 업데이트", @@ -1887,6 +1895,7 @@ "STT Model": "STT 모델", "STT Settings": "STT 설정", "Stylized PDF Export": "서식이 적용된 PDF 내보내기", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1932,6 +1941,7 @@ "Text Splitter": "텍스트 나누기", "Text-to-Speech": "텍스트-음성 변환", "Text-to-Speech Engine": "텍스트-음성 변환 엔진", + "Th_day_of_week": "", "Thanks for your feedback!": "피드백 감사합니다!", "The Application Account DN you bind with for search": "검색을 위해 바인딩하는 애플리케이션 계정 DN", "The base to search for users": "사용자를 검색할 수 있는 기반", @@ -2045,6 +2055,7 @@ "TTS Model": "TTS 모델", "TTS Settings": "TTS 설정", "TTS Voice": "TTS 음성", + "Tu_day_of_week": "", "Type": "입력", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (다운로드) URL 입력", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "주의: 이 기능을 활성화하면 사용자가 서버에 임의 코드를 업로드할 수 있습니다.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "경고: Jupyter 실행은 임의의 코드 실행을 가능하게 하여 심각한 보안 위험을 초래합니다. — 매우 신중하게 진행하세요.", + "We_day_of_week": "", "Web": "웹", "Web API": "웹 API", "Web Loader Engine": "웹 로더 엔진", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI가 \"{{url}}\"로 요청을 보냅니다", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI가 \"{{url}}/api/chat\"로 요청을 보냅니다", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI가 \"{{url}}/chat/completions\"로 요청을 보냅니다", + "Weekly": "", "What are you trying to achieve?": "무엇을 성취하고 싶으신가요?", "What are you working on?": "어떤 작업을 하고 계신가요?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/lt-LT/translation.json b/src/lib/i18n/locales/lt-LT/translation.json index f6e1694dfc..87819d1d2e 100644 --- a/src/lib/i18n/locales/lt-LT/translation.json +++ b/src/lib/i18n/locales/lt-LT/translation.json @@ -456,6 +456,7 @@ "Create a new note": "", "Create Account": "Créer un compte", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -481,6 +482,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Tamsus", @@ -971,6 +973,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1045,6 +1048,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Kuo galėčiau Jums padėti ?", "How would you rate this response?": "", @@ -1267,10 +1271,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' modelis sėkmingai atsisiųstas.", "Model '{{modelTag}}' is already in queue for downloading.": "Modelis '{{modelTag}}' jau atsisiuntimų eilėje.", - "Model {{modelId}} not found": "Modelis {{modelId}} nerastas", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Modelis {{modelName}} neturi vaizdo gebėjimų", "Model {{name}} is now {{status}}": "Modelis {{name}} dabar {{status}}", @@ -1311,6 +1315,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "Daugiau", "More Concise": "", "More options": "", @@ -1431,6 +1436,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama versija", "On": "Aktyvuota", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1515,6 +1521,7 @@ "Persistent": "", "Personalization": "Personalizacija", "Pin": "Smeigtukas", + "Pin to Sidebar": "", "Pinned": "Įsmeigta", "Pinned Messages": "", "Pinned Models": "", @@ -1677,6 +1684,7 @@ "Running": "Veikia", "Running...": "Veikia...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Išsaugoti", "Save & Create": "Išsaugoti ir sukurti", "Save & Update": "Išsaugoti ir atnaujinti", @@ -1893,6 +1901,7 @@ "STT Model": "STT modelis", "STT Settings": "STT nustatymai", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1938,6 +1947,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "Balso sintezės modelis", + "Th_day_of_week": "", "Thanks for your feedback!": "Ačiū už atsiliepimus", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2051,6 +2061,7 @@ "TTS Model": "TTS modelis", "TTS Settings": "TTS parametrai", "TTS Voice": "TTS balsas", + "Tu_day_of_week": "", "Type": "Tipas", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Įveskite Hugging Face Resolve nuorodą", @@ -2153,6 +2164,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "", @@ -2169,6 +2181,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/lv-LV/translation.json b/src/lib/i18n/locales/lv-LV/translation.json index d3ce9d12f7..648f032641 100644 --- a/src/lib/i18n/locales/lv-LV/translation.json +++ b/src/lib/i18n/locales/lv-LV/translation.json @@ -455,6 +455,7 @@ "Create a new note": "Izveidot jaunu piezīmi", "Create Account": "Izveidot kontu", "Create Admin Account": "Izveidot administratora kontu", + "Create and manage scheduled automations": "", "Create Channel": "Izveidot kanālu", "Create Folder": "Izveidot mapi", "Create Image": "Izveidot attēlu", @@ -480,6 +481,7 @@ "Custom Gender": "", "Custom Parameter Name": "Pielāgota parametra nosaukums", "Custom Parameter Value": "Pielāgota parametra vērtība", + "Daily": "", "Daily Messages": "", "Danger Zone": "Bīstamā zona", "Dark": "Tumšs", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Pārsūta sistēmas lietotāja OAuth piekļuves tokenu autentifikācijai", "Forwards system user session credentials to authenticate": "Pārsūta sistēmas lietotāja sesijas akreditācijas datus autentifikācijai", + "Fr_day_of_week": "", "Full Context Mode": "Pilna konteksta režīms", "Function": "Funkcija", "Function Calling": "Funkciju izsaukšana", @@ -1044,6 +1047,7 @@ "History": "", "Home": "Sākums", "Host": "Hosts", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Kā es varu jums šodien palīdzēt?", "How would you rate this response?": "Kā jūs novērtētu šo atbildi?", @@ -1266,10 +1270,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Nepieciešama Mistral OCR API atslēga.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Modelis", "Model '{{modelName}}' has been successfully downloaded.": "Modelis '{{modelName}}' ir veiksmīgi lejupielādēts.", "Model '{{modelTag}}' is already in queue for downloading.": "Modelis '{{modelTag}}' jau ir lejupielādes rindā.", - "Model {{modelId}} not found": "Modelis {{modelId}} nav atrasts", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Modelis {{modelName}} neatbalsta redzi", "Model {{name}} is now {{status}}": "Modelis {{name}} tagad ir {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "Modeļu kopīgošana", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek Search API atslēga", + "Monthly": "", "More": "Vairāk", "More Concise": "Kodolīgāk", "More options": "", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "Ollama Cloud API atslēga", "Ollama Version": "Ollama versija", "On": "Ieslēgts", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Aktīvs tikai, ja ir ieslēgts iestatījums \"Ielīmēt lielu tekstu kā failu\".", "Only active when the chat input is in focus and an LLM is generating a response.": "Aktīvs tikai, kad tērzēšanas ievade ir fokusā un LLM ģenerē atbildi.", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Personalizācija", "Pin": "Piespraust", + "Pin to Sidebar": "", "Pinned": "Piesprausts", "Pinned Messages": "Piespraustie ziņojumi", "Pinned Models": "", @@ -1675,6 +1682,7 @@ "Running": "Darbojas", "Running...": "Darbojas...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Palaiž iegulšanas uzdevumus vienlaicīgi, lai paātrinātu apstrādi. Izslēdziet, ja rodas ātruma ierobežojumu problēmas.", + "Sa_day_of_week": "", "Save": "Saglabāt", "Save & Create": "Saglabāt un izveidot", "Save & Update": "Saglabāt un atjaunināt", @@ -1891,6 +1899,7 @@ "STT Model": "STT modelis", "STT Settings": "STT iestatījumi", "Stylized PDF Export": "Stilizēts PDF eksports", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "Apakšvirsraksts", @@ -1936,6 +1945,7 @@ "Text Splitter": "Teksta sadalītājs", "Text-to-Speech": "Teksts uz runu", "Text-to-Speech Engine": "Teksta uz runas dzinējs", + "Th_day_of_week": "", "Thanks for your feedback!": "Paldies par jūsu atsauksmēm!", "The Application Account DN you bind with for search": "Lietojumprogrammas konta DN, ar kuru saistāties meklēšanai", "The base to search for users": "Bāze lietotāju meklēšanai", @@ -2049,6 +2059,7 @@ "TTS Model": "TTS modelis", "TTS Settings": "TTS iestatījumi", "TTS Voice": "TTS balss", + "Tu_day_of_week": "", "Type": "Tips", "Type here...": "Rakstiet šeit...", "Type Hugging Face Resolve (Download) URL": "Ievadiet Hugging Face Resolve (lejupielādes) URL", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Brīdinājums: Šīs opcijas iespējošana ļaus lietotājiem augšupielādēt patvaļīgu kodu serverī.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Brīdinājums: Jupyter izpilde ļauj patvaļīgu koda izpildi, radot nopietnus drošības riskus — rīkojieties ar ārkārtēju piesardzību.", + "We_day_of_week": "", "Web": "Tīmeklis", "Web API": "Tīmekļa API", "Web Loader Engine": "Tīmekļa ielādētāja dzinējs", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI veiks pieprasījumus uz \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI veiks pieprasījumus uz \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI veiks pieprasījumus uz \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Ko jūs mēģināt sasniegt?", "What are you working on?": "Pie kā jūs strādājat?", "What is NOT shared:": "Kas NETIEK kopīgots:", diff --git a/src/lib/i18n/locales/ms-MY/translation.json b/src/lib/i18n/locales/ms-MY/translation.json index a819fa33b4..cf1f755f08 100644 --- a/src/lib/i18n/locales/ms-MY/translation.json +++ b/src/lib/i18n/locales/ms-MY/translation.json @@ -453,6 +453,7 @@ "Create a new note": "Buat nota baru", "Create Account": "Cipta Akaun", "Create Admin Account": "Buat Akaun Admin", + "Create and manage scheduled automations": "", "Create Channel": "Buat Saluran", "Create Folder": "Buat Folder", "Create Image": "Buat Imej", @@ -478,6 +479,7 @@ "Custom Gender": "Jantina Tersuai", "Custom Parameter Name": "Nama Parameter Tersuai", "Custom Parameter Value": "Nilai Parameter Tersuai", + "Daily": "", "Daily Messages": "Mesej Harian", "Danger Zone": "Zon Bahaya", "Dark": "Gelap", @@ -968,6 +970,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Meneruskan token akses OAuth pengguna sistem untuk pengesahan", "Forwards system user session credentials to authenticate": "Meneruskan bukti kelayakan sesi pengguna sistem untuk pengesahan", + "Fr_day_of_week": "", "Full Context Mode": "Mod Konteks Penuh", "Function": "Fungsi", "Function Calling": "Pemanggilan Fungsi", @@ -1042,6 +1045,7 @@ "History": "Sejarah", "Home": "Halaman Utama", "Host": "Hos", + "Hourly": "", "Hourly Messages": "Mesej Setiap Jam", "How can I help you today?": "Bagaimana saya boleh membantu anda hari ini?", "How would you rate this response?": "Bagaimana anda menilai respons ini?", @@ -1264,10 +1268,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Kunci API Mistral OCR diperlukan.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{ modelName }}' telah berjaya dimuat turun.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{ modelTag }}' sudah dalam baris gilir untuk dimuat turun.", - "Model {{modelId}} not found": "Model {{ modelId }} tidak dijumpai", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{ modelName }} tidak mempunyai keupayaan penglihatan", "Model {{name}} is now {{status}}": "Model {{name}} kini {{status}}", @@ -1308,6 +1312,7 @@ "Models Sharing": "Perkongsian Model", "Mojeek": "Mojeek", "Mojeek Search API Key": "Kunci API Pencarian Mojeek", + "Monthly": "", "More": "Lagi", "More Concise": "Lebih Ringkas", "More options": "Lebih banyak pilihan", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "Kunci API Ollama Cloud", "Ollama Version": "Versi Ollama", "On": "Hidup", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Hanya aktif apabila tetapan \"Tampal Teks Besar sebagai Fail\" dihidupkan.", "Only active when the chat input is in focus and an LLM is generating a response.": "Hanya aktif apabila input sembang berada dalam fokus dan LLM sedang menjana respons.", @@ -1512,6 +1518,7 @@ "Persistent": "", "Personalization": "Personalisasi", "Pin": "Pin", + "Pin to Sidebar": "", "Pinned": "Disemat", "Pinned Messages": "Mesej Disematkan", "Pinned Models": "Model Tersapu", @@ -1671,6 +1678,7 @@ "Running": "Sedang dijalankan", "Running...": "Sedang dijalankan...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Menjalankan tugas penyisipan secara serentak untuk mempercepatkan pemprosesan. Matikan jika had kadar menjadi isu.", + "Sa_day_of_week": "", "Save": "Simpan", "Save & Create": "Simpan & Cipta", "Save & Update": "Simpan & Kemas Kini", @@ -1887,6 +1895,7 @@ "STT Model": "Model STT", "STT Settings": "Tetapan STT", "Stylized PDF Export": "Eksport PDF Bergaya", + "Su_day_of_week": "", "Submit question": "Hantar soalan", "Submit suggestion": "Hantar cadangan", "Subtitle": "Subtitle", @@ -1932,6 +1941,7 @@ "Text Splitter": "Pemisah Teks", "Text-to-Speech": "Teks-ke-Ucapan", "Text-to-Speech Engine": "Enjin Teks-ke-Ucapan", + "Th_day_of_week": "", "Thanks for your feedback!": "Terima kasih atas maklum balas anda!", "The Application Account DN you bind with for search": "DN Akaun Aplikasi yang anda ikat untuk carian", "The base to search for users": "Pangkalan untuk mencari pengguna", @@ -2045,6 +2055,7 @@ "TTS Model": "Model TTS", "TTS Settings": "Tetapan TTS", "TTS Voice": "Suara TTS", + "Tu_day_of_week": "", "Type": "jenis", "Type here...": "Taip di sini...", "Type Hugging Face Resolve (Download) URL": "Taip URL 'Hugging Face Resolve (Download)'", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Amaran: Mengaktifkan ini akan membenarkan pengguna memuat naik kod arbitrari pada pelayan.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Amaran: Pelaksanaan Jupyter membolehkan pelaksanaan kod arbitrari, menimbulkan risiko keselamatan yang teruk—teruskan dengan berhati-hati.", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "Enjin Pemuatan Web", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI akan membuat permintaan ke \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI akan membuat permintaan ke \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI akan membuat permintaan ke \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Apa yang ingin anda capai?", "What are you working on?": "Apa yang sedang anda kerjakan?", "What is NOT shared:": "Apa yang TIDAK dikongsi:", diff --git a/src/lib/i18n/locales/nb-NO/translation.json b/src/lib/i18n/locales/nb-NO/translation.json index 2c2d6ba0d2..0fdd082794 100644 --- a/src/lib/i18n/locales/nb-NO/translation.json +++ b/src/lib/i18n/locales/nb-NO/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Opprett konto", "Create Admin Account": "Opprett administratorkonto", + "Create and manage scheduled automations": "", "Create Channel": "Opprett kanal", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Mørk", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "Modus for full kontekst", "Function": "Funksjon", "Function Calling": "Kalling av funksjon", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Hjem", "Host": "Host", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Hva kan jeg hjelpe deg med i dag?", "How would you rate this response?": "Hvordan vurderer du dette svaret?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Modell", "Model '{{modelName}}' has been successfully downloaded.": "Modellen {{modelName}} er lastet ned.", "Model '{{modelTag}}' is already in queue for downloading.": "Modellen {{modelTag}} er allerede i nedlastingskøen.", - "Model {{modelId}} not found": "Finner ikke modellen {{modelId}}", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Modellen {{modelName}} er ikke egnet til visuelle data", "Model {{name}} is now {{status}}": "Modellen {{name}} er nå {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "API-nøekkel for Mojeek Search", + "Monthly": "", "More": "Mer", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama-versjon", "On": "Aktivert", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Tilpassing", "Pin": "Fest", + "Pin to Sidebar": "", "Pinned": "Festet", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Kjører", "Running...": "Kjører...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Lagre", "Save & Create": "Lagre og opprett", "Save & Update": "Lagre og oppdater", @@ -1889,6 +1897,7 @@ "STT Model": "STT-modell", "STT Settings": "STT-innstillinger", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Oppdeling av tekst", "Text-to-Speech": "", "Text-to-Speech Engine": "Tekst-til-tale-motor", + "Th_day_of_week": "", "Thanks for your feedback!": "Takk for tilbakemeldingen!", "The Application Account DN you bind with for search": "Applikasjonskontoens DN du binder deg med for søking", "The base to search for users": "Basen for å søke etter brukere", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS-modell", "TTS Settings": "TTS-innstillinger", "TTS Voice": "TTS-stemme", + "Tu_day_of_week": "", "Type": "Type", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Angi nedlastings-Resolve-URL for Hugging Face", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Advarsel: Hvis du aktiverer denne funksjonen, kan brukere laste opp vilkårlig kode på serveren.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Advarsel! Jupyter gjør det mulig å kjøre vilkårlig kode, noe som utgjør en alvorlig sikkerhetsrisiko. Utvis ekstrem forsiktighet.", + "We_day_of_week": "", "Web": "Web", "Web API": "Web-API", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI vil rette forespørsler til \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI vil rette forespørsler til \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Hva prøver du å oppnå?", "What are you working on?": "Hva jobber du på nå?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/nl-NL/translation.json b/src/lib/i18n/locales/nl-NL/translation.json index 9feab00ffe..f54b0190b3 100644 --- a/src/lib/i18n/locales/nl-NL/translation.json +++ b/src/lib/i18n/locales/nl-NL/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Maak account", "Create Admin Account": "Maak admin-account", + "Create and manage scheduled automations": "", "Create Channel": "Maak kanaal", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "Gevarenzone", "Dark": "Donker", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "Volledige contextmodus", "Function": "Functie", "Function Calling": "Functieaanroep", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Thuis", "Host": "Host", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Hoe kan ik je vandaag helpen?", "How would you rate this response?": "Hoe zou je dit antwoord beoordelen?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' is succesvol gedownload.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' staat al in de wachtrij voor downloaden.", - "Model {{modelId}} not found": "Model {{modelId}} niet gevonden", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} is niet geschikt voor visie", "Model {{name}} is now {{status}}": "Model {{name}} is nu {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Mojeek Search API-sleutel", + "Monthly": "", "More": "Meer", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama Versie", "On": "Aan", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Personalisatie", "Pin": "Zet vast", + "Pin to Sidebar": "", "Pinned": "Vastgezet", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Aan het uitvoeren", "Running...": "Aan het uitvoeren...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Opslaan", "Save & Create": "Opslaan & Creëren", "Save & Update": "Opslaan & Bijwerken", @@ -1889,6 +1897,7 @@ "STT Model": "STT Model", "STT Settings": "STT Instellingen", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Tekst splitser", "Text-to-Speech": "", "Text-to-Speech Engine": "Tekst-naar-Spraak Engine", + "Th_day_of_week": "", "Thanks for your feedback!": "Bedankt voor je feedback!", "The Application Account DN you bind with for search": "Het applicatieaccount DN waarmee je zoekt", "The base to search for users": "De basis om gebruikers te zoeken", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS Model", "TTS Settings": "TTS instellingen", "TTS Voice": "TTS Stem", + "Tu_day_of_week": "", "Type": "Type", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Type Hugging Face Resolve (Download) URL", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Waarschuwing: Door dit in te schakelen kunnen gebruikers willekeurige code uploaden naar de server.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Waarschuwing: Jupyter kan willekeurige code uitvoeren, wat ernstige veiligheidsrisico's met zich meebrengt - ga uiterst voorzichtig te werk. ", + "We_day_of_week": "", "Web": "Web", "Web API": "Web-API", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI zal verzoeken doen aan \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI zal verzoeken doen aan \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Wat probeer je te bereiken?", "What are you working on?": "Waar werk je aan?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/pa-IN/translation.json b/src/lib/i18n/locales/pa-IN/translation.json index 952a681610..4e43ac1ff3 100644 --- a/src/lib/i18n/locales/pa-IN/translation.json +++ b/src/lib/i18n/locales/pa-IN/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "ਖਾਤਾ ਬਣਾਓ", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "ਗੂੜ੍ਹਾ", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "ਮੈਂ ਅੱਜ ਤੁਹਾਡੀ ਕਿਵੇਂ ਮਦਦ ਕਰ ਸਕਦਾ ਹਾਂ?", "How would you rate this response?": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "", "Model '{{modelName}}' has been successfully downloaded.": "ਮਾਡਲ '{{modelName}}' ਸਫਲਤਾਪੂਰਵਕ ਡਾਊਨਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ।", "Model '{{modelTag}}' is already in queue for downloading.": "ਮਾਡਲ '{{modelTag}}' ਪਹਿਲਾਂ ਹੀ ਡਾਊਨਲੋਡ ਲਈ ਕਤਾਰ ਵਿੱਚ ਹੈ।", - "Model {{modelId}} not found": "ਮਾਡਲ {{modelId}} ਨਹੀਂ ਮਿਲਿਆ", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "ਮਾਡਲ {{modelName}} ਦ੍ਰਿਸ਼ਟੀ ਸਮਰੱਥ ਨਹੀਂ ਹੈ", "Model {{name}} is now {{status}}": "ਮਾਡਲ {{name}} ਹੁਣ {{status}} ਹੈ", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "ਹੋਰ", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "ਓਲਾਮਾ ਵਰਜਨ", "On": "ਚਾਲੂ", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "ਪਰਸੋਨਲਿਸ਼ਮ", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "", "Running...": "ਚੱਲ ਰਿਹਾ ਹੈ...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "ਸੰਭਾਲੋ", "Save & Create": "ਸੰਭਾਲੋ ਅਤੇ ਬਣਾਓ", "Save & Update": "ਸੰਭਾਲੋ ਅਤੇ ਅੱਪਡੇਟ ਕਰੋ", @@ -1889,6 +1897,7 @@ "STT Model": "", "STT Settings": "STT ਸੈਟਿੰਗਾਂ", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "ਪਾਠ-ਤੋਂ-ਬੋਲ ਇੰਜਣ", + "Th_day_of_week": "", "Thanks for your feedback!": "ਤੁਹਾਡੇ ਫੀਡਬੈਕ ਲਈ ਧੰਨਵਾਦ!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "", "TTS Settings": "TTS ਸੈਟਿੰਗਾਂ", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "ਕਿਸਮ", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (ਡਾਊਨਲੋਡ) URL ਟਾਈਪ ਕਰੋ", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "ਵੈਬ", "Web API": "", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/pl-PL/translation.json b/src/lib/i18n/locales/pl-PL/translation.json index 5194bf8be7..c41577a730 100644 --- a/src/lib/i18n/locales/pl-PL/translation.json +++ b/src/lib/i18n/locales/pl-PL/translation.json @@ -456,6 +456,7 @@ "Create a new note": "Utwórz nową notatkę", "Create Account": "Utwórz konto", "Create Admin Account": "Utwórz konto administratora", + "Create and manage scheduled automations": "", "Create Channel": "Utwórz kanał", "Create Folder": "Utwórz folder", "Create Image": "Utwórz obraz", @@ -481,6 +482,7 @@ "Custom Gender": "", "Custom Parameter Name": "Nazwa parametru niestandardowego", "Custom Parameter Value": "Wartość parametru niestandardowego", + "Daily": "", "Daily Messages": "", "Danger Zone": "Strefa krytyczna", "Dark": "Ciemny", @@ -971,6 +973,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Przekazuje token dostępu OAuth użytkownika systemowego", "Forwards system user session credentials to authenticate": "Przekazuje poświadczenia sesji użytkownika systemowego", + "Fr_day_of_week": "", "Full Context Mode": "Tryb pełnego kontekstu", "Function": "Funkcja", "Function Calling": "Wywoływanie funkcji", @@ -1045,6 +1048,7 @@ "History": "", "Home": "Strona główna", "Host": "Host", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "W czym mogę pomóc?", "How would you rate this response?": "Jak oceniasz tę odpowiedź?", @@ -1267,10 +1271,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Wymagany klucz API Mistral OCR.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "Model '{{modelName}}' został pomyślnie pobrany.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' jest już w kolejce pobierania.", - "Model {{modelId}} not found": "Nie znaleziono modelu {{modelId}}", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} nie obsługuje widzenia (Vision)", "Model {{name}} is now {{status}}": "Model {{name}} jest teraz {{status}}", @@ -1311,6 +1315,7 @@ "Models Sharing": "Udostępnianie modeli", "Mojeek": "Mojeek", "Mojeek Search API Key": "Klucz API Mojeek Search", + "Monthly": "", "More": "Więcej", "More Concise": "Bardziej zwięzły", "More options": "", @@ -1431,6 +1436,7 @@ "Ollama Cloud API Key": "Klucz API Ollama Cloud", "Ollama Version": "Wersja Ollama", "On": "Wł.", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Aktywne tylko gdy opcja \"Wklej duży tekst jako plik\" jest włączona.", "Only active when the chat input is in focus and an LLM is generating a response.": "Aktywne tylko gdy pole czatu jest aktywne a LLM generuje odpowiedź.", @@ -1515,6 +1521,7 @@ "Persistent": "", "Personalization": "Personalizacja", "Pin": "Przypnij", + "Pin to Sidebar": "", "Pinned": "Przypięte", "Pinned Messages": "Przypięte wiadomości", "Pinned Models": "", @@ -1677,6 +1684,7 @@ "Running": "Działa", "Running...": "Działa...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Uruchamia zadania embeddingu współbieżnie. Wyłącz, jeśli masz limity API.", + "Sa_day_of_week": "", "Save": "Zapisz", "Save & Create": "Zapisz i utwórz", "Save & Update": "Zapisz i aktualizuj", @@ -1893,6 +1901,7 @@ "STT Model": "Model STT", "STT Settings": "Ustawienia STT", "Stylized PDF Export": "Stylizowany eksport PDF", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "Podtytuł", @@ -1938,6 +1947,7 @@ "Text Splitter": "Text Splitter", "Text-to-Speech": "Silnik syntezy mowy (TTS)", "Text-to-Speech Engine": "Silnik syntezy mowy (TTS)", + "Th_day_of_week": "", "Thanks for your feedback!": "Dzięki za opinię!", "The Application Account DN you bind with for search": "DN konta aplikacji do wyszukiwania", "The base to search for users": "Baza wyszukiwania użytkowników", @@ -2051,6 +2061,7 @@ "TTS Model": "Model TTS", "TTS Settings": "Ustawienia TTS", "TTS Voice": "Głos TTS", + "Tu_day_of_week": "", "Type": "Typ", "Type here...": "Pisz tutaj...", "Type Hugging Face Resolve (Download) URL": "Wpisz URL Hugging Face Resolve (Pobieranie)", @@ -2153,6 +2164,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Uwaga: Włączenie tego pozwoli użytkownikom na przesyłanie dowolnego kodu na serwer.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Uwaga: Jupyter umożliwia wykonanie dowolnego kodu (ryzyko bezpieczeństwa) – zachowaj szczególną ostrożność.", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "Silnik ładowania stron", @@ -2169,6 +2181,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI będzie wysyłać żądania do \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI będzie wysyłać żądania do \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI będzie wysyłać żądania do \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Co chcesz osiągnąć?", "What are you working on?": "Nad czym pracujesz?", "What is NOT shared:": "Co NIE jest udostępniane:", diff --git a/src/lib/i18n/locales/pt-BR/translation.json b/src/lib/i18n/locales/pt-BR/translation.json index 3eb41f721e..6f898b892d 100644 --- a/src/lib/i18n/locales/pt-BR/translation.json +++ b/src/lib/i18n/locales/pt-BR/translation.json @@ -183,7 +183,7 @@ "Are you sure you want to archive all chats? This action cannot be undone.": "Tem certeza de que deseja arquivar todos os chats? Esta ação não pode ser desfeita.", "Are you sure you want to clear all memories? This action cannot be undone.": "Tem certeza de que deseja apagar todas as memórias? Esta ação não pode ser desfeita.", "Are you sure you want to delete \"{{NAME}}\"?": "Tem certeza de que deseja excluir \"{{NAME}}\"?", - "Are you sure you want to delete **{{modelName}}**?": "", + "Are you sure you want to delete **{{modelName}}**?": "Tem certeza de que deseja excluir **{{modelName}}**?", "Are you sure you want to delete all chats? This action cannot be undone.": "Tem certeza de que deseja excluir todas as conversas? Esta ação não pode ser desfeita.", "Are you sure you want to delete this channel?": "Tem certeza de que deseja excluir este canal?", "Are you sure you want to delete this connection? This action cannot be undone.": "Tem certeza de que deseja excluir esta conexão? Esta ação não pode ser desfeita.", @@ -200,7 +200,7 @@ "Assistant": "Assistente", "Async Embedding Processing": "Processamento de Embedding assíncrono", "Attach File From Knowledge": "Anexar arquivo da base de conhecimento", - "Attach Files": "", + "Attach Files": "Anexar arquivos", "Attach Knowledge": "Anexar Base de Conhecimento", "Attach Notes": "Anexar Notas", "Attach Webpage": "Anexar Página Web", @@ -223,13 +223,13 @@ "AUTOMATIC1111 Base URL": "URL Base AUTOMATIC1111", "AUTOMATIC1111 Base URL is required.": "URL Base AUTOMATIC1111 é necessária.", "Automatically inject system tools in native function calling mode (e.g., timestamps, memory, chat history, notes, etc.)": "Injetar automaticamente ferramentas do sistema no modo de chamada de função nativa (por exemplo, carimbos de data/hora, memória, histórico de chat, notas, etc.)", - "Automation": "", - "Automation created": "", - "Automation Name": "", - "Automation title": "", - "Automation triggered": "", - "Automation updated": "", - "Automations": "", + "Automation": "Automação", + "Automation created": "Automação criada", + "Automation Name": "Nome da automação", + "Automation title": "Título de automação", + "Automation triggered": "Automação acionada", + "Automation updated": "Automação atualizada", + "Automations": "Automações", "Available list": "Lista disponível", "Available models": "Modelos disponíveis", "Available Tools": "Ferramentas disponíveis", @@ -260,7 +260,7 @@ "Boosting or penalizing specific tokens for constrained responses. Bias values will be clamped between -100 and 100 (inclusive). (Default: none)": "Aumentar ou penalizar tokens específicos para respostas restritas. Os valores de viés serão fixados entre -100 e 100 (inclusive). (Padrão: nenhum)", "Brave": "Brave", "Brave Search API Key": "Chave API do Brave Search", - "Break down complex requests into trackable steps": "", + "Break down complex requests into trackable steps": "Divida solicitações complexas em etapas rastreáveis.", "Browse and query knowledge bases": "Navegue e consulte bases de conhecimento.", "Builtin Tools": "Ferramentas integradas", "Bullet List": "Lista com marcadores", @@ -394,7 +394,7 @@ "Concurrent Requests": "Solicitações simultâneas", "Config": "Configuração", "Config imported successfully": "Configuração importada com sucesso", - "Configuration": "", + "Configuration": "Configuração", "Configure": "Configurar", "Confirm": "Confirmar", "Confirm Password": "Confirmar Senha", @@ -455,6 +455,7 @@ "Create a new note": "Criar uma nova nota", "Create Account": "Criar Conta", "Create Admin Account": "Criar Conta de Administrador", + "Create and manage scheduled automations": "Criar e gerenciar automações agendadas", "Create Channel": "Criar Canal", "Create Folder": "Criar Pasta", "Create Image": "Criar imagem", @@ -464,7 +465,7 @@ "Create new secret key": "Criar nova chave secreta", "Create note": "Criar nota", "Create Note": "Criar Nota", - "Create scheduled prompts that run automatically on a recurring basis.": "", + "Create scheduled prompts that run automatically on a recurring basis.": "Crie prompts agendados que sejam executados automaticamente de forma recorrente.", "Create your first note by clicking on the plus button below.": "Crie sua primeira nota clicando no botão de adição abaixo.", "Created at": "Criado em", "Created At": "Criado Em", @@ -480,13 +481,14 @@ "Custom Gender": "Gênero personalizado", "Custom Parameter Name": "Nome do parâmetro personalizado", "Custom Parameter Value": "Valor do parâmetro personalizado", + "Daily": "Diário", "Daily Messages": "Mensagens Diárias", "Danger Zone": "Zona de Perigo", "Dark": "Escuro", "Data Controls": "Controle de Dados", "Database": "Banco de Dados", "Datalab Marker API": "API do Marcador do Datalab", - "Day": "", + "Day": "Dia", "DD/MM/YYYY": "DD/MM/AAAA", "DDGS Backend": "Backend DDGS", "December": "Dezembro", @@ -517,7 +519,7 @@ "Delete All": "Excluir tudo", "Delete All Chats": "Excluir Todos os Chats", "Delete all contents inside this folder": "Apague todo o conteúdo desta pasta.", - "Delete automation?": "", + "Delete automation?": "Excluir automação?", "Delete Chat": "Excluir Chat", "Delete chat?": "Excluir chat?", "Delete File": "Excluir arquivo", @@ -662,7 +664,7 @@ "Embedding Concurrent Requests": "Solicitações Simultâneas de Embedding", "Embedding Model": "Modelo de Embedding", "Embedding Model Engine": "Motor do Modelo de Embedding", - "Emojis": "", + "Emojis": "Emojis", "Empty message": "Mensagem vazia", "Enable All": "Ativar tudo", "Enable API Keys": "Habilitar Chaves de API", @@ -754,7 +756,7 @@ "Enter Perplexity Search API URL": "Insira a URL da API de pesquisa Perplexity", "Enter Playwright Timeout": "Insira o tempo limite do Playwright", "Enter Playwright WebSocket URL": "Insira a URL do WebSocket do Playwright", - "Enter prompt here.": "", + "Enter prompt here.": "Insira o prompt aqui.", "Enter proxy URL (e.g. https://user:password@host:port)": "Insira a URL do proxy (por exemplo, https://usuário:senha@host:porta)", "Enter reasoning effort": "Insira o esforço de raciocínio", "Enter Score": "Digite a Pontuação", @@ -779,7 +781,7 @@ "Enter system prompt here": "Insira o prompt do sistema aqui", "Enter Tavily API Key": "Digite a Chave API do Tavily", "Enter Tavily Extract Depth": "Insira a profundidade de extração do Tavily", - "Enter the prompt instructions for this automation...": "", + "Enter the prompt instructions for this automation...": "Insira as instruções para esta automação...", "Enter the public URL of your WebUI. This URL will be used to generate links in the notifications.": "Insira a URL pública da sua WebUI. Esta URL será usada para gerar links nas notificações.", "Enter the URL of the function to import": "Digite a URL da função a ser importada", "Enter the URL to import": "Digite a URL para importar", @@ -819,7 +821,7 @@ "Error accessing directory": "Erro ao acessar o diretório", "Error accessing Google Drive: {{error}}": "Erro ao acessar o Google Drive: {{error}}", "Error accessing media devices.": "Erro ao acessar dispositivos de mídia.", - "Error deleting model: {{error}}": "", + "Error deleting model: {{error}}": "Erro ao excluir o modelo: {{error}}", "Error starting recording.": "Erro ao iniciar a gravação.", "Error unloading model: {{error}}": "Erro ao descarregar modelo: {{error}}", "Error uploading file: {{error}}": "Erro ao carregar o arquivo: {{error}}", @@ -837,7 +839,7 @@ "Execute code": "Executar código", "Execute code for analysis": "Executar código para análise", "Executing **{{NAME}}**...": "Executando **{{NAME}}**...", - "Execution Logs": "", + "Execution Logs": "Registros de execução", "Expand": "Expandir", "Experimental": "Experimental", "Explain": "Explicar", @@ -847,8 +849,8 @@ "Export": "Exportar", "Export All Archived Chats": "Exportar todos os chats arquivados", "Export All Chats (All Users)": "Exportar Todos os Chats (Todos os Usuários)", - "Export as CSV": "", - "Export as JSON": "", + "Export as CSV": "Exportar como CSV", + "Export as JSON": "Exportar como JSON", "Export chat (.json)": "Exportar chat (.json)", "Export Chats": "Exportar Chats", "Export Config": "Exportar Configuração", @@ -970,6 +972,7 @@ "Forward": "Encaminhar", "Forwards system user OAuth access token to authenticate": "Encaminha o token de acesso OAuth do usuário do sistema para autenticação", "Forwards system user session credentials to authenticate": "Encaminha as credenciais da sessão do usuário do sistema para autenticação", + "Fr_day_of_week": "Sex", "Full Context Mode": "Modo de contexto completo", "Function": "Função", "Function Calling": "Chamada de função", @@ -1044,6 +1047,7 @@ "History": "Histórico", "Home": "Início", "Host": "Servidor", + "Hourly": "A cada hora", "Hourly Messages": "Mensagens por Hora", "How can I help you today?": "Como posso ajudar você hoje?", "How would you rate this response?": "Como você avalia essa resposta?", @@ -1103,7 +1107,7 @@ "Insert Suggestion Prompt to Input": "Inserir prompt de sugestão para entrada", "Install from Github URL": "Instalar da URL do Github", "Instant Auto-Send After Voice Transcription": "Envio Automático Instantâneo Após Transcrição de Voz", - "Instructions": "", + "Instructions": "Instruções", "Integration": "Integração", "Integrations": "Integrações", "Interface": "Interface", @@ -1163,7 +1167,7 @@ "Last 90 days": "Últimos 90 dias", "Last Active": "Última Atividade", "Last Modified": "Última Modificação", - "Last ran": "", + "Last ran": "Última execução", "Last reply": "Última resposta", "LDAP": "LDAP", "LDAP server updated": "Servidor LDAP atualizado", @@ -1266,11 +1270,11 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Chave de API do Mistral OCR necessária.", "MistralAI": "MistralAI", + "Mo_day_of_week": "Seg", "Model": "Modelo", "Model '{{modelName}}' has been successfully downloaded.": "Modelo '{{modelName}}' foi baixado com sucesso.", "Model '{{modelTag}}' is already in queue for downloading.": "Modelo '{{modelTag}}' já está na fila para download.", - "Model {{modelId}} not found": "Modelo {{modelId}} não encontrado", - "Model {{modelName}} deleted successfully": "", + "Model {{modelName}} deleted successfully": "Modelo {{modelName}} excluído com sucesso", "Model {{modelName}} is not vision capable": "Modelo {{modelName}} não é capaz de visão", "Model {{name}} is now {{status}}": "Modelo {{name}} está agora {{status}}", "Model {{name}} is now hidden": "O modelo {{name}} agora está oculto", @@ -1310,6 +1314,7 @@ "Models Sharing": "Compartilhamento de Modelos", "Mojeek": "Mojeek", "Mojeek Search API Key": "Chave de API Mojeek Search", + "Monthly": "Mensal", "More": "Mais", "More Concise": "Mais conciso", "More options": "Mais opções", @@ -1320,11 +1325,11 @@ "Name": "Nome", "Name and ID are required, please fill them out": "Nome e ID são obrigatórios, por favor preencha-os", "Name your knowledge base": "Nome da sua base de conhecimento", - "Name, prompt, and model are required": "", + "Name, prompt, and model are required": "Nome, prompt e modelo são obrigatórios.", "Native": "Nativo", - "Never": "", + "Never": "Nunca", "New": "Novo", - "New Automation": "", + "New Automation": "Nova Automação", "New Button": "Novo Botão", "New Chat": "Novo Chat", "New File": "Novo Arquivo", @@ -1343,11 +1348,11 @@ "New Webhook": "Novo Webhook", "new-channel": "novo-canal", "Next message": "Próxima mensagem", - "Next run": "", + "Next run": "Próxima execução", "No access grants. Private to you.": "Sem permissões de acesso. Privacidade exclusiva para você.", "No activity data": "Sem dados de atividade", "No authentication": "Sem autenticação", - "No automations found": "", + "No automations found": "Nenhuma automação encontrada", "No chats found": "Nenhum chat encontrado", "No chats found for this user.": "Nenhum chat encontrado para este usuário.", "No chats found.": "Nenhum chat encontrado.", @@ -1358,7 +1363,7 @@ "No data": "Sem dados", "No data found": "Nenhum dado encontrado", "No distance available": "Sem distância disponível", - "No execution logs available yet": "", + "No execution logs available yet": "Ainda não há registros de execução disponíveis.", "No expiration can pose security risks.": "A ausência de expiração pode representar riscos de segurança.", "No feedback found": "Nenhum feedback encontrado", "No file selected": "Nenhum arquivo selecionado", @@ -1405,7 +1410,7 @@ "Not factually correct": "Não está factualmente correto", "Not helpful": "Não é útil", "Not Registered": "Não registrado", - "Not scheduled": "", + "Not scheduled": "Não agendado", "Note": "Nota", "Note deleted successfully": "Nota excluída com sucesso", "Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.": "Nota: Se você definir uma pontuação mínima, a pesquisa retornará apenas documentos com pontuação igual ou superior à pontuação mínima.", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "Chave da API Ollama Cloud", "Ollama Version": "Versão Ollama", "On": "Ligado", + "Once": "Uma vez", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Ativo somente quando a configuração \"Colar texto grande como arquivo\" estiver ativada.", "Only active when the chat input is in focus and an LLM is generating a response.": "Ativo somente quando o campo de entrada do chat está em foco e um LLM está gerando uma resposta.", @@ -1479,7 +1485,7 @@ "or": "ou", "Ordered List": "Lista ordenada", "Other": "Outro", - "out of": "", + "out of": "de", "Output": "Saída", "OUTPUT": "SAÍDA", "Output format": "Formato de saída", @@ -1495,7 +1501,7 @@ "Password": "Senha", "Passwords do not match.": "As senhas não coincidem.", "Paste Large Text as File": "Cole Textos Longos como Arquivo", - "Paused": "", + "Paused": "Em pausa", "PDF document (.pdf)": "Documento PDF (.pdf)", "PDF Extract Images (OCR)": "Extrair Imagens do PDF (OCR)", "PDF Loader Mode": "Modo de carregamento de PDF", @@ -1514,6 +1520,7 @@ "Persistent": "Persistente", "Personalization": "Personalização", "Pin": "Fixar", + "Pin to Sidebar": "Fixar na barra lateral", "Pinned": "Fixado", "Pinned Messages": "Mensagens fixadas", "Pinned Models": "Modelos Fixados", @@ -1600,7 +1607,7 @@ "Reason": "Razão", "Reasoning Effort": "Esforço de raciocínio", "Reasoning Tags": "Tags de raciocínio", - "Recently Used": "", + "Recently Used": "Usado recentemente", "Record": "Gravar", "Record voice": "Gravar voz", "Redirecting you to Open WebUI Community": "Redirecionando você para a Comunidade OpenWebUI", @@ -1636,7 +1643,7 @@ "Renamed to {{name}}": "Renomeado para {{name}}", "Render Markdown in Previews": "Renderizar Markdown nas Pré-visualizações", "Reorder Models": "Reordenar modelos", - "Repeats": "", + "Repeats": "Repetições", "Reply": "Responder", "Reply in Thread": "Responder no tópico", "Reply to thread...": "Responder ao tópico...", @@ -1670,11 +1677,12 @@ "RTL": "Direita para Esquerda", "Run": "Executar", "Run All": "Executar Tudo", - "Run now": "", - "Run Now": "", + "Run now": "Executar agora", + "Run Now": "Executar Agora", "Running": "Executando", "Running...": "Executando...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Executa tarefas de incorporação simultaneamente para acelerar o processamento. Desative se os limites de taxa se tornarem um problema.", + "Sa_day_of_week": "Sáb", "Save": "Salvar", "Save & Create": "Salvar e Criar", "Save & Update": "Salvar e Atualizar", @@ -1682,15 +1690,15 @@ "Save Chat": "Salvar Chat", "Saved": "Armazenado", "Saving chat logs directly to your browser's storage is no longer supported. Please take a moment to download and delete your chat logs by clicking the button below. Don't worry, you can easily re-import your chat logs to the backend through": "Salvar registros de chat diretamente no armazenamento do seu navegador não é mais suportado. Por favor, reserve um momento para baixar e excluir seus registros de chat clicando no botão abaixo. Não se preocupe, você pode facilmente reimportar seus registros de chat para o backend através de", - "Schedule": "", - "Scheduled time must be in the future": "", + "Schedule": "Agendar", + "Scheduled time must be in the future": "O horário agendado deve ser no futuro.", "Scroll On Branch Change": "Rolar na mudança de ramo", "Search": "Pesquisar", "Search a model": "Pesquisar um modelo", "Search all emojis": "Pesquisar todos os emojis", "Search and manage user memories": "Pesquisar e gerenciar memórias de usuários", "Search and view user chat history": "Pesquise e visualize o histórico de chat do usuário", - "Search Automations": "", + "Search Automations": "Pesquisar Automações", "Search Base": "Pesquisar Base", "Search channels and channel messages": "Pesquisar canais e mensagens de canais", "Search Chats": "Pesquisar Chats", @@ -1760,7 +1768,7 @@ "Select how to split message text for TTS requests": "Selecione como dividir o texto da mensagem para solicitações TTS", "Select Knowledge": "Selecionar Conhecimento", "Select Method": "Selecione o método", - "Select model": "", + "Select model": "Selecione o modelo", "Select only one model to call": "Selecione apenas um modelo para chamar", "Select view": "Selecionar visualização", "Selected model: {{modelName}}": "Modelo selecionado: {{modelName}}", @@ -1870,7 +1878,7 @@ "Start of the channel": "Início do canal", "Start Tag": "Tag inicial", "Starting kernel...": "Iniciando kernel...", - "State": "", + "State": "Estado", "Status": "Status", "Status cleared successfully": "Status liberado com sucesso", "Status updated successfully": "Status atualizado com sucesso", @@ -1891,6 +1899,7 @@ "STT Model": "Modelo STT", "STT Settings": "Configurações STT", "Stylized PDF Export": "Exportação de PDF estilizado", + "Su_day_of_week": "Dom", "Submit question": "Enviar pergunta", "Submit suggestion": "Enviar sugestão", "Subtitle": "Subtítulo", @@ -1921,10 +1930,10 @@ "Talk to Model": "Fale com o modelo", "Tap to interrupt": "Toque para interromper", "Task List": "Lista de tarefas", - "Task Management": "", + "Task Management": "Gerenciamento de Tarefas", "Task Model": "Modelo de Tarefa", "Tasks": "Tarefas", - "tasks completed": "", + "tasks completed": "tarefas concluídas", "Tavily API Key": "Chave da API Tavily", "Tavily Extract Depth": "Profundidade de extração do Tavily", "Tell us more:": "Conte-nos mais:", @@ -1936,6 +1945,7 @@ "Text Splitter": "Divisor de Texto", "Text-to-Speech": "Texto-para-Fala", "Text-to-Speech Engine": "Motor de Texto para Fala", + "Th_day_of_week": "Qui", "Thanks for your feedback!": "Obrigado pelo seu comentário!", "The Application Account DN you bind with for search": "O DN (Distinguished Name) da Conta de Aplicação com a qual você se conecta para pesquisa.", "The base to search for users": "Base para pesquisar usuários.", @@ -1991,7 +2001,7 @@ "Tika": "Tika", "Tika Server URL required.": "URL do servidor Tika necessária.", "Tiktoken": "Tiktoken", - "Time": "", + "Time": "Tempo", "Time & Calculation": "Tempo e Cálculo", "Timeout": "Tempo limite", "Title": "Título", @@ -2009,7 +2019,7 @@ "To select toolkits here, add them to the \"Tools\" workspace first.": "Para selecionar kits de ferramentas aqui, adicione-os ao espaço de trabalho \"Ferramentas\" primeiro.", "Toast notifications for new updates": "Notificações de alerta para novas atualizações", "Today": "Hoje", - "Today at": "", + "Today at": "Hoje em", "Today at {{LOCALIZED_TIME}}": "Hoje às {{LOCALIZED_TIME}}", "Toggle {{COUNT}} sources": "Alternar {{COUNT}} origens", "Toggle 1 source": "Alternar 1 origem", @@ -2049,6 +2059,7 @@ "TTS Model": "Modelo TTS", "TTS Settings": "Configurações TTS", "TTS Voice": "Voz TTS", + "Tu_day_of_week": "Ter", "Type": "Tipo", "Type here...": "Digite aqui...", "Type Hugging Face Resolve (Download) URL": "Digite o URL de download do Hugging Face", @@ -2148,9 +2159,10 @@ "Waiting for upload...": "Aguardando upload...", "Warning": "Aviso", "Warning:": "Aviso:", - "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", + "Warning: Enabling this will allow users to run scheduled prompts automatically.": "Aviso: Habilitar esta opção permitirá que os usuários executem solicitações agendadas automaticamente.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Aviso: Habilitar isso permitirá que os usuários façam upload de código arbitrário no servidor.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Aviso: a execução do Jupyter permite a execução de código arbitrário, o que representa sérios riscos de segurança. Prossiga com extremo cuidado.", + "We_day_of_week": "Qua", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "Motor de carregamento da Web", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "A WebUI fará requisições para \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "A WebUI fará requisições para \"{{url}}/api/chat\".", "WebUI will make requests to \"{{url}}/chat/completions\"": "A WebUI fará requisições para \"{{url}}/chat/completions\".", + "Weekly": "Semanal", "What are you trying to achieve?": "O que está tentando alcançar?", "What are you working on?": "No que está trabalhando?", "What is NOT shared:": "O que NÃO é compartilhado:", @@ -2183,7 +2196,7 @@ "Width": "Largura", "Wikipedia": "Wikipédia", "Won": "Ganhou", - "Working Directory": "", + "Working Directory": "Diretório de Trabalho", "Works together with top-k. A higher value (e.g., 0.95) will lead to more diverse text, while a lower value (e.g., 0.5) will generate more focused and conservative text.": "Funciona em conjunto com o top-k. Um valor mais alto (por exemplo, 0,95) resultará em um texto mais diverso, enquanto um valor mais baixo (por exemplo, 0,5) gerará um texto mais focado e conservador.", "Workspace": "Espaço de Trabalho", "Workspace Permissions": "Permissões do espaço de trabalho", diff --git a/src/lib/i18n/locales/pt-PT/translation.json b/src/lib/i18n/locales/pt-PT/translation.json index 7861982f6c..76c97853a2 100644 --- a/src/lib/i18n/locales/pt-PT/translation.json +++ b/src/lib/i18n/locales/pt-PT/translation.json @@ -455,6 +455,7 @@ "Create a new note": "Criar uma nova nota", "Create Account": "Criar Conta", "Create Admin Account": "Criar Conta de Administrador", + "Create and manage scheduled automations": "", "Create Channel": "Criar Canal", "Create Folder": "Criar Pasta", "Create Image": "Criar Imagem", @@ -480,6 +481,7 @@ "Custom Gender": "Género Personalizado", "Custom Parameter Name": "Nome do Parâmetro Personalizado", "Custom Parameter Value": "Valor do Parâmetro Personalizado", + "Daily": "", "Daily Messages": "Mensagens Diárias", "Danger Zone": "Zona de Perigo", "Dark": "Escuro", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Encaminha o token de acesso OAuth do utilizador do sistema para autenticação", "Forwards system user session credentials to authenticate": "Encaminha as credenciais de sessão do utilizador do sistema para autenticação", + "Fr_day_of_week": "", "Full Context Mode": "Modo de Contexto Completo", "Function": "Função", "Function Calling": "Chamada de Função", @@ -1044,6 +1047,7 @@ "History": "Histórico", "Home": "Início", "Host": "Host", + "Hourly": "", "Hourly Messages": "Mensagens Horárias", "How can I help you today?": "Como posso ajudá-lo hoje?", "How would you rate this response?": "Como você avaliaria esta resposta?", @@ -1266,10 +1270,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Chave API do Mistral OCR necessária.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Modelo", "Model '{{modelName}}' has been successfully downloaded.": "O modelo '{{modelName}}' foi descarregado com sucesso.", "Model '{{modelTag}}' is already in queue for downloading.": "O modelo '{{modelTag}}' já está na fila para descarregar.", - "Model {{modelId}} not found": "Modelo {{modelId}} não foi encontrado", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "O modelo {{modelName}} não é capaz de visão", "Model {{name}} is now {{status}}": "Modelo {{name}} agora é {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "Partilha de Modelos", "Mojeek": "Mojeek", "Mojeek Search API Key": "Chave API de Pesquisa Mojeek", + "Monthly": "", "More": "Mais", "More Concise": "Mais Conciso", "More options": "Mais opções", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "Chave da API Ollama Cloud", "Ollama Version": "Versão do Ollama", "On": "Ligado", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Apenas ativo quando a configuração \"Colar Texto Grande como Arquivo\" está ativada.", "Only active when the chat input is in focus and an LLM is generating a response.": "Apenas ativo quando a entrada do chat está em foco e um LLM está gerando uma resposta.", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Personalização", "Pin": "Fixar", + "Pin to Sidebar": "", "Pinned": "Fixado", "Pinned Messages": "Mensagens Fixadas", "Pinned Models": "Modelos Fixados", @@ -1675,6 +1682,7 @@ "Running": "A correr", "Running...": "A correr...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Corre tarefas de vetorização simultaneamente para acelerar o processamento. Desative se os limites de taxa se tornarem um problema.", + "Sa_day_of_week": "", "Save": "Guardar", "Save & Create": "Guardar e Criar", "Save & Update": "Guardar e Atualizar", @@ -1891,6 +1899,7 @@ "STT Model": "Modelo STT", "STT Settings": "Configurações STT", "Stylized PDF Export": "Exportação de PDF Estilizado", + "Su_day_of_week": "", "Submit question": "Enviar pergunta", "Submit suggestion": "Enviar sugestão", "Subtitle": "Legenda", @@ -1936,6 +1945,7 @@ "Text Splitter": "Divisor de Texto", "Text-to-Speech": "Texto para Fala", "Text-to-Speech Engine": "Motor de Texto para Fala", + "Th_day_of_week": "", "Thanks for your feedback!": "Obrigado pelo seu feedback!", "The Application Account DN you bind with for search": "A DN de Conta de Aplicação que você vincula para pesquisa", "The base to search for users": "A base para pesquisar utilizadores", @@ -2049,6 +2059,7 @@ "TTS Model": "Modelo TTS", "TTS Settings": "Configurações TTS", "TTS Voice": "Voz TTS", + "Tu_day_of_week": "", "Type": "Tipo", "Type here...": "Escreva aqui...", "Type Hugging Face Resolve (Download) URL": "Escreva o URL do Hugging Face Resolve (Descarregar)", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Aviso: Ativar isso permitirá que os utilizadores carreguem código arbitrário no servidor.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Aviso: A execução do Jupyter permite a execução de código arbitrário, representando riscos de segurança graves - prossiga com extrema cautela.", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "Motor de Carregamento Web", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "O WebUI fará solicitações para \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "O WebUI fará solicitações para \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "O WebUI fará solicitações para \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "O que você está tentando alcançar?", "What are you working on?": "Em que você está trabalhando?", "What is NOT shared:": "O que NÃO é compartilhado:", diff --git a/src/lib/i18n/locales/ro-RO/translation.json b/src/lib/i18n/locales/ro-RO/translation.json index 49e4e7dba2..83ffb71fb9 100644 --- a/src/lib/i18n/locales/ro-RO/translation.json +++ b/src/lib/i18n/locales/ro-RO/translation.json @@ -455,6 +455,7 @@ "Create a new note": "", "Create Account": "Creează Cont", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "Creează canal", "Create Folder": "", "Create Image": "", @@ -480,6 +481,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Întunecat", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "Funcție", "Function Calling": "", @@ -1044,6 +1047,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Cum te pot ajuta astăzi?", "How would you rate this response?": "", @@ -1266,10 +1270,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "Modelul '{{modelName}}' a fost descărcat cu succes.", "Model '{{modelTag}}' is already in queue for downloading.": "Modelul '{{modelTag}}' este deja în coada de descărcare.", - "Model {{modelId}} not found": "Modelul {{modelId}} nu a fost găsit", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Modelul {{modelName}} nu are capacități de viziune", "Model {{name}} is now {{status}}": "Modelul {{name}} este acum {{status}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "Mai multe", "More Concise": "", "More options": "", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Versiune Ollama", "On": "Activat", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Personalizare", "Pin": "Fixează", + "Pin to Sidebar": "", "Pinned": "Fixat", "Pinned Messages": "", "Pinned Models": "", @@ -1675,6 +1682,7 @@ "Running": "Rulează", "Running...": "Rulează...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Salvează", "Save & Create": "Salvează & Creează", "Save & Update": "Salvează & Actualizează", @@ -1891,6 +1899,7 @@ "STT Model": "Model STT", "STT Settings": "Setări STT", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1936,6 +1945,7 @@ "Text Splitter": "Divizor de Text", "Text-to-Speech": "", "Text-to-Speech Engine": "Motor de Conversie a Textului în Vorbire", + "Th_day_of_week": "", "Thanks for your feedback!": "Mulțumim pentru feedback!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2049,6 +2059,7 @@ "TTS Model": "Model TTS", "TTS Settings": "Setări TTS", "TTS Voice": "Voce TTS", + "Tu_day_of_week": "", "Type": "Tip", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Introduceți URL-ul de Rezolvare (Descărcare) Hugging Face", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "API Web", "Web Loader Engine": "", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ru-RU/translation.json b/src/lib/i18n/locales/ru-RU/translation.json index ebb4e76654..6c62e7a09d 100644 --- a/src/lib/i18n/locales/ru-RU/translation.json +++ b/src/lib/i18n/locales/ru-RU/translation.json @@ -456,6 +456,7 @@ "Create a new note": "Создать новую заметку", "Create Account": "Создать аккаунт", "Create Admin Account": "Создать аккаунт Администратора", + "Create and manage scheduled automations": "", "Create Channel": "Создать канал", "Create Folder": "Создать папку", "Create Image": "Создать изображение", @@ -481,6 +482,7 @@ "Custom Gender": "Другой пол", "Custom Parameter Name": "Название пользовательского параметра", "Custom Parameter Value": "Значение пользовательского параметра", + "Daily": "", "Daily Messages": "Сообщений в день", "Danger Zone": "Опасная зона", "Dark": "Темная", @@ -971,6 +973,7 @@ "Forward": "Вперёд", "Forwards system user OAuth access token to authenticate": "Передаёт токен доступа OAuth системного пользователя для аутентификации", "Forwards system user session credentials to authenticate": "Перенаправляет учетные данные сеанса системного пользователя для проверки подлинности", + "Fr_day_of_week": "", "Full Context Mode": "Режим полного контекста", "Function": "Функция", "Function Calling": "Вызов функции", @@ -1045,6 +1048,7 @@ "History": "История", "Home": "Домой", "Host": "Хост", + "Hourly": "", "Hourly Messages": "Сообщений в час", "How can I help you today?": "Чем я могу помочь вам сегодня?", "How would you rate this response?": "Как бы вы оценили этот ответ?", @@ -1267,10 +1271,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Требуется API ключ Mistral OCR.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "Модель", "Model '{{modelName}}' has been successfully downloaded.": "Модель '{{modelName}}' успешно загружена.", "Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' уже находится в очереди на загрузку.", - "Model {{modelId}} not found": "Модель {{modelId}} не найдена", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Модель {{modelName}} не поддерживает зрение", "Model {{name}} is now {{status}}": "Модель {{name}} теперь {{status}}", @@ -1311,6 +1315,7 @@ "Models Sharing": "Общий доступ к моделям", "Mojeek": "Mojeek", "Mojeek Search API Key": "Ключ API для поиска Mojeek", + "Monthly": "", "More": "Больше", "More Concise": "Более кратко", "More options": "Больше опций", @@ -1431,6 +1436,7 @@ "Ollama Cloud API Key": "API-ключ Ollama Cloud", "Ollama Version": "Версия Ollama", "On": "Включено", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "Активно только при включённой настройке «Вставлять большой текст как файл».", "Only active when the chat input is in focus and an LLM is generating a response.": "Активно только при фокусе на поле ввода и генерации ответа.", @@ -1515,6 +1521,7 @@ "Persistent": "Постоянный", "Personalization": "Персонализация", "Pin": "Закрепить", + "Pin to Sidebar": "", "Pinned": "Закреплено", "Pinned Messages": "Закреплённые сообщения", "Pinned Models": "Закреплённые модели", @@ -1677,6 +1684,7 @@ "Running": "Выполняется", "Running...": "Выполняется...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "Параллельное выполнение задач эмбеддингов для ускорения. Отключите при проблемах с лимитами запросов.", + "Sa_day_of_week": "", "Save": "Сохранить", "Save & Create": "Сохранить и создать", "Save & Update": "Сохранить и обновить", @@ -1893,6 +1901,7 @@ "STT Model": "Модель распознавания речи", "STT Settings": "Настройки распознавания речи", "Stylized PDF Export": "Стилизованный экспорт в формате PDF", + "Su_day_of_week": "", "Submit question": "Отправить вопрос", "Submit suggestion": "Отправить предложение", "Subtitle": "Подзаголовок", @@ -1938,6 +1947,7 @@ "Text Splitter": "Разделитель текста", "Text-to-Speech": "Текст в речь", "Text-to-Speech Engine": "Система синтеза речи", + "Th_day_of_week": "", "Thanks for your feedback!": "Спасибо за вашу обратную связь!", "The Application Account DN you bind with for search": "Логин учетной записи приложения, к которому вы привязываетесь для поиска", "The base to search for users": "База для поиска пользователей", @@ -2051,6 +2061,7 @@ "TTS Model": "Модель TTS", "TTS Settings": "Настройки TTS", "TTS Voice": "Голос TTS", + "Tu_day_of_week": "", "Type": "Тип", "Type here...": "Введите текст...", "Type Hugging Face Resolve (Download) URL": "Введите URL-адрес Hugging Face Resolve (загрузки)", @@ -2153,6 +2164,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Предупреждение. Включение этого параметра позволит пользователям загружать произвольный код на сервер.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Предупреждение: Выполнение Jupyter позволяет выполнять произвольный код, что создает серьезные угрозы безопасности — действуйте с особой осторожностью.", + "We_day_of_week": "", "Web": "Веб", "Web API": "Веб API", "Web Loader Engine": "Движок веб-загрузчика", @@ -2169,6 +2181,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI будет отправлять запросы к \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI будет отправлять запросы к \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI будет отправлять запросы к \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Чего вы пытаетесь достичь?", "What are you working on?": "Над чем вы работаете?", "What is NOT shared:": "Что НЕ передаётся:", diff --git a/src/lib/i18n/locales/sk-SK/translation.json b/src/lib/i18n/locales/sk-SK/translation.json index a828c7fb3b..cfb4343e49 100644 --- a/src/lib/i18n/locales/sk-SK/translation.json +++ b/src/lib/i18n/locales/sk-SK/translation.json @@ -456,6 +456,7 @@ "Create a new note": "Vytvoriť novú poznámku", "Create Account": "Vytvoriť účet", "Create Admin Account": "Vytvoriť admin účet", + "Create and manage scheduled automations": "", "Create Channel": "Vytvoriť kanál", "Create Folder": "Vytvoriť priečinok", "Create Image": "Vytvoriť obrázok", @@ -481,6 +482,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "Nebezpečná zóna", "Dark": "Tmavý", @@ -971,6 +973,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "Funkcia", "Function Calling": "", @@ -1045,6 +1048,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Ako vám môžem dnes pomôcť?", "How would you rate this response?": "", @@ -1267,10 +1271,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "Model „{{modelName}}“ bol úspešne stiahnutý.", "Model '{{modelTag}}' is already in queue for downloading.": "Model '{{modelTag}}' je už zaradený do fronty na sťahovanie.", - "Model {{modelId}} not found": "Model {{modelId}} nebol nájdený", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} nie je schopný spracovávať vizuálne údaje.", "Model {{name}} is now {{status}}": "Model {{name}} je teraz {{status}}.", @@ -1311,6 +1315,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "Viac", "More Concise": "", "More options": "", @@ -1431,6 +1436,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Verzia Ollama", "On": "Zapnuté", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1515,6 +1521,7 @@ "Persistent": "", "Personalization": "Personalizácia", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1677,6 +1684,7 @@ "Running": "Spúšťanie", "Running...": "Spúšťanie...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Uložiť", "Save & Create": "Uložiť a Vytvoriť", "Save & Update": "Uložiť a aktualizovať", @@ -1893,6 +1901,7 @@ "STT Model": "Model rozpoznávania reči na text (STT)", "STT Settings": "Nastavenia STT (Rozpoznávanie reči)", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1938,6 +1947,7 @@ "Text Splitter": "Rozdeľovač textu", "Text-to-Speech": "", "Text-to-Speech Engine": "Stroj na prevod textu na reč", + "Th_day_of_week": "", "Thanks for your feedback!": "Ďakujeme za vašu spätnú väzbu!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2051,6 +2061,7 @@ "TTS Model": "Model prevodu textu na reč (TTS)", "TTS Settings": "Nastavenia TTS (Text-to-Speech)", "TTS Voice": "TTS hlas", + "Tu_day_of_week": "", "Type": "Napíšte", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Zadajte URL na úspešné stiahnutie z Hugging Face.", @@ -2153,6 +2164,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "Webové API", "Web Loader Engine": "", @@ -2169,6 +2181,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "Čo sa snažíte dosiahnuť?", "What are you working on?": "Na čom pracujete?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/sr-RS/translation.json b/src/lib/i18n/locales/sr-RS/translation.json index ddfe1d46fc..7e1b205850 100644 --- a/src/lib/i18n/locales/sr-RS/translation.json +++ b/src/lib/i18n/locales/sr-RS/translation.json @@ -455,6 +455,7 @@ "Create a new note": "", "Create Account": "Направи налог", "Create Admin Account": "Направи админ налог", + "Create and manage scheduled automations": "", "Create Channel": "Направи канал", "Create Folder": "", "Create Image": "", @@ -480,6 +481,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Тамна", @@ -970,6 +972,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1044,6 +1047,7 @@ "History": "", "Home": "", "Host": "Домаћин", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Како могу да вам помогнем данас?", "How would you rate this response?": "", @@ -1266,10 +1270,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Модел", "Model '{{modelName}}' has been successfully downloaded.": "Модел „{{modelName}}“ је успешно преузет.", "Model '{{modelTag}}' is already in queue for downloading.": "Модел „{{modelTag}}“ је већ у реду за преузимање.", - "Model {{modelId}} not found": "Модел {{modelId}} није пронађен", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Модел {{моделНаме}} није способан за вид", "Model {{name}} is now {{status}}": "Модел {{наме}} је сада {{статус}}", @@ -1310,6 +1314,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "Више", "More Concise": "", "More options": "", @@ -1430,6 +1435,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Издање Ollama-е", "On": "Укључено", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1514,6 +1520,7 @@ "Persistent": "", "Personalization": "Прилагођавање", "Pin": "Закачи", + "Pin to Sidebar": "", "Pinned": "Закачено", "Pinned Messages": "", "Pinned Models": "", @@ -1675,6 +1682,7 @@ "Running": "Покрећем", "Running...": "Покрећем...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Сачувај", "Save & Create": "Сачувај и направи", "Save & Update": "Сачувај и ажурирај", @@ -1891,6 +1899,7 @@ "STT Model": "STT модел", "STT Settings": "STT подешавања", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1936,6 +1945,7 @@ "Text Splitter": "Раздвајач текста", "Text-to-Speech": "", "Text-to-Speech Engine": "Мотор за текст у говор", + "Th_day_of_week": "", "Thanks for your feedback!": "Хвала на вашем коментару!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2049,6 +2059,7 @@ "TTS Model": "TTS модел", "TTS Settings": "TTS подешавања", "TTS Voice": "TTS глас", + "Tu_day_of_week": "", "Type": "Тип", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Унесите Hugging Face Resolve (Download) адресу", @@ -2151,6 +2162,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Веб", "Web API": "Веб АПИ", "Web Loader Engine": "", @@ -2167,6 +2179,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/sv-SE/translation.json b/src/lib/i18n/locales/sv-SE/translation.json index d8b0674e34..e097438fb6 100644 --- a/src/lib/i18n/locales/sv-SE/translation.json +++ b/src/lib/i18n/locales/sv-SE/translation.json @@ -454,6 +454,7 @@ "Create a new note": "Skapa en ny anteckning", "Create Account": "Skapa konto", "Create Admin Account": "Skapa administratörskonto", + "Create and manage scheduled automations": "", "Create Channel": "Skapa kanal", "Create Folder": "Skapa mapp", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "Anpassat parameternamn", "Custom Parameter Value": "Anpassat parametervärde", + "Daily": "", "Daily Messages": "", "Danger Zone": "Fara", "Dark": "Mörk", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Vidarebefordrar systemanvändarsessionens autentiseringsuppgifter för att autentisera", + "Fr_day_of_week": "", "Full Context Mode": "Fullständigt kontextläge", "Function": "Funktion", "Function Calling": "Funktionsanrop", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Hem", "Host": "Värd", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Hur kan jag hjälpa dig idag?", "How would you rate this response?": "Hur skulle du betygsätta detta svar?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR API-nyckel krävs.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Modell", "Model '{{modelName}}' has been successfully downloaded.": "Modellen '{{modelName}}' har laddats ner.", "Model '{{modelTag}}' is already in queue for downloading.": "Modellen '{{modelTag}}' är redan i kö för nedladdning.", - "Model {{modelId}} not found": "Modell {{modelId}} hittades inte", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Modellen {{modelName}} har inte stöd för vision/syn", "Model {{name}} is now {{status}}": "Modellen {{name}} är nu {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Mojeek Sök API-nyckel", + "Monthly": "", "More": "Mer", "More Concise": "Mer kortfattat", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama-version", "On": "På", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Personalisering", "Pin": "Fäst", + "Pin to Sidebar": "", "Pinned": "Fäst", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Kör", "Running...": "Kör...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Spara", "Save & Create": "Spara och skapa", "Save & Update": "Spara och uppdatera", @@ -1889,6 +1897,7 @@ "STT Model": "Tal-till-text-modell", "STT Settings": "Tal-till-text-inställningar", "Stylized PDF Export": "Stiliserad PDF-export", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Textdelare", "Text-to-Speech": "Text-till-tal", "Text-to-Speech Engine": "Text-till-tal-motor", + "Th_day_of_week": "", "Thanks for your feedback!": "Tack för din feedback!", "The Application Account DN you bind with for search": "Applikationskontots DN du binder med för sökning", "The base to search for users": "Basen för att söka efter användare", @@ -2047,6 +2057,7 @@ "TTS Model": "Text-till-tal-modell", "TTS Settings": "Text-till-tal-inställningar", "TTS Voice": "Text-till-tal-röst", + "Tu_day_of_week": "", "Type": "Typ", "Type here...": "Skriv här...", "Type Hugging Face Resolve (Download) URL": "Ange Hugging Face Resolve (Download) URL", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Varning för detta: Om du aktiverar detta kan användare ladda upp godtycklig kod på servern.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Varning: Jupyter-exekvering möjliggör godtycklig kodkörning, vilket innebär allvarliga säkerhetsrisker - fortsätt med extrem försiktighet", + "We_day_of_week": "", "Web": "Webb", "Web API": "Webb-API", "Web Loader Engine": "Webbladdarmotor", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI kommer att göra förfrågningar till \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI kommer att göra förfrågningar till \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI kommer att göra förfrågningar till \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Vad försöker du uppnå?", "What are you working on?": "Var arbetar du med?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ta-IN/translation.json b/src/lib/i18n/locales/ta-IN/translation.json index d4b413514a..4f773125d6 100644 --- a/src/lib/i18n/locales/ta-IN/translation.json +++ b/src/lib/i18n/locales/ta-IN/translation.json @@ -454,6 +454,7 @@ "Create a new note": "புதிய குறிப்பை உருவாக்கவும்", "Create Account": "கணக்கை உருவாக்கவும்", "Create Admin Account": "நிர்வாகி கணக்கை உருவாக்கவும்", + "Create and manage scheduled automations": "", "Create Channel": "சேனலை உருவாக்கவும்", "Create Folder": "கோப்புறையை உருவாக்கவும்", "Create Image": "படத்தை உருவாக்கவும்", @@ -479,6 +480,7 @@ "Custom Gender": "தனிப்பயன் பாலினம்", "Custom Parameter Name": "தனிப்பயன் அளவுரு பெயர்", "Custom Parameter Value": "தனிப்பயன் அளவுரு மதிப்பு", + "Daily": "", "Daily Messages": "தினசரி செய்திகள்", "Danger Zone": "ஆபத்து மண்டலம்", "Dark": "இருண்ட", @@ -969,6 +971,7 @@ "Forward": "முன்னோக்கி", "Forwards system user OAuth access token to authenticate": "அங்கீகரிக்க கணினி பயனர் OAuth அணுகல் டோக்கனை அனுப்புகிறது", "Forwards system user session credentials to authenticate": "அங்கீகரிக்க கணினி பயனர் அமர்வு நற்சான்றிதழ்களை அனுப்புகிறது", + "Fr_day_of_week": "", "Full Context Mode": "முழு சூழல் பயன்முறை", "Function": "செயல்பாடு", "Function Calling": "செயல்பாடு அழைப்பு", @@ -1043,6 +1046,7 @@ "History": "வரலாறு", "Home": "வீடு", "Host": "புரவலன்", + "Hourly": "", "Hourly Messages": "மணிநேர செய்திகள்", "How can I help you today?": "இன்று நான் உங்களுக்கு எப்படி உதவலாம்?", "How would you rate this response?": "இந்த பதிலை எப்படி மதிப்பிடுவீர்கள்?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "மிஸ்ட்ரல் OCR", "Mistral OCR API Key required.": "மிஸ்ட்ரல் OCR API விசை தேவை.", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "மாதிரி", "Model '{{modelName}}' has been successfully downloaded.": "மாடல் '{{modelName}}' வெற்றிகரமாகப் பதிவிறக்கப்பட்டது.", "Model '{{modelTag}}' is already in queue for downloading.": "மாடல் '{{modelTag}}' ஏற்கனவே பதிவிறக்குவதற்கு வரிசையில் உள்ளது.", - "Model {{modelId}} not found": "மாதிரி {{modelId}} கிடைக்கவில்லை", "Model {{modelName}} deleted successfully": "மாதிரி {{modelName}} வெற்றிகரமாக நீக்கப்பட்டது", "Model {{modelName}} is not vision capable": "மாதிரி {{modelName}} பார்வை திறன் இல்லை", "Model {{name}} is now {{status}}": "மாடல் {{name}} இப்போது {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "மாதிரிகள் பகிர்வு", "Mojeek": "மொஜீக்", "Mojeek Search API Key": "Mojeek தேடல் API விசை", + "Monthly": "", "More": "மேலும்", "More Concise": "மேலும் சுருக்கமானது", "More options": "மேலும் விருப்பங்கள்", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "Ollama கிளவுட் API விசை", "Ollama Version": "Ollama பதிப்பு", "On": "அன்று", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "\"பெரிய உரையை கோப்பாக ஒட்டவும்\" அமைப்பு மாறியிருந்தால் மட்டுமே செயலில் இருக்கும்.", "Only active when the chat input is in focus and an LLM is generating a response.": "அரட்டை உள்ளீடு ஃபோகஸ் மற்றும் LLM பதிலை உருவாக்கும் போது மட்டுமே செயலில் இருக்கும்.", @@ -1513,6 +1519,7 @@ "Persistent": "பிடிவாதமான", "Personalization": "தனிப்பயனாக்கம்", "Pin": "பின்", + "Pin to Sidebar": "", "Pinned": "நிலைநிறுத்தப்பட்டவை", "Pinned Messages": "பின் செய்யப்பட்ட செய்திகள்", "Pinned Models": "பின் செய்யப்பட்ட மாதிரிகள்", @@ -1673,6 +1680,7 @@ "Running": "ஓடுகிறது", "Running...": "இயங்குகிறது...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "செயலாக்கத்தை விரைவுபடுத்த ஒரே நேரத்தில் உட்பொதிக்கும் பணிகளை இயக்குகிறது. கட்டண வரம்புகள் சிக்கலாக இருந்தால் அணைக்கவும்.", + "Sa_day_of_week": "", "Save": "சேமி", "Save & Create": "சேமித்து உருவாக்கவும்", "Save & Update": "சேமி & புதுப்பிக்கவும்", @@ -1889,6 +1897,7 @@ "STT Model": "STT மாதிரி", "STT Settings": "STT அமைப்புகள்", "Stylized PDF Export": "பகட்டான PDF ஏற்றுமதி", + "Su_day_of_week": "", "Submit question": "கேள்வியை சமர்ப்பிக்கவும்", "Submit suggestion": "பரிந்துரையைச் சமர்ப்பிக்கவும்", "Subtitle": "வசனம்", @@ -1934,6 +1943,7 @@ "Text Splitter": "உரை பிரிப்பான்", "Text-to-Speech": "உரையிலிருந்து பேச்சு", "Text-to-Speech Engine": "உரையிலிருந்து பேச்சு இயந்திரம்", + "Th_day_of_week": "", "Thanks for your feedback!": "உங்கள் கருத்துக்கு நன்றி!", "The Application Account DN you bind with for search": "தேடலுக்காக நீங்கள் இணைக்கும் பயன்பாட்டுக் கணக்கு DN", "The base to search for users": "பயனர்களைத் தேடுவதற்கான அடிப்படை", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS மாதிரி", "TTS Settings": "TTS அமைப்புகள்", "TTS Voice": "TTS குரல்", + "Tu_day_of_week": "", "Type": "வகை", "Type here...": "இங்கே தட்டச்சு செய்யவும்...", "Type Hugging Face Resolve (Download) URL": "கட்டிப்பிடிக்கும் முகம் தீர்வு (பதிவிறக்கம்) URL", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "எச்சரிக்கை: இதை இயக்கினால் பயனர்கள் திட்டமிட்ட வினாக்களை தானாக இயக்க அனுமதிக்கும்.", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "எச்சரிக்கை: இதை இயக்குவது பயனர்கள் சர்வரில் தன்னிச்சையான குறியீட்டைப் பதிவேற்ற அனுமதிக்கும்.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "எச்சரிக்கை: வியாழன் இயக்கமானது தன்னிச்சையான குறியீடு செயல்படுத்தலை செயல்படுத்துகிறது, இது கடுமையான பாதுகாப்பு அபாயங்களை ஏற்படுத்துகிறது-அதிக எச்சரிக்கையுடன் தொடரவும்.", + "We_day_of_week": "", "Web": "வலை", "Web API": "வலை API", "Web Loader Engine": "வலை ஏற்றி இயந்திரம்", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI \"{{url}}\" க்கு கோரிக்கைகளை வைக்கும்", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI \"{{url}}/api/chat\" க்கு கோரிக்கைகளை வைக்கும்", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI \"{{url}}/chat/completions\" க்கு கோரிக்கைகளை வைக்கும்", + "Weekly": "", "What are you trying to achieve?": "நீங்கள் எதை அடைய முயற்சிக்கிறீர்கள்?", "What are you working on?": "நீங்கள் என்ன வேலை செய்கிறீர்கள்?", "What is NOT shared:": "NOT என்பது என்ன பகிரப்பட்டது:", diff --git a/src/lib/i18n/locales/th-TH/translation.json b/src/lib/i18n/locales/th-TH/translation.json index 3282b044fe..d337319a8b 100644 --- a/src/lib/i18n/locales/th-TH/translation.json +++ b/src/lib/i18n/locales/th-TH/translation.json @@ -453,6 +453,7 @@ "Create a new note": "สร้างบันทึกใหม่", "Create Account": "สร้างบัญชี", "Create Admin Account": "สร้างบัญชีผู้ดูแลระบบ", + "Create and manage scheduled automations": "", "Create Channel": "สร้างช่องทาง", "Create Folder": "สร้างโฟลเดอร์", "Create Image": "สร้างรูปภาพ", @@ -478,6 +479,7 @@ "Custom Gender": "", "Custom Parameter Name": "ชื่อพารามิเตอร์แบบกำหนดเอง", "Custom Parameter Value": "ค่าพารามิเตอร์กำหนดเอง", + "Daily": "", "Daily Messages": "", "Danger Zone": "เขตอันตราย", "Dark": "มืด", @@ -968,6 +970,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "ส่งต่อโทเค็นการเข้าถึง OAuth ของผู้ใช้ระบบเพื่อยืนยันตัวตน", "Forwards system user session credentials to authenticate": "ส่งต่อข้อมูลรับรอง Session ของผู้ใช้ระบบเพื่อใช้ยืนยันตัวตน", + "Fr_day_of_week": "", "Full Context Mode": "โหมดบริบทเต็ม", "Function": "ฟังก์ชัน", "Function Calling": "การเรียกใช้ฟังก์ชัน", @@ -1042,6 +1045,7 @@ "History": "", "Home": "หน้าแรก", "Host": "โฮสต์", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "วันนี้ฉันจะช่วยคุณได้อย่างไร?", "How would you rate this response?": "คุณจะให้คะแนนคำตอบนี้อย่างไร?", @@ -1264,10 +1268,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "จำเป็นต้องมี Mistral OCR API Key", "MistralAI": "MistralAI", + "Mo_day_of_week": "", "Model": "โมเดล", "Model '{{modelName}}' has been successfully downloaded.": "โมเดล '{{modelName}}' ถูกดาวน์โหลดเรียบร้อยแล้ว", "Model '{{modelTag}}' is already in queue for downloading.": "โมเดล '{{modelTag}}' อยู่ในคิวสำหรับการดาวน์โหลดแล้ว", - "Model {{modelId}} not found": "ไม่พบโมเดล {{modelId}}", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "โมเดล {{modelName}} ไม่รองรับฟีเจอร์ Vision", "Model {{name}} is now {{status}}": "โมเดล {{name}} ขณะนี้ {{status}}", @@ -1308,6 +1312,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "API Key สำหรับ Mojeek Search", + "Monthly": "", "More": "เพิ่มเติม", "More Concise": "กระชับมากขึ้น", "More options": "", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "API Key ของ Ollama Cloud", "Ollama Version": "เวอร์ชัน Ollama", "On": "เปิด", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "จะทำงานเฉพาะเมื่อเปิดการตั้งค่า \"วางข้อความขนาดใหญ่เป็นไฟล์\"", "Only active when the chat input is in focus and an LLM is generating a response.": "ทำงานเฉพาะเมื่อช่องป้อนข้อความแชทถูกโฟกัสอยู่ และ LLM กำลังสร้างคำตอบ", @@ -1512,6 +1518,7 @@ "Persistent": "", "Personalization": "การปรับแต่ง", "Pin": "ปักหมุด", + "Pin to Sidebar": "", "Pinned": "ปักหมุดแล้ว", "Pinned Messages": "", "Pinned Models": "", @@ -1671,6 +1678,7 @@ "Running": "กำลังทำงาน", "Running...": "กำลังทำงาน...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "บันทึก", "Save & Create": "บันทึกและสร้าง", "Save & Update": "บันทึกและอัปเดต", @@ -1887,6 +1895,7 @@ "STT Model": "โมเดลแปลงเสียงเป็นข้อความ", "STT Settings": "การตั้งค่าแปลงเสียงเป็นข้อความ", "Stylized PDF Export": "ส่งออก PDF แบบมีสไตล์", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1932,6 +1941,7 @@ "Text Splitter": "ตัวแบ่งข้อความ", "Text-to-Speech": "แปลงข้อความเป็นเสียง", "Text-to-Speech Engine": "เครื่องมือแปลงข้อความเป็นเสียง", + "Th_day_of_week": "", "Thanks for your feedback!": "ขอบคุณสำหรับคำติชมของคุณ!", "The Application Account DN you bind with for search": "DN ของบัญชีแอปพลิเคชันที่คุณใช้ bind เพื่อค้นหา", "The base to search for users": "ฐานสำหรับค้นหาผู้ใช้", @@ -2045,6 +2055,7 @@ "TTS Model": "โมเดล TTS", "TTS Settings": "การตั้งค่า TTS", "TTS Voice": "เสียง TTS", + "Tu_day_of_week": "", "Type": "ประเภท", "Type here...": "พิมพ์ที่นี่...", "Type Hugging Face Resolve (Download) URL": "พิมพ์ URL Hugging Face Resolve (ดาวน์โหลด)", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "คำเตือน: การเปิดใช้งานตัวเลือกนี้จะอนุญาตให้ผู้ใช้อัปโหลดโค้ดใดๆ บนเซิร์ฟเวอร์", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "คำเตือน: การรัน Jupyter อนุญาตให้รันโค้ดใดๆ ก็ได้ ซึ่งก่อให้เกิดความเสี่ยงด้านความปลอดภัยอย่างร้ายแรง—โปรดดำเนินการด้วยความระมัดระวังอย่างยิ่ง", + "We_day_of_week": "", "Web": "เว็บ", "Web API": "เว็บ API", "Web Loader Engine": "เอนจินโหลดเว็บ", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI จะส่งคำขอไปยัง \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI จะส่งคำขอไปที่ \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI จะส่งคำขอไปยัง \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "คุณพยายามจะทำอะไร?", "What are you working on?": "คุณกำลังทำอะไรอยู่?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/tk-TM/translation.json b/src/lib/i18n/locales/tk-TM/translation.json index a39d4984cc..cb777190b4 100644 --- a/src/lib/i18n/locales/tk-TM/translation.json +++ b/src/lib/i18n/locales/tk-TM/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Hasap döret", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "Garaňky", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Baş Sahypa", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "", "How would you rate this response?": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "", "Model '{{modelTag}}' is already in queue for downloading.": "", - "Model {{modelId}} not found": "", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "", "Model {{name}} is now {{status}}": "", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "Has köp", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "", "On": "Işjeň", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "", "Pin": "", + "Pin to Sidebar": "", "Pinned": "", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "", "Running...": "Işleýär...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Sakla", "Save & Create": "", "Save & Update": "", @@ -1889,6 +1897,7 @@ "STT Model": "", "STT Settings": "", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "", "Text-to-Speech": "", "Text-to-Speech Engine": "", + "Th_day_of_week": "", "Thanks for your feedback!": "", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "", "TTS Settings": "", "TTS Voice": "", + "Tu_day_of_week": "", "Type": "Typ", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "", "Web API": "", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/tr-TR/translation.json b/src/lib/i18n/locales/tr-TR/translation.json index 68f5afaba4..62350354d5 100644 --- a/src/lib/i18n/locales/tr-TR/translation.json +++ b/src/lib/i18n/locales/tr-TR/translation.json @@ -454,6 +454,7 @@ "Create a new note": "Yeni bir not oluştur", "Create Account": "Hesap Oluştur", "Create Admin Account": "Yönetici Hesabı Oluştur", + "Create and manage scheduled automations": "", "Create Channel": "Kanal Oluştur", "Create Folder": "Klasör Oluştur", "Create Image": "Görsel Oluştur", @@ -479,6 +480,7 @@ "Custom Gender": "Özel Cinsiyet", "Custom Parameter Name": "Özel Parametre Adı", "Custom Parameter Value": "Özel Parametre Değeri", + "Daily": "", "Daily Messages": "Günlük Mesajlar", "Danger Zone": "Tehlikeli Bölge", "Dark": "Koyu", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "Kimlik doğrulamak için sistem kullanıcı OAuth erişim belirtecini iletir", "Forwards system user session credentials to authenticate": "Kimlik doğrulamak için sistem kullanıcı oturum kimlik bilgilerini iletir", + "Fr_day_of_week": "", "Full Context Mode": "Tam Bağlam Modu", "Function": "Fonksiyon", "Function Calling": "Fonksiyon Çağırma", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Ana Sayfa", "Host": "Ana bilgisayar", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Bugün size nasıl yardımcı olabilirim?", "How would you rate this response?": "Bu yanıtı nasıl değerlendirirsiniz?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR API Anahtarı Gerekli", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "'{{modelName}}' başarıyla indirildi.", "Model '{{modelTag}}' is already in queue for downloading.": "'{{modelTag}}' zaten indirme sırasında.", - "Model {{modelId}} not found": "{{modelId}} bulunamadı", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} görüntü yeteneğine sahip değil", "Model {{name}} is now {{status}}": "{{name}} modeli artık {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "Model Paylaşımı", "Mojeek": "", "Mojeek Search API Key": "Mojeek Search API Anahtarı", + "Monthly": "", "More": "Daha Fazla", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama Sürümü", "On": "Açık", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Kişiselleştirme", "Pin": "Sabitle", + "Pin to Sidebar": "", "Pinned": "Sabitlenmiş", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Çalışıyor", "Running...": "Çalışıyor...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Kaydet", "Save & Create": "Kaydet ve Oluştur", "Save & Update": "Kaydet ve Güncelle", @@ -1889,6 +1897,7 @@ "STT Model": "STT Modeli", "STT Settings": "STT Ayarları", "Stylized PDF Export": "Biçimlendirilmiş PDF Dışa Aktarımı", + "Su_day_of_week": "", "Submit question": "Soru gönder", "Submit suggestion": "Öneri gönder", "Subtitle": "Altyazı", @@ -1934,6 +1943,7 @@ "Text Splitter": "Metin Bölücü", "Text-to-Speech": "Metinden Sese", "Text-to-Speech Engine": "Metinden Sese Motoru", + "Th_day_of_week": "", "Thanks for your feedback!": "Geri bildiriminiz için teşekkürler!", "The Application Account DN you bind with for search": "Arama için bağlandığınız Uygulama Hesap DN'si", "The base to search for users": "Kullanıcıları aramak için temel", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS Modeli", "TTS Settings": "TTS Ayarları", "TTS Voice": "TTS Sesi", + "Tu_day_of_week": "", "Type": "Tür", "Type here...": "Buraya yaz...", "Type Hugging Face Resolve (Download) URL": "HuggingFace Çözümleme (İndirme) URL'sini Yazın", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Uyarı: Bu etkinleştirildiğinde, kullanıcıların sunucuya rastgele kod yüklemesine izin verilecektir.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI, \"{{url}}/api/chat\" adresine istek yapacak", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI, \"{{url}}/chat/completions\" adresine istek yapacak", + "Weekly": "", "What are you trying to achieve?": "Ne yapmaya çalışıyorsunuz?", "What are you working on?": "Üzerinde çalıştığınız nedir?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ug-CN/translation.json b/src/lib/i18n/locales/ug-CN/translation.json index 5301df3b2e..b7fee9fb65 100644 --- a/src/lib/i18n/locales/ug-CN/translation.json +++ b/src/lib/i18n/locales/ug-CN/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "ھېساب قۇرۇش", "Create Admin Account": "باشقۇرغۇچى ھېساباتى قۇرۇش", + "Create and manage scheduled automations": "", "Create Channel": "قانال قۇرۇش", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "ئۆزلۈك پارامېتىر نامى", "Custom Parameter Value": "ئۆزلۈك پارامېتىر قىممىتى", + "Daily": "", "Daily Messages": "", "Danger Zone": "خەۋپلىك رايون", "Dark": "قاراڭغۇ", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "سىستېما ئىشلەتكۈچىسى ئۇچۇرلىرىنى دەلىللەشكە يوللايدۇ", + "Fr_day_of_week": "", "Full Context Mode": "تولۇق مەزمۇن ھالىتى", "Function": "فۇنكسىيە", "Function Calling": "فۇنكسىيە چاقىرىش", @@ -1043,6 +1046,7 @@ "History": "", "Home": "باش بەت", "Host": "مۇلازىمېتىر", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "بۈگۈن سىزگە قانداق ياردەم بېرەي؟", "How would you rate this response?": "بۇ ئىنكاسقا قانداق باھا بېرىسىز؟", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR API ئاچقۇچى زۆرۈر.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "مودېل", "Model '{{modelName}}' has been successfully downloaded.": "مودېل '{{modelName}}' مۇۋەپپەقىيەتلىك چۈشۈرۈلدى.", "Model '{{modelTag}}' is already in queue for downloading.": "مودېل '{{modelTag}}' ئاللىقاچان چۈشۈرۈش قاتارىغا قوشۇلغان.", - "Model {{modelId}} not found": "مودېل {{modelId}} تېپىلمىدى", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "مودېل {{modelName}} كۆرۈنۈش ئىقتىدارى يوق", "Model {{name}} is now {{status}}": "مودېل {{name}} ھازىر {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Mojeek ئىزدەش API ئاچقۇچى", + "Monthly": "", "More": "تېخىمۇ كۆپ", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama نەشرى", "On": "قوزغىتىلغان", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "شەخسىيلاشتۇرۇش", "Pin": "مۇقىملا", + "Pin to Sidebar": "", "Pinned": "مۇقىملاندى", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "ئىجرا قىلىنىۋاتىدۇ", "Running...": "ئىجرا قىلىنىۋاتىدۇ...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "ساقلاش", "Save & Create": "ساقلا ۋە قۇر", "Save & Update": "ساقلا ۋە يېڭىلا", @@ -1889,6 +1897,7 @@ "STT Model": "STT مودېلى", "STT Settings": "STT تەڭشەكلىرى", "Stylized PDF Export": "ئۇسلۇبلۇق PDF چىقىرىش", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "تېكست بۆلگۈچ", "Text-to-Speech": "تېكستتىن ئاۋازغا", "Text-to-Speech Engine": "تېكست ئاۋاز ماتورى", + "Th_day_of_week": "", "Thanks for your feedback!": "پىكىرىڭىزگە رەھمەت!", "The Application Account DN you bind with for search": "ئىزدەش ئۈچۈن باغلانغان قوللىنىشچان DN", "The base to search for users": "ئىشلەتكۈچى ئىزدەش ئاساسى", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS مودېلى", "TTS Settings": "TTS تەڭشەكلىرى", "TTS Voice": "TTS ئاۋازى", + "Tu_day_of_week": "", "Type": "تىپى", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Hugging Face چۈشۈرۈش URL كىرگۈزۈڭ", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "ئاگاھلاندۇرۇش: بۇ قوزغىتىلسا، ئىشلەتكۈچىلەر خالىغان كودنى مۇلازىمېتىرغا چىقىرىدۇ.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "ئاگاھلاندۇرۇش: Jupyter ئىجرا قىلىش خالىغان كود ئىجرا قىلىشقا يول قويىدۇ، بىخەتەرلىك خەۋپى يۇقىرى — ئىنتايىن دىققەت قىلىڭ.", + "We_day_of_week": "", "Web": "تور", "Web API": "تور API", "Web Loader Engine": "تور يۈكلىگۈچ ماتورى", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI \"{{url}}\" غا تەلەپ يوللايدۇ", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI \"{{url}}/api/chat\" غا تەلەپ يوللايدۇ", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI \"{{url}}/chat/completions\" غا تەلەپ يوللايدۇ", + "Weekly": "", "What are you trying to achieve?": "نېمىگە ئېرىشمىكچىسىز؟", "What are you working on?": "نېمە ئىش قىلىۋاتىسىز؟", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/uk-UA/translation.json b/src/lib/i18n/locales/uk-UA/translation.json index 4c2adc74c3..67ab638362 100644 --- a/src/lib/i18n/locales/uk-UA/translation.json +++ b/src/lib/i18n/locales/uk-UA/translation.json @@ -456,6 +456,7 @@ "Create a new note": "", "Create Account": "Створити обліковий запис", "Create Admin Account": "Створити обліковий запис адміністратора", + "Create and manage scheduled automations": "", "Create Channel": "Створити канал", "Create Folder": "", "Create Image": "", @@ -481,6 +482,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "Зона небезпеки", "Dark": "Темна", @@ -971,6 +973,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "Режим повного контексту", "Function": "Функція", "Function Calling": "Виклик функцій", @@ -1045,6 +1048,7 @@ "History": "", "Home": "Головна", "Host": "Хост", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Чим я можу допомогти вам сьогодні?", "How would you rate this response?": "Як би ви оцінили цю відповідь?", @@ -1267,10 +1271,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Модель", "Model '{{modelName}}' has been successfully downloaded.": "Модель '{{modelName}}' успішно завантажено.", "Model '{{modelTag}}' is already in queue for downloading.": "Модель '{{modelTag}}' вже знаходиться в черзі на завантаження.", - "Model {{modelId}} not found": "Модель {{modelId}} не знайдено", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Модель {{modelName}} не здатна бачити", "Model {{name}} is now {{status}}": "Модель {{name}} тепер має {{status}}", @@ -1311,6 +1315,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "API ключ для пошуку Mojeek", + "Monthly": "", "More": "Більше", "More Concise": "", "More options": "", @@ -1431,6 +1436,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Версія Ollama", "On": "Увімк", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1515,6 +1521,7 @@ "Persistent": "", "Personalization": "Персоналізація", "Pin": "Зачепити", + "Pin to Sidebar": "", "Pinned": "Зачеплено", "Pinned Messages": "", "Pinned Models": "", @@ -1677,6 +1684,7 @@ "Running": "Виконується", "Running...": "Виконується...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Зберегти", "Save & Create": "Зберегти та створити", "Save & Update": "Зберегти та оновити", @@ -1893,6 +1901,7 @@ "STT Model": "Модель STT ", "STT Settings": "Налаштування STT", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1938,6 +1947,7 @@ "Text Splitter": "Роздільник тексту", "Text-to-Speech": "", "Text-to-Speech Engine": "Система синтезу мови", + "Th_day_of_week": "", "Thanks for your feedback!": "Дякуємо за ваш відгук!", "The Application Account DN you bind with for search": "DN облікового запису застосунку, з яким ви здійснюєте прив'язку для пошуку", "The base to search for users": "База для пошуку користувачів", @@ -2051,6 +2061,7 @@ "TTS Model": "Модель TTS", "TTS Settings": "Налаштування TTS", "TTS Voice": "Голос TTS", + "Tu_day_of_week": "", "Type": "Тип", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Введіть URL ресурсу Hugging Face Resolve (завантаження)", @@ -2153,6 +2164,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Попередження: Увімкнення цього дозволить користувачам завантажувати довільний код на сервер.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Попередження: Виконання коду в Jupyter дозволяє виконувати任 будь-який код, що становить серйозні ризики для безпеки — дійте з крайньою обережністю.", + "We_day_of_week": "", "Web": "Веб", "Web API": "Веб-API", "Web Loader Engine": "", @@ -2169,6 +2181,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI надсилатиме запити до \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI надсилатиме запити до \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Чого ви прагнете досягти?", "What are you working on?": "Над чим ти працюєш?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/ur-PK/translation.json b/src/lib/i18n/locales/ur-PK/translation.json index 7a43ef0db5..8ac674cc4b 100644 --- a/src/lib/i18n/locales/ur-PK/translation.json +++ b/src/lib/i18n/locales/ur-PK/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "اکاؤنٹ بنائیں", "Create Admin Account": "", + "Create and manage scheduled automations": "", "Create Channel": "", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "", "Dark": "ڈارک", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "", + "Fr_day_of_week": "", "Full Context Mode": "", "Function": "فنکشن", "Function Calling": "", @@ -1043,6 +1046,7 @@ "History": "", "Home": "", "Host": "", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "میں آج آپ کی کس طرح مدد کر سکتا ہوں؟", "How would you rate this response?": "", @@ -1265,10 +1269,10 @@ "Mistral OCR": "", "Mistral OCR API Key required.": "", "MistralAI": "", + "Mo_day_of_week": "", "Model": "ماڈل", "Model '{{modelName}}' has been successfully downloaded.": "ماڈل '{{modelName}}' کامیابی سے ڈاؤن لوڈ ہو گیا ہے", "Model '{{modelTag}}' is already in queue for downloading.": "ماڈل '{{modelTag}}' پہلے ہی ڈاؤن لوڈ کے لیے قطار میں ہے", - "Model {{modelId}} not found": "ماڈل {{modelId}} نہیں ملا", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "ماڈل {{modelName}} بصری صلاحیت نہیں رکھتا", "Model {{name}} is now {{status}}": "ماڈل {{name}} اب {{status}} ہے", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "", + "Monthly": "", "More": "مزید", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "اولاما ورژن", "On": "چالو", + "Once": "", "OneDrive": "", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "شخصی ترتیبات", "Pin": "پن", + "Pin to Sidebar": "", "Pinned": "پن کیا گیا", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "چل رہا ہے", "Running...": "چل رہا ہے...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "محفوظ کریں", "Save & Create": "محفوظ کریں اور تخلیق کریں", "Save & Update": "محفوظ کریں اور اپ ڈیٹ کریں", @@ -1889,6 +1897,7 @@ "STT Model": "ایس ٹی ٹی ماڈل", "STT Settings": "ایس ٹی ٹی ترتیبات", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "متن تقسیم کنندہ", "Text-to-Speech": "", "Text-to-Speech Engine": "ٹیکسٹ ٹو اسپیچ انجن", + "Th_day_of_week": "", "Thanks for your feedback!": "آپ کی رائے کا شکریہ!", "The Application Account DN you bind with for search": "", "The base to search for users": "", @@ -2047,6 +2057,7 @@ "TTS Model": "ٹی ٹی ایس ماڈل", "TTS Settings": "ٹی ٹی ایس ترتیبات", "TTS Voice": "ٹی ٹی ایس آواز", + "Tu_day_of_week": "", "Type": "ٹائپ کریں", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "قسم ہگنگ فیس ری زولو (ڈاؤن لوڈ) یو آر ایل", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "", + "We_day_of_week": "", "Web": "ویب", "Web API": "ویب اے پی آئی", "Web Loader Engine": "", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "", "WebUI will make requests to \"{{url}}/api/chat\"": "", "WebUI will make requests to \"{{url}}/chat/completions\"": "", + "Weekly": "", "What are you trying to achieve?": "", "What are you working on?": "", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json b/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json index b2928870f7..3ffd9ab2fa 100644 --- a/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json +++ b/src/lib/i18n/locales/uz-Cyrl-UZ/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Ҳисоб яратиш", "Create Admin Account": "Администратор ҳисобини яратинг", + "Create and manage scheduled automations": "", "Create Channel": "Канал яратиш", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "Махсус параметр номи", "Custom Parameter Value": "Махсус параметр қиймати", + "Daily": "", "Daily Messages": "", "Danger Zone": "Хавфли зона", "Dark": "Қоронғи", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Аутентификация қилиш учун тизим фойдаланувчиси сеанси ҳисоб маълумотларини йўналтиради", + "Fr_day_of_week": "", "Full Context Mode": "Тўлиқ контекст режими", "Function": "Функция", "Function Calling": "Функцияни чақириш", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Уй", "Host": "Хост", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Бугун сизга қандай ёрдам бера оламан?", "How would you rate this response?": "Бу жавобни қандай баҳолайсиз?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral ОCР", "Mistral OCR API Key required.": "Mistral ОCР АПИ калити талаб қилинади.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Модел", "Model '{{modelName}}' has been successfully downloaded.": "“{{modelName}}” модели юклаб олинди.", "Model '{{modelTag}}' is already in queue for downloading.": "“{{modelTag}}” модели аллақачон юклаб олиш учун навбатда турибди.", - "Model {{modelId}} not found": "{{modelId}} модели топилмади", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "{{modelName}} модели кўриш қобилиятига эга эмас", "Model {{name}} is now {{status}}": "{{name}} модели энди {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Можеэк қидирув АПИ калити", + "Monthly": "", "More": "Кўпроқ", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama версияси", "On": "Ёниқ", + "Once": "", "OneDrive": "ОнеДриве", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Шахсийлаштириш", "Pin": "Пин", + "Pin to Sidebar": "", "Pinned": "Қадалган", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Ишлаётган", "Running...": "Ишлаётган...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Сақлаш", "Save & Create": "Сақлаш ва яратиш", "Save & Update": "Сақлаш ва янгилаш", @@ -1889,6 +1897,7 @@ "STT Model": "СТТ модели", "STT Settings": "СТТ созламалари", "Stylized PDF Export": "Услубий PDF экспорти", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Матн ажратувчи", "Text-to-Speech": "", "Text-to-Speech Engine": "Матнни нутққа айлантириш механизми", + "Th_day_of_week": "", "Thanks for your feedback!": "Фикр-мулоҳазангиз учун ташаккур!", "The Application Account DN you bind with for search": "Қидириш учун сиз боғланган илова ҳисоби ДН", "The base to search for users": "Фойдаланувчиларни қидириш учун асос", @@ -2047,6 +2057,7 @@ "TTS Model": "ТТС модели", "TTS Settings": "ТТС созламалари", "TTS Voice": "ТТС овози", + "Tu_day_of_week": "", "Type": "Тури", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (юклаб олиш) URL манзилини киритинг", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Огоҳлантириш: Буни ёқиш фойдаланувчиларга серверга ихтиёрий кодни юклаш имконини беради.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Огоҳлантириш: Жупйтер ижроси ўзбошимчалик билан код бажарилишини таъминлайди, бу хавфсизликка жиддий хавф туғдиради - жуда эҳтиёткорлик билан давом этинг.", + "We_day_of_week": "", "Web": "Веб", "Web API": "Web API", "Web Loader Engine": "Web Loader Engine", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI “{{url}}” манзилига сўров юборади", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI “{{url}}/api/chat” манзилига сўров юборади", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI \"{{url}}/chat/completions\" манзилига сўров юборади", + "Weekly": "", "What are you trying to achieve?": "Нимага эришмоқчисиз?", "What are you working on?": "Нима устида ишлаяпсиз?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/uz-Latn-Uz/translation.json b/src/lib/i18n/locales/uz-Latn-Uz/translation.json index 767dd2bb23..06bd7cf1fa 100644 --- a/src/lib/i18n/locales/uz-Latn-Uz/translation.json +++ b/src/lib/i18n/locales/uz-Latn-Uz/translation.json @@ -454,6 +454,7 @@ "Create a new note": "", "Create Account": "Hisob yaratish", "Create Admin Account": "Administrator hisobini yarating", + "Create and manage scheduled automations": "", "Create Channel": "Kanal yaratish", "Create Folder": "", "Create Image": "", @@ -479,6 +480,7 @@ "Custom Gender": "", "Custom Parameter Name": "Maxsus parametr nomi", "Custom Parameter Value": "Maxsus parametr qiymati", + "Daily": "", "Daily Messages": "", "Danger Zone": "Xavfli zona", "Dark": "Qorong'i", @@ -969,6 +971,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Autentifikatsiya qilish uchun tizim foydalanuvchisi seansi hisob ma'lumotlarini yo'naltiradi", + "Fr_day_of_week": "", "Full Context Mode": "To'liq kontekst rejimi", "Function": "Funktsiya", "Function Calling": "Funktsiyani chaqirish", @@ -1043,6 +1046,7 @@ "History": "", "Home": "Uy", "Host": "Xost", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Bugun sizga qanday yordam bera olaman?", "How would you rate this response?": "Bu javobni qanday baholaysiz?", @@ -1265,10 +1269,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Mistral OCR API kaliti talab qilinadi.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Model", "Model '{{modelName}}' has been successfully downloaded.": "“{{modelName}}” modeli yuklab olindi.", "Model '{{modelTag}}' is already in queue for downloading.": "“{{modelTag}}” modeli allaqachon yuklab olish uchun navbatda turibdi.", - "Model {{modelId}} not found": "{{modelId}} modeli topilmadi", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "{{modelName}} modeli ko'rish qobiliyatiga ega emas", "Model {{name}} is now {{status}}": "{{name}} modeli endi {{status}}", @@ -1309,6 +1313,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Mojeek qidiruv API kaliti", + "Monthly": "", "More": "Ko'proq", "More Concise": "", "More options": "", @@ -1429,6 +1434,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Ollama versiyasi", "On": "Yoniq", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1513,6 +1519,7 @@ "Persistent": "", "Personalization": "Shaxsiylashtirish", "Pin": "Pin", + "Pin to Sidebar": "", "Pinned": "Qadalgan", "Pinned Messages": "", "Pinned Models": "", @@ -1673,6 +1680,7 @@ "Running": "Yugurish", "Running...": "Yugurish...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Saqlash", "Save & Create": "Saqlash va yaratish", "Save & Update": "Saqlash va yangilash", @@ -1889,6 +1897,7 @@ "STT Model": "STT modeli", "STT Settings": "STT sozlamalari", "Stylized PDF Export": "Stillashtirilgan PDF eksporti", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1934,6 +1943,7 @@ "Text Splitter": "Matn ajratuvchi", "Text-to-Speech": "", "Text-to-Speech Engine": "Matnni nutqqa aylantirish mexanizmi", + "Th_day_of_week": "", "Thanks for your feedback!": "Fikr-mulohazangiz uchun tashakkur!", "The Application Account DN you bind with for search": "Qidirish uchun siz bog'langan ilova hisobi DN", "The base to search for users": "Foydalanuvchilarni qidirish uchun asos", @@ -2047,6 +2057,7 @@ "TTS Model": "TTS modeli", "TTS Settings": "TTS sozlamalari", "TTS Voice": "TTS ovozi", + "Tu_day_of_week": "", "Type": "Turi", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Hugging Face Resolve (Yuklab olish) URL manzilini kiriting", @@ -2149,6 +2160,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Ogohlantirish: Buni yoqish foydalanuvchilarga serverga ixtiyoriy kodni yuklash imkonini beradi.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Ogohlantirish: Jupyter ijrosi o'zboshimchalik bilan kod bajarilishini ta'minlaydi, bu xavfsizlikka jiddiy xavf tug'diradi - juda ehtiyotkorlik bilan davom eting.", + "We_day_of_week": "", "Web": "Veb", "Web API": "Web API", "Web Loader Engine": "Web Loader Engine", @@ -2165,6 +2177,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI “{{url}}” manziliga so‘rov yuboradi", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI “{{url}}/api/chat” manziliga so‘rov yuboradi", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI \"{{url}}/chat/completions\" manziliga so'rov yuboradi", + "Weekly": "", "What are you trying to achieve?": "Nimaga erishmoqchisiz?", "What are you working on?": "Nima ustida ishlayapsiz?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/vi-VN/translation.json b/src/lib/i18n/locales/vi-VN/translation.json index d2917e0c20..604dac83f6 100644 --- a/src/lib/i18n/locales/vi-VN/translation.json +++ b/src/lib/i18n/locales/vi-VN/translation.json @@ -453,6 +453,7 @@ "Create a new note": "", "Create Account": "Tạo Tài khoản", "Create Admin Account": "Tạo Tài khoản Quản trị", + "Create and manage scheduled automations": "", "Create Channel": "Tạo Kênh", "Create Folder": "", "Create Image": "", @@ -478,6 +479,7 @@ "Custom Gender": "", "Custom Parameter Name": "", "Custom Parameter Value": "", + "Daily": "", "Daily Messages": "", "Danger Zone": "Vùng Nguy hiểm", "Dark": "Tối", @@ -968,6 +970,7 @@ "Forward": "", "Forwards system user OAuth access token to authenticate": "", "Forwards system user session credentials to authenticate": "Chuyển tiếp thông tin xác thực phiên người dùng hệ thống để xác thực", + "Fr_day_of_week": "", "Full Context Mode": "Chế độ Ngữ cảnh Đầy đủ", "Function": "Function", "Function Calling": "Gọi Function", @@ -1042,6 +1045,7 @@ "History": "", "Home": "Trang chủ", "Host": "Host", + "Hourly": "", "Hourly Messages": "", "How can I help you today?": "Tôi có thể giúp gì cho bạn hôm nay?", "How would you rate this response?": "Bạn đánh giá phản hồi này thế nào?", @@ -1264,10 +1268,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "Yêu cầu Khóa API Mistral OCR.", "MistralAI": "", + "Mo_day_of_week": "", "Model": "Mô hình", "Model '{{modelName}}' has been successfully downloaded.": "Mô hình '{{modelName}}' đã được tải xuống thành công.", "Model '{{modelTag}}' is already in queue for downloading.": "Mô hình '{{modelTag}}' đã có trong hàng đợi để tải xuống.", - "Model {{modelId}} not found": "Không tìm thấy Mô hình {{modelId}}", "Model {{modelName}} deleted successfully": "", "Model {{modelName}} is not vision capable": "Model {{modelName}} không có khả năng nhìn", "Model {{name}} is now {{status}}": "Model {{name}} bây giờ là {{status}}", @@ -1308,6 +1312,7 @@ "Models Sharing": "", "Mojeek": "", "Mojeek Search API Key": "Khóa API Mojeek Search", + "Monthly": "", "More": "Thêm", "More Concise": "", "More options": "", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "", "Ollama Version": "Phiên bản Ollama", "On": "Bật", + "Once": "", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "", "Only active when the chat input is in focus and an LLM is generating a response.": "", @@ -1512,6 +1518,7 @@ "Persistent": "", "Personalization": "Cá nhân hóa", "Pin": "Ghim", + "Pin to Sidebar": "", "Pinned": "Đã ghim", "Pinned Messages": "", "Pinned Models": "", @@ -1671,6 +1678,7 @@ "Running": "Đang chạy", "Running...": "Đang chạy...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "", + "Sa_day_of_week": "", "Save": "Lưu", "Save & Create": "Lưu & Tạo", "Save & Update": "Lưu & Cập nhật", @@ -1887,6 +1895,7 @@ "STT Model": "Mô hình STT", "STT Settings": "Cài đặt Nhận dạng Giọng nói", "Stylized PDF Export": "", + "Su_day_of_week": "", "Submit question": "", "Submit suggestion": "", "Subtitle": "", @@ -1932,6 +1941,7 @@ "Text Splitter": "Bộ chia Văn bản", "Text-to-Speech": "", "Text-to-Speech Engine": "Công cụ Chuyển Văn bản thành Giọng nói", + "Th_day_of_week": "", "Thanks for your feedback!": "Cám ơn bạn đã gửi phản hồi!", "The Application Account DN you bind with for search": "DN Tài khoản Ứng dụng bạn liên kết để tìm kiếm", "The base to search for users": "Cơ sở để tìm kiếm người dùng", @@ -2045,6 +2055,7 @@ "TTS Model": "Mô hình TTS", "TTS Settings": "Cài đặt Chuyển văn bản thành Giọng nói", "TTS Voice": "Giọng nói TTS", + "Tu_day_of_week": "", "Type": "Kiểu", "Type here...": "", "Type Hugging Face Resolve (Download) URL": "Nhập URL Hugging Face Resolve (Tải xuống)", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "Cảnh báo: Bật tính năng này sẽ cho phép người dùng tải lên mã tùy ý trên máy chủ.", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "Cảnh báo: Thực thi Jupyter cho phép thực thi mã tùy ý, gây ra rủi ro bảo mật nghiêm trọng—hãy tiến hành hết sức thận trọng.", + "We_day_of_week": "", "Web": "Web", "Web API": "Web API", "Web Loader Engine": "", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI sẽ thực hiện yêu cầu đến \"{{url}}\"", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI sẽ thực hiện yêu cầu đến \"{{url}}/api/chat\"", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI sẽ thực hiện yêu cầu đến \"{{url}}/chat/completions\"", + "Weekly": "", "What are you trying to achieve?": "Bạn đang cố gắng đạt được điều gì?", "What are you working on?": "Bạn đang làm gì vậy?", "What is NOT shared:": "", diff --git a/src/lib/i18n/locales/zh-CN/translation.json b/src/lib/i18n/locales/zh-CN/translation.json index 5146725806..eca4f34e43 100644 --- a/src/lib/i18n/locales/zh-CN/translation.json +++ b/src/lib/i18n/locales/zh-CN/translation.json @@ -453,6 +453,7 @@ "Create a new note": "新建笔记", "Create Account": "创建账号", "Create Admin Account": "创建管理员账号", + "Create and manage scheduled automations": "创建和管理计划自动化任务", "Create Channel": "创建频道", "Create Folder": "创建分组", "Create Image": "图片生成", @@ -478,6 +479,7 @@ "Custom Gender": "自定义性别", "Custom Parameter Name": "自定义参数名称", "Custom Parameter Value": "自定义参数值", + "Daily": "每日", "Daily Messages": "每日消息数", "Danger Zone": "危险区域", "Dark": "暗色", @@ -968,6 +970,7 @@ "Forward": "前进", "Forwards system user OAuth access token to authenticate": "转发用户的 OAuth 访问令牌(Access Token)以进行身份验证", "Forwards system user session credentials to authenticate": "转发用户的会话凭证(Session Credentials)以进行身份验证", + "Fr_day_of_week": "周五", "Full Context Mode": "完整上下文模式", "Function": "函数", "Function Calling": "函数调用 (Function Calling)", @@ -1042,6 +1045,7 @@ "History": "历史记录", "Home": "主页", "Host": "主机", + "Hourly": "每小时", "Hourly Messages": "每小时消息数", "How can I help you today?": "有什么我能帮您的吗?", "How would you rate this response?": "您如何评价这个回答?", @@ -1264,10 +1268,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "需要 Mistral OCR 接口密钥", "MistralAI": "MistralAI", + "Mo_day_of_week": "周一", "Model": "模型", "Model '{{modelName}}' has been successfully downloaded.": "模型“{{modelName}}”已成功下载", "Model '{{modelTag}}' is already in queue for downloading.": "模型“{{modelTag}}”已在下载队列中", - "Model {{modelId}} not found": "未找到模型 {{modelId}}", "Model {{modelName}} deleted successfully": "成功删除模型:{{modelName}}", "Model {{modelName}} is not vision capable": "模型 {{modelName}} 不支持视觉能力", "Model {{name}} is now {{status}}": "模型 {{name}} 现在是 {{status}}", @@ -1308,6 +1312,7 @@ "Models Sharing": "分享模型", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek Search 接口密钥", + "Monthly": "每月", "More": "更多", "More Concise": "精炼表达", "More options": "更多选项", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "Ollama Cloud 接口密钥", "Ollama Version": "Ollama 版本", "On": "开启", + "Once": "单次", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "仅在启用“粘贴超长文本为文件”选项时有效。", "Only active when the chat input is in focus and an LLM is generating a response.": "仅在聚焦对话框且大语言模型正在生成回答时有效。", @@ -1512,6 +1518,7 @@ "Persistent": "持久化", "Personalization": "个性化", "Pin": "置顶", + "Pin to Sidebar": "固定到侧边栏", "Pinned": "已置顶", "Pinned Messages": "置顶消息", "Pinned Models": "固定在侧边栏的模型", @@ -1671,6 +1678,7 @@ "Running": "运行中", "Running...": "运行中...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "并行运行嵌入任务以加快处理速度。如果遇到限速问题,请关闭此选项。", + "Sa_day_of_week": "周六", "Save": "保存", "Save & Create": "保存并创建", "Save & Update": "保存并更新", @@ -1887,6 +1895,7 @@ "STT Model": "语音转文本模型", "STT Settings": "语音转文本设置", "Stylized PDF Export": "美化 PDF 导出", + "Su_day_of_week": "周日", "Submit question": "提交问题", "Submit suggestion": "提交建议", "Subtitle": "副标题", @@ -1932,6 +1941,7 @@ "Text Splitter": "文本切分器", "Text-to-Speech": "文本转语音", "Text-to-Speech Engine": "文本转语音引擎", + "Th_day_of_week": "周四", "Thanks for your feedback!": "感谢您的反馈!", "The Application Account DN you bind with for search": "您所绑定用于搜索的 Application Account DN", "The base to search for users": "搜索用户的 Base", @@ -2045,6 +2055,7 @@ "TTS Model": "文本转语音模型", "TTS Settings": "文本转语音设置", "TTS Voice": "文本转语音音色", + "Tu_day_of_week": "周二", "Type": "类型", "Type here...": "请输入内容...", "Type Hugging Face Resolve (Download) URL": "输入 Hugging Face 模型解析(下载)地址", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "警告:启用后将允许用户自动执行定时提示词任务。", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "警告:启用此功能将允许用户在服务器上上传任意代码", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "警告:启用 Jupyter 执行将允许运行任意代码,存在严重安全风险——务必谨慎操作", + "We_day_of_week": "周三", "Web": "网页", "Web API": "网页 API", "Web Loader Engine": "网页加载引擎", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI 将向 \"{{url}}\" 发出请求", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI 将向 \"{{url}}/api/chat\" 发出请求", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI 将向 \"{{url}}/chat/completions\" 发出请求", + "Weekly": "每周", "What are you trying to achieve?": "您想要达到什么目标?", "What are you working on?": "您在忙于什么?", "What is NOT shared:": "不共享的内容:", diff --git a/src/lib/i18n/locales/zh-TW/translation.json b/src/lib/i18n/locales/zh-TW/translation.json index d15e7b486f..70f075130a 100644 --- a/src/lib/i18n/locales/zh-TW/translation.json +++ b/src/lib/i18n/locales/zh-TW/translation.json @@ -453,6 +453,7 @@ "Create a new note": "新建筆記", "Create Account": "建立帳號", "Create Admin Account": "建立管理員帳號", + "Create and manage scheduled automations": "建立和管理計畫自動化任務", "Create Channel": "建立頻道", "Create Folder": "建立分組", "Create Image": "產生圖片", @@ -478,6 +479,7 @@ "Custom Gender": "自訂性別", "Custom Parameter Name": "自訂參數名稱", "Custom Parameter Value": "自訂參數值", + "Daily": "每日", "Daily Messages": "每日訊息數", "Danger Zone": "危險區域", "Dark": "深色", @@ -968,6 +970,7 @@ "Forward": "前進", "Forwards system user OAuth access token to authenticate": "轉發使用者 OAuth 存取權杖(Access Token)以進行驗證", "Forwards system user session credentials to authenticate": "轉發使用者工作階段憑證(Session Credentials)以進行驗證", + "Fr_day_of_week": "週五", "Full Context Mode": "完整上下文模式", "Function": "函式", "Function Calling": "函式呼叫", @@ -1042,6 +1045,7 @@ "History": "歷史紀錄", "Home": "首頁", "Host": "主機", + "Hourly": "每小時", "Hourly Messages": "每小時訊息數", "How can I help you today?": "今天我能為您做些什麼?", "How would you rate this response?": "您如何評價此回應?", @@ -1264,10 +1268,10 @@ "Mistral OCR": "Mistral OCR", "Mistral OCR API Key required.": "需要提供 Mistral OCR API 金鑰。", "MistralAI": "MistralAI", + "Mo_day_of_week": "週一", "Model": "模型", "Model '{{modelName}}' has been successfully downloaded.": "模型「{{modelName}}」已成功下載。", "Model '{{modelTag}}' is already in queue for downloading.": "模型「{{modelTag}}」已在下載佇列中。", - "Model {{modelId}} not found": "未找到模型 {{modelId}}", "Model {{modelName}} deleted successfully": "模型 {{modelName}} 已成功刪除", "Model {{modelName}} is not vision capable": "模型 {{modelName}} 不具備視覺能力", "Model {{name}} is now {{status}}": "模型 {{name}} 現在狀態為 {{status}}", @@ -1308,6 +1312,7 @@ "Models Sharing": "分享模型", "Mojeek": "Mojeek", "Mojeek Search API Key": "Mojeek 搜尋 API 金鑰", + "Monthly": "每月", "More": "更多", "More Concise": "精煉表達", "More options": "更多選項", @@ -1428,6 +1433,7 @@ "Ollama Cloud API Key": "Ollama Cloud API 金鑰", "Ollama Version": "Ollama 版本", "On": "開啟", + "Once": "一次", "OneDrive": "OneDrive", "Only active when \"Paste Large Text as File\" setting is toggled on.": "僅在啟用「將大型文字作為檔案貼上」設定時有效。", "Only active when the chat input is in focus and an LLM is generating a response.": "僅在對話輸入框聚焦且大型語言模型正在產生回應時有效。", @@ -1512,6 +1518,7 @@ "Persistent": "持久性", "Personalization": "個人化", "Pin": "釘選", + "Pin to Sidebar": "固定到側邊欄", "Pinned": "已釘選", "Pinned Messages": "置頂訊息", "Pinned Models": "固定於側邊欄的模型", @@ -1671,6 +1678,7 @@ "Running": "正在執行", "Running...": "正在執行...", "Runs embedding tasks concurrently to speed up processing. Turn off if rate limits become an issue.": "同時執行嵌入任務以加快處理速度。如果遇到速率限制問題,請關閉此功能。", + "Sa_day_of_week": "週六", "Save": "儲存", "Save & Create": "儲存並建立", "Save & Update": "儲存並更新", @@ -1887,6 +1895,7 @@ "STT Model": "語音轉文字 (STT) 模型", "STT Settings": "語音轉文字 (STT) 設定", "Stylized PDF Export": "風格化 PDF 匯出", + "Su_day_of_week": "週日", "Submit question": "提交問題", "Submit suggestion": "提交建議", "Subtitle": "副標題", @@ -1932,6 +1941,7 @@ "Text Splitter": "文字分割器", "Text-to-Speech": "文字轉語音", "Text-to-Speech Engine": "文字轉語音引擎", + "Th_day_of_week": "週四", "Thanks for your feedback!": "感謝您的回饋!", "The Application Account DN you bind with for search": "您綁定用於搜尋的應用程式帳號 DN", "The base to search for users": "搜尋使用者的基礎", @@ -2045,6 +2055,7 @@ "TTS Model": "文字轉語音 (TTS) 模型", "TTS Settings": "文字轉語音 (TTS) 設定", "TTS Voice": "文字轉語音 (TTS) 聲音", + "Tu_day_of_week": "週二", "Type": "類型", "Type here...": "在此輸入...", "Type Hugging Face Resolve (Download) URL": "輸入 Hugging Face 的解析(下載)URL", @@ -2147,6 +2158,7 @@ "Warning: Enabling this will allow users to run scheduled prompts automatically.": "警告:啟用後將允許使用者自動執行排程提示詞。", "Warning: Enabling this will allow users to upload arbitrary code on the server.": "警告:啟用此功能將允許使用者在伺服器上上傳任意程式碼。", "Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.": "警告:Jupyter 執行允許任意程式碼執行,構成嚴重安全風險 —— 請務必極度謹慎。", + "We_day_of_week": "週三", "Web": "網頁", "Web API": "網頁 API", "Web Loader Engine": "網頁載入引擎", @@ -2163,6 +2175,7 @@ "WebUI will make requests to \"{{url}}\"": "WebUI 將向 \"{{url}}\" 傳送請求", "WebUI will make requests to \"{{url}}/api/chat\"": "WebUI 將向 \"{{url}}/api/chat\" 傳送請求", "WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI 將向 \"{{url}}/chat/completions\" 傳送請求", + "Weekly": "每週", "What are you trying to achieve?": "您正在試圖完成什麼?", "What are you working on?": "您現在的工作是什麼?", "What is NOT shared:": "不會分享的內容:", diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 05bd83ea1f..a59944f50c 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -59,6 +59,7 @@ export const channelId = writable(null); export const chats = writable(null); export const pinnedChats = writable([]); +export const pinnedNotes = writable([]); export const tags = writable([]); export const folders = writable([]); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index c11c33ac33..ecaeefab04 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -520,7 +520,7 @@ export const copyToClipboard = async (text, html = null, formatted = false) => { textArea.style.position = 'fixed'; document.body.appendChild(textArea); - textArea.focus(); + textArea.focus({ preventScroll: true }); textArea.select(); try { @@ -923,8 +923,19 @@ export const processDetails = (content) => { attributes[attributeMatch[1]] = attributeMatch[2]; } + // New format: result in body content; Old format: result in attribute + let resultText = ''; if (attributes.result) { - content = content.replace(match, unescapeHtml(attributes.result)); + resultText = unescapeHtml(attributes.result); + } else { + // Extract body content (strip ...) + const bodyMatch = match.match(/[\s\S]*?<\/summary>\s*([\s\S]*?)\s*<\/details>/i); + if (bodyMatch && bodyMatch[1].trim()) { + resultText = unescapeHtml(bodyMatch[1].trim()); + } + } + if (resultText) { + content = content.replace(match, resultText); } } } diff --git a/src/lib/utils/marked/extension.ts b/src/lib/utils/marked/extension.ts index eec17adbd0..13f9f7cdc3 100644 --- a/src/lib/utils/marked/extension.ts +++ b/src/lib/utils/marked/extension.ts @@ -60,7 +60,7 @@ function detailsTokenizer(src: string) { } function detailsStart(src: string) { - return src.match(/^
/) ? 0 : -1; + return src.match(/^]/) ? 0 : -1; } function detailsRenderer(token: any) { diff --git a/src/lib/utils/marked/katex-extension.ts b/src/lib/utils/marked/katex-extension.ts index 13860fab30..a890ff4135 100644 --- a/src/lib/utils/marked/katex-extension.ts +++ b/src/lib/utils/marked/katex-extension.ts @@ -66,6 +66,44 @@ function generateRegexRules(delimiters) { const { inlineRule, blockRule } = generateRegexRules(DELIMITER_LIST); +const isAllowedTrailing = (src: string, i: number): boolean => + i >= src.length || ALLOWED_SURROUNDING_CHARS_REGEX.test(src.charAt(i)); + +const isBlockBoundary = (src: string, i: number): boolean => + /^(?:[ \t]*\r?\n|$)/.test(src.slice(i)); + +const findClosingDelimiter = (src: string, i: number): number => + i >= src.length - 1 + ? -1 + : src[i] === '\\' + ? findClosingDelimiter(src, i + 2) + : src[i] === '$' && src[i + 1] === '$' + ? i + : findClosingDelimiter(src, i + 1); + +export const tokenizeDisplayMath = ( + src: string, + type: 'inlineKatex' | 'blockKatex', + requireBlockBoundary = false +) => { + if (!src.startsWith('$$')) return; + + const endIndex = findClosingDelimiter(src, 2); + if (endIndex === -1) return; + + const raw = src.slice(0, endIndex + 2); + const text = raw.slice(2, -2); + const afterClose = endIndex + 2; + + const validators: Array<() => boolean> = [ + () => text.trim().length > 0, + () => isAllowedTrailing(src, afterClose), + () => !requireBlockBoundary || isBlockBoundary(src, afterClose) + ]; + + return validators.every((v) => v()) ? { type, raw, text, displayMode: true } : undefined; +}; + export default function (options = {}) { return { extensions: [inlineKatex(options), blockKatex(options)] @@ -102,6 +140,17 @@ function katexStart(src, displayMode: boolean) { } function katexTokenizer(src, tokens, displayMode: boolean) { + if (src.startsWith('$$')) { + const displayToken = tokenizeDisplayMath( + src, + displayMode ? 'blockKatex' : 'inlineKatex', + displayMode + ); + if (displayToken) { + return displayToken; + } + } + const ruleReg = displayMode ? blockRule : inlineRule; const type = displayMode ? 'blockKatex' : 'inlineKatex'; diff --git a/src/lib/utils/marked/mention-extension.ts b/src/lib/utils/marked/mention-extension.ts index b56edfbee3..835ce30714 100644 --- a/src/lib/utils/marked/mention-extension.ts +++ b/src/lib/utils/marked/mention-extension.ts @@ -41,7 +41,11 @@ export function mentionExtension(opts: MentionOptions = {}) { // mentionStart fires on every '<' in the document, making the tokenizer a hot path. const trigger = opts.triggerChar ?? '@'; const re = new RegExp(`^<\\${trigger}([\\w.\\-:/]+)(?:\\|([^>]*))?>`); - const snapshot: MentionOptions = { triggerChar: trigger, className: opts.className, extraAttrs: opts.extraAttrs }; + const snapshot: MentionOptions = { + triggerChar: trigger, + className: opts.className, + extraAttrs: opts.extraAttrs + }; return { name: 'mention', diff --git a/src/lib/utils/onedrive-file-picker.ts b/src/lib/utils/onedrive-file-picker.ts index 136d239294..198a8bcafb 100644 --- a/src/lib/utils/onedrive-file-picker.ts +++ b/src/lib/utils/onedrive-file-picker.ts @@ -78,7 +78,8 @@ class OneDriveConfig { const msalParams = { auth: { authority: `https://login.microsoftonline.com/${authorityEndpoint}`, - clientId: clientId + clientId: clientId, + redirectUri: window.location.origin } }; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 252e1cf7a8..8aeda16594 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -728,7 +728,8 @@ // Apply theme classes (mirrors logic from chat/Settings/General.svelte) const themes = ['dark', 'light', 'oled-dark']; - let themeToApply = newTheme === 'oled-dark' ? 'dark' : newTheme === 'her' ? 'light' : newTheme; + let themeToApply = + newTheme === 'oled-dark' ? 'dark' : newTheme === 'her' ? 'light' : newTheme; if (newTheme === 'system') { themeToApply = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } @@ -988,13 +989,15 @@ console.error('Error refreshing backend config:', error); } - // Relay auth token to desktop app for API access - if (window.electronAPI?.send) { - window.electronAPI.send({ - type: 'token:update', - token: localStorage.token - }).catch(() => {}); - } + // Relay auth token to desktop app for API access + if (window.electronAPI?.send) { + window.electronAPI + .send({ + type: 'token:update', + token: localStorage.token + }) + .catch(() => {}); + } } else { // Redirect Invalid Session User to /auth Page localStorage.removeItem('token');