mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-07 02:58:21 +00:00
Merge branch 'open-webui:dev' into dev
This commit is contained in:
commit
e046ff8db7
29 changed files with 2416 additions and 2266 deletions
21
Dockerfile
21
Dockerfile
|
|
@ -135,29 +135,30 @@ RUN apt-get update && \
|
|||
# install python dependencies
|
||||
COPY --chown=$UID:$GID ./backend/requirements.txt ./requirements.txt
|
||||
|
||||
RUN pip3 install --no-cache-dir uv && \
|
||||
RUN set -e; \
|
||||
pip3 install --no-cache-dir uv; \
|
||||
if [ "$USE_CUDA" = "true" ]; then \
|
||||
# If you use CUDA the whisper and embedding model will be downloaded on first use
|
||||
# fix: pin torch<=2.9.1 - torch 2.10.0 aarch64 wheels cause SIGILL on ARM devices (RPi 4 Cortex-A72) #21349
|
||||
pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir && \
|
||||
uv pip install --system -r requirements.txt --no-cache-dir && \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')" && \
|
||||
pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/$USE_CUDA_DOCKER_VER --no-cache-dir; \
|
||||
uv pip install --system -r requirements.txt --no-cache-dir; \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')"; \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')"; \
|
||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
|
||||
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
|
||||
python -c "import nltk; nltk.download('punkt_tab')"; \
|
||||
else \
|
||||
pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir && \
|
||||
uv pip install --system -r requirements.txt --no-cache-dir && \
|
||||
pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir; \
|
||||
uv pip install --system -r requirements.txt --no-cache-dir; \
|
||||
if [ "$USE_SLIM" != "true" ]; then \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')" && \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')" && \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ['RAG_EMBEDDING_MODEL'], device='cpu')"; \
|
||||
python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')"; \
|
||||
python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \
|
||||
python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \
|
||||
python -c "import nltk; nltk.download('punkt_tab')"; \
|
||||
fi; \
|
||||
fi; \
|
||||
mkdir -p /app/backend/data && chown -R $UID:$GID /app/backend/data/ && \
|
||||
mkdir -p /app/backend/data; chown -R $UID:$GID /app/backend/data/; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
||||
# Install Ollama if requested
|
||||
|
|
|
|||
|
|
@ -964,6 +964,12 @@ AUDIT_EXCLUDED_PATHS = os.getenv("AUDIT_EXCLUDED_PATHS", "/chats,/chat,/folders"
|
|||
AUDIT_EXCLUDED_PATHS = [path.strip() for path in AUDIT_EXCLUDED_PATHS]
|
||||
AUDIT_EXCLUDED_PATHS = [path.lstrip("/") for path in AUDIT_EXCLUDED_PATHS]
|
||||
|
||||
# Comma separated list of urls to include in audit (whitelist mode)
|
||||
# When set, only these paths are audited and AUDIT_EXCLUDED_PATHS is ignored
|
||||
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]
|
||||
|
||||
|
||||
####################################
|
||||
# OPENTELEMETRY
|
||||
|
|
@ -1007,6 +1013,9 @@ OTEL_TRACES_SAMPLER = os.environ.get(
|
|||
).lower()
|
||||
OTEL_BASIC_AUTH_USERNAME = os.environ.get("OTEL_BASIC_AUTH_USERNAME", "")
|
||||
OTEL_BASIC_AUTH_PASSWORD = os.environ.get("OTEL_BASIC_AUTH_PASSWORD", "")
|
||||
OTEL_METRICS_EXPORT_INTERVAL_MILLIS = int(
|
||||
os.environ.get("OTEL_METRICS_EXPORT_INTERVAL_MILLIS", "10000")
|
||||
)
|
||||
|
||||
OTEL_METRICS_BASIC_AUTH_USERNAME = os.environ.get(
|
||||
"OTEL_METRICS_BASIC_AUTH_USERNAME", OTEL_BASIC_AUTH_USERNAME
|
||||
|
|
|
|||
|
|
@ -469,6 +469,7 @@ from open_webui.env import (
|
|||
ENABLE_CUSTOM_MODEL_FALLBACK,
|
||||
LICENSE_KEY,
|
||||
AUDIT_EXCLUDED_PATHS,
|
||||
AUDIT_INCLUDED_PATHS,
|
||||
AUDIT_LOG_LEVEL,
|
||||
CHANGELOG,
|
||||
REDIS_URL,
|
||||
|
|
@ -1585,6 +1586,7 @@ if audit_level != AuditLevel.NONE:
|
|||
AuditLoggingMiddleware,
|
||||
audit_level=audit_level,
|
||||
excluded_paths=AUDIT_EXCLUDED_PATHS,
|
||||
included_paths=AUDIT_INCLUDED_PATHS,
|
||||
max_body_size=MAX_BODY_LOG_SIZE,
|
||||
)
|
||||
##################################
|
||||
|
|
|
|||
|
|
@ -151,7 +151,12 @@ def upgrade() -> None:
|
|||
continue
|
||||
|
||||
history = chat_data.get("history", {})
|
||||
if not isinstance(history, dict):
|
||||
continue
|
||||
|
||||
messages = history.get("messages", {})
|
||||
if not isinstance(messages, dict):
|
||||
continue
|
||||
|
||||
for message_id, message in messages.items():
|
||||
if not isinstance(message, dict):
|
||||
|
|
|
|||
|
|
@ -88,9 +88,15 @@ async def get_tools(
|
|||
# OpenAPI Tool Servers
|
||||
server_access_grants = {}
|
||||
for server in await get_tool_servers(request):
|
||||
connection = request.app.state.config.TOOL_SERVER_CONNECTIONS[
|
||||
server.get("idx", 0)
|
||||
]
|
||||
server_idx = server.get("idx", 0)
|
||||
connections = request.app.state.config.TOOL_SERVER_CONNECTIONS
|
||||
if server_idx >= len(connections):
|
||||
log.warning(
|
||||
f"Tool server index {server_idx} out of range "
|
||||
f"(have {len(connections)} connections), skipping server {server.get('id')}"
|
||||
)
|
||||
continue
|
||||
connection = connections[server_idx]
|
||||
server_config = connection.get("config", {})
|
||||
|
||||
server_id = f"server:{server.get('id')}"
|
||||
|
|
|
|||
|
|
@ -168,8 +168,8 @@ async def search_web(
|
|||
engine = __request__.app.state.config.WEB_SEARCH_ENGINE
|
||||
user = UserModel(**__user__) if __user__ else None
|
||||
|
||||
# Use admin-configured result count if configured, falling back to model-provided count of provided, else default to 5
|
||||
count = __request__.app.state.config.WEB_SEARCH_RESULT_COUNT or count
|
||||
# Enforce maximum result count from config to prevent abuse
|
||||
count = count if count < __request__.app.state.config.WEB_SEARCH_RESULT_COUNT else __request__.app.state.config.WEB_SEARCH_RESULT_COUNT
|
||||
|
||||
results = await asyncio.to_thread(_search_web, __request__, engine, query, user)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
from typing import Optional, Set, Union, List, Dict, Any
|
||||
from open_webui.models.users import Users, UserModel
|
||||
from open_webui.models.groups import Groups
|
||||
|
||||
|
||||
from open_webui.config import DEFAULT_USER_PERMISSIONS
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from open_webui.models.users import UserModel
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.access_grants import (
|
||||
has_public_read_access_grant,
|
||||
has_user_access_grant,
|
||||
strip_user_access_grants,
|
||||
)
|
||||
from open_webui.config import DEFAULT_USER_PERMISSIONS
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
def fill_missing_permissions(
|
||||
permissions: Dict[str, Any], default_permissions: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
permissions: dict[str, Any], default_permissions: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Recursively fills in missing properties in the permissions dictionary
|
||||
using the default permissions as a template.
|
||||
|
|
@ -27,9 +33,9 @@ def fill_missing_permissions(
|
|||
|
||||
def get_permissions(
|
||||
user_id: str,
|
||||
default_permissions: Dict[str, Any],
|
||||
db: Optional[Any] = None,
|
||||
) -> Dict[str, Any]:
|
||||
default_permissions: dict[str, Any],
|
||||
db: Session | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Get all permissions for a user by combining the permissions of all groups the user is a member of.
|
||||
If a permission is defined in multiple groups, the most permissive value is used (True > False).
|
||||
|
|
@ -37,8 +43,8 @@ def get_permissions(
|
|||
"""
|
||||
|
||||
def combine_permissions(
|
||||
permissions: Dict[str, Any], group_permissions: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
permissions: dict[str, Any], group_permissions: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Combine permissions from multiple groups by taking the most permissive value."""
|
||||
for key, value in group_permissions.items():
|
||||
if isinstance(value, dict):
|
||||
|
|
@ -72,8 +78,8 @@ def get_permissions(
|
|||
def has_permission(
|
||||
user_id: str,
|
||||
permission_key: str,
|
||||
default_permissions: Dict[str, Any] = {},
|
||||
db: Optional[Any] = None,
|
||||
default_permissions: dict[str, Any] = {},
|
||||
db: Session | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a user has a specific permission by checking the group permissions
|
||||
|
|
@ -82,7 +88,7 @@ def has_permission(
|
|||
Permission keys can be hierarchical and separated by dots ('.').
|
||||
"""
|
||||
|
||||
def get_permission(permissions: Dict[str, Any], keys: List[str]) -> bool:
|
||||
def get_permission(permissions: dict[str, Any], keys: list[str]) -> bool:
|
||||
"""Traverse permissions dict using a list of keys (from dot-split permission_key)."""
|
||||
for key in keys:
|
||||
if key not in permissions:
|
||||
|
|
@ -110,9 +116,9 @@ def has_permission(
|
|||
def has_access(
|
||||
user_id: str,
|
||||
permission: str = "read",
|
||||
access_grants: Optional[list] = None,
|
||||
user_group_ids: Optional[Set[str]] = None,
|
||||
db: Optional[Any] = None,
|
||||
access_grants: list | None = None,
|
||||
user_group_ids: set[str] | None = None,
|
||||
db: Session | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a user has the specified permission using an in-memory access_grants list.
|
||||
|
|
@ -156,7 +162,7 @@ def has_access(
|
|||
def has_connection_access(
|
||||
user: UserModel,
|
||||
connection: dict,
|
||||
user_group_ids: Optional[Set[str]] = None,
|
||||
user_group_ids: set[str] | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a user can access a server connection (tool server, terminal, etc.)
|
||||
|
|
@ -194,7 +200,7 @@ def migrate_access_control(
|
|||
if access_control is None and ac_key not in data:
|
||||
return
|
||||
|
||||
grants: List[Dict[str, str]] = []
|
||||
grants: list[dict[str, str]] = []
|
||||
if access_control and isinstance(access_control, dict):
|
||||
for perm in ["read", "write"]:
|
||||
perm_data = access_control.get(perm, {})
|
||||
|
|
@ -221,20 +227,13 @@ def migrate_access_control(
|
|||
data.pop(ac_key, None)
|
||||
|
||||
|
||||
from open_webui.models.access_grants import (
|
||||
has_public_read_access_grant,
|
||||
has_user_access_grant,
|
||||
strip_user_access_grants,
|
||||
)
|
||||
|
||||
|
||||
def filter_allowed_access_grants(
|
||||
default_permissions: Dict[str, Any],
|
||||
default_permissions: dict[str, Any],
|
||||
user_id: str,
|
||||
user_role: str,
|
||||
access_grants: list,
|
||||
public_permission_key: str,
|
||||
db: Optional[Any] = None,
|
||||
db: Session | None = None,
|
||||
) -> list:
|
||||
"""
|
||||
Checks if the user has the required permissions to grant access to a resource.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import logging
|
||||
from typing import Optional, Any
|
||||
|
||||
from open_webui.models.users import UserModel
|
||||
from open_webui.models.files import Files
|
||||
|
|
@ -10,14 +9,16 @@ from open_webui.models.groups import Groups
|
|||
from open_webui.models.models import Models
|
||||
from open_webui.models.access_grants import AccessGrants
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def has_access_to_file(
|
||||
file_id: Optional[str],
|
||||
file_id: str | None,
|
||||
access_type: str,
|
||||
user: UserModel,
|
||||
db: Optional[Any] = None,
|
||||
db: Session | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a user has the specified access to a file through any of:
|
||||
|
|
|
|||
|
|
@ -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, MAX_BODY_LOG_SIZE
|
||||
from open_webui.env import AUDIT_LOG_LEVEL, 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
|
||||
|
||||
|
|
@ -129,15 +129,23 @@ class AuditLoggingMiddleware:
|
|||
app: ASGI3Application,
|
||||
*,
|
||||
excluded_paths: Optional[list[str]] = None,
|
||||
included_paths: Optional[list[str]] = None,
|
||||
max_body_size: int = MAX_BODY_LOG_SIZE,
|
||||
audit_level: AuditLevel = AuditLevel.NONE,
|
||||
) -> 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.audit_level = audit_level
|
||||
|
||||
if self.included_paths and self.excluded_paths:
|
||||
logger.warning(
|
||||
"Both AUDIT_INCLUDED_PATHS and AUDIT_EXCLUDED_PATHS are set. "
|
||||
"AUDIT_INCLUDED_PATHS (whitelist) takes precedence."
|
||||
)
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
scope: ASGIScope,
|
||||
|
|
@ -226,7 +234,16 @@ class AuditLoggingMiddleware:
|
|||
):
|
||||
return True
|
||||
|
||||
# match either /api/<resource>/...(for the endpoint /api/chat case) or /api/v1/<resource>/...
|
||||
# Whitelist mode: only log paths that match included_paths
|
||||
if self.included_paths:
|
||||
pattern = re.compile(
|
||||
r"^/api(?:/v1)?/(" + "|".join(self.included_paths) + r")\b"
|
||||
)
|
||||
if not pattern.match(request.url.path):
|
||||
return True # Skip: path not in whitelist
|
||||
return False # Do NOT skip: path is in whitelist
|
||||
|
||||
# Blacklist mode: skip paths that match excluded_paths
|
||||
pattern = re.compile(
|
||||
r"^/api(?:/v1)?/(" + "|".join(self.excluded_paths) + r")\b"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -762,18 +762,22 @@ def parse_ollama_modelfile(model_text):
|
|||
return data
|
||||
|
||||
|
||||
def convert_logit_bias_input_to_json(user_input) -> Optional[str]:
|
||||
if user_input:
|
||||
logit_bias_pairs = user_input.split(",")
|
||||
logit_bias_json = {}
|
||||
for pair in logit_bias_pairs:
|
||||
token, bias = pair.split(":")
|
||||
token = str(token.strip())
|
||||
bias = int(bias.strip())
|
||||
bias = 100 if bias > 100 else -100 if bias < -100 else bias
|
||||
logit_bias_json[token] = bias
|
||||
return json.dumps(logit_bias_json)
|
||||
return None
|
||||
def convert_logit_bias_input_to_json(logit_bias_input) -> Optional[str]:
|
||||
if not logit_bias_input:
|
||||
return None
|
||||
|
||||
if isinstance(logit_bias_input, dict):
|
||||
return json.dumps(logit_bias_input)
|
||||
|
||||
logit_bias_pairs = logit_bias_input.split(",")
|
||||
logit_bias_json = {}
|
||||
for pair in logit_bias_pairs:
|
||||
token, bias = pair.split(":")
|
||||
token = str(token.strip())
|
||||
bias = int(bias.strip())
|
||||
bias = 100 if bias > 100 else -100 if bias < -100 else bias
|
||||
logit_bias_json[token] = bias
|
||||
return json.dumps(logit_bias_json)
|
||||
|
||||
|
||||
def freeze(value):
|
||||
|
|
|
|||
|
|
@ -44,11 +44,10 @@ from open_webui.env import (
|
|||
OTEL_METRICS_BASIC_AUTH_PASSWORD,
|
||||
OTEL_METRICS_OTLP_SPAN_EXPORTER,
|
||||
OTEL_METRICS_EXPORTER_OTLP_INSECURE,
|
||||
OTEL_METRICS_EXPORT_INTERVAL_MILLIS,
|
||||
)
|
||||
from open_webui.models.users import Users
|
||||
|
||||
_EXPORT_INTERVAL_MILLIS = 10_000 # 10 seconds
|
||||
|
||||
|
||||
def _build_meter_provider(resource: Resource) -> MeterProvider:
|
||||
"""Return a configured MeterProvider."""
|
||||
|
|
@ -67,7 +66,7 @@ def _build_meter_provider(resource: Resource) -> MeterProvider:
|
|||
OTLPHttpMetricExporter(
|
||||
endpoint=OTEL_METRICS_EXPORTER_OTLP_ENDPOINT, headers=headers
|
||||
),
|
||||
export_interval_millis=_EXPORT_INTERVAL_MILLIS,
|
||||
export_interval_millis=OTEL_METRICS_EXPORT_INTERVAL_MILLIS,
|
||||
)
|
||||
]
|
||||
else:
|
||||
|
|
@ -78,7 +77,7 @@ def _build_meter_provider(resource: Resource) -> MeterProvider:
|
|||
insecure=OTEL_METRICS_EXPORTER_OTLP_INSECURE,
|
||||
headers=headers,
|
||||
),
|
||||
export_interval_millis=_EXPORT_INTERVAL_MILLIS,
|
||||
export_interval_millis=OTEL_METRICS_EXPORT_INTERVAL_MILLIS,
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -275,11 +275,14 @@ async def get_tools(
|
|||
continue
|
||||
|
||||
tool_server_idx = tool_server_data.get("idx", 0)
|
||||
tool_server_connection = (
|
||||
request.app.state.config.TOOL_SERVER_CONNECTIONS[
|
||||
tool_server_idx
|
||||
]
|
||||
)
|
||||
connections = request.app.state.config.TOOL_SERVER_CONNECTIONS
|
||||
if tool_server_idx >= len(connections):
|
||||
log.warning(
|
||||
f"Tool server index {tool_server_idx} out of range "
|
||||
f"(have {len(connections)} connections), skipping server {server_id}"
|
||||
)
|
||||
continue
|
||||
tool_server_connection = connections[tool_server_idx]
|
||||
|
||||
# Check access control for tool server
|
||||
if not has_connection_access(
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ youtube-transcript-api==1.2.4
|
|||
pytube==15.0.0
|
||||
|
||||
pydub
|
||||
ddgs==9.11.2
|
||||
ddgs==9.11.3
|
||||
|
||||
azure-ai-documentintelligence==1.0.2
|
||||
azure-identity==1.25.2
|
||||
|
|
@ -119,7 +119,7 @@ pgvector==0.4.2
|
|||
|
||||
PyMySQL==1.1.2
|
||||
boto3==1.42.62
|
||||
mariadb==1.1.14
|
||||
# mariadb==1.1.14 should be added if you want to support MariaDB
|
||||
|
||||
pymilvus==2.6.9
|
||||
qdrant-client==1.17.0
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ dependencies = [
|
|||
"pytube==15.0.0",
|
||||
|
||||
"pydub",
|
||||
"ddgs==9.11.2",
|
||||
"ddgs==9.11.3",
|
||||
|
||||
"google-api-python-client",
|
||||
"google-auth-httplib2",
|
||||
|
|
|
|||
30
src/app.css
30
src/app.css
|
|
@ -187,31 +187,45 @@ select {
|
|||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
background-position: 100% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
background-position: -100% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shimmer {
|
||||
background: linear-gradient(90deg, #9a9b9e 25%, #2a2929 50%, #9a9b9e 75%);
|
||||
background: linear-gradient(
|
||||
110deg,
|
||||
#b4b4b4 0%,
|
||||
#b4b4b4 43%,
|
||||
#e8e8e8 50%,
|
||||
#b4b4b4 57%,
|
||||
#b4b4b4 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: shimmer 4s linear infinite;
|
||||
color: #818286; /* Fallback color */
|
||||
animation: shimmer 1.5s cubic-bezier(0.7, 0, 1, 0.4) infinite;
|
||||
color: #b4b4b4;
|
||||
}
|
||||
|
||||
:global(.dark) .shimmer {
|
||||
background: linear-gradient(90deg, #818286 25%, #eae5e5 50%, #818286 75%);
|
||||
background: linear-gradient(
|
||||
110deg,
|
||||
#9a9a9a 0%,
|
||||
#9a9a9a 43%,
|
||||
#5e5e5e 50%,
|
||||
#9a9a9a 57%,
|
||||
#9a9a9a 100%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: shimmer 4s linear infinite;
|
||||
color: #a1a3a7; /* Darker fallback color for dark mode */
|
||||
animation: shimmer 1.5s cubic-bezier(0.7, 0, 1, 0.4) infinite;
|
||||
color: #9a9a9a;
|
||||
}
|
||||
|
||||
@keyframes smoothFadeIn {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
import AccessControlModal from '$lib/components/workspace/common/AccessControlModal.svelte';
|
||||
import LockClosed from '$lib/components/icons/LockClosed.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import { getTerminalConfig } from '$lib/apis/terminal';
|
||||
|
||||
export let show = false;
|
||||
export let edit = false;
|
||||
|
|
@ -56,6 +58,22 @@
|
|||
init();
|
||||
}
|
||||
|
||||
const verifyHandler = async () => {
|
||||
const _url = url.replace(/\/$/, '');
|
||||
if (!_url) {
|
||||
toast.error($i18n.t('Please enter a valid URL'));
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await getTerminalConfig(_url, key);
|
||||
|
||||
if (res) {
|
||||
toast.success($i18n.t('Server connection verified'));
|
||||
} else {
|
||||
toast.error($i18n.t('Server connection failed'));
|
||||
}
|
||||
};
|
||||
|
||||
const submitHandler = () => {
|
||||
if (url === '') {
|
||||
toast.error($i18n.t('Please enter a valid URL'));
|
||||
|
|
@ -176,6 +194,31 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tooltip content={$i18n.t('Verify Connection')} className="self-end -mb-1">
|
||||
<button
|
||||
class="self-center p-1 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg transition"
|
||||
on:click={() => {
|
||||
verifyHandler();
|
||||
}}
|
||||
type="button"
|
||||
aria-label={$i18n.t('Verify Connection')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
|
|
|
|||
|
|
@ -122,7 +122,11 @@
|
|||
}
|
||||
|
||||
if (modelSuggestions) {
|
||||
_models = [...$models.map((m) => ({ type: 'model', id: m.id, label: m.name, data: m }))];
|
||||
_models = [
|
||||
...$models
|
||||
.filter((m) => !m?.direct)
|
||||
.map((m) => ({ type: 'model', id: m.id, label: m.name, data: m }))
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@
|
|||
import Folder from '../icons/Folder.svelte';
|
||||
import Document from '../icons/Document.svelte';
|
||||
import PenAlt from '../icons/PenAlt.svelte';
|
||||
import Reset from '../icons/Reset.svelte';
|
||||
import ZoomReset from '../icons/ZoomReset.svelte';
|
||||
|
||||
import Spinner from '../common/Spinner.svelte';
|
||||
import Tooltip from '../common/Tooltip.svelte';
|
||||
import ConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||
|
|
@ -172,6 +173,9 @@
|
|||
|
||||
if (terminal && terminal.url !== prevTerminalUrl) {
|
||||
prevTerminalUrl = terminal.url;
|
||||
loading = true;
|
||||
error = null;
|
||||
entries = [];
|
||||
(async () => {
|
||||
// Discover server features (terminal enabled/disabled)
|
||||
const config = await getTerminalConfig(terminal.url, terminal.key);
|
||||
|
|
@ -531,6 +535,7 @@
|
|||
});
|
||||
|
||||
if (!handledDisplayFile) {
|
||||
loading = true;
|
||||
if (savedPath === '/') {
|
||||
const rawCwd = await getCwd(terminal.url, terminal.key);
|
||||
const cwd = rawCwd ? normalizePath(rawCwd) : null;
|
||||
|
|
@ -652,7 +657,7 @@
|
|||
on:click={() => filePreviewRef?.resetImageView()}
|
||||
aria-label={$i18n.t('Reset view')}
|
||||
>
|
||||
<Reset className="size-3.5" />
|
||||
<ZoomReset className="size-3.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
|
@ -663,7 +668,7 @@
|
|||
on:click={() => filePreviewRef?.resetPdfView()}
|
||||
aria-label={$i18n.t('Reset view')}
|
||||
>
|
||||
<Reset className="size-3.5" />
|
||||
<ZoomReset className="size-3.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
import InputVariablesModal from './MessageInput/InputVariablesModal.svelte';
|
||||
import Voice from '../icons/Voice.svelte';
|
||||
import Cloud from '../icons/Cloud.svelte';
|
||||
import Terminal from '../icons/Terminal.svelte';
|
||||
import IntegrationsMenu from './MessageInput/IntegrationsMenu.svelte';
|
||||
import TerminalMenu from './MessageInput/TerminalMenu.svelte';
|
||||
import Component from '../icons/Component.svelte';
|
||||
|
|
@ -1756,7 +1756,7 @@
|
|||
? 'm-1'
|
||||
: 'focus:outline-hidden rounded-full'}"
|
||||
>
|
||||
<Cloud className="size-3.5" strokeWidth="2" />
|
||||
<Terminal className="size-3.5" strokeWidth="2" />
|
||||
|
||||
<div class="hidden group-hover:block">
|
||||
<XMark className="size-4" strokeWidth="1.75" />
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
const updateIconHandler = async (iconName) => {
|
||||
const res = await updateFolderById(localStorage.token, folder.id, {
|
||||
meta: {
|
||||
icon: iconName
|
||||
icon: iconName ?? ''
|
||||
}
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
});
|
||||
|
||||
if (res) {
|
||||
folder.meta = { ...folder.meta, icon: iconName };
|
||||
folder.meta = { ...folder.meta, icon: iconName ?? '' };
|
||||
|
||||
toast.success($i18n.t('Folder updated successfully'));
|
||||
|
||||
|
|
@ -167,6 +167,7 @@
|
|||
<div class="text-center flex gap-3.5 items-center">
|
||||
<EmojiPicker
|
||||
onClose={() => {}}
|
||||
selected={folder?.meta?.icon ?? null}
|
||||
onSubmit={(name) => {
|
||||
console.log(name);
|
||||
updateIconHandler(name);
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@
|
|||
}
|
||||
connected = true;
|
||||
connecting = false;
|
||||
// Focus the terminal so it receives keyboard input immediately
|
||||
term?.focus();
|
||||
// Send initial resize
|
||||
if (term && ws) {
|
||||
ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
|
||||
|
|
@ -226,6 +228,10 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Ensure all key events are processed by xterm.js and not intercepted
|
||||
// by the browser or surrounding UI (fixes vi/vim keystroke handling).
|
||||
term.attachCustomKeyEventHandler(() => true);
|
||||
|
||||
// Handle resize
|
||||
term.onResize(({ cols, rows }) => {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
export let side = 'top';
|
||||
export let align = 'start';
|
||||
export let user = null;
|
||||
export let selected = null;
|
||||
|
||||
let show = false;
|
||||
let emojis = emojiShortCodes;
|
||||
|
|
@ -97,7 +98,11 @@
|
|||
// Handle emoji selection
|
||||
function selectEmoji(emoji) {
|
||||
const selectedCode = emoji.shortCodes[0];
|
||||
onSubmit(selectedCode);
|
||||
if (selected === selectedCode) {
|
||||
onSubmit(null);
|
||||
} else {
|
||||
onSubmit(selectedCode);
|
||||
}
|
||||
show = false;
|
||||
}
|
||||
</script>
|
||||
|
|
@ -131,6 +136,8 @@
|
|||
bind:value={search}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Virtualized Emoji List -->
|
||||
<div class="w-full flex justify-start h-96 overflow-y-auto px-3 pb-3 text-sm">
|
||||
{#if emojiRows.length === 0}
|
||||
|
|
@ -155,7 +162,7 @@
|
|||
placement="top"
|
||||
>
|
||||
<button
|
||||
class="p-1.5 rounded-lg cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition"
|
||||
class="p-1.5 rounded-lg cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-700 transition {selected === emojiItem.shortCodes[0] ? 'bg-gray-200 dark:bg-gray-700' : ''}"
|
||||
on:click={() => selectEmoji(emojiItem)}
|
||||
>
|
||||
<img
|
||||
|
|
|
|||
22
src/lib/components/icons/ZoomReset.svelte
Normal file
22
src/lib/components/icons/ZoomReset.svelte
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<rect x="2" y="4" width="20" height="16" rx="3" fill="none" />
|
||||
<!-- The "1" -->
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 9v6M6 10.5l1.5-1.5" />
|
||||
<!-- The ":" -->
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 10.5v.01M12 13.5v.01" stroke-width="2" />
|
||||
<!-- The other "1" -->
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 9v6M15 10.5l1.5-1.5" />
|
||||
</svg>
|
||||
|
|
@ -57,6 +57,7 @@
|
|||
bind:groupIds
|
||||
includeGroups={true}
|
||||
includeUsers={shareUsers}
|
||||
includeSessionUser={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
export let includeGroups = true;
|
||||
export let includeUsers = true;
|
||||
export let pagination = false;
|
||||
export let includeSessionUser = false;
|
||||
|
||||
export let groupIds = [];
|
||||
export let userIds = [];
|
||||
|
|
@ -245,7 +246,7 @@
|
|||
|
||||
<div>
|
||||
{#each users as user, userIdx (user.id)}
|
||||
{#if user?.id !== $_user?.id}
|
||||
{#if includeSessionUser || user?.id !== $_user?.id}
|
||||
<button
|
||||
class=" dark:border-gray-850 text-xs flex items-center justify-between w-full"
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -19,11 +19,11 @@
|
|||
"{{COUNT}} Rows": "{{COUNT}} files",
|
||||
"{{COUNT}} Sources": "{{COUNT}} fonts",
|
||||
"{{COUNT}} words": "{{COUNT}} paraules",
|
||||
"{{COUNT}}d_time_ago": "{{COUNT}}d_time_ago",
|
||||
"{{COUNT}}h_time_ago": "{{COUNT}}h_time_ago",
|
||||
"{{COUNT}}m_time_ago": "{{COUNT}}m_time_ago",
|
||||
"{{COUNT}}w_time_ago": "{{COUNT}}w_time_ago",
|
||||
"{{COUNT}}y_time_ago": "{{COUNT}}y_time_ago",
|
||||
"{{COUNT}}d_time_ago": "{{COUNT}}d",
|
||||
"{{COUNT}}h_time_ago": "{{COUNT}}h",
|
||||
"{{COUNT}}m_time_ago": "{{COUNT}}m",
|
||||
"{{COUNT}}w_time_ago": "{{COUNT}}s",
|
||||
"{{COUNT}}y_time_ago": "{{COUNT}}a",
|
||||
"{{LOCALIZED_DATE}} at {{LOCALIZED_TIME}}": "{{LOCALIZED_DATE}} a les {{LOCALIZED_TIME}}",
|
||||
"{{model}} download has been canceled": "La descàrrega del model {{model}} s'ha cancel·lat",
|
||||
"{{modelName}} profile image": "Imatge del perfil {{modelName}}",
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
"Are you sure you want to delete this channel?": "Estàs segur que vols eliminar aquest canal?",
|
||||
"Are you sure you want to delete this message?": "Estàs segur que vols eliminar aquest missatge?",
|
||||
"Are you sure you want to delete this version? Child versions will be relinked to this version's parent.": "Estàs segur que vols suprimir aquesta versió? Les versions filles es tornaran a enllaçar amb la versió principal d'aquesta versió.",
|
||||
"Are you sure you want to delete this?": "",
|
||||
"Are you sure you want to delete this?": "Estàs segur que vols eliminar això",
|
||||
"Are you sure you want to unarchive all archived chats?": "Estàs segur que vols desarxivar tots els xats arxivats?",
|
||||
"Arena Models": "Models de l'Arena",
|
||||
"Artifacts": "Artefactes",
|
||||
|
|
@ -283,7 +283,7 @@
|
|||
"Character limit for autocomplete generation input": "Límit de caràcters per a l'entrada de generació automàtica",
|
||||
"Chart new frontiers": "Traça noves fronteres",
|
||||
"Chat": "Xat",
|
||||
"Chat archived.": "",
|
||||
"Chat archived.": "Xar arxivat",
|
||||
"Chat Background Image": "Imatge de fons del xat",
|
||||
"Chat Bubble UI": "Chat Bubble UI",
|
||||
"Chat Completions": "Completacions de xat",
|
||||
|
|
@ -487,7 +487,7 @@
|
|||
"Default User Role": "Rol d'usuari per defecte",
|
||||
"Defaults": "Valors per defecte",
|
||||
"Delete": "Eliminar",
|
||||
"Delete {{name}}": "",
|
||||
"Delete {{name}}": "Eliminar {{name}}",
|
||||
"Delete a model": "Eliminar un model",
|
||||
"Delete All": "Eliminar tot",
|
||||
"Delete All Chats": "Eliminar tots els xats",
|
||||
|
|
@ -580,7 +580,7 @@
|
|||
"Downloading stats...": "Descarregant estadístiques...",
|
||||
"Draw": "Dibuixar",
|
||||
"Drop any files here to upload": "Arrossega aquí qualsevol fitxer per pujar-lo",
|
||||
"Drop files here": "",
|
||||
"Drop files here": "Arrossega els arxius aquí",
|
||||
"Drop files here to upload": "Deixa anar els arxius aquí per pujar-los",
|
||||
"DuckDuckGo": "DuckDuckGo",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "p. ex. '30s','10m'. Les unitats de temps vàlides són 's', 'm', 'h'.",
|
||||
|
|
@ -633,7 +633,7 @@
|
|||
"Embedding Concurrent Requests": "Peticions concurrents d'incrustació",
|
||||
"Embedding Model": "Model d'incrustació",
|
||||
"Embedding Model Engine": "Motor de model d'incrustació",
|
||||
"Empty message": "",
|
||||
"Empty message": "Missatge buit",
|
||||
"Enable All": "Habilitar tot",
|
||||
"Enable API Keys": "Permetre claus API",
|
||||
"Enable autocomplete generation for chat messages": "Activar la generació automàtica per als missatges del xat",
|
||||
|
|
@ -827,7 +827,7 @@
|
|||
"Fade Effect for Streaming Text": "Efecte de fos a negre per al text en streaming",
|
||||
"Failed to add file.": "No s'ha pogut afegir l'arxiu.",
|
||||
"Failed to add members": "No s'han pogut afegir el membres",
|
||||
"Failed to archive chat.": "",
|
||||
"Failed to archive chat.": "No s'ha pogut arxivar el xat",
|
||||
"Failed to attach file": "No s'ha pogut adjuntar l'arxiu",
|
||||
"Failed to clear status": "No s'ha pogut esborar l'estat",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "No s'ha pogut connecta al servidor d'eines OpenAPI {{URL}}",
|
||||
|
|
@ -842,11 +842,11 @@
|
|||
"Failed to generate title": "No s'ha pogut generar el títol",
|
||||
"Failed to import models": "No s'han pogut importar el models",
|
||||
"Failed to load chat preview": "No s'ha pogut carregar la previsualització del xat",
|
||||
"Failed to load DOCX file. Please try downloading it instead.": "",
|
||||
"Failed to load DOCX file. Please try downloading it instead.": "No s'ha pogut carregar el fitxer DOCX. Si us plau, prova de descarregar-lo.",
|
||||
"Failed to load Excel/CSV file. Please try downloading it instead.": "No s'ha pogut carregar el fitxer Excel/CSV. Si us plau, prova de descarregar-lo.",
|
||||
"Failed to load file content.": "No s'ha pogut carregar el contingut del fitxer",
|
||||
"Failed to load Interface settings": "No s'ha pogut carregar la configuració de la interficie",
|
||||
"Failed to load PPTX file. Please try downloading it instead.": "",
|
||||
"Failed to load PPTX file. Please try downloading it instead.": "No s'ha pogut carregar el fitxer PPTX. Si us plau, prova de descarregar-lo.",
|
||||
"Failed to move chat": "No s'ha pogut moure el xat",
|
||||
"Failed to process URL: {{url}}": "No s'ha pogut processar la URL: {{url}}",
|
||||
"Failed to read clipboard contents": "No s'ha pogut llegir el contingut del porta-retalls",
|
||||
|
|
@ -901,7 +901,7 @@
|
|||
"Focus Chat Input": "Posa el focus en l'entrada del xat",
|
||||
"Folder": "Carpeta",
|
||||
"Folder Background Image": "Imatge del fons de la carpeta",
|
||||
"Folder created successfully": "",
|
||||
"Folder created successfully": "Carpeta creada correctament",
|
||||
"Folder deleted successfully": "Carpeta eliminada correctament",
|
||||
"Folder Max File Count": "Nombre màxim d'arxius per carpeta",
|
||||
"Folder name": "Nom de carpeta",
|
||||
|
|
@ -1266,7 +1266,7 @@
|
|||
"More options": "Més opcions",
|
||||
"More Options": "Més opcions",
|
||||
"Move": "Moure",
|
||||
"Moved {{name}}": "",
|
||||
"Moved {{name}}": "S'ha mogut {{name}}",
|
||||
"My Terminal": "El meu terminal",
|
||||
"Name": "Nom",
|
||||
"Name and ID are required, please fill them out": "El nom i l'ID són necessaris, emplena'ls, si us plau",
|
||||
|
|
@ -1309,13 +1309,13 @@
|
|||
"No file selected": "No s'ha escollit cap fitxer",
|
||||
"No files found": "No s'han trobat arxius",
|
||||
"No files in this knowledge base.": "No hi ha arxius a la base de coneixement.",
|
||||
"No files yet. Upload files or run Python code to create them.": "",
|
||||
"No files yet. Upload files or run Python code to create them.": "Encara no hi ha fitxers. Puja fitxers o executa codi Python per crear-los.",
|
||||
"No functions found": "No s'han trobat funcions",
|
||||
"No groups found": "No s'han trobat grups",
|
||||
"No history available": "No hi ha historial disponible",
|
||||
"No HTML, CSS, or JavaScript content found.": "No s'ha trobat contingut HTML, CSS o JavaScript.",
|
||||
"No inference engine with management support found": "No s'ha trobat un motor d'inferència amb suport de gestió",
|
||||
"No kernel": "",
|
||||
"No kernel": "No hi ha cap kernel",
|
||||
"No knowledge bases found.": "No s'han trobat bases de coneixement.",
|
||||
"No knowledge found": "No s'ha trobat Coneixement",
|
||||
"No memories to clear": "No hi ha memòries per netejar",
|
||||
|
|
@ -1331,7 +1331,7 @@
|
|||
"No results": "No s'han trobat resultats",
|
||||
"No results found": "No s'han trobat resultats",
|
||||
"No search query generated": "No s'ha generat cap consulta",
|
||||
"No servers detected": "",
|
||||
"No servers detected": "No s'han detectat servidors",
|
||||
"No skills found": "No s'han trobat habilitats",
|
||||
"No source available": "Sense font disponible",
|
||||
"No sources found": "No s'han trobat fonts",
|
||||
|
|
@ -1487,7 +1487,7 @@
|
|||
"Please select at least one user for Direct Message channel.": "Selecciona com a mínim un usuari per al canal de missatge directe.",
|
||||
"Please wait until all files are uploaded.": "Si us plau, espera fins que s'hagin carregat tots els fitxers.",
|
||||
"Port": "Port",
|
||||
"Ports": "",
|
||||
"Ports": "Ports",
|
||||
"Positive attitude": "Actitud positiva",
|
||||
"Prefer not to say": "Prefereixo no dir-ho",
|
||||
"Prefix ID": "Identificador del prefix",
|
||||
|
|
@ -1517,7 +1517,7 @@
|
|||
"Pull \"{{searchValue}}\" from Ollama.com": "Obtenir \"{{searchValue}}\" de Ollama.com",
|
||||
"Pull a model from Ollama.com": "Obtenir un model d'Ollama.com",
|
||||
"Pull Model": "Obtenir un model",
|
||||
"Pyodide file browser": "",
|
||||
"Pyodide file browser": "Navegador de fitxers Pyodide",
|
||||
"Query Generation Prompt": "Indicació per a generació de consulta",
|
||||
"Querying": "Consultes",
|
||||
"Quick Actions": "Accions ràpides",
|
||||
|
|
@ -1585,7 +1585,7 @@
|
|||
"Response splitting": "Divisió de la resposta",
|
||||
"Response Watermark": "Marca d'aigua de la resposta",
|
||||
"Responses": "Respostes",
|
||||
"Restart": "",
|
||||
"Restart": "Reiniciar",
|
||||
"Result": "Resultat",
|
||||
"RESULT": "Resultat",
|
||||
"Retrieval": "Retrieval",
|
||||
|
|
@ -1599,7 +1599,7 @@
|
|||
"Role": "Rol",
|
||||
"RTL": "RTL",
|
||||
"Run": "Executar",
|
||||
"Run All": "",
|
||||
"Run All": "Executar tot",
|
||||
"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.",
|
||||
|
|
@ -1790,7 +1790,7 @@
|
|||
"Start a new conversation": "Iniciar una nova conversa",
|
||||
"Start of the channel": "Inici del canal",
|
||||
"Start Tag": "Etiqueta d'inici",
|
||||
"Starting kernel...": "",
|
||||
"Starting kernel...": "Iniciant el kernel...",
|
||||
"Status": "Estat",
|
||||
"Status cleared successfully": "S'ha eliminat correctament el teu estat",
|
||||
"Status updated successfully": "S'ha actualitzat correctament el teu estat",
|
||||
|
|
@ -2135,8 +2135,8 @@
|
|||
"You're now logged in.": "Ara estàs connectat.",
|
||||
"Your Account": "El teu compte",
|
||||
"Your account status is currently pending activation.": "El compte està actualment pendent d'activació",
|
||||
"Your browser does not support the audio tag.": "",
|
||||
"Your browser does not support the video tag.": "",
|
||||
"Your browser does not support the audio tag.": "El teu navegador no admet l'etiqueta d'àudio.",
|
||||
"Your browser does not support the video tag.": "El teu navegador no admet l'etiqueta de vídeo.",
|
||||
"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.": "Tota la teva contribució anirà directament al desenvolupador del complement; Open WebUI no se'n queda cap percentatge. Tanmateix, la plataforma de finançament escollida pot tenir les seves pròpies comissions.",
|
||||
"Your message text or inputs": "El text o les entrades del teu missatge",
|
||||
"Your usage stats have been successfully synced.": "Les teves estadístiques d'ús s'han sincronitzat correctament.",
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
"Add Content": "Lisää sisältöä",
|
||||
"Add content here": "Lisää sisältöä tähän",
|
||||
"Add Custom Parameter": "Lisää mukautettu parametri",
|
||||
"Add Custom Prompt": "Lisää mukautettu kehoite",
|
||||
"Add Custom Prompt": "Lisää mukautettu kehote",
|
||||
"Add Details": "Lisää yksityiskohtia",
|
||||
"Add Files": "Lisää tiedostoja",
|
||||
"Add Image": "Lisää kuva",
|
||||
|
|
@ -111,7 +111,7 @@
|
|||
"All models are now hidden": "Kaikki mallit ovat nyt piilotettu",
|
||||
"All models are now visible": "Kaikki mallit ovat nyt näkyvissä",
|
||||
"All models deleted successfully": "Kaikki mallit poistettu onnistuneesti",
|
||||
"All time": "Koko ajan",
|
||||
"All time": "Kokoajalta",
|
||||
"All Users": "Kaikki käyttäjät",
|
||||
"Allow Call": "Salli puhelut",
|
||||
"Allow Chat Controls": "Salli keskustelujen hallinta",
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
"Are you sure you want to delete this channel?": "Haluatko varmasti poistaa tämän kanavan?",
|
||||
"Are you sure you want to delete this message?": "Haluatko varmasti poistaa tämän viestin?",
|
||||
"Are you sure you want to delete this version? Child versions will be relinked to this version's parent.": "Haluatko varamsti poistaa tämän version? Alaversiot linkitetään uudelleen tämän version ylätason versioon.",
|
||||
"Are you sure you want to delete this?": "",
|
||||
"Are you sure you want to delete this?": "Haluatko varmasti poistää tämän?",
|
||||
"Are you sure you want to unarchive all archived chats?": "Haluatko varmasti purkaa kaikkien arkistoitujen keskustelujen arkistoinnin?",
|
||||
"Arena Models": "Arena-mallit",
|
||||
"Artifacts": "Artefaktit",
|
||||
|
|
@ -250,7 +250,7 @@
|
|||
"Bullet List": "Luettelo",
|
||||
"Button ID": "Painikkeen ID",
|
||||
"Button Label": "Painikkeen nimi",
|
||||
"Button Prompt": "Painikkeen kehoite",
|
||||
"Button Prompt": "Painikkeen kehote",
|
||||
"by {{name}}": "tekijä {{name}}",
|
||||
"By {{name}}": "Tekijä {{name}}",
|
||||
"Bypass Embedding and Retrieval": "Ohita upotus ja haku",
|
||||
|
|
@ -283,7 +283,7 @@
|
|||
"Character limit for autocomplete generation input": "Automaattisen täydennyksen syötteen merkkiraja",
|
||||
"Chart new frontiers": "Kartoita uusia rajapintoja",
|
||||
"Chat": "Keskustelu",
|
||||
"Chat archived.": "",
|
||||
"Chat archived.": "Keskustelu arkistoitu.",
|
||||
"Chat Background Image": "Keskustelun taustakuva",
|
||||
"Chat Bubble UI": "Keskustelu-pallojen käyttöliittymä",
|
||||
"Chat Completions": "",
|
||||
|
|
@ -418,7 +418,7 @@
|
|||
"Copy Last Response": "Kopioi viimeisin vastaus",
|
||||
"Copy link": "Kopioi linkki",
|
||||
"Copy Link": "Kopioi linkki",
|
||||
"Copy Prompt": "Kopioi kehoite",
|
||||
"Copy Prompt": "Kopioi kehote",
|
||||
"Copy Share Link": "Kopioi jakolinkki",
|
||||
"Copy to clipboard": "Kopioi leikepöydälle",
|
||||
"Copy Token": "Kopioi tokeni",
|
||||
|
|
@ -487,7 +487,7 @@
|
|||
"Default User Role": "Oletuskäyttäjärooli",
|
||||
"Defaults": "Oletukset",
|
||||
"Delete": "Poista",
|
||||
"Delete {{name}}": "",
|
||||
"Delete {{name}}": "Poista {{name}}",
|
||||
"Delete a model": "Poista malli",
|
||||
"Delete All": "Poista kaikki",
|
||||
"Delete All Chats": "Poista kaikki keskustelut",
|
||||
|
|
@ -580,7 +580,7 @@
|
|||
"Downloading stats...": "Ladataa tilastoja...",
|
||||
"Draw": "Piirros",
|
||||
"Drop any files here to upload": "Pudota tähän ladattavat tiedostot",
|
||||
"Drop files here": "",
|
||||
"Drop files here": "Pudota tiedostot tähän",
|
||||
"Drop files here to upload": "Pudota tiedostot tähän ladataksesi",
|
||||
"DuckDuckGo": "",
|
||||
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "esim. '30s', '10m'. Kelpoiset aikayksiköt ovat 's', 'm', 'h'.",
|
||||
|
|
@ -615,7 +615,7 @@
|
|||
"Edit Image": "Kuvan muokkaus",
|
||||
"Edit Last Message": "Muokkaa viimeisintä viestiä",
|
||||
"Edit Memory": "Muokkaa muistia",
|
||||
"Edit Prompt": "Muokkaa kehoitetta",
|
||||
"Edit Prompt": "Muokkaa kehotetta",
|
||||
"Edit Terminal Connection": "Muokkaa pääteyhteyttä",
|
||||
"Edit User": "Muokkaa käyttäjää",
|
||||
"Edit User Group": "Muokkaa käyttäjäryhmää",
|
||||
|
|
@ -633,7 +633,7 @@
|
|||
"Embedding Concurrent Requests": "Samanaikaiset upotuspyynnöt",
|
||||
"Embedding Model": "Upotusmalli",
|
||||
"Embedding Model Engine": "Upotusmallin moottori",
|
||||
"Empty message": "",
|
||||
"Empty message": "Tyhjä viesti",
|
||||
"Enable All": "Ota kaikki käyttöön",
|
||||
"Enable API Keys": "Ota API-avaimet käyttöön",
|
||||
"Enable autocomplete generation for chat messages": "Ota automaattinen täydennys käyttöön keskusteluviesteissä",
|
||||
|
|
@ -827,7 +827,7 @@
|
|||
"Fade Effect for Streaming Text": "Häivytystehoste suoratoistetulle tekstille",
|
||||
"Failed to add file.": "Tiedoston lisääminen epäonnistui.",
|
||||
"Failed to add members": "Jäsenten lisääminen epäonnistui",
|
||||
"Failed to archive chat.": "",
|
||||
"Failed to archive chat.": "Keskustelun arkistointi epäonnistui.",
|
||||
"Failed to attach file": "Tiedoston liittäminen epäonnistui",
|
||||
"Failed to clear status": "Tilan tyhjentäminen epäonnistui",
|
||||
"Failed to connect to {{URL}} OpenAPI tool server": "Yhdistäminen {{URL}} OpenAPI työkalu palvelimeen epäonnistui",
|
||||
|
|
@ -842,11 +842,11 @@
|
|||
"Failed to generate title": "Otsikon luonti epäonnistui",
|
||||
"Failed to import models": "Mallien tuonti epäonnistui",
|
||||
"Failed to load chat preview": "Keskustelun esikatselun lataaminen epäonnistui",
|
||||
"Failed to load DOCX file. Please try downloading it instead.": "",
|
||||
"Failed to load DOCX file. Please try downloading it instead.": "DOCX-tiedoston avaaminen epäonnistui. Yritä ladata se sen sijaan.",
|
||||
"Failed to load Excel/CSV file. Please try downloading it instead.": "Excel/CSV tiedoston lataaminen epäonnistui. Yritä ladata se sen sijaan.",
|
||||
"Failed to load file content.": "Tiedoston sisällön lataaminen epäonnistui.",
|
||||
"Failed to load Interface settings": "Käyttöliittymän asetusten lataaminen epäonnistui",
|
||||
"Failed to load PPTX file. Please try downloading it instead.": "",
|
||||
"Failed to load PPTX file. Please try downloading it instead.": "PPTX-tiedoston lataaminen epäonnistui. Yritä ladata se sen sijaan.",
|
||||
"Failed to move chat": "Keskustelun siirto epäonnistui",
|
||||
"Failed to process URL: {{url}}": "Verkko-osoitteen käsittely epäonnistui: {{url}}",
|
||||
"Failed to read clipboard contents": "Leikepöydän sisällön lukeminen epäonnistui",
|
||||
|
|
@ -901,7 +901,7 @@
|
|||
"Focus Chat Input": "Kohdista keskustelu tekstikenttään",
|
||||
"Folder": "Kansio",
|
||||
"Folder Background Image": "Kansion taustakuva",
|
||||
"Folder created successfully": "",
|
||||
"Folder created successfully": "Kansio luotu onnistuneesti",
|
||||
"Folder deleted successfully": "Kansio poistettu onnistuneesti",
|
||||
"Folder Max File Count": "Kansion tiedostojen enimmäismäärä",
|
||||
"Folder name": "Kansion nimi",
|
||||
|
|
@ -913,7 +913,7 @@
|
|||
"Folders": "Kansiot",
|
||||
"Follow up": "Jatkokysymykset",
|
||||
"Follow Up Generation": "Jatkokysymysten luonti",
|
||||
"Follow Up Generation Prompt": "Jatkokysymysten luonti kehoite",
|
||||
"Follow Up Generation Prompt": "Jatkokysymysten luonti kehote",
|
||||
"Follow up: {{question}}": "Jatkokysymys: {{question}}",
|
||||
"Follow-Up Auto-Generation": "Jatkokysymysten automaattinen luonti",
|
||||
"Followed instructions perfectly": "Noudatti ohjeita täydellisesti",
|
||||
|
|
@ -1056,7 +1056,7 @@
|
|||
"Insert": "Lisää",
|
||||
"Insert Follow-Up Prompt to Input": "Lisää jatkokysymys syötteeseen",
|
||||
"Insert Prompt as Rich Text": "Lisää kehote RTF-muodossa",
|
||||
"Insert Suggestion Prompt to Input": "Lisää kehoite ehdotus syötteeseen",
|
||||
"Insert Suggestion Prompt to Input": "Lisää kehote ehdotus syötteeseen",
|
||||
"Install from Github URL": "Asenna Github-URL:stä",
|
||||
"Instant Auto-Send After Voice Transcription": "Heti automaattinen lähetys äänitunnistuksen jälkeen",
|
||||
"Integration": "Integrointi",
|
||||
|
|
@ -1262,7 +1262,7 @@
|
|||
"Mojeek": "",
|
||||
"Mojeek Search API Key": "Mojeek Search API -avain",
|
||||
"More": "Lisää",
|
||||
"More Concise": "Ytimekkäämpi",
|
||||
"More Concise": "Lyhyemmin",
|
||||
"More options": "Lisää vaihtoehtoja",
|
||||
"More Options": "Lisää vaihtoehtoja",
|
||||
"Move": "Siirrä",
|
||||
|
|
@ -1283,7 +1283,7 @@
|
|||
"New Model": "Uusi malli",
|
||||
"New Note": "Uusi muistiinpano",
|
||||
"New Password": "Uusi salasana",
|
||||
"New Prompt": "Uusi kehoite",
|
||||
"New Prompt": "Uusi kehote",
|
||||
"New Skill": "Uusi taito",
|
||||
"New Temporary Chat": "Uusi väliaikainen keskustelu",
|
||||
"New Terminal": "Uusi pääte",
|
||||
|
|
@ -1309,13 +1309,13 @@
|
|||
"No file selected": "Tiedostoa ei ole valittu",
|
||||
"No files found": "Tiedostoja ei löytynyt",
|
||||
"No files in this knowledge base.": "Tässä tietokannassa ei ole tiedostoja.",
|
||||
"No files yet. Upload files or run Python code to create them.": "",
|
||||
"No files yet. Upload files or run Python code to create them.": "Ei vielä tiedostoja. Lataa tiedostoja tai suorita Python-koodia niiden luomiseksi.",
|
||||
"No functions found": "Funktioita ei löytynyt",
|
||||
"No groups found": "Ryhmiä ei löytynyt",
|
||||
"No history available": "Historiaa ei saatavilla",
|
||||
"No HTML, CSS, or JavaScript content found.": "HTML-, CSS- tai JavaScript-sisältöä ei löytynyt.",
|
||||
"No inference engine with management support found": "",
|
||||
"No kernel": "",
|
||||
"No kernel": "Ei kerneliä",
|
||||
"No knowledge bases found.": "Tietokantoja ei löytynyt.",
|
||||
"No knowledge found": "Tietoa ei löytynyt",
|
||||
"No memories to clear": "Ei muistia tyhjennettäväksi",
|
||||
|
|
@ -1331,7 +1331,7 @@
|
|||
"No results": "Ei tuloksia",
|
||||
"No results found": "Ei tuloksia",
|
||||
"No search query generated": "Hakukyselyä ei luotu",
|
||||
"No servers detected": "",
|
||||
"No servers detected": "Palvelimia ei havaittu",
|
||||
"No skills found": "Taitoja ei löytynyt",
|
||||
"No source available": "Lähdettä ei saatavilla",
|
||||
"No sources found": "Lähteitä ei löytynyt",
|
||||
|
|
@ -1487,7 +1487,7 @@
|
|||
"Please select at least one user for Direct Message channel.": "Valitse vähintään yksi käyttäjä suoraviestikanavalle.",
|
||||
"Please wait until all files are uploaded.": "Odota kunnes kaikki tiedostot ovat ladattu.",
|
||||
"Port": "Portti",
|
||||
"Ports": "",
|
||||
"Ports": "Portit",
|
||||
"Positive attitude": "Positiivinen asenne",
|
||||
"Prefer not to say": "En halua sanoa",
|
||||
"Prefix ID": "Etuliite-ID",
|
||||
|
|
@ -1517,7 +1517,7 @@
|
|||
"Pull \"{{searchValue}}\" from Ollama.com": "Lataa \"{{searchValue}}\" Ollama.comista",
|
||||
"Pull a model from Ollama.com": "Lataa malli Ollama.comista",
|
||||
"Pull Model": "Lataa malli",
|
||||
"Pyodide file browser": "",
|
||||
"Pyodide file browser": "Pyodide tiedostoselain",
|
||||
"Query Generation Prompt": "Kyselytulosten luontikehote",
|
||||
"Querying": "Kysely",
|
||||
"Quick Actions": "Pikatoiminnot",
|
||||
|
|
@ -1585,7 +1585,7 @@
|
|||
"Response splitting": "Vastauksen jakaminen",
|
||||
"Response Watermark": "Vastauksen vesileima",
|
||||
"Responses": "",
|
||||
"Restart": "",
|
||||
"Restart": "Käynnistä uudelleen",
|
||||
"Result": "Tulos",
|
||||
"RESULT": "Tulos",
|
||||
"Retrieval": "Haku",
|
||||
|
|
@ -1598,7 +1598,7 @@
|
|||
"Role": "Rooli",
|
||||
"RTL": "RTL",
|
||||
"Run": "Suorita",
|
||||
"Run All": "",
|
||||
"Run All": "Suorita kaikki",
|
||||
"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.",
|
||||
|
|
@ -1685,7 +1685,7 @@
|
|||
"Select only one model to call": "Valitse vain yksi malli kutsuttavaksi",
|
||||
"Select view": "Valitse näkymä",
|
||||
"Selected model: {{modelName}}": "Valittu malli: {{modelName}}",
|
||||
"Selected model(s) do not support image inputs": "Valitut mallit eivät tue kuvasöytteitä",
|
||||
"Selected model(s) do not support image inputs": "Valitut mallit eivät tue kuvasyötteitä",
|
||||
"Selected Models": "Valitut mallit",
|
||||
"semantic": "Semaattinen",
|
||||
"Send": "Lähetä",
|
||||
|
|
@ -1789,7 +1789,7 @@
|
|||
"Start a new conversation": "Aloita uusi keskustelu",
|
||||
"Start of the channel": "Kanavan alku",
|
||||
"Start Tag": "Aloitus tagi",
|
||||
"Starting kernel...": "",
|
||||
"Starting kernel...": "Käynnistetään kerneliä...",
|
||||
"Status": "Tila",
|
||||
"Status cleared successfully": "Tila poistettu onnistuneesti",
|
||||
"Status updated successfully": "Tila päivitetty onnistuneesti",
|
||||
|
|
@ -1800,7 +1800,7 @@
|
|||
"Stop Download": "Lopeta lataus",
|
||||
"Stop Generating": "Lopeta generointi",
|
||||
"Stop Sequence": "Lopetussekvenssi",
|
||||
"Stream Chat Response": "Streamaa keskusteluvastaus",
|
||||
"Stream Chat Response": "Striimaa keskusteluvastaus",
|
||||
"Stream Delta Chunk Size": "Striimin delta-lohkon koko",
|
||||
"Streamable HTTP": "Streamable HTTP",
|
||||
"Strikethrough": "Yliviivaus",
|
||||
|
|
@ -1888,7 +1888,7 @@
|
|||
"This model is not publicly available. Please select another model.": "Tämä malli ei ole julkisesti saatavilla. Valitse toinen malli.",
|
||||
"This option controls how long the model will stay loaded into memory following the request (default: 5m)": "Tämä asetus määrittää kuinka kauan malli pysyy ladattuna muistissa pyynnön jälkeen (oletusarvo: 5m)",
|
||||
"This option controls how many tokens are preserved when refreshing the context. For example, if set to 2, the last 2 tokens of the conversation context will be retained. Preserving context can help maintain the continuity of a conversation, but it may reduce the ability to respond to new topics.": "Tämä asetus määrittää, kuinka monta tokenia säilytetään kontekstia päivitettäessä. Jos arvoksi on asetettu esimerkiksi 2, keskustelukontekstin kaksi viimeistä tokenia säilytetään. Kontekstin säilyttäminen voi auttaa ylläpitämään keskustelun jatkuvuutta, mutta se voi heikentää kykyä vastata uusiin aiheisiin.",
|
||||
"This option enables or disables the use of the reasoning feature in Ollama, which allows the model to think before generating a response. When enabled, the model can take a moment to process the conversation context and generate a more thoughtful response.": "",
|
||||
"This option enables or disables the use of the reasoning feature in Ollama, which allows the model to think before generating a response. When enabled, the model can take a moment to process the conversation context and generate a more thoughtful response.": "Tämä vaihtoehto mahdollistaa tai estää päättelyominaisuuden käytön Ollama:ssa, mikä antaa mallille mahdollisuuden miettiä ennen vastauksen tuottamista. Kun se on käytössä, malli voi ottaa hetken aikaa käsitellä keskustelun kontekstia ja tuottaa ajatellumman vastauksen.",
|
||||
"This option sets the maximum number of tokens the model can generate in its response. Increasing this limit allows the model to provide longer answers, but it may also increase the likelihood of unhelpful or irrelevant content being generated.": "Tämä vaihtoehto asettaa mallin vastauksessaan luomien tokenien enimmäismäärän. Tämän rajan nostaminen antaa mallille mahdollisuuden tarjota pidempiä vastauksia, mutta se voi myös lisätä hyödyttömän tai epäolennaisen sisällön luomisen todennäköisyyttä.",
|
||||
"This option will delete all existing files in the collection and replace them with newly uploaded files.": "Tämä vaihtoehto poistaa kaikki kokoelman nykyiset tiedostot ja korvaa ne uusilla ladatuilla tiedostoilla.",
|
||||
"This response was generated by \"{{model}}\"": "Tämän vastauksen tuotti \"{{model}}\"",
|
||||
|
|
@ -2055,8 +2055,8 @@
|
|||
"Voice": "Ääni",
|
||||
"Voice Input": "Äänitulolaitteen käyttö",
|
||||
"Voice mode": "Puhetila",
|
||||
"Voice Mode Custom Prompt": "Puhetilan mukautettu kehoite",
|
||||
"Voice Mode Prompt": "Puhetilan kehoite",
|
||||
"Voice Mode Custom Prompt": "Puhetilan mukautettu kehote",
|
||||
"Voice Mode Prompt": "Puhetilan kehote",
|
||||
"Waiting for upload...": "Odottaa latausta...",
|
||||
"Warning": "Varoitus",
|
||||
"Warning:": "Varoitus:",
|
||||
|
|
@ -2094,7 +2094,7 @@
|
|||
"Width": "Leveys",
|
||||
"Wikipedia": "",
|
||||
"Won": "Voitti",
|
||||
"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.": "",
|
||||
"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.": "Toimii top-k:n kanssa. Korkeampi arvo (esim. 0.95) johtaa monipuolisempaan tekstiin, kun taas matalampi arvo (esim. 0.5) tuottaa kohdennetumpaa ja konservatiivisempaa teksti.",
|
||||
"Workspace": "Työtila",
|
||||
"Workspace Permissions": "Työtilan käyttöoikeudet",
|
||||
"Write": "Kirjoita",
|
||||
|
|
@ -2134,8 +2134,8 @@
|
|||
"You're now logged in.": "Olet nyt kirjautunut sisään.",
|
||||
"Your Account": "Tilisi",
|
||||
"Your account status is currently pending activation.": "Tilisi tila on tällä hetkellä odottaa aktivointia.",
|
||||
"Your browser does not support the audio tag.": "",
|
||||
"Your browser does not support the video tag.": "",
|
||||
"Your browser does not support the audio tag.": "Selaimesi ei tue audio-tagia.",
|
||||
"Your browser does not support the video tag.": "Selaimesi ei tue video-tagia.",
|
||||
"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.": "Koko panoksesi menee suoraan lisäosan kehittäjälle; Open WebUI ei pidätä prosenttiosuutta. Valittu rahoitusalusta voi kuitenkin periä omia maksujaan.",
|
||||
"Your message text or inputs": "Viestisi teksti tai syöteet",
|
||||
"Your usage stats have been successfully synced.": "Käyttötilastosi on synkronoitu onnistuneesti.",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue