Merge pull request #22469 from open-webui/dev

0.8.10
This commit is contained in:
Tim Baek
2026-03-09 04:09:43 +04:00
committed by GitHub
102 changed files with 2781 additions and 1680 deletions
+33
View File
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.8.10] - 2026-03-08
### Added
- 🔐 **Custom OIDC logout endpoint.** Administrators can now configure a custom OpenID Connect logout URL via OPENID_END_SESSION_ENDPOINT, enabling logout functionality for OIDC providers that require custom endpoints like AWS Cognito. [Commit](https://github.com/open-webui/open-webui/commit/3f350f865920daf2844769a758b2d2e6a7ee3efa)
- 🗄️ **MariaDB Vector community support.** Added MariaDB Vector as a new vector database backend, enabling deployments with VECTOR_DB=mariadb-vector; supports cosine and euclidean distance strategies with configurable HNSW indexing. [#21931](https://github.com/open-webui/open-webui/pull/21931)
- 📝 **Task message truncation.** Chat messages sent to task models for title and tag generation can now be truncated using a filter in the prompt template, reducing token usage and processing time for long conversations. [#21499](https://github.com/open-webui/open-webui/issues/21499)
- 🔄 **General improvements.** Various improvements were implemented across the application to enhance performance, stability, and security.
- 🌐 Translations for Portuguese (Brazil), Spanish, and Malay were enhanced and expanded.
### Fixed
- 🔗 **Pipeline filter HTTP errors.** Fixed a bug where HTTP errors in pipeline inlet/outlet filters would silently corrupt the user's chat payload; errors are now properly raised before parsing the response. [#22445](https://github.com/open-webui/open-webui/pull/22445)
- 📚 **Knowledge file embedding updates.** Fixed a bug where updating knowledge files left old embeddings in the database, causing search results to include duplicate and stale data. [#20558](https://github.com/open-webui/open-webui/issues/20558)
- 📁 **Files list stability.** Fixed the files list ordering to use created_at with id as secondary sort, ensuring consistent ordering and preventing page crashes when managing many files. [#21879](https://github.com/open-webui/open-webui/issues/21879)
- 📨 **Teams webhook crash.** Fixed a TypeError crash in the Teams webhook handler when user data is missing from the event payload. [#22444](https://github.com/open-webui/open-webui/pull/22444)
- 🛠️ **Process shutdown handling.** Fixed bare except clauses in the main process that prevented clean shutdown; replaced with proper exception handling. [#22423](https://github.com/open-webui/open-webui/pull/22423)
- 🐳 **Docker deployment startup.** Docker deployments now start correctly; the missing OpenTelemetry system metrics dependency was added. [#22447](https://github.com/open-webui/open-webui/pull/22447), [#22401](https://github.com/open-webui/open-webui/issues/22401)
- 🛠️ **Tool access for non-admin users.** Fixed a NameError that prevented non-admin users from viewing tools; the missing has_access function is now properly imported. [#22393](https://github.com/open-webui/open-webui/issues/22393)
- 🔐 **OAuth error handling.** Fixed a bug where bare except clauses silently caught SystemExit and KeyboardInterrupt, preventing clean process shutdown during OAuth authentication. [#22420](https://github.com/open-webui/open-webui/pull/22420)
- 🛠️ **Exception error messages.** Fixed three locations where incorrect exception raising caused confusing TypeError messages instead of proper error descriptions, making debugging much easier. [#22446](https://github.com/open-webui/open-webui/pull/22446)
- 📄 **YAML file processing.** Fixed an error when uploading YAML files with Docling enabled; YAML and YML files are now properly recognized as text files and processed correctly. [#22399](https://github.com/open-webui/open-webui/pull/22399), [#22263](https://github.com/open-webui/open-webui/issues/22263)
- 📅 **Time range month names.** Fixed month names in time range labels appearing in the wrong language when OS regional settings differ from browser language; month names now consistently display in English. [#22454](https://github.com/open-webui/open-webui/pull/22454)
- 🔐 **OAuth error URL encoding.** Fixed OAuth error messages with special characters causing malformed redirect URLs; error messages are now properly URL-encoded. [#22415](https://github.com/open-webui/open-webui/pull/22415)
- 🛠️ **Internal tool method filtering.** Tools no longer expose internal methods starting with underscore to the LLM, reducing clutter and improving accuracy. [#22408](https://github.com/open-webui/open-webui/pull/22408)
- 🔊 **Azure TTS locale extraction.** Fixed Azure text-to-speech using incomplete locale codes in SSML; now correctly uses full locale like "en-US" instead of just "en". [#22443](https://github.com/open-webui/open-webui/pull/22443)
- 🎤 **Azure speech transcription errors.** Improved Azure AI Speech error handling to display user-friendly messages instead of generic connection errors; empty transcripts, no language identified, and other Azure-specific errors now show clear descriptions. [#20485](https://github.com/open-webui/open-webui/issues/20485)
- 📊 **Analytics group filtering.** Fixed token usage analytics not being filtered by user group; the query now properly respects group filters like other analytics metrics. [#22167](https://github.com/open-webui/open-webui/pull/22167)
- 🔍 **Web search favicon fallback.** Fixed web search sources showing broken image icons when favicons couldn't be loaded from external sources; now falls back to the default Open WebUI favicon. [#21897](https://github.com/open-webui/open-webui/pull/21897)
- 🔄 **Custom model fallback.** Fixed custom model fallback not working when the base model is unavailable; the base model ID is now correctly retrieved from model info instead of empty params. [#22456](https://github.com/open-webui/open-webui/issues/22456)
- 🖼️ **Pending message image display.** Fixed images in queued messages appearing blank; image thumbnails are now properly displayed in the pending message queue. [#22256](https://github.com/open-webui/open-webui/issues/22256)
- 🛠️ **File metadata sanitization.** Fixed file uploads failing with JSON serialization errors when metadata contained non-serializable objects like callable functions; metadata is now sanitized before database insertion. [#20561](https://github.com/open-webui/open-webui/issues/20561)
## [0.8.9] - 2026-03-07
### Added
+1
View File
@@ -127,6 +127,7 @@ RUN chown -R $UID:$GID /app $HOME
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git build-essential pandoc gcc netcat-openbsd curl jq \
libmariadb-dev \
python3-dev \
ffmpeg libsm6 libxext6 zstd \
&& rm -rf /var/lib/apt/lists/*
+88 -2
View File
@@ -469,6 +469,12 @@ OPENID_PROVIDER_URL = PersistentConfig(
os.environ.get("OPENID_PROVIDER_URL", ""),
)
OPENID_END_SESSION_ENDPOINT = PersistentConfig(
"OPENID_END_SESSION_ENDPOINT",
"oauth.oidc.end_session_endpoint",
os.environ.get("OPENID_END_SESSION_ENDPOINT", ""),
)
OPENID_REDIRECT_URI = PersistentConfig(
"OPENID_REDIRECT_URI",
"oauth.oidc.redirect_uri",
@@ -844,13 +850,18 @@ def load_oauth_providers():
if FEISHU_CLIENT_ID.value:
configured_providers.append("Feishu")
if configured_providers and not OPENID_PROVIDER_URL.value:
if (
configured_providers
and not OPENID_PROVIDER_URL.value
and not OPENID_END_SESSION_ENDPOINT.value
):
provider_list = ", ".join(configured_providers)
log.warning(
f"⚠️ OAuth providers configured ({provider_list}) but OPENID_PROVIDER_URL not set - logout will not work!"
)
log.warning(
f"Set OPENID_PROVIDER_URL to your OAuth provider's OpenID Connect discovery endpoint to fix logout functionality."
f"Set OPENID_PROVIDER_URL to your OAuth provider's OpenID Connect discovery endpoint,"
f" or set OPENID_END_SESSION_ENDPOINT to a custom logout URL to fix logout functionality."
)
@@ -2359,6 +2370,81 @@ if VECTOR_DB == "chroma":
CHROMA_HTTP_SSL = os.environ.get("CHROMA_HTTP_SSL", "false").lower() == "true"
# this uses the model defined in the Dockerfile ENV variable. If you dont use docker or docker based deployments such as k8s, the default embedding model will be used (sentence-transformers/all-MiniLM-L6-v2)
# MariaDB Vector (mariadb-vector)
MARIADB_VECTOR_DB_URL = os.environ.get("MARIADB_VECTOR_DB_URL", "").strip()
MARIADB_VECTOR_INITIALIZE_MAX_VECTOR_LENGTH = int(
os.environ.get("MARIADB_VECTOR_INITIALIZE_MAX_VECTOR_LENGTH", "1536").strip()
or "1536"
)
# Distance strategy:
# - cosine => vec_distance_cosine(...)
# - euclidean => vec_distance_euclidean(...)
MARIADB_VECTOR_DISTANCE_STRATEGY = (
os.environ.get("MARIADB_VECTOR_DISTANCE_STRATEGY", "cosine").strip().lower()
)
# HNSW M parameter (MariaDB VECTOR INDEX ... M=<int>)
MARIADB_VECTOR_INDEX_M = int(
os.environ.get("MARIADB_VECTOR_INDEX_M", "8").strip() or "8"
)
# Pooling (MariaDB-Vector)
MARIADB_VECTOR_POOL_SIZE = os.environ.get("MARIADB_VECTOR_POOL_SIZE", None)
if MARIADB_VECTOR_POOL_SIZE != None:
try:
MARIADB_VECTOR_POOL_SIZE = int(MARIADB_VECTOR_POOL_SIZE)
except Exception:
MARIADB_VECTOR_POOL_SIZE = None
MARIADB_VECTOR_POOL_MAX_OVERFLOW = os.environ.get("MARIADB_VECTOR_POOL_MAX_OVERFLOW", 0)
if MARIADB_VECTOR_POOL_MAX_OVERFLOW == "":
MARIADB_VECTOR_POOL_MAX_OVERFLOW = 0
else:
try:
MARIADB_VECTOR_POOL_MAX_OVERFLOW = int(MARIADB_VECTOR_POOL_MAX_OVERFLOW)
except Exception:
MARIADB_VECTOR_POOL_MAX_OVERFLOW = 0
MARIADB_VECTOR_POOL_TIMEOUT = os.environ.get("MARIADB_VECTOR_POOL_TIMEOUT", 30)
if MARIADB_VECTOR_POOL_TIMEOUT == "":
MARIADB_VECTOR_POOL_TIMEOUT = 30
else:
try:
MARIADB_VECTOR_POOL_TIMEOUT = int(MARIADB_VECTOR_POOL_TIMEOUT)
except Exception:
MARIADB_VECTOR_POOL_TIMEOUT = 30
MARIADB_VECTOR_POOL_RECYCLE = os.environ.get("MARIADB_VECTOR_POOL_RECYCLE", 3600)
if MARIADB_VECTOR_POOL_RECYCLE == "":
MARIADB_VECTOR_POOL_RECYCLE = 3600
else:
try:
MARIADB_VECTOR_POOL_RECYCLE = int(MARIADB_VECTOR_POOL_RECYCLE)
except Exception:
MARIADB_VECTOR_POOL_RECYCLE = 3600
ENABLE_MARIADB_VECTOR = True
if VECTOR_DB == "mariadb-vector":
if not MARIADB_VECTOR_DB_URL:
ENABLE_MARIADB_VECTOR = False
else:
try:
parsed = urlparse(MARIADB_VECTOR_DB_URL)
scheme = (parsed.scheme or "").lower()
# Require official driver so VECTOR binds as float32 bytes correctly
if scheme != "mariadb+mariadbconnector":
ENABLE_MARIADB_VECTOR = False
except Exception:
ENABLE_MARIADB_VECTOR = False
# Milvus
MILVUS_URI = os.environ.get("MILVUS_URI", f"{DATA_DIR}/vector_db/milvus.db")
MILVUS_DB = os.environ.get("MILVUS_DB", "default")
+4 -4
View File
@@ -1732,8 +1732,8 @@ async def chat_completion(
}
# Check base model existence for custom models
if model_info_params.get("base_model_id"):
base_model_id = model_info_params.get("base_model_id")
if model_info and model_info.base_model_id:
base_model_id = model_info.base_model_id
if base_model_id not in request.app.state.MODELS:
if ENABLE_CUSTOM_MODEL_FALLBACK:
default_models = (
@@ -1864,7 +1864,7 @@ async def chat_completion(
"model": model_id,
},
)
except:
except Exception:
pass
ctx = build_chat_response_context(
@@ -1911,7 +1911,7 @@ async def chat_completion(
{"type": "chat:tasks:cancel"},
)
except:
except Exception:
pass
finally:
try:
@@ -420,11 +420,13 @@ class ChatMessageTable:
self,
start_date: Optional[int] = None,
end_date: Optional[int] = None,
group_id: Optional[str] = None,
db: Optional[Session] = None,
) -> dict[str, dict]:
"""Aggregate token usage by user using database-level aggregation."""
with get_db_context(db) as db:
from sqlalchemy import func, cast, Integer
from open_webui.models.groups import GroupMember
dialect = db.bind.dialect.name
@@ -464,6 +466,13 @@ class ChatMessageTable:
query = query.filter(ChatMessage.created_at >= start_date)
if end_date:
query = query.filter(ChatMessage.created_at <= end_date)
if group_id:
group_users = (
db.query(GroupMember.user_id)
.filter(GroupMember.group_id == group_id)
.subquery()
)
query = query.filter(ChatMessage.user_id.in_(group_users))
results = query.group_by(ChatMessage.user_id).all()
+11 -3
View File
@@ -4,6 +4,7 @@ from typing import Optional
from sqlalchemy.orm import Session
from open_webui.internal.db import Base, JSONField, get_db, get_db_context
from open_webui.utils.misc import sanitize_metadata
from pydantic import BaseModel, ConfigDict, model_validator
from sqlalchemy import BigInteger, Column, String, Text, JSON
@@ -127,9 +128,16 @@ class FilesTable:
self, user_id: str, form_data: FileForm, db: Optional[Session] = None
) -> Optional[FileModel]:
with get_db_context(db) as db:
file_data = form_data.model_dump()
# Sanitize meta to remove non-JSON-serializable objects
# (e.g. callable tool functions, MCP client instances from middleware)
if file_data.get("meta"):
file_data["meta"] = sanitize_metadata(file_data["meta"])
file = FileModel(
**{
**form_data.model_dump(),
**file_data,
"user_id": user_id,
"created_at": int(time.time()),
"updated_at": int(time.time()),
@@ -289,7 +297,7 @@ class FilesTable:
db: Optional database session.
Returns:
List of matching FileModel objects, ordered by updated_at descending.
List of matching FileModel objects, ordered by created_at descending.
"""
with get_db_context(db) as db:
query = db.query(File)
@@ -303,7 +311,7 @@ class FilesTable:
return [
FileModel.model_validate(file)
for file in query.order_by(File.updated_at.desc())
for file in query.order_by(File.created_at.desc(), File.id.desc())
.offset(skip)
.limit(limit)
.all()
@@ -86,6 +86,9 @@ known_source_ext = [
"hs",
"lhs",
"json",
"yaml",
"yml",
"toml",
]
+6 -2
View File
@@ -590,7 +590,9 @@ def generate_openai_batch_embeddings(
if "data" in data:
return [elem["embedding"] for elem in data["data"]]
else:
raise "Something went wrong :/"
raise ValueError(
"Unexpected OpenAI embeddings response: missing 'data' key"
)
except Exception as e:
log.exception(f"Error generating openai batch embeddings: {e}")
return None
@@ -767,7 +769,9 @@ def generate_ollama_batch_embeddings(
if "embeddings" in data:
return data["embeddings"]
else:
raise "Something went wrong :/"
raise ValueError(
"Unexpected Ollama embeddings response: missing 'embeddings' key"
)
except Exception as e:
log.exception(f"Error generating ollama batch embeddings: {e}")
return None
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
from elasticsearch import Elasticsearch, BadRequestError
from typing import Optional
import ssl
@@ -0,0 +1,593 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
from __future__ import annotations
import array
import json
import logging
import math
import re
import sys
from contextlib import contextmanager
from typing import Any, Dict, List, Optional, Tuple
from sqlalchemy import create_engine
from sqlalchemy.pool import NullPool, QueuePool
from open_webui.config import (
MARIADB_VECTOR_DB_URL,
MARIADB_VECTOR_DISTANCE_STRATEGY,
MARIADB_VECTOR_INDEX_M,
MARIADB_VECTOR_INITIALIZE_MAX_VECTOR_LENGTH,
MARIADB_VECTOR_POOL_SIZE,
MARIADB_VECTOR_POOL_MAX_OVERFLOW,
MARIADB_VECTOR_POOL_TIMEOUT,
MARIADB_VECTOR_POOL_RECYCLE,
)
from open_webui.retrieval.vector.main import (
GetResult,
SearchResult,
VectorDBBase,
VectorItem,
)
from open_webui.retrieval.vector.utils import process_metadata
log = logging.getLogger(__name__)
VECTOR_LENGTH = int(MARIADB_VECTOR_INITIALIZE_MAX_VECTOR_LENGTH)
def _embedding_to_f32_bytes(vec: List[float]) -> bytes:
"""
Convert a Python float vector into the binary payload expected by MariaDB VECTOR.
MariaDB Vector expects the vector argument to be bound as a little-endian float32
byte sequence. We use array('f') to avoid a numpy dependency and byteswap on
big-endian platforms for portability.
"""
a = array.array("f", [float(x) for x in vec]) # float32
if sys.byteorder != "little":
a.byteswap()
return a.tobytes()
def _safe_json(v: Any) -> Dict[str, Any]:
"""
Normalize a potentially JSON-like value into a Python dict.
Accepts:
- dict: returned as-is
- str / bytes: parsed as JSON if possible
- None / other types: returns {}
"""
if v is None:
return {}
if isinstance(v, dict):
return v
if isinstance(v, (bytes, bytearray)):
try:
v = v.decode("utf-8")
except Exception:
return {}
if isinstance(v, str):
try:
j = json.loads(v)
return j if isinstance(j, dict) else {}
except Exception:
return {}
return {}
class MariaDBVectorClient(VectorDBBase):
"""
MariaDB + MariaDB Vector backend using DBAPI cursor parameter binding.
IMPORTANT:
- Intended for: mariadb+mariadbconnector://... (official MariaDB driver).
- Uses qmark ("?") params and binds vectors as float32 bytes.
- Uses binary binding for BOTH inserts/updates and distance computations.
"""
def __init__(
self,
db_url: Optional[str] = None,
vector_length: int = VECTOR_LENGTH,
distance_strategy: str = MARIADB_VECTOR_DISTANCE_STRATEGY,
index_m: int = MARIADB_VECTOR_INDEX_M,
) -> None:
"""
Initialize a MariaDB Vector-backed VectorDBBase implementation.
Validates URL scheme/driver requirements, ensures schema exists, and guards
against dimension mismatch with an existing VECTOR(n) column.
"""
self.db_url = (db_url or MARIADB_VECTOR_DB_URL).strip()
self.vector_length = int(vector_length)
self.distance_strategy = (distance_strategy or "cosine").strip().lower()
self.index_m = int(index_m)
if self.distance_strategy not in {"cosine", "euclidean"}:
raise ValueError("distance_strategy must be 'cosine' or 'euclidean'")
if not self.db_url.lower().startswith("mariadb+mariadbconnector://"):
raise ValueError(
"MariaDBVectorClient requires mariadb+mariadbconnector:// (official MariaDB driver) "
"to ensure qmark paramstyle and correct VECTOR binding."
)
if isinstance(MARIADB_VECTOR_POOL_SIZE, int):
if MARIADB_VECTOR_POOL_SIZE > 0:
self.engine = create_engine(
self.db_url,
pool_size=MARIADB_VECTOR_POOL_SIZE,
max_overflow=MARIADB_VECTOR_POOL_MAX_OVERFLOW,
pool_timeout=MARIADB_VECTOR_POOL_TIMEOUT,
pool_recycle=MARIADB_VECTOR_POOL_RECYCLE,
pool_pre_ping=True,
poolclass=QueuePool,
)
else:
self.engine = create_engine(
self.db_url, pool_pre_ping=True, poolclass=NullPool
)
else:
self.engine = create_engine(self.db_url, pool_pre_ping=True)
self._init_schema()
self._check_vector_length()
@contextmanager
def _connect(self):
"""
Yield a context-managed DBAPI connection (SQLAlchemy raw_connection()).
Callers can use:
with self._connect() as conn:
with conn.cursor() as cur:
...
"""
conn = self.engine.raw_connection()
try:
yield conn
finally:
try:
conn.close()
except Exception:
pass
def _init_schema(self) -> None:
"""
Create the backing table and vector index if they do not exist.
Uses a PK definition compatible with MariaDB Vector's VECTOR INDEX key-size constraints.
"""
with self._connect() as conn:
with conn.cursor() as cur:
try:
dist = self.distance_strategy
cur.execute(f"""
CREATE TABLE IF NOT EXISTS document_chunk (
-- MariaDB Vector requires the table PRIMARY KEY used with a VECTOR INDEX to be <= 256 bytes.
-- VARCHAR has internal length/metadata overhead, so VARCHAR(255) can exceed the 256-byte limit.
-- We use VARCHAR(254) to stay safely under the limit, and force ASCII (1 byte/char) so the byte
-- size is predictable (avoid utf8mb4 where a "255 char" key could be up to 1020 bytes).
-- ascii_bin gives bytewise, case-sensitive comparisons for stable ID matching.
id VARCHAR(254) CHARACTER SET ascii COLLATE ascii_bin PRIMARY KEY,
embedding VECTOR({self.vector_length}) NOT NULL,
collection_name VARCHAR(255) NOT NULL,
text LONGTEXT NULL,
vmetadata JSON NULL,
VECTOR INDEX (embedding) M={self.index_m} DISTANCE={dist},
INDEX idx_document_chunk_collection_name (collection_name)
) ENGINE=InnoDB;
""")
conn.commit()
except Exception as e:
conn.rollback()
log.exception(f"Error during database initialization: {e}")
raise
def _check_vector_length(self) -> None:
"""
Validate that the existing VECTOR column dimension matches this client's configured dimension.
Dimension guard: if table already exists with
a different VECTOR(n), refuse to silently mismatch.
"""
with self._connect() as conn:
with conn.cursor() as cur:
cur.execute("SHOW CREATE TABLE document_chunk")
row = cur.fetchone()
if not row or len(row) < 2:
return
ddl = row[1]
m = re.search(r"vector\\((\\d+)\\)", ddl, flags=re.IGNORECASE)
if not m:
return
existing = int(m.group(1))
if existing != int(self.vector_length):
raise Exception(
f"VECTOR_LENGTH {self.vector_length} does not match existing vector column dimension {existing}. "
"Cannot change vector size after initialization without migrating the data."
)
def adjust_vector_length(self, vector: List[float]) -> List[float]:
"""
Pad or truncate a vector to match `self.vector_length`.
"""
n = len(vector)
if n < self.vector_length:
return vector + [0.0] * (self.vector_length - n)
if n > self.vector_length:
return vector[: self.vector_length]
return vector
def _dist_fn(self) -> str:
"""
Return the MariaDB Vector distance function name for the configured strategy.
"""
return (
"vec_distance_cosine"
if self.distance_strategy == "cosine"
else "vec_distance_euclidean"
)
def _score_from_dist(self, dist: float) -> float:
"""
Convert a DB distance value into a normalized score in (0, 1].
- cosine: score ~= 1 - cosine_distance, clamped to [0, 1]
- euclidean: score = 1 / (1 + dist)
"""
if self.distance_strategy == "cosine":
score = 1.0 - dist
if score < 0.0:
score = 0.0
if score > 1.0:
score = 1.0
return score
return 1.0 / (1.0 + max(0.0, dist))
def _build_filter_sql_qmark(self, expr: Any) -> Tuple[str, List[Any]]:
"""
Build a WHERE-clause fragment and qmark params from a minimal Mongo-like filter.
Supported forms:
- {"field": "v"}
- {"field": {"$in": ["a","b"]}}
- {"$and": [ ... ]}
- {"$or": [ ... ]}
"""
if not expr or not isinstance(expr, dict):
return "", []
if "$and" in expr:
parts: List[str] = []
params: List[Any] = []
for e in expr.get("$and") or []:
s, p = self._build_filter_sql_qmark(e)
if s:
parts.append(s)
params.extend(p)
return ("(" + " AND ".join(parts) + ")") if parts else "", params
if "$or" in expr:
parts: List[str] = []
params: List[Any] = []
for e in expr.get("$or") or []:
s, p = self._build_filter_sql_qmark(e)
if s:
parts.append(s)
params.extend(p)
return ("(" + " OR ".join(parts) + ")") if parts else "", params
clauses: List[str] = []
params: List[Any] = []
for key, value in expr.items():
if key.startswith("$"):
continue
json_expr = f"JSON_UNQUOTE(JSON_EXTRACT(vmetadata, '$.{key}'))"
if isinstance(value, dict) and "$in" in value:
vals = [str(v) for v in (value.get("$in") or [])]
if not vals:
clauses.append("0=1")
continue
ors = []
for v in vals:
ors.append(f"{json_expr} = ?")
params.append(v)
clauses.append("(" + " OR ".join(ors) + ")")
else:
clauses.append(f"{json_expr} = ?")
params.append(str(value))
return ("(" + " AND ".join(clauses) + ")") if clauses else "", params
def insert(self, collection_name: str, items: List[VectorItem]) -> None:
"""
Insert items into the given collection (best-effort, ignores duplicates).
Uses executemany() with binary VECTOR binding for high-throughput ingestion.
"""
if not items:
return
with self._connect() as conn:
with conn.cursor() as cur:
try:
sql = """
INSERT IGNORE INTO document_chunk
(id, embedding, collection_name, text, vmetadata)
VALUES
(?, ?, ?, ?, ?)
"""
params: List[Tuple[Any, ...]] = []
for item in items:
v = self.adjust_vector_length(item["vector"])
emb = _embedding_to_f32_bytes(v)
meta = process_metadata(item.get("metadata") or {})
params.append(
(
item["id"],
emb,
collection_name,
item.get("text"),
json.dumps(meta),
)
)
cur.executemany(sql, params)
conn.commit()
except Exception as e:
conn.rollback()
log.exception(f"Error during insert: {e}")
raise
def upsert(self, collection_name: str, items: List[VectorItem]) -> None:
"""
Insert or update items in the given collection by primary key.
Uses executemany() and updates embedding/text/metadata on conflicts.
"""
if not items:
return
with self._connect() as conn:
with conn.cursor() as cur:
try:
sql = """
INSERT INTO document_chunk
(id, embedding, collection_name, text, vmetadata)
VALUES
(?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
embedding = VALUES(embedding),
collection_name = VALUES(collection_name),
text = VALUES(text),
vmetadata = VALUES(vmetadata)
"""
params: List[Tuple[Any, ...]] = []
for item in items:
v = self.adjust_vector_length(item["vector"])
emb = _embedding_to_f32_bytes(v)
meta = process_metadata(item.get("metadata") or {})
params.append(
(
item["id"],
emb,
collection_name,
item.get("text"),
json.dumps(meta),
)
)
cur.executemany(sql, params)
conn.commit()
except Exception as e:
conn.rollback()
log.exception(f"Error during upsert: {e}")
raise
def search(
self,
collection_name: str,
vectors: List[List[float]],
filter: Optional[Dict[str, Any]] = None,
limit: int = 10,
) -> Optional[SearchResult]:
"""
Perform a vector similarity search.
Args:
collection_name: Logical collection partition key.
vectors: One or more query vectors.
filter: Optional metadata filter (Mongo-like subset).
limit: Top-k per query vector.
Returns a SearchResult where distances are normalized scores (higher is better).
"""
if not vectors:
return None
dist_fn = self._dist_fn()
ids: List[List[str]] = [[] for _ in vectors]
distances: List[List[float]] = [[] for _ in vectors]
documents: List[List[str]] = [[] for _ in vectors]
metadatas: List[List[Any]] = [[] for _ in vectors]
try:
with self._connect() as conn:
with conn.cursor() as cur:
fsql, fparams = self._build_filter_sql_qmark(filter or {})
where = "collection_name = ?"
base_params: List[Any] = [collection_name]
if fsql:
where = where + " AND " + fsql
base_params.extend(fparams)
sql = f"""
SELECT
id,
text,
vmetadata,
{dist_fn}(embedding, ?) AS dist
FROM document_chunk
WHERE {where}
ORDER BY dist ASC
LIMIT ?
"""
for q_idx, q in enumerate(vectors):
qv = self.adjust_vector_length(q)
qbin = _embedding_to_f32_bytes(qv)
params = [qbin] + list(base_params) + [int(limit)]
cur.execute(sql, params)
rows = cur.fetchall()
for r in rows:
rid, rtext, rmeta, rdist = r[0], r[1], r[2], r[3]
ids[q_idx].append(str(rid))
try:
dist = float(rdist) if rdist is not None else 1.0
except Exception:
dist = 1.0
if math.isnan(dist) or math.isinf(dist):
dist = 1.0
distances[q_idx].append(self._score_from_dist(dist))
documents[q_idx].append(rtext)
metadatas[q_idx].append(_safe_json(rmeta))
return SearchResult(
ids=ids,
distances=distances,
documents=documents,
metadatas=metadatas,
)
except Exception as e:
log.exception(f"[MARIADB_VECTOR] search() failed: {e}")
return None
def query(
self, collection_name: str, filter: Dict[str, Any], limit: Optional[int] = None
) -> Optional[GetResult]:
"""
Retrieve documents by metadata filter (non-vector query).
"""
with self._connect() as conn:
with conn.cursor() as cur:
fsql, fparams = self._build_filter_sql_qmark(filter or {})
where = "collection_name = ?"
params: List[Any] = [collection_name]
if fsql:
where = where + " AND " + fsql
params.extend(fparams)
sql = f"SELECT id, text, vmetadata FROM document_chunk WHERE {where}"
if limit is not None:
sql += " LIMIT ?"
params.append(int(limit))
cur.execute(sql, params)
rows = cur.fetchall()
if not rows:
return None
ids = [[str(r[0]) for r in rows]]
documents = [[r[1] for r in rows]]
metadatas = [[_safe_json(r[2]) for r in rows]]
return GetResult(ids=ids, documents=documents, metadatas=metadatas)
def get(
self, collection_name: str, limit: Optional[int] = None
) -> Optional[GetResult]:
"""
Retrieve documents in a collection without filtering (optionally limited).
"""
with self._connect() as conn:
with conn.cursor() as cur:
sql = "SELECT id, text, vmetadata FROM document_chunk WHERE collection_name = ?"
params: List[Any] = [collection_name]
if limit is not None:
sql += " LIMIT ?"
params.append(int(limit))
cur.execute(sql, params)
rows = cur.fetchall()
if not rows:
return None
ids = [[str(r[0]) for r in rows]]
documents = [[r[1] for r in rows]]
metadatas = [[_safe_json(r[2]) for r in rows]]
return GetResult(ids=ids, documents=documents, metadatas=metadatas)
def delete(
self,
collection_name: str,
ids: Optional[List[str]] = None,
filter: Optional[Dict[str, Any]] = None,
) -> None:
"""
Delete rows from a collection by id list and/or metadata filter.
If both are provided, they are combined with AND semantics.
"""
with self._connect() as conn:
with conn.cursor() as cur:
try:
where = ["collection_name = ?"]
params: List[Any] = [collection_name]
if ids:
ph = ", ".join(["?"] * len(ids))
where.append(f"id IN ({ph})")
params.extend(ids)
if filter:
fsql, fparams = self._build_filter_sql_qmark(filter)
if fsql:
where.append(fsql)
params.extend(fparams)
sql = "DELETE FROM document_chunk WHERE " + " AND ".join(where)
cur.execute(sql, params)
conn.commit()
except Exception as e:
conn.rollback()
log.exception(f"Error during delete: {e}")
raise
def reset(self) -> None:
"""
Truncate the vector table (drops all collections).
"""
with self._connect() as conn:
with conn.cursor() as cur:
try:
cur.execute("TRUNCATE TABLE document_chunk")
conn.commit()
except Exception as e:
conn.rollback()
log.exception(f"Error during reset: {e}")
raise
def has_collection(self, collection_name: str) -> bool:
"""
Return True if the collection contains at least one row, else False.
"""
try:
with self._connect() as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT 1 FROM document_chunk WHERE collection_name = ? LIMIT 1",
(collection_name,),
)
return cur.fetchone() is not None
except Exception:
return False
def delete_collection(self, collection_name: str) -> None:
"""
Delete all rows in a collection.
"""
self.delete(collection_name)
def close(self) -> None:
"""
Dispose the underlying SQLAlchemy engine.
"""
try:
self.engine.dispose()
except Exception as e:
log.exception(f"Error during dispose the underlying SQLAlchemy engine: {e}")
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
from pymilvus import MilvusClient as Client
from pymilvus import FieldSchema, DataType
from pymilvus import connections, Collection
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
import logging
from typing import Optional, Tuple, List, Dict, Any
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
from typing import Optional, List, Dict, Any
import logging
import re
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
from opensearchpy import OpenSearch
from opensearchpy.helpers import bulk
from typing import Optional
@@ -1,4 +1,6 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
Oracle 23ai Vector Database Client - Fixed Version
# .env
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
from typing import Optional, List, Dict, Any, Union
import logging
import time # for measuring elapsed time
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
from typing import Optional
import logging
from urllib.parse import urlparse
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
import logging
from typing import Optional, Tuple, List, Dict, Any
from urllib.parse import urlparse
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
from open_webui.retrieval.vector.utils import process_metadata
from open_webui.retrieval.vector.main import (
VectorDBBase,
@@ -61,6 +65,11 @@ class S3VectorClient(VectorDBBase):
dataType=data_type,
dimension=dimension,
distanceMetric=distance_metric,
metadataConfiguration={
"nonFilterableMetadataKeys": [
"text",
]
},
)
log.info(
f"Created S3 index: {index_name} (dim={dimension}, type={data_type}, metric={distance_metric})"
@@ -1,3 +1,7 @@
"""
NOTE: This vector database integration is community-supported and maintained on a best-effort basis.
"""
import weaviate
import re
import uuid
@@ -57,6 +57,12 @@ class Vector:
from open_webui.retrieval.vector.dbs.opengauss import OpenGaussClient
return OpenGaussClient()
case VectorType.MARIADB_VECTOR:
from open_webui.retrieval.vector.dbs.mariadb_vector import (
MariaDBVectorClient,
)
return MariaDBVectorClient()
case VectorType.ELASTICSEARCH:
from open_webui.retrieval.vector.dbs.elasticsearch import (
ElasticsearchClient,
@@ -3,6 +3,7 @@ from enum import StrEnum
class VectorType(StrEnum):
MILVUS = "milvus"
MARIADB_VECTOR = "mariadb-vector"
QDRANT = "qdrant"
CHROMA = "chroma"
PINECONE = "pinecone"
+1 -1
View File
@@ -86,7 +86,7 @@ async def get_user_analytics(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
token_usage = ChatMessages.get_token_usage_by_user(
start_date=start_date, end_date=end_date, db=db
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
# Get user info for top users
+26 -3
View File
@@ -492,7 +492,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
region = request.app.state.config.TTS_AZURE_SPEECH_REGION or "eastus"
base_url = request.app.state.config.TTS_AZURE_SPEECH_BASE_URL
language = request.app.state.config.TTS_VOICE
locale = "-".join(request.app.state.config.TTS_VOICE.split("-")[:1])
locale = "-".join(request.app.state.config.TTS_VOICE.split("-")[:2])
output_format = request.app.state.config.TTS_AZURE_SPEECH_OUTPUT_FORMAT
try:
@@ -852,17 +852,34 @@ def transcription_handler(request, file_path, metadata, user=None):
except requests.exceptions.RequestException as e:
log.exception(e)
detail = None
status_code = getattr(r, "status_code", 500) if r else 500
try:
if r is not None and r.status_code != 200:
res = r.json()
if "error" in res:
# Azure returns {"code": "...", "message": "...", "innerError": {...}}
if "code" in res and "message" in res:
azure_code = res.get("innerError", {}).get("code", res["code"])
user_facing_codes = {
"EmptyAudioFile",
"AudioLengthLimitExceeded",
"NoLanguageIdentified",
"MultipleLanguagesIdentified",
}
if azure_code in user_facing_codes:
detail = res["message"]
else:
log.error(
f"Azure STT error [{azure_code}]: {res['message']}"
)
detail = "An error occurred during transcription."
elif "error" in res:
detail = f"External: {res['error'].get('message', '')}"
except Exception:
detail = f"External: {e}"
raise HTTPException(
status_code=getattr(r, "status_code", 500) if r else 500,
status_code=status_code,
detail=detail if detail else "Open WebUI: Server Connection Error",
)
@@ -1087,6 +1104,8 @@ def transcribe(
for future in futures:
try:
results.append(future.result())
except HTTPException:
raise
except Exception as transcribe_exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -1226,6 +1245,8 @@ def transcription(
"filename": os.path.basename(file_path),
}
except HTTPException:
raise
except Exception as e:
log.exception(e)
@@ -1234,6 +1255,8 @@ def transcription(
detail="Transcription failed.",
)
except HTTPException:
raise
except Exception as e:
log.exception(e)
+14
View File
@@ -46,6 +46,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import RedirectResponse, Response, JSONResponse
from open_webui.config import (
OPENID_PROVIDER_URL,
OPENID_END_SESSION_ENDPOINT,
ENABLE_OAUTH_SIGNUP,
ENABLE_LDAP,
ENABLE_PASSWORD_AUTH,
@@ -824,6 +825,19 @@ async def signout(
response.delete_cookie("oauth_session_id")
session = OAuthSessions.get_session_by_id(oauth_session_id, db=db)
# If a custom end_session_endpoint is configured (e.g. AWS Cognito), redirect
# there directly instead of attempting OIDC discovery.
if OPENID_END_SESSION_ENDPOINT.value:
return JSONResponse(
status_code=200,
content={
"status": True,
"redirect_url": OPENID_END_SESSION_ENDPOINT.value,
},
headers=response.headers,
)
oauth_server_metadata_url = (
request.app.state.oauth_manager.get_server_metadata_url(session.provider)
if session
+24
View File
@@ -583,12 +583,36 @@ def update_file_data_content_by_id(
request,
ProcessFileForm(file_id=id, content=form_data.content),
user=user,
db=db,
)
file = Files.get_file_by_id(id=id, db=db)
except Exception as e:
log.exception(e)
log.error(f"Error processing file: {file.id}")
# Propagate content change to all knowledge collections referencing
# this file. Without this the old embeddings remain in the knowledge
# collection and RAG returns both stale and current data (#20558).
knowledges = Knowledges.get_knowledges_by_file_id(id, db=db)
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}
)
# Re-add from the now-updated file-{file_id} collection
process_file(
request,
ProcessFileForm(file_id=id, collection_name=knowledge.id),
user=user,
db=db,
)
except Exception as e:
log.warning(
f"Failed to update knowledge {knowledge.id} after "
f"content change for file {id}: {e}"
)
return {"content": file.data.get("content", "")}
else:
raise HTTPException(
+3 -1
View File
@@ -1747,7 +1747,9 @@ async def download_file_stream(
yield f"data: {json.dumps(res)}\n\n"
else:
raise "Ollama: Could not create blob, Please try again."
raise RuntimeError(
"Ollama: Could not create blob, Please try again."
)
# url = "https://huggingface.co/TheBloke/stablelm-zephyr-3b-GGUF/resolve/main/stablelm-zephyr-3b.Q2_K.gguf"
+2 -2
View File
@@ -92,8 +92,8 @@ async def process_pipeline_inlet_filter(request, payload, user, models):
json=request_data,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
) as response:
payload = await response.json()
response.raise_for_status()
payload = await response.json()
except aiohttp.ClientResponseError as e:
res = (
await response.json()
@@ -145,8 +145,8 @@ async def process_pipeline_outlet_filter(request, payload, user, models):
json=request_data,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
) as response:
payload = await response.json()
response.raise_for_status()
payload = await response.json()
except aiohttp.ClientResponseError as e:
try:
res = (
+5 -1
View File
@@ -30,7 +30,11 @@ from open_webui.utils.plugin import (
)
from open_webui.utils.tools import get_tool_specs
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
from open_webui.utils.access_control import (
has_permission,
has_access,
filter_allowed_access_grants,
)
from open_webui.utils.tools import get_tool_servers
from open_webui.config import CACHE_DIR, BYPASS_ADMIN_ACCESS_CONTROL
+47
View File
@@ -566,6 +566,53 @@ def sanitize_data_for_db(obj):
return obj
def sanitize_metadata(metadata: dict) -> dict:
"""
Return a JSON-safe copy of a metadata dict for database storage.
The middleware metadata accumulates non-serializable Python objects
(e.g. callable tool functions, MCP client instances) that cause
PostgreSQL JSON inserts to fail. This helper strips those out while
preserving the primitive data needed for file-to-chat linking.
"""
if not isinstance(metadata, dict):
return metadata
def _sanitize(obj):
if isinstance(obj, (str, int, float, bool, type(None))):
return obj
if isinstance(obj, dict):
return {
k: _sanitize(v)
for k, v in obj.items()
if not callable(v) and _is_serializable(v)
}
if isinstance(obj, list):
return [
_sanitize(v) for v in obj if not callable(v) and _is_serializable(v)
]
if callable(obj):
return None
# Last resort: try to see if it's serializable
try:
json.dumps(obj)
return obj
except (TypeError, ValueError):
return None
def _is_serializable(obj):
"""Quick check whether a value can survive JSON serialization."""
if isinstance(obj, (str, int, float, bool, type(None), dict, list)):
return True
try:
json.dumps(obj)
return True
except (TypeError, ValueError):
return False
return _sanitize(metadata)
def extract_folders_after_data_docs(path):
# Convert the path to a Path object if it's not already
path = Path(path)
+4 -2
View File
@@ -620,7 +620,7 @@ class OAuthClientManager:
payload = json.loads(response_text)
error = payload.get("error")
error_description = payload.get("error_description", "")
except:
except Exception:
pass
else:
error_description = response_text
@@ -1706,7 +1706,9 @@ class OAuthManager:
redirect_url = f"{redirect_base_url}/auth"
if error_message:
redirect_url = f"{redirect_url}?error={error_message}"
redirect_url = (
f"{redirect_url}?error={urllib.parse.quote_plus(error_message)}"
)
return RedirectResponse(url=redirect_url, headers=response.headers)
response = RedirectResponse(url=redirect_url, headers=response.headers)
+101 -21
View File
@@ -142,41 +142,121 @@ def replace_prompt_variable(template: str, prompt: str) -> str:
return template
def truncate_content(content: str, max_chars: int, mode: str = "middletruncate") -> str:
"""Truncate a string to max_chars using the specified mode.
Modes:
- middletruncate: keep beginning and end, join with '...'
- start: keep first max_chars characters
- end: keep last max_chars characters
"""
if not content or len(content) <= max_chars:
return content
if mode == "start":
return content[:max_chars]
elif mode == "end":
return content[-max_chars:]
else: # middletruncate
half = max_chars // 2
return f"{content[:half]}...{content[-(max_chars - half):]}"
def apply_content_filter(messages: list[dict], filter_str: str) -> list[dict]:
"""Apply a content filter to each message's content.
filter_str is like 'middletruncate:500', 'start:200', or 'end:200'.
Returns a new list with truncated content (original messages are not mutated).
"""
parts = filter_str.split(":")
if len(parts) != 2:
return messages
mode = parts[0].lower()
try:
max_chars = int(parts[1])
except ValueError:
return messages
if mode not in ("middletruncate", "start", "end"):
return messages
result = []
for msg in messages:
new_msg = dict(msg)
if isinstance(new_msg.get("content"), str):
new_msg["content"] = truncate_content(new_msg["content"], max_chars, mode)
elif isinstance(new_msg.get("content"), list):
new_content = []
for item in new_msg["content"]:
if isinstance(item, dict) and item.get("type") == "text":
new_item = dict(item)
new_item["text"] = truncate_content(
item.get("text", ""), max_chars, mode
)
new_content.append(new_item)
else:
new_content.append(item)
new_msg["content"] = new_content
result.append(new_msg)
return result
def replace_messages_variable(
template: str, messages: Optional[list[dict]] = None
) -> str:
def replacement_function(match):
full_match = match.group(0)
start_length = match.group(1)
end_length = match.group(2)
middle_length = match.group(3)
# Groups: (1) filter for bare MESSAGES
# (2) START count, (3) filter for START
# (4) END count, (5) filter for END
# (6) MIDDLE count,(7) filter for MIDDLE
bare_filter = match.group(1)
start_length = match.group(2)
start_filter = match.group(3)
end_length = match.group(4)
end_filter = match.group(5)
middle_length = match.group(6)
middle_filter = match.group(7)
# If messages is None, handle it as an empty list
if messages is None:
return ""
# Process messages based on the number of messages required
if full_match == "{{MESSAGES}}":
return get_messages_content(messages)
elif start_length is not None:
return get_messages_content(messages[: int(start_length)])
# Select messages based on the variant
if start_length is not None:
selected = messages[: int(start_length)]
content_filter = start_filter
elif end_length is not None:
return get_messages_content(messages[-int(end_length) :])
selected = messages[-int(end_length) :]
content_filter = end_filter
elif middle_length is not None:
mid = int(middle_length)
if len(messages) <= mid:
return get_messages_content(messages)
# Handle middle truncation: split to get start and end portions of the messages list
half = mid // 2
start_msgs = messages[:half]
end_msgs = messages[-half:] if mid % 2 == 0 else messages[-(half + 1) :]
formatted_start = get_messages_content(start_msgs)
formatted_end = get_messages_content(end_msgs)
return f"{formatted_start}\n{formatted_end}"
return ""
selected = messages
else:
half = mid // 2
start_msgs = messages[:half]
end_msgs = messages[-half:] if mid % 2 == 0 else messages[-(half + 1) :]
selected = start_msgs + end_msgs
content_filter = middle_filter
else:
# Bare {{MESSAGES}} or {{MESSAGES|filter}}
selected = messages
content_filter = bare_filter
# Apply content filter if present
if content_filter:
selected = apply_content_filter(selected, content_filter)
return get_messages_content(selected)
template = re.sub(
r"{{MESSAGES}}|{{MESSAGES:START:(\d+)}}|{{MESSAGES:END:(\d+)}}|{{MESSAGES:MIDDLETRUNCATE:(\d+)}}",
r"(?:"
r"\{\{MESSAGES(?:\|(\w+:\d+))?\}\}"
r"|\{\{MESSAGES:START:(\d+)(?:\|(\w+:\d+))?\}\}"
r"|\{\{MESSAGES:END:(\d+)(?:\|(\w+:\d+))?\}\}"
r"|\{\{MESSAGES:MIDDLETRUNCATE:(\d+)(?:\|(\w+:\d+))?\}\}"
r")",
replacement_function,
template,
)
+2 -2
View File
@@ -707,8 +707,8 @@ def get_functions_from_tool(tool: object) -> list[Callable]:
getattr(tool, func)
) # checks if the attribute is callable (a method or function).
and not func.startswith(
"__"
) # filters out special (dunder) methods like init, str, etc. — these are usually built-in functions of an object that you might not need to use directly.
"_"
) # filters out internal methods (starting with _) and special (dunder) methods.
and not inspect.isclass(
getattr(tool, func)
) # ensures that the callable is not a class itself, just a method or function.
+6 -2
View File
@@ -26,9 +26,13 @@ async def post_webhook(name: str, url: str, message: str, event_data: dict) -> b
# Microsoft Teams Webhooks
elif "webhook.office.com" in url:
action = event_data.get("action", "undefined")
user_data = event_data.get("user", "{}")
if isinstance(user_data, dict):
user_dict = user_data
else:
user_dict = json.loads(user_data)
facts = [
{"name": name, "value": value}
for name, value in json.loads(event_data.get("user", {})).items()
{"name": name, "value": value} for name, value in user_dict.items()
]
payload = {
"@type": "MessageCard",
+2
View File
@@ -119,6 +119,7 @@ pgvector==0.4.2
PyMySQL==1.1.2
boto3==1.42.62
mariadb==1.1.14
pymilvus==2.6.9
qdrant-client==1.17.0
@@ -155,3 +156,4 @@ opentelemetry-instrumentation-requests==0.61b0
opentelemetry-instrumentation-logging==0.61b0
opentelemetry-instrumentation-httpx==0.61b0
opentelemetry-instrumentation-aiohttp-client==0.61b0
opentelemetry-instrumentation-system-metrics==0.61b0
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "open-webui",
"version": "0.8.9",
"version": "0.8.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "open-webui",
"version": "0.8.9",
"version": "0.8.10",
"dependencies": {
"@azure/msal-browser": "^4.5.0",
"@codemirror/lang-javascript": "^6.2.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "open-webui",
"version": "0.8.9",
"version": "0.8.10",
"private": true,
"scripts": {
"dev": "npm run pyodide:fetch && vite dev --host",
+4
View File
@@ -137,11 +137,15 @@ postgres = [
"psycopg2-binary==2.9.11",
"pgvector==0.4.2",
]
mariadb = [
"mariadb==1.1.14",
]
all = [
"pymongo",
"psycopg2-binary==2.9.11",
"pgvector==0.4.2",
"mariadb==1.1.14",
"moto[s3]>=5.0.26",
"gcp-storage-emulator>=2024.8.3",
"docker~=7.1.0",
@@ -1187,6 +1187,7 @@
<QueuedMessageItem
id={queuedMessage.id}
content={queuedMessage.prompt}
files={queuedMessage.files}
onSendNow={onQueueSendNow}
onEdit={onQueueEdit}
onDelete={onQueueDelete}
@@ -1,14 +1,17 @@
<script lang="ts">
import { getContext } from 'svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Image from '$lib/components/common/Image.svelte';
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
import EditPencil from '$lib/components/icons/EditPencil.svelte';
import ArrowForward from '$lib/components/icons/ArrowForward.svelte';
import { WEBUI_API_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
export let id: string;
export let content: string;
export let files: any[] = [];
export let onSendNow: (id: string) => void;
export let onEdit: (id: string) => void;
export let onDelete: (id: string) => void;
@@ -21,8 +24,34 @@
</div>
<!-- Message content -->
<div class="flex-1 min-w-0">
<p class="text-sm text-gray-600 dark:text-gray-300 truncate">{content}</p>
<div class="flex-1 min-w-0 flex items-center gap-2">
{#if files.length > 0}
<div class="flex items-center gap-1 shrink-0">
{#each files as file}
{#if file.type === 'image' || (file?.content_type ?? '').startsWith('image/')}
{@const fileUrl =
file.url?.startsWith('data') || file.url?.startsWith('http')
? file.url
: `${WEBUI_API_BASE_URL}/files/${file.url}${file?.content_type ? '/content' : ''}`}
<Image src={fileUrl} alt="" imageClassName="size-6 rounded-md object-cover" />
{:else}
<div
class="flex items-center px-1.5 py-0.5 rounded-md bg-gray-100 dark:bg-gray-800 text-xs text-gray-500 dark:text-gray-400"
>
<span class="max-w-[80px] truncate">{file.name ?? 'file'}</span>
</div>
{/if}
{/each}
</div>
{/if}
{#if content}
<p class="text-sm text-gray-600 dark:text-gray-300 truncate">{content}</p>
{:else if files.length === 0}
<p class="text-sm text-gray-400 dark:text-gray-500 truncate italic">
{$i18n.t('Empty message')}
</p>
{/if}
</div>
<!-- Actions -->
@@ -178,6 +178,9 @@
src="https://www.google.com/s2/favicons?sz=32&domain={citation.source.name}"
alt="favicon"
class="size-4 rounded-full shrink-0 border border-white dark:border-gray-850 bg-white dark:bg-gray-900"
on:error={(e) => {
e.target.src = '/favicon.png';
}}
/>
{/each}
</div>
@@ -78,10 +78,6 @@
let chat = null;
let mouseOver = false;
let draggable = false;
$: if (mouseOver) {
loadChat();
}
const loadChat = async () => {
if (!chat) {
@@ -375,7 +371,7 @@
id="sidebar-chat-group"
bind:this={itemElement}
class=" w-full {className} relative group"
draggable={draggable && !confirmEdit}
draggable={!confirmEdit}
>
{#if confirmEdit}
<div
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "نموذج التضمين",
"Embedding Model Engine": "تضمين محرك النموذج",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
+1
View File
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "نموذج التضمين",
"Embedding Model Engine": "تضمين محرك النموذج",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "تفعيل توليد الإكمال التلقائي لرسائل الدردشة",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Модел за вграждане",
"Embedding Model Engine": "Двигател на модела за вграждане",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Активиране на автоматично довършване на съобщения в чата",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "ইমেজ ইমেবডিং মডেল",
"Embedding Model Engine": "ইমেজ ইমেবডিং মডেল ইঞ্জিন",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "ཚུད་འཇུག་དཔེ་དབྱིབས།",
"Embedding Model Engine": "ཚུད་འཇུག་དཔེ་དབྱིབས་འཕྲུལ་འཁོར།",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "ཁ་བརྡའི་འཕྲིན་ཡིག་གི་ཆེད་དུ་རང་འཚང་བཟོ་སྐྲུན་སྒུལ་བསྐྱོད་བྱེད་པ།",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Embedding model",
"Embedding Model Engine": "Embedding model pogon",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "Peticions concurrents d'incrustació",
"Embedding Model": "Model d'incrustació",
"Embedding Model Engine": "Motor de model d'incrustació",
"Empty message": "",
"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",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "",
"Embedding Model Engine": "",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Model pro vektorizaci",
"Embedding Model Engine": "Jádro modelu pro vektorizaci",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Povolit generování automatického dokončování pro zprávy v konverzaci",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Embedding Model",
"Embedding Model Engine": "Embedding Model engine",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "Aktiver API nøgler",
"Enable autocomplete generation for chat messages": "Aktiver autofuldførsel for chatbeskeder",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "Gleichzeitige Embedding Anfragen",
"Embedding Model": "Embedding-Modell",
"Embedding Model Engine": "Embedding-Modell-Engine",
"Empty message": "",
"Enable All": "Alle aktivieren",
"Enable API Keys": "API-Schlüssel aktivieren",
"Enable autocomplete generation for chat messages": "Autovervollständigung für Chat-Nachrichten aktivieren",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "",
"Embedding Model Engine": "",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Μοντέλο Ενσωμάτωσης",
"Embedding Model Engine": "Μηχανή Μοντέλου Ενσωμάτωσης",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Ενεργοποίηση αυτόματης συμπλήρωσης για συνομιλίες",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "",
"Embedding Model Engine": "",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "",
"Embedding Model Engine": "",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "Número de Peticiones Concurrentes en Incrustración",
"Embedding Model": "Modelo de Incrustación",
"Embedding Model Engine": "Motor del Modelo de Incrustación",
"Empty message": "",
"Enable All": "Habilitar Todo",
"Enable API Keys": "Habilitar Claves API",
"Enable autocomplete generation for chat messages": "Habilitar generación de autocompletado para mensajes de chat",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Manustamise mudel",
"Embedding Model Engine": "Manustamise mudeli mootor",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Luba automaattäitmise genereerimine vestlussõnumitele",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Embedding Eredua",
"Embedding Model Engine": "Embedding Eredu Motorea",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "مدل پیدائش",
"Embedding Model Engine": "محرک مدل پیدائش",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "فعال\u200cسازی تولید تکمیل خودکار برای پیام\u200cهای چت",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "Samanaikaiset upotuspyynnöt",
"Embedding Model": "Upotusmalli",
"Embedding Model Engine": "Upotusmallin moottori",
"Empty message": "",
"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ä",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Modèle d'embedding",
"Embedding Model Engine": "Moteur de modèle d'embedding",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Activer la génération des suggestions pour les messages",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Modèle d'embedding",
"Embedding Model Engine": "Moteur de modèle d'embedding",
"Empty message": "",
"Enable All": "Activer tout",
"Enable API Keys": "Autoriser les clés API",
"Enable autocomplete generation for chat messages": "Activer la génération des suggestions pour les messages",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Modelo de Embedding",
"Embedding Model Engine": "Motor de Modelo de Embedding",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Habilitar xeneración de autocompletado para mensaxes de chat",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "מודל הטמעה",
"Embedding Model Engine": "מנוע מודל הטמעה",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "मॉडेल अनुकूलन",
"Embedding Model Engine": "एंबेडिंग मॉडल इंजन",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Embedding model",
"Embedding Model Engine": "Embedding model pogon",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Beágyazási modell",
"Embedding Model Engine": "Beágyazási modell motor",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Automatikus kiegészítés engedélyezése csevegőüzenetekhez",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Model Penyematan",
"Embedding Model Engine": "Mesin Model Penyematan",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "Iarratais Chomhuaineacha a Leabú",
"Embedding Model": "Samhail Leabháilte",
"Embedding Model Engine": "Inneall Samhail Leabaithe",
"Empty message": "",
"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á",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Modello Embedding",
"Embedding Model Engine": "Motore Modello di Embedding",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Abilita generazione autocompletamento per i messaggi di chat",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "埋め込みモデル",
"Embedding Model Engine": "埋め込みモデルエンジン",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "API キーを有効にする",
"Enable autocomplete generation for chat messages": "チャットメッセージの自動補完を有効にする",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "მოდელის ჩაშენება",
"Embedding Model Engine": "ჩაშენებული მოდელის ძრავა",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Tamudemt n ujmak",
"Embedding Model Engine": "Amsedday n tmudemt n ujmak",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Rmed tasuta tawurmant tummidt i udiwenni iznan",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "임베딩 모델",
"Embedding Model Engine": "임베딩 모델 엔진",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "채팅 메시지에 대한 자동 완성 생성 활성화",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Embedding modelis",
"Embedding Model Engine": "Embedding modelio variklis",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Iegulšanas modelis",
"Embedding Model Engine": "Iegulšanas modeļa dzinējs",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "Iespējot API atslēgas",
"Enable autocomplete generation for chat messages": "Iespējot automātisko pabeigšanu tērzēšanas ziņojumiem",
File diff suppressed because it is too large Load Diff
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Innbyggingsmodell",
"Embedding Model Engine": "Motor for innbygging av modeller",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Aktiver automatisk utfylling av chatmeldinger",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Embedding Model",
"Embedding Model Engine": "Embedding Model Engine",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Automatische aanvullingsgeneratie voor chatberichten inschakelen",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "ਐਮਬੈੱਡਿੰਗ ਮਾਡਲ",
"Embedding Model Engine": "ਐਮਬੈੱਡਿੰਗ ਮਾਡਲ ਇੰਜਣ",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Model embeddingów",
"Embedding Model Engine": "Silnik modelu embeddingów",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "Włącz klucze API",
"Enable autocomplete generation for chat messages": "Włącz autouzupełnianie w czacie",
+25 -24
View File
@@ -19,11 +19,11 @@
"{{COUNT}} Rows": "{{COUNT}} Linhas",
"{{COUNT}} Sources": "{{COUNT}} Origens",
"{{COUNT}} words": "{{COUNT}} palavras",
"{{COUNT}}d_time_ago": "",
"{{COUNT}}h_time_ago": "",
"{{COUNT}}m_time_ago": "",
"{{COUNT}}w_time_ago": "",
"{{COUNT}}y_time_ago": "",
"{{COUNT}}d_time_ago": "{{COUNT}}d atrás",
"{{COUNT}}h_time_ago": "{{COUNT}}h atrás",
"{{COUNT}}m_time_ago": "{{COUNT}}m atrás",
"{{COUNT}}w_time_ago": "{{COUNT}}sem atrás",
"{{COUNT}}y_time_ago": "{{COUNT}}a atrás",
"{{LOCALIZED_DATE}} at {{LOCALIZED_TIME}}": "{{LOCALIZED_DATE}} às {{LOCALIZED_TIME}}",
"{{model}} download has been canceled": "O download do {{model}} foi cancelado",
"{{modelName}} profile image": "Imagem de perfil de {{modelName}}",
@@ -183,7 +183,7 @@
"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 message?": "Tem certeza de que deseja excluir esta mensagem?",
"Are you sure you want to delete this version? Child versions will be relinked to this version's parent.": "Tem certeza de que deseja excluir esta versão? As versões filhas serão vinculadas novamente à versão pai.",
"Are you sure you want to delete this?": "",
"Are you sure you want to delete this?": "Tem certeza de que deseja excluir isto?",
"Are you sure you want to unarchive all archived chats?": "Você tem certeza que deseja desarquivar todos os chats arquivados?",
"Arena Models": "Arena de Modelos",
"Artifacts": "Artefatos",
@@ -283,7 +283,7 @@
"Character limit for autocomplete generation input": "Limite de caracteres para entrada de geração de preenchimento automático",
"Chart new frontiers": "Trace novas fronteiras",
"Chat": "Chat",
"Chat archived.": "",
"Chat archived.": "Chat arquivado.",
"Chat Background Image": "Imagem de Fundo do Chat",
"Chat Bubble UI": "Interface de Bolha de Chat",
"Chat Completions": "Gerar Resposta",
@@ -487,7 +487,7 @@
"Default User Role": "Padrão para novos usuários",
"Defaults": "Padrões",
"Delete": "Excluir",
"Delete {{name}}": "",
"Delete {{name}}": "Excluir {{name}}",
"Delete a model": "Excluir um modelo",
"Delete All": "Excluir tudo",
"Delete All Chats": "Excluir Todos os Chats",
@@ -580,7 +580,7 @@
"Downloading stats...": "Baixando estatísticas...",
"Draw": "Empate",
"Drop any files here to upload": "Solte qualquer arquivo aqui para fazer upload",
"Drop files here": "",
"Drop files here": "Solte os arquivos aqui",
"Drop files here to upload": "Solte os arquivos aqui para fazer upload",
"DuckDuckGo": "DuckDuckGo",
"e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.": "por exemplo, '30s', '10m'. Unidades de tempo válidas são 's', 'm', 'h'.",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "Solicitações Simultâneas de Embedding",
"Embedding Model": "Modelo de Embedding",
"Embedding Model Engine": "Motor do Modelo de Embedding",
"Empty message": "",
"Enable All": "Ativar tudo",
"Enable API Keys": "Habilitar Chaves de API",
"Enable autocomplete generation for chat messages": "Habilitar geração de preenchimento automático para mensagens do chat",
@@ -826,7 +827,7 @@
"Fade Effect for Streaming Text": "Efeito de Fade para texto em streaming",
"Failed to add file.": "Falha ao adicionar arquivo.",
"Failed to add members": "Falha ao adicionar membros",
"Failed to archive chat.": "",
"Failed to archive chat.": "Falha ao arquivar o chat.",
"Failed to attach file": "Falha ao anexar arquivo",
"Failed to clear status": "Falha ao limpar o status",
"Failed to connect to {{URL}} OpenAPI tool server": "Falha ao conectar ao servidor da ferramenta OpenAPI {{URL}}",
@@ -841,11 +842,11 @@
"Failed to generate title": "Falha ao gerar título",
"Failed to import models": "Falha ao importar modelos",
"Failed to load chat preview": "Falha ao carregar a pré-visualização do chat",
"Failed to load DOCX file. Please try downloading it instead.": "",
"Failed to load DOCX file. Please try downloading it instead.": "Não foi possível carregar o arquivo DOCX. Tente baixá-lo em vez disso.",
"Failed to load Excel/CSV file. Please try downloading it instead.": "Não foi possível carregar o arquivo Excel/CSV. Tente baixá-lo em vez disso.",
"Failed to load file content.": "Falha ao carregar o conteúdo do arquivo.",
"Failed to load Interface settings": "Falha ao carregar configurações da Interface",
"Failed to load PPTX file. Please try downloading it instead.": "",
"Failed to load PPTX file. Please try downloading it instead.": "Não foi possível carregar o arquivo PPTX. Tente baixá-lo em vez disso.",
"Failed to move chat": "Falha ao mover o chat",
"Failed to process URL: {{url}}": "Falha ao processar URL: {{url}}",
"Failed to read clipboard contents": "Falha ao ler o conteúdo da área de transferência",
@@ -900,7 +901,7 @@
"Focus Chat Input": "Foco na janela do Chat",
"Folder": "Pasta",
"Folder Background Image": "Imagem de fundo da pasta",
"Folder created successfully": "",
"Folder created successfully": "Pasta criada com sucesso",
"Folder deleted successfully": "Pasta excluída com sucesso",
"Folder Max File Count": "Contagem máxima de arquivos por pasta",
"Folder name": "Nome da pasta",
@@ -1265,7 +1266,7 @@
"More options": "Mais opções",
"More Options": "Mais opções",
"Move": "Mover",
"Moved {{name}}": "",
"Moved {{name}}": "{{name}} movido",
"My Terminal": "Meu Terminal",
"Name": "Nome",
"Name and ID are required, please fill them out": "Nome e ID são obrigatórios, por favor preencha-os",
@@ -1308,13 +1309,13 @@
"No file selected": "Nenhum arquivo selecionado",
"No files found": "Nenhum arquivo encontrado",
"No files in this knowledge base.": "Não existem arquivos nesta base de conhecimento.",
"No files yet. Upload files or run Python code to create them.": "",
"No files yet. Upload files or run Python code to create them.": "Nenhum arquivo ainda. Envie arquivos ou execute código Python para criá-los.",
"No functions found": "Nenhuma função encontrada",
"No groups found": "Nenhum grupo encontrado",
"No history available": "Não há histórico disponível.",
"No HTML, CSS, or JavaScript content found.": "Nenhum conteúdo HTML, CSS ou JavaScript encontrado.",
"No inference engine with management support found": "Nenhum mecanismo de inferência com suporte de gerenciamento encontrado",
"No kernel": "",
"No kernel": "Sem kernel",
"No knowledge bases found.": "Nenhuma base de conhecimento encontrada.",
"No knowledge found": "Nenhum conhecimento encontrado",
"No memories to clear": "Nenhuma memória para limpar",
@@ -1330,7 +1331,7 @@
"No results": "Nenhum resultado encontrado",
"No results found": "Nenhum resultado encontrado",
"No search query generated": "Nenhuma consulta de pesquisa gerada",
"No servers detected": "",
"No servers detected": "Nenhum servidor detectado",
"No skills found": "Nenhuma skill encontrada",
"No source available": "Nenhuma fonte disponível",
"No sources found": "Nenhuma fonte encontrada",
@@ -1486,7 +1487,7 @@
"Please select at least one user for Direct Message channel.": "Por favor, selecione pelo menos um usuário para o canal de Mensagens Diretas.",
"Please wait until all files are uploaded.": "Aguarde até que todos os arquivos sejam enviados.",
"Port": "Porta",
"Ports": "",
"Ports": "Portas",
"Positive attitude": "Atitude positiva",
"Prefer not to say": "Prefiro não dizer",
"Prefix ID": "Prefixo ID",
@@ -1516,7 +1517,7 @@
"Pull \"{{searchValue}}\" from Ollama.com": "Obter \"{{searchValue}}\" de Ollama.com",
"Pull a model from Ollama.com": "Obter um modelo de Ollama.com",
"Pull Model": "Obter Modelo",
"Pyodide file browser": "",
"Pyodide file browser": "Navegador de arquivos Pyodide",
"Query Generation Prompt": "Prompt de Geração de Consulta",
"Querying": "Consultando",
"Quick Actions": "Ações rápidas",
@@ -1584,7 +1585,7 @@
"Response splitting": "Divisão da Resposta",
"Response Watermark": "Marca d'água de resposta",
"Responses": "Respostas",
"Restart": "",
"Restart": "Reiniciar",
"Result": "Resultado",
"RESULT": "Resultado",
"Retrieval": "Recuperação",
@@ -1598,7 +1599,7 @@
"Role": "Função",
"RTL": "Direita para Esquerda",
"Run": "Executar",
"Run All": "",
"Run All": "Executar Tudo",
"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.",
@@ -1789,7 +1790,7 @@
"Start a new conversation": "Iniciar uma nova conversa",
"Start of the channel": "Início do canal",
"Start Tag": "Tag inicial",
"Starting kernel...": "",
"Starting kernel...": "Iniciando kernel...",
"Status": "Status",
"Status cleared successfully": "Status liberado com sucesso",
"Status updated successfully": "Status atualizado com sucesso",
@@ -2134,8 +2135,8 @@
"You're now logged in.": "Você agora está logado.",
"Your Account": "Sua conta",
"Your account status is currently pending activation.": "O status da sua conta está atualmente aguardando ativação.",
"Your browser does not support the audio tag.": "",
"Your browser does not support the video tag.": "",
"Your browser does not support the audio tag.": "Seu navegador não suporta a tag de áudio.",
"Your browser does not support the video tag.": "Seu navegador não suporta a tag 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.": "Toda a sua contribuição irá diretamente para o desenvolvedor do plugin; o Open WebUI não retém nenhuma porcentagem. No entanto, a plataforma de financiamento escolhida pode ter suas próprias taxas.",
"Your message text or inputs": "Seu texto de mensagem ou entradas",
"Your usage stats have been successfully synced.": "Suas estatísticas de uso foram sincronizadas com sucesso.",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Modelo de Embedding",
"Embedding Model Engine": "Motor de Modelo de Embedding",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Model de Încapsulare",
"Embedding Model Engine": "Motor de Model de Încapsulare",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Activează generarea automată pentru mesajele de chat",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Модель встраивания",
"Embedding Model Engine": "Движок модели встраивания",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Включить генерацию автозаполнения для сообщений чата",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Vkladací model (Embedding Model)",
"Embedding Model Engine": "",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Модел уградње",
"Embedding Model Engine": "Мотор модела уградње",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Inbäddningsmodell",
"Embedding Model Engine": "Motor för inbäddningsmodell",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Aktivera automatisk komplettering av generering för chattmeddelanden",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "โมเดล Embedding",
"Embedding Model Engine": "เอ็นจินโมเดล Embedding",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "เปิดใช้งานการเติมข้อความอัตโนมัติสำหรับข้อความแชท",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "",
"Embedding Model Engine": "",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Gömme Modeli",
"Embedding Model Engine": "Gömme Modeli Motoru",
"Empty message": "",
"Enable All": "Tümünü Etkinleştir",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Sohbet mesajları için otomatik tamamlama üretimini etkinleştir",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "سىڭدۈرۈش مودېلى",
"Embedding Model Engine": "سىڭدۈرۈش مودېل ماتورى",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "سۆھبەت ئۇچۇرلىرىغا ئاپتوماتىك تولدۇرۇش قوزغىتىش",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Модель вбудовування",
"Embedding Model Engine": "Рушій моделі вбудовування ",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Увімкнути генерацію автозаповнення для повідомлень чату",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "ایمبیڈنگ ماڈل",
"Embedding Model Engine": "ایمبیڈنگ ماڈل انجن",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Ўрнатиш модели",
"Embedding Model Engine": "Двигател моделини ўрнатиш",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Чат хабарлари учун автоматик тўлдиришни яратишни ёқинг",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "O'rnatish modeli",
"Embedding Model Engine": "Dvigatel modelini o'rnatish",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Chat xabarlari uchun avtomatik toldirishni yaratishni yoqing",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "",
"Embedding Model": "Mô hình embedding",
"Embedding Model Engine": "Trình xử lý embedding",
"Empty message": "",
"Enable All": "",
"Enable API Keys": "",
"Enable autocomplete generation for chat messages": "Bật tạo tự động hoàn thành cho tin nhắn chat",
@@ -633,6 +633,7 @@
"Embedding Concurrent Requests": "嵌入并发请求数",
"Embedding Model": "嵌入模型",
"Embedding Model Engine": "嵌入模型引擎",
"Empty message": "",
"Enable All": "全部启用",
"Enable API Keys": "启用接口密钥",
"Enable autocomplete generation for chat messages": "启用对话输入框内容自动补全",

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