Merge remote-tracking branch 'upstream/dev' into fix/mcp-structured-content

# Conflicts:
#	backend/open_webui/utils/mcp/client.py
This commit is contained in:
Classic298 2026-09-30 17:19:01 +02:00
commit 52406a98dc
79 changed files with 3190 additions and 2845 deletions

View file

@ -1302,6 +1302,8 @@ TAVILY_API_KEY = os.getenv('TAVILY_API_KEY', '')
TAVILY_EXTRACT_DEPTH = os.getenv('TAVILY_EXTRACT_DEPTH', 'basic')
TAVILY_SEARCH_DEPTH = os.getenv('TAVILY_SEARCH_DEPTH', 'basic')
STAAN_API_KEY = os.getenv('STAAN_API_KEY', '')
STAAN_MARKET = os.getenv('STAAN_MARKET', 'en-us')
@ -2998,6 +3000,7 @@ DEFAULT_CONFIG = {
'web.search.sougou_api_sk': SOUGOU_API_SK,
'web.search.tavily_api_key': TAVILY_API_KEY,
'web.search.tavily_extract_depth': TAVILY_EXTRACT_DEPTH,
'web.search.tavily_search_depth': TAVILY_SEARCH_DEPTH,
'web.search.staan_api_key': STAAN_API_KEY,
'web.search.staan_market': STAAN_MARKET,
'web.search.staan_max_snippets': STAAN_MAX_SNIPPETS,

View file

@ -58,6 +58,7 @@ class ERROR_MESSAGES(str, Enum):
INVALID_TOKEN = 'Your session has expired or the token is invalid. Please sign in again.'
INVALID_CRED = 'The email or password provided is incorrect. Please check for typos and try logging in again.'
OAUTH_LOGIN_FAILED = 'Sign-in with your identity provider failed. Please contact your administrator for assistance.'
INVALID_EMAIL_FORMAT = "The email format you entered is invalid. Please double-check and make sure you're using a valid email address (e.g., yourname@example.com)."
INCORRECT_PASSWORD = 'The password provided is incorrect. Please check for typos and try again.'
INVALID_TRUSTED_HEADER = (

View file

@ -221,6 +221,7 @@ from open_webui.utils.auth import (
get_http_authorization_cred,
get_license_data,
get_verified_user,
is_valid_token,
)
from open_webui.utils.chat import (
chat_completed as chat_completed_handler,
@ -237,6 +238,7 @@ from open_webui.utils.chat_variables import (
normalize_chat_variables,
)
from open_webui.utils.embeddings import generate_embeddings
from open_webui.utils.headers import get_headers_and_cookies
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.json_response import apply_orjson_http_json
from open_webui.utils.logger import start_logger
@ -994,14 +996,12 @@ async def unload_model(request: Request, form_data: ModelUnloadForm, user=Depend
try:
timeout = aiohttp.ClientTimeout(total=30)
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
headers = {
'Content-Type': 'application/json',
**({'Authorization': f'Bearer {key}'} if key else {}),
}
headers, cookies = await get_headers_and_cookies(request, url, key, api_config, user=user)
async with session.post(
f'{url}/api/generate',
data=payload,
headers=headers,
cookies=cookies,
) as r:
if not r.ok:
errors.append({'url_idx': idx, 'error': await r.text()})
@ -1035,14 +1035,12 @@ async def unload_model(request: Request, form_data: ModelUnloadForm, user=Depend
try:
timeout = aiohttp.ClientTimeout(total=30)
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
headers = {
'Content-Type': 'application/json',
**({'Authorization': f'Bearer {key}'} if key else {}),
}
headers, cookies = await get_headers_and_cookies(request, base_url, key, api_config, user=user)
async with session.post(
f'{root_url}/models/unload',
json={'model': actual_model},
headers=headers,
cookies=cookies,
) as r:
if not r.ok:
detail = await r.text()
@ -1490,7 +1488,9 @@ async def chat_completion(
asyncio.create_task(run_initial_title_generation())
else:
# Existing chat — verify ownership
if not await Chats.is_chat_owner(chat_id, user.id) and user.role != 'admin':
if not await Chats.is_chat_owner(chat_id, user.id) and not (
user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS
):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.DEFAULT(),
@ -1645,7 +1645,8 @@ async def chat_completion(
async def process_chat(request, form_data, user, metadata, model, tasks=None):
try:
ctx = None
if metadata.get('assistant_message_id'):
# Saved chats load the message after approved tool calls run, so their results are kept
if metadata.get('assistant_message_id') and not is_saved_chat_id(metadata.get('chat_id')):
ctx = await build_chat_response_context(request, form_data, user, model, metadata, tasks, [])
form_data, metadata, events = await process_chat_payload(request, form_data, user, metadata, model)
@ -2078,7 +2079,7 @@ async def verify_chat_ownership(chat_id: str | None, user) -> None:
detail='Channel chats are not supported on this endpoint',
)
if user.role != 'admin' and not await Chats.is_chat_owner(chat_id, user.id):
if not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS) and not await Chats.is_chat_owner(chat_id, user.id):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=ERROR_MESSAGES.DEFAULT(),
@ -2142,11 +2143,11 @@ async def list_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=De
socket_id = get_temporary_chat_session_id(chat_id)
if socket_id:
owner_id = get_user_id_from_session_pool(socket_id)
if owner_id != user.id and user.role != 'admin':
if owner_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS):
return {'task_ids': []}
else:
chat = await Chats.get_chat_by_id(chat_id)
if chat is None or (chat.user_id != user.id and user.role != 'admin'):
if chat is None or (chat.user_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS)):
return {'task_ids': []}
task_ids = await list_task_ids_by_item_id(request.app.state.redis, chat_id)
@ -2161,11 +2162,11 @@ async def stop_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=De
chat = None
if socket_id:
owner_id = get_user_id_from_session_pool(socket_id)
if owner_id != user.id and user.role != 'admin':
if owner_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
else:
chat = await Chats.get_chat_by_id(chat_id)
if chat is None or (chat.user_id != user.id and user.role != 'admin'):
if chat is None or (chat.user_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS)):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
result = await stop_item_tasks(request.app.state.redis, chat_id)
@ -2242,7 +2243,7 @@ async def get_app_config(request: Request):
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid token',
)
if data is not None and 'id' in data:
if data is not None and 'id' in data and await is_valid_token(data, request.app.state.redis):
user = await Users.get_user_by_id(data['id'])
onboarding = False

View file

@ -195,7 +195,7 @@ class AutomationTable:
stmt = stmt.filter(
or_(
Automation.name.ilike(f'%{query}%'),
*(data_text.ilike(f'%{variant}%') for variant in json_text_variants(query)),
*(data_text.icontains(variant, autoescape=True) for variant in json_text_variants(query)),
)
)
@ -244,6 +244,15 @@ class AutomationTable:
await db.commit()
return AutomationModel.model_validate(row)
async def update_last_run_at(self, id: str, db: Optional[AsyncSession] = None) -> Optional[AutomationModel]:
async with get_async_db_context(db) as db:
row = await db.get(Automation, id)
if not row:
return None
row.last_run_at = int(time.time_ns())
await db.commit()
return AutomationModel.model_validate(row)
async def clear_folder_ids(
self,
user_id: str,

View file

@ -2522,6 +2522,8 @@ class ChatTable:
async def delete_chats_by_user_id_and_folder_id(
self, user_id: str, folder_id: str, db: AsyncSession | None = None
) -> bool:
from open_webui.models.shared_chats import SharedChat as SharedChatTable
try:
async with get_async_db_context(db) as session:
chat_ids_stmt = select(Chat.id).filter_by(user_id=user_id, folder_id=folder_id)
@ -2529,6 +2531,7 @@ class ChatTable:
update(AutomationRun).filter(AutomationRun.chat_id.in_(chat_ids_stmt)).values(chat_id=None)
)
await session.execute(delete(ChatMessage).filter(ChatMessage.chat_id.in_(chat_ids_stmt)))
await session.execute(delete(SharedChatTable).filter(SharedChatTable.chat_id.in_(chat_ids_stmt)))
await session.execute(delete(Chat).filter_by(user_id=user_id, folder_id=folder_id))
await session.commit()

View file

@ -392,7 +392,9 @@ class ModelsTable:
else:
meta_text = func.lower(cast(Model.meta, String))
variants = json_text_variants(tag.lower())
stmt = stmt.filter(or_(*(meta_text.like(f'%"{variant}"%') for variant in variants)))
stmt = stmt.filter(
or_(*(meta_text.contains(f'"{variant}"', autoescape=True) for variant in variants))
)
order_by = filter.get('order_by')
direction = filter.get('direction')

View file

@ -346,7 +346,10 @@ class PromptsTable:
# Fallback for dialects with no JSON array function: LIKE on the text.
tags_text = func.lower(cast(Prompt.tags, String))
tag_clause = or_(
*(tags_text.like(f'%"{variant}"%') for variant in json_text_variants(tag_lower))
*(
tags_text.contains(f'"{variant}"', autoescape=True)
for variant in json_text_variants(tag_lower)
)
)
tag_lower = None

View file

@ -745,7 +745,7 @@ class Loader:
)
loader = TextLoader(file_path, encoding=self._detect_text_encoding(file_path))
elif file_ext in ['htm', 'html']:
loader = HTMLLoader(file_path, encoding='unicode_escape')
loader = HTMLLoader(file_path, encoding=self._detect_text_encoding(file_path))
elif file_ext == 'md':
loader = TextLoader(file_path, encoding=self._detect_text_encoding(file_path))
elif file_content_type == 'application/epub+zip':

View file

@ -30,6 +30,7 @@ from open_webui.env import (
AIOHTTP_CLIENT_SESSION_SSL,
AIOHTTP_CLIENT_TIMEOUT,
BYPASS_RETRIEVAL_ACCESS_CONTROL,
ENABLE_ADMIN_CHAT_ACCESS,
ENABLE_FORWARD_USER_INFO_HEADERS,
ENABLE_RETRIEVAL_UNSCOPED_COLLECTIONS,
MPS_INFERENCE_LOCK,
@ -838,6 +839,7 @@ async def query_collection_with_hybrid_search(
return name, await ASYNC_VECTOR_DB_CLIENT.get(collection_name=name)
except Exception as e:
log.exception(f'Failed to fetch collection {name}: {e}')
failed_collection_names.add(name)
return name, None
collection_results = dict(await asyncio.gather(*(_fetch_collection(name) for name in collection_names)))
@ -1461,7 +1463,9 @@ async def get_sources_from_items(
elif item.get('type') == 'chat':
# Chat Attached
chat = await Chats.get_chat_by_id(item.get('id'))
has_read_access = bool(chat and (user.role == 'admin' or chat.user_id == user.id))
has_read_access = bool(
chat and ((user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS) or chat.user_id == user.id)
)
if chat and not has_read_access:
has_read_access = await AccessGrants.has_access(

View file

@ -28,6 +28,7 @@ from qdrant_client.http.models import PointStruct
from qdrant_client.models import models
NO_LIMIT = 999999999
SCROLL_PAGE_SIZE = 1000
log = logging.getLogger(__name__)
@ -92,6 +93,24 @@ class QdrantClient(VectorDBBase):
}
)
def _scroll_points(
self, collection_name: str, scroll_filter: Optional[models.Filter] = None, limit: Optional[int] = None
) -> list:
# Paged so a strict-mode max_query_limit does not reject the read
points = []
offset = None
while True:
page_size = SCROLL_PAGE_SIZE if limit is None else min(SCROLL_PAGE_SIZE, limit - len(points))
page, offset = self.client.scroll(
collection_name=f'{self.collection_prefix}_{collection_name}',
scroll_filter=scroll_filter,
limit=page_size,
offset=offset,
)
points.extend(page)
if offset is None or len(points) == limit:
return points
def _create_collection(self, collection_name: str, dimension: int):
collection_name_with_prefix = f'{self.collection_prefix}_{collection_name}'
self.client.create_collection(
@ -180,32 +199,22 @@ class QdrantClient(VectorDBBase):
if not self.has_collection(collection_name):
return None
try:
if limit is None:
limit = NO_LIMIT # otherwise qdrant would set limit to 10!
field_conditions = []
for key, value in filter.items():
field_conditions.append(
models.FieldCondition(key=f'metadata.{key}', match=models.MatchValue(value=value))
)
points = self.client.scroll(
collection_name=f'{self.collection_prefix}_{collection_name}',
scroll_filter=models.Filter(should=field_conditions),
limit=limit,
)
return self._result_to_get_result(points[0])
points = self._scroll_points(collection_name, models.Filter(should=field_conditions), limit)
return self._result_to_get_result(points)
except Exception as e:
log.exception(f"Error querying a collection '{collection_name}': {e}")
return None
def get(self, collection_name: str) -> Optional[GetResult]:
# Get all the items in the collection.
points = self.client.scroll(
collection_name=f'{self.collection_prefix}_{collection_name}',
limit=NO_LIMIT, # otherwise qdrant would set limit to 10!
)
return self._result_to_get_result(points[0])
points = self._scroll_points(collection_name)
return self._result_to_get_result(points)
def insert(self, collection_name: str, items: list[VectorItem]):
# Insert the items into the collection, if the collection does not exist, it will be created.

View file

@ -29,7 +29,7 @@ from qdrant_client.http.exceptions import UnexpectedResponse
from qdrant_client.http.models import PointStruct
from qdrant_client.models import models
NO_LIMIT = 999999999
SCROLL_PAGE_SIZE = 1000
TENANT_ID_FIELD = 'tenant_id'
DEFAULT_DIMENSION = 384
@ -97,6 +97,21 @@ class QdrantClient(VectorDBBase):
metadatas.append(payload['metadata'])
return GetResult(ids=[ids], documents=[documents], metadatas=[metadatas])
def _scroll_points(self, collection_name: str, scroll_filter: models.Filter, limit: Optional[int] = None) -> List:
# Paged so a strict-mode max_query_limit does not reject the read
points, offset = [], None
while True:
page_size = SCROLL_PAGE_SIZE if limit is None else min(SCROLL_PAGE_SIZE, limit - len(points))
page, offset = self.client.scroll(
collection_name=collection_name,
scroll_filter=scroll_filter,
limit=page_size,
offset=offset,
)
points.extend(page)
if offset is None or len(points) == limit:
return points
def _get_collection_and_tenant_id(self, collection_name: str) -> Tuple[str, str]:
"""
Maps the traditional collection name to multi-tenant collection and tenant ID.
@ -287,17 +302,11 @@ class QdrantClient(VectorDBBase):
if not self.client.collection_exists(collection_name=mt_collection):
log.debug("Collection %s doesn't exist, query returns None", mt_collection)
return None
if limit is None:
limit = NO_LIMIT
tenant_filter = _tenant_filter(tenant_id)
field_conditions = [_metadata_filter(k, '$eq', v) for k, v in filter.items()]
combined_filter = models.Filter(must=[tenant_filter, *field_conditions])
points = self.client.scroll(
collection_name=mt_collection,
scroll_filter=combined_filter,
limit=limit,
)
return self._result_to_get_result(points[0])
points = self._scroll_points(mt_collection, combined_filter, limit)
return self._result_to_get_result(points)
def get(self, collection_name: str) -> Optional[GetResult]:
"""
@ -310,12 +319,8 @@ class QdrantClient(VectorDBBase):
log.debug("Collection %s doesn't exist, get returns None", mt_collection)
return None
tenant_filter = _tenant_filter(tenant_id)
points = self.client.scroll(
collection_name=mt_collection,
scroll_filter=models.Filter(must=[tenant_filter]),
limit=NO_LIMIT,
)
return self._result_to_get_result(points[0])
points = self._scroll_points(mt_collection, models.Filter(must=[tenant_filter]))
return self._result_to_get_result(points)
def upsert(self, collection_name: str, items: List[VectorItem]):
"""

View file

@ -14,6 +14,7 @@ def search_tavily(
query: str,
count: int,
filter_list: list[str] | None = None,
search_depth: str = 'basic',
# **kwargs,
) -> list[SearchResult]:
"""Search using Tavily's Search API and return the results as a list of SearchResult objects.
@ -22,6 +23,7 @@ def search_tavily(
api_key (str): A Tavily Search API key
query (str): The query to search for
count (int): The maximum number of results to return
search_depth (str): Tavily search depth
Returns:
A list of SearchResult objects.
@ -31,7 +33,7 @@ def search_tavily(
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}',
}
data = {'query': query, 'max_results': count}
data = {'query': query, 'max_results': count, 'search_depth': search_depth}
response = requests.post(url, headers=headers, json=data)
response.raise_for_status()

View file

@ -341,6 +341,7 @@ async def run_automation_by_id(
await check_automations_permission(request, user)
automation = await Automations.get_by_id(id, db=db)
check_automation_access(automation, user)
automation = await Automations.update_last_run_at(automation.id, db=db)
asyncio.create_task(execute_automation(request.app, automation))
await publish_event(
request,

View file

@ -642,6 +642,9 @@ async def add_members_by_id(
if channel.user_id != user.id and user.role != 'admin':
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
if channel.type == 'dm':
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
try:
memberships = await Channels.add_members_to_channel(
channel.id, user.id, form_data.user_ids, form_data.group_ids, db=db
@ -686,9 +689,12 @@ async def remove_members_by_id(
if channel.user_id != user.id and user.role != 'admin':
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
if channel.type == 'dm':
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
try:
deleted = await Channels.remove_members_from_channel(channel.id, form_data.user_ids, db=db)
if channel.type in ['group', 'dm']:
if channel.type == 'group':
await leave_room_for_users(f'channel:{channel.id}', form_data.user_ids)
await publish_event(

View file

@ -680,7 +680,7 @@ async def export_single_chat_stats(
)
# Verify the chat belongs to the user (unless admin)
if chat.user_id != user.id and user.role != 'admin':
if chat.user_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -1428,7 +1428,7 @@ async def update_chat_message_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if chat.user_id != user.id and user.role != 'admin':
if chat.user_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -1489,7 +1489,7 @@ async def delete_chat_message_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if chat.user_id != user.id and user.role != 'admin':
if chat.user_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -1537,7 +1537,7 @@ async def send_chat_message_event_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
if chat.user_id != user.id and user.role != 'admin':
if chat.user_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -1584,6 +1584,8 @@ async def delete_chat_by_id(
# not be reachable for a chat the caller may not delete.
if user.role == 'admin':
chat = await Chats.get_chat_by_id(id, db=db)
if chat and chat.user_id != user.id and not ENABLE_ADMIN_CHAT_ACCESS:
chat = None
else:
if not await has_permission(user.id, 'chat.delete', await Config.get('user.permissions')):
raise HTTPException(
@ -1848,21 +1850,9 @@ async def clone_shared_chat_by_id(
):
await require_chat_import_permission(request, user, db)
chat = await Chats.get_chat_by_share_id(id, db=db)
# Fallback: admins can also access any chat directly by chat ID
if not chat and user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
chat = await Chats.get_chat_by_id(id, db=db)
if not chat:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.NOT_FOUND,
)
# Enforce access grants (owner and admins bypass)
shared = await SharedChats.get_by_id(id, db=db)
if shared and user.role != 'admin' and shared.user_id != user.id:
if shared and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS) and shared.user_id != user.id:
has_grant = await is_open_shared_chat(shared, db=db) or await AccessGrants.has_access(
user_id=user.id,
resource_type='shared_chat',
@ -1876,6 +1866,18 @@ async def clone_shared_chat_by_id(
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
)
chat = await Chats.get_chat_by_share_id(id, db=db) if shared else None
# Fallback: admins can also access any chat directly by chat ID
if not chat and user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
chat = await Chats.get_chat_by_id(id, db=db)
if not chat:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.NOT_FOUND,
)
updated_chat = {
**chat.chat,
'originalChatId': chat.id,
@ -2042,7 +2044,7 @@ async def update_shared_chat_access_by_id(
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
if user.role == 'admin':
if user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
chat = await Chats.get_chat_by_id(id, db=db)
else:
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
@ -2078,7 +2080,7 @@ async def get_shared_chat_access_by_id(
user=Depends(get_verified_user),
db: AsyncSession = Depends(get_async_session),
):
if user.role == 'admin':
if user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
chat = await Chats.get_chat_by_id(id, db=db)
else:
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)

View file

@ -7,6 +7,7 @@ import logging
import mimetypes
import re
import uuid
from contextlib import nullcontext
from pathlib import Path
from types import SimpleNamespace
from typing import Optional
@ -488,14 +489,18 @@ async def get_image_data(data: str, headers=None, trusted_base_url: str | None =
# that would follow arbitrary redirects.
if trusted_base_url and _is_same_origin(data, trusted_base_url):
log.debug('Skipping URL validation for trusted backend: %s', data)
session_context = nullcontext(await get_session())
else:
await asyncio.to_thread(validate_url, data)
session = await get_session()
async with session.get(
data,
headers=headers,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
) as r:
session_context = get_ssrf_safe_session()
async with (
session_context as session,
session.get(
data,
headers=headers,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
) as r,
):
r.raise_for_status()
content_type = r.headers.get('content-type', '')
if content_type.split('/')[0] == 'image':

View file

@ -22,8 +22,6 @@ from open_webui.env import (
AIOHTTP_CLIENT_TIMEOUT_MODEL_LIST,
AIOHTTP_FILE_STREAM_CHUNK_SIZE,
BYPASS_MODEL_ACCESS_CONTROL,
ENABLE_FORWARD_USER_INFO_HEADERS,
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
MODELS_CACHE_TTL,
REDIS_KEY_PREFIX,
)
@ -36,7 +34,7 @@ from open_webui.models.models import Models
from open_webui.models.users import UserModel
from open_webui.utils.access_control import check_model_access
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import get_custom_headers, include_user_info_headers
from open_webui.utils.headers import get_headers_and_cookies
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import calculate_sha256
from open_webui.utils.model_ids import strip_provider_model_prefix
@ -67,24 +65,21 @@ def _clean_proxy_headers(raw_headers) -> dict:
async def send_get_request(
url: str,
key: str | None = None,
user: UserModel | None = None,
request: Request = None,
url=None,
key=None,
user: UserModel = None,
config=None,
):
"""Issue a GET request to an Ollama backend and return JSON, or *None* on failure."""
try:
session = await get_session()
headers: dict = {
'Content-Type': 'application/json',
}
if key:
headers['Authorization'] = f'Bearer {key}'
if ENABLE_FORWARD_USER_INFO_HEADERS and user:
headers = include_user_info_headers(headers, user)
headers, cookies = await get_headers_and_cookies(request, url, key, config, user=user)
async with session.get(
url,
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
timeout=_MODEL_LIST_TIMEOUT,
) as r:
@ -114,25 +109,14 @@ async def send_request(
try:
session = await get_session()
headers = {
'Content-Type': 'application/json',
**({'Authorization': f'Bearer {key}'} if key else {}),
}
if ENABLE_FORWARD_USER_INFO_HEADERS and user:
headers = include_user_info_headers(headers, user, request=request)
if metadata and metadata.get('chat_id'):
headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = metadata.get('chat_id')
# Custom per-connection headers last so admin-set headers take precedence.
if api_config and api_config.get('headers'):
headers.update(await get_custom_headers(api_config['headers'], user, metadata, request=request))
headers, cookies = await get_headers_and_cookies(request, url, key, api_config, metadata, user=user)
r = await session.request(
method,
url,
data=payload,
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
timeout=get_client_timeout(stream=stream),
)
@ -249,24 +233,26 @@ class ConnectionVerificationForm(BaseModel):
url: str
key: str | None = None
config: dict | None = None
@router.post('/verify')
async def verify_connection(
request: Request,
form_data: ConnectionVerificationForm,
user=Depends(get_admin_user),
):
"""Verify that an Ollama backend at *form_data.url* is reachable."""
try:
session = await get_session()
headers: dict = {}
if form_data.key:
headers['Authorization'] = f'Bearer {form_data.key}'
if ENABLE_FORWARD_USER_INFO_HEADERS and user:
headers = include_user_info_headers(headers, user)
headers, cookies = await get_headers_and_cookies(
request, form_data.url, form_data.key, form_data.config, user=user
)
async with session.get(
f'{form_data.url}/api/version',
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
timeout=_MODEL_LIST_TIMEOUT,
) as r:
@ -403,9 +389,11 @@ async def get_all_models(request: Request, user: UserModel | None = None):
for idx, url in enumerate(base_urls):
api_config = resolve_api_config(api_configs, idx, url)
if not api_config:
tasks.append(send_get_request(f'{url}/api/tags', user=user))
tasks.append(send_get_request(request, f'{url}/api/tags', user=user))
elif api_config.get('enable', True):
tasks.append(send_get_request(f'{url}/api/tags', api_config.get('key'), user=user))
tasks.append(
send_get_request(request, f'{url}/api/tags', api_config.get('key'), user=user, config=api_config)
)
else:
tasks.append(asyncio.ensure_future(asyncio.sleep(0, None)))
@ -495,9 +483,15 @@ async def get_ollama_tags(
if url_idx is None:
result = await get_all_models(request, user=user)
else:
url = (await Config.get('ollama.base_urls', []))[url_idx]
key = get_api_key(url_idx, url, (await Config.get('ollama.api_configs', {})))
result = await send_request(f'{url}/api/tags', 'GET', key=key, user=user)
url, api_config, key = await get_ollama_connection(url_idx)
result = await send_request(
f'{url}/api/tags',
'GET',
key=key,
user=user,
api_config=api_config,
request=request,
)
if user.role == 'user' and not BYPASS_MODEL_ACCESS_CONTROL:
result['models'] = await get_filtered_models(result, user)
@ -524,9 +518,11 @@ async def get_ollama_loaded_models(
continue
api_config = resolve_api_config(api_configs, idx, url)
if not api_config:
tasks.append(send_get_request(f'{url}/api/ps', user=user))
tasks.append(send_get_request(request, f'{url}/api/ps', user=user))
elif api_config.get('enable', True):
tasks.append(send_get_request(f'{url}/api/ps', api_config.get('key'), user=user))
tasks.append(
send_get_request(request, f'{url}/api/ps', api_config.get('key'), user=user, config=api_config)
)
else:
tasks.append(asyncio.ensure_future(asyncio.sleep(0, None)))
@ -559,8 +555,15 @@ async def get_ollama_versions(
return {'version': False}
if url_idx is not None:
url = (await Config.get('ollama.base_urls', []))[url_idx]
return await send_request(f'{url}/api/version', 'GET')
url, api_config, key = await get_ollama_connection(url_idx)
return await send_request(
f'{url}/api/version',
'GET',
key=key,
user=user,
api_config=api_config,
request=request,
)
# Fan-out to every enabled backend
tasks = []
@ -570,7 +573,9 @@ async def get_ollama_versions(
(await Config.get('ollama.api_configs', {})).get(url, {}),
)
if api_config.get('enable', True):
tasks.append(send_get_request(f'{url}/api/version', api_config.get('key')))
tasks.append(
send_get_request(request, f'{url}/api/version', api_config.get('key'), user=user, config=api_config)
)
raw = await asyncio.gather(*tasks)
valid = [r for r in raw if r is not None]
@ -634,6 +639,8 @@ async def unload_model(
payload=JSONCodec.dumps(payload),
key=key,
user=user,
api_config=api_config,
request=request,
)
results.append({'url_idx': idx, 'success': True, 'response': res})
except Exception as e:
@ -663,17 +670,19 @@ async def pull_model(
form_data = form_data.model_dump(exclude_none=True)
form_data['model'] = form_data.get('model', form_data.get('name'))
url = (await Config.get('ollama.base_urls', []))[url_idx]
url, api_config, key = await get_ollama_connection(url_idx)
log.info('url: %s', url)
# Admins may pull from any registry
return await send_request(
f'{url}/api/pull',
payload=JSONCodec.dumps({**form_data, 'insecure': True}),
key=get_api_key(url_idx, url, (await Config.get('ollama.api_configs', {}))),
key=key,
user=user,
stream=True,
passthrough=True,
api_config=api_config,
request=request,
)
@ -704,16 +713,18 @@ async def push_model(
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.model))
url_idx = models[form_data.model]['urls'][0]
url = (await Config.get('ollama.base_urls', []))[url_idx]
url, api_config, key = await get_ollama_connection(url_idx)
log.debug('url: %s', url)
return await send_request(
f'{url}/api/push',
payload=form_data.model_dump_json(exclude_none=True).encode(),
key=get_api_key(url_idx, url, (await Config.get('ollama.api_configs', {}))),
key=key,
user=user,
stream=True,
passthrough=True,
api_config=api_config,
request=request,
)
@ -738,15 +749,17 @@ async def create_model(
raise HTTPException(status_code=503, detail=ERROR_MESSAGES.OLLAMA_API_DISABLED)
log.debug('form_data: %s', form_data)
url = (await Config.get('ollama.base_urls', []))[url_idx]
url, api_config, key = await get_ollama_connection(url_idx)
return await send_request(
f'{url}/api/create',
payload=form_data.model_dump_json(exclude_none=True).encode(),
key=get_api_key(url_idx, url, (await Config.get('ollama.api_configs', {}))),
key=key,
user=user,
stream=True,
passthrough=True,
api_config=api_config,
request=request,
)
@ -776,14 +789,15 @@ async def copy_model(
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.MODEL_NOT_FOUND(form_data.source))
url_idx = models[form_data.source]['urls'][0]
url = (await Config.get('ollama.base_urls', []))[url_idx]
key = get_api_key(url_idx, url, (await Config.get('ollama.api_configs', {})))
url, api_config, key = await get_ollama_connection(url_idx)
await send_request(
f'{url}/api/copy',
payload=form_data.model_dump_json(exclude_none=True).encode(),
key=key,
user=user,
api_config=api_config,
request=request,
)
await publish_event(
request,
@ -818,8 +832,7 @@ async def delete_model(
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.MODEL_NOT_FOUND(model))
url_idx = models[model]['urls'][0]
url = (await Config.get('ollama.base_urls', []))[url_idx]
key = get_api_key(url_idx, url, (await Config.get('ollama.api_configs', {})))
url, api_config, key = await get_ollama_connection(url_idx)
await send_request(
f'{url}/api/delete',
@ -827,6 +840,8 @@ async def delete_model(
payload=JSONCodec.dumps(payload),
key=key,
user=user,
api_config=api_config,
request=request,
)
await publish_event(
request,
@ -861,14 +876,15 @@ async def show_model_info(
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.MODEL_NOT_FOUND(model))
url_idx = random.choice(models[model]['urls'])
url = (await Config.get('ollama.base_urls', []))[url_idx]
key = get_api_key(url_idx, url, (await Config.get('ollama.api_configs', {})))
url, api_config, key = await get_ollama_connection(url_idx)
return await send_request(
f'{url}/api/show',
payload=JSONCodec.dumps(payload),
key=key,
user=user,
api_config=api_config,
request=request,
)
@ -922,6 +938,8 @@ async def embed(
payload=form_data.model_dump_json(exclude_none=True).encode(),
key=key,
user=user,
api_config=api_config,
request=request,
)
@ -973,6 +991,8 @@ async def embeddings(
payload=form_data.model_dump_json(exclude_none=True).encode(),
key=key,
user=user,
api_config=api_config,
request=request,
)
@ -1030,6 +1050,8 @@ async def generate_completion(
user=user,
stream=True,
passthrough=True,
api_config=api_config,
request=request,
)
@ -1501,8 +1523,15 @@ async def get_openai_models(
model_list = await get_all_models(request, user=user)
raw_models = model_list['models']
else:
url = (await Config.get('ollama.base_urls', []))[url_idx]
model_list = await send_request(f'{url}/api/tags', 'GET')
url, api_config, key = await get_ollama_connection(url_idx)
model_list = await send_request(
f'{url}/api/tags',
'GET',
key=key,
user=user,
api_config=api_config,
request=request,
)
raw_models = model_list.get('models', [])
now_ts = int(time.time())
@ -1552,6 +1581,8 @@ async def download_file_stream(
file_url: str,
file_path: str,
file_name: str,
ollama_headers: dict,
ollama_cookies: dict,
chunk_size: int = AIOHTTP_FILE_STREAM_CHUNK_SIZE,
):
"""Stream a model file download from *file_url*, then push the blob to Ollama."""
@ -1590,7 +1621,12 @@ async def download_file_stream(
async with session.post(
blob_url,
data=blob_chunks(),
headers={'Content-Length': str(blob_size)},
headers={
**ollama_headers,
'Content-Type': 'application/octet-stream',
'Content-Length': str(blob_size),
},
cookies=ollama_cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
timeout=aiohttp.ClientTimeout(total=30),
) as blob_resp:
@ -1617,15 +1653,17 @@ async def download_model(
detail='Invalid file_url. Only URLs from allowed hosts are permitted.',
)
url = (await Config.get('ollama.base_urls', []))[url_idx if url_idx is not None else 0]
url, api_config, key = await get_ollama_connection(url_idx if url_idx is not None else 0)
file_name = parse_huggingface_url(form_data.url)
if not file_name:
return None
headers, cookies = await get_headers_and_cookies(request, url, key, api_config, user=user)
file_path = os.path.join(UPLOAD_DIR, file_name)
return StreamingResponse(
download_file_stream(url, form_data.url, file_path, file_name),
download_file_stream(url, form_data.url, file_path, file_name, headers, cookies),
)
@ -1638,7 +1676,8 @@ async def upload_model(
user=Depends(get_admin_user),
):
"""Upload a local model file, push it as a blob, and create the model in Ollama."""
ollama_url = (await Config.get('ollama.base_urls', []))[url_idx if url_idx is not None else 0]
ollama_url, api_config, key = await get_ollama_connection(url_idx if url_idx is not None else 0)
headers, cookies = await get_headers_and_cookies(request, ollama_url, key, api_config, user=user)
filename = os.path.basename(file.filename)
file_path = os.path.join(UPLOAD_DIR, filename)
@ -1680,7 +1719,12 @@ async def upload_model(
async with session.post(
blob_url,
data=blob_chunks(),
headers={'Content-Length': str(total_size)},
headers={
**headers,
'Content-Type': 'application/octet-stream',
'Content-Length': str(total_size),
},
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_SSL,
timeout=get_client_timeout(),
) as resp:
@ -1702,7 +1746,8 @@ async def upload_model(
async with session.post(
f'{ollama_url}/api/create',
headers={'Content-Type': 'application/json'},
headers=headers,
cookies=cookies,
data=JSONCodec.dumps(create_payload),
ssl=AIOHTTP_CLIENT_SESSION_SSL,
timeout=get_client_timeout(),

View file

@ -10,7 +10,6 @@ from urllib.parse import quote, urlparse
import aiofiles
import aiohttp
from aiocache import cached
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.responses import (
FileResponse,
@ -28,7 +27,6 @@ from open_webui.env import (
BYPASS_MODEL_ACCESS_CONTROL,
ENABLE_FORWARD_USER_INFO_HEADERS,
ENABLE_OPENAI_API_PASSTHROUGH,
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
MODELS_CACHE_TTL,
REDIS_KEY_PREFIX,
)
@ -42,7 +40,7 @@ from open_webui.models.users import UserModel
from open_webui.utils.access_control import check_model_access, has_connection_access, has_permission
from open_webui.utils.anthropic import ANTHROPIC_VERSION, get_anthropic_models, is_anthropic_url
from open_webui.utils.auth import get_admin_user, get_verified_user
from open_webui.utils.headers import get_custom_headers, include_user_info_headers
from open_webui.utils.headers import get_headers_and_cookies, include_user_info_headers
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import convert_logit_bias_input_to_json
from open_webui.utils.model_ids import strip_provider_model_prefix
@ -152,87 +150,6 @@ def openai_reasoning_model_handler(payload):
return payload
async def get_headers_and_cookies(
request: Request,
url,
key=None,
config=None,
metadata: dict | None = None,
user: UserModel = None,
):
cookies = getattr(request, 'cookies', {}) if config.get('forward_cookies', False) else {}
headers = {
'Content-Type': 'application/json',
**(
{
# LICENSE covers this Open WebUI upstream metadata identifier.
# Do not alter, remove, obscure, or replace it except as LICENSE permits:
# https://docs.openwebui.com/license.
'HTTP-Referer': 'https://openwebui.com/',
'X-Title': 'Open WebUI',
}
if 'openrouter.ai' in url
else {}
),
}
if ENABLE_FORWARD_USER_INFO_HEADERS and user:
headers = include_user_info_headers(headers, user, request=request)
if metadata and metadata.get('chat_id'):
headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = metadata.get('chat_id')
token = None
auth_type = config.get('auth_type')
if auth_type == 'bearer' or auth_type is None:
# Default to bearer if not specified
token = f'{key}'
elif auth_type == 'none':
token = None
elif auth_type == 'session':
token = request.state.token.credentials
elif auth_type == 'system_oauth':
oauth_token = None
try:
if request.cookies.get('oauth_session_id', None):
oauth_token = await request.app.state.oauth_manager.get_oauth_token(
user.id,
request.cookies.get('oauth_session_id', None),
)
except Exception as e:
log.error(f'Error getting OAuth token: {e}')
if oauth_token:
token = f'{oauth_token.get("access_token", "")}'
elif auth_type in ('azure_ad', 'microsoft_entra_id'):
token = get_microsoft_entra_id_access_token()
if token:
headers['Authorization'] = f'Bearer {token}'
if config.get('headers') and isinstance(config.get('headers'), dict):
custom_headers = await get_custom_headers(config.get('headers'), user, metadata, request=request)
headers.update(custom_headers)
return headers, cookies
def get_microsoft_entra_id_access_token():
"""
Get Microsoft Entra ID access token using DefaultAzureCredential for Azure OpenAI.
Returns the token string or None if authentication fails.
"""
try:
token_provider = get_bearer_token_provider(
DefaultAzureCredential(), 'https://cognitiveservices.azure.com/.default'
)
return token_provider()
except Exception as e:
log.error(f'Error getting Microsoft Entra ID access token: {e}')
return None
##########################################
#
# API routes

View file

@ -387,6 +387,7 @@ RETRIEVAL_CONFIG_KEYS = {
'STAAN_MAX_SNIPPETS': 'web.search.staan_max_snippets',
'TAVILY_API_KEY': 'web.search.tavily_api_key',
'TAVILY_EXTRACT_DEPTH': 'web.search.tavily_extract_depth',
'TAVILY_SEARCH_DEPTH': 'web.search.tavily_search_depth',
'TEXT_SPLITTER': 'rag.text_splitter',
'TIKA_SERVER_URL': 'rag.tika_server_url',
'TIKA_SERVER_VERSION': 'rag.tika_server_version',
@ -773,6 +774,7 @@ async def get_rag_config(request: Request, user=Depends(get_admin_user)):
'FIRECRAWL_API_BASE_URL': config.FIRECRAWL_API_BASE_URL,
'FIRECRAWL_TIMEOUT': config.FIRECRAWL_TIMEOUT,
'TAVILY_EXTRACT_DEPTH': config.TAVILY_EXTRACT_DEPTH,
'TAVILY_SEARCH_DEPTH': config.TAVILY_SEARCH_DEPTH,
'EXTERNAL_WEB_SEARCH_URL': config.EXTERNAL_WEB_SEARCH_URL,
'EXTERNAL_WEB_SEARCH_API_KEY': config.EXTERNAL_WEB_SEARCH_API_KEY,
'EXTERNAL_WEB_LOADER_URL': config.EXTERNAL_WEB_LOADER_URL,
@ -856,6 +858,7 @@ class WebConfig(BaseModel):
FIRECRAWL_API_BASE_URL: str | None = None
FIRECRAWL_TIMEOUT: str | None = None
TAVILY_EXTRACT_DEPTH: str | None = None
TAVILY_SEARCH_DEPTH: str | None = None
EXTERNAL_WEB_SEARCH_URL: str | None = None
EXTERNAL_WEB_SEARCH_API_KEY: str | None = None
EXTERNAL_WEB_LOADER_URL: str | None = None
@ -1386,6 +1389,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
config.EXTERNAL_WEB_LOADER_URL = form_data.web.EXTERNAL_WEB_LOADER_URL
config.EXTERNAL_WEB_LOADER_API_KEY = form_data.web.EXTERNAL_WEB_LOADER_API_KEY
config.TAVILY_EXTRACT_DEPTH = form_data.web.TAVILY_EXTRACT_DEPTH
config.TAVILY_SEARCH_DEPTH = form_data.web.TAVILY_SEARCH_DEPTH
config.YOUTUBE_LOADER_LANGUAGE = form_data.web.YOUTUBE_LOADER_LANGUAGE
config.YOUTUBE_LOADER_PROXY_URL = form_data.web.YOUTUBE_LOADER_PROXY_URL
request.app.state.YOUTUBE_LOADER_TRANSLATION = form_data.web.YOUTUBE_LOADER_TRANSLATION
@ -1535,6 +1539,7 @@ async def update_rag_config(request: Request, form_data: ConfigForm, user=Depend
'FIRECRAWL_API_BASE_URL': config.FIRECRAWL_API_BASE_URL,
'FIRECRAWL_TIMEOUT': config.FIRECRAWL_TIMEOUT,
'TAVILY_EXTRACT_DEPTH': config.TAVILY_EXTRACT_DEPTH,
'TAVILY_SEARCH_DEPTH': config.TAVILY_SEARCH_DEPTH,
'EXTERNAL_WEB_SEARCH_URL': config.EXTERNAL_WEB_SEARCH_URL,
'EXTERNAL_WEB_SEARCH_API_KEY': config.EXTERNAL_WEB_SEARCH_API_KEY,
'EXTERNAL_WEB_LOADER_URL': config.EXTERNAL_WEB_LOADER_URL,
@ -2376,7 +2381,16 @@ async def process_url(
}
config = await get_retrieval_config()
url_result = await _fetch_url(form_data.url, config.FILE_MAX_SIZE)
try:
url_result = await _fetch_url(form_data.url, config.FILE_MAX_SIZE)
except HTTPException:
raise
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT(e, f'Could not read content from {form_data.url}'),
)
if url_result['kind'] == 'web':
result = await process_web(request, form_data, process=process, user=user)
@ -2457,6 +2471,13 @@ async def process_web(
detail=ERROR_MESSAGES.DEFAULT(e, f'Could not read content from {form_data.url}'),
)
# web loaders swallow fetch errors and return no documents
if not docs:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=ERROR_MESSAGES.DEFAULT(f'Could not read content from {form_data.url}'),
)
try:
log.debug('text_content: %s', content)
@ -2705,6 +2726,7 @@ async def search_web(request: Request, engine: str, query: str, user=None) -> li
query,
config.WEB_SEARCH_RESULT_COUNT,
config.WEB_SEARCH_DOMAIN_FILTER_LIST,
search_depth=config.TAVILY_SEARCH_DEPTH,
)
else:
raise Exception('No TAVILY_API_KEY found in environment variables')

View file

@ -36,6 +36,7 @@ from open_webui.env import (
from open_webui.models.access_grants import AccessGrants
from open_webui.models.channels import Channels
from open_webui.models.chats import Chats
from open_webui.models.config import Config
from open_webui.models.folders import Folders
from open_webui.models.notes import Notes, NoteUpdateForm
from open_webui.models.users import UserNameResponse, Users
@ -392,8 +393,12 @@ async def enter_room_for_users(room: str, user_ids: list[str]):
user_ids (list[str]): The target user's IDs.
"""
try:
for user_id in user_ids:
session_ids = get_session_ids_from_room(f'user:{user_id}')
default_permissions = await Config.get('user.permissions')
for user in await Users.get_users_by_user_ids(user_ids):
if user.role != 'admin' and not await has_permission(user.id, 'features.channels', default_permissions):
continue
session_ids = get_session_ids_from_room(f'user:{user.id}')
for sid in session_ids:
await sio.enter_room(sid, room)
except Exception as e:
@ -501,7 +506,7 @@ async def user_join(sid, data):
await sio.enter_room(sid, f'user:{user.id}')
# Join all the channels only if user has channels permission
if user.role == 'admin' or await has_permission(user.id, 'features.channels'):
if user.role == 'admin' or await has_permission(user.id, 'features.channels', await Config.get('user.permissions')):
channels = await Channels.get_channels_by_user_id(user.id)
log.debug('channels=%r', channels)
for channel in channels:
@ -533,7 +538,7 @@ async def join_channel(sid, data):
return
# Join all the channels only if user has channels permission
if user.role == 'admin' or await has_permission(user.id, 'features.channels'):
if user.role == 'admin' or await has_permission(user.id, 'features.channels', await Config.get('user.permissions')):
channels = await Channels.get_channels_by_user_id(user.id)
log.debug('channels=%r', channels)
for channel in channels:

View file

@ -432,8 +432,8 @@ def convert_anthropic_to_openai_payload(
else:
openai_payload[param] = anthropic_payload[param]
# Tools conversion: Anthropic → OpenAI
if 'tools' in anthropic_payload:
# Tools conversion: Anthropic → OpenAI (backends reject an empty tools array)
if anthropic_payload.get('tools'):
openai_tools = []
for tool in anthropic_payload['tools']:
openai_tools.append(
@ -452,7 +452,7 @@ def convert_anthropic_to_openai_payload(
openai_payload['tools'] = openai_tools
# tool_choice
if 'tool_choice' in anthropic_payload:
if 'tool_choice' in anthropic_payload and 'tools' in openai_payload:
tool_choice = anthropic_payload['tool_choice']
if isinstance(tool_choice, dict):
tool_choice_type = tool_choice.get('type', 'auto')
@ -616,6 +616,7 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
server_tool_use = None
service_tier = None
stop_reason = 'end_turn'
error_message = None
# Track content blocks with a running index.
# Each text block or tool_use block gets its own index.
@ -671,6 +672,13 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
except (JSONCodec.JSONDecodeError, TypeError):
continue
error = data.get('error')
if error:
error_message = (
error.get('message') if isinstance(error, dict) else error
) or 'Chat completion stream failed'
break
usage_data = data.get('usage')
if isinstance(usage_data, dict):
cache_creation = usage_data.get('cache_creation_input_tokens')
@ -904,8 +912,18 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
}
stop_reason = stop_reason_map.get(finish_reason, 'end_turn')
if error_message:
break
except Exception as e:
log.error(f'Error in Anthropic stream conversion: {e}')
error_message = 'Chat completion stream failed'
# Skip message_stop so a failed stream is not reported as complete.
if error_message:
error_event = {'type': 'error', 'error': {'type': 'api_error', 'message': error_message}}
yield f'event: error\ndata: {JSONCodec.dumps(error_event)}\n\n'.encode()
return
# Close any open thinking block
if thinking_block_open:

View file

@ -282,11 +282,12 @@ class AuditLoggingMiddleware:
response_body = context.response_body.decode('utf-8', errors='replace')
# Redact sensitive information
if 'password' in request_body:
if 'password' in request_body.lower():
request_body = re.sub(
r'"password":\s*"(.*?)"',
'"password": "********"',
r'"(\w*password)":\s*".*?"',
r'"\1": "********"',
request_body,
flags=re.IGNORECASE,
)
entry = AuditLogEntry(

View file

@ -261,6 +261,13 @@ async def _execute_channel_automation(
if not channel_id or not await Config.get('channels.enable'):
raise ValueError('Channel not found')
from open_webui.utils.access_control import has_permission
if user.role != 'admin' and not await has_permission(
user.id, 'features.channels', await Config.get('user.permissions')
):
raise ValueError('Owner no longer permitted to use channels')
model = getattr(app.state, 'MODELS', {}).get(model_id, {})
request = _build_request(app, token=token)

View file

@ -40,7 +40,6 @@ def expand_recurring_event(
range_start = to_local_datetime(range_start_ns)
range_end = to_local_datetime(range_end_ns)
scan_start = range_start - dt.timedelta(days=1)
original_start_ns = event_dict['start_at']
original_start = to_local_datetime(original_start_ns)
@ -55,6 +54,9 @@ def expand_recurring_event(
original_end_ns = event_dict.get('end_at')
duration_ns = (original_end_ns - original_start_ns) if original_end_ns else None
# Look back by the event length so occurrences still running at range start are found
event_length = dt.timedelta(microseconds=max(duration_ns or 0, 0) // 1000)
scan_start = range_start - dt.timedelta(days=1) - event_length
instances = []
previous_start = None
@ -64,12 +66,13 @@ def expand_recurring_event(
previous_start = occurrence_start
instance_start_ns = int(occurrence_start.replace(tzinfo=user_timezone).timestamp() * 1_000_000_000)
instance_end_ns = (instance_start_ns + duration_ns) if duration_ns else None
if instance_start_ns >= range_start_ns:
if instance_start_ns >= range_start_ns or (instance_end_ns and instance_end_ns > range_start_ns):
instance = {
**event_dict,
'start_at': instance_start_ns,
'end_at': (instance_start_ns + duration_ns) if duration_ns else None,
'end_at': instance_end_ns,
'instance_id': f'{event_dict["id"]}_{instance_start_ns}',
}
instances.append(instance)

View file

@ -5,7 +5,10 @@ from typing import Any, Optional
from urllib.parse import quote
import jwt
from fastapi import Request
from open_webui.env import (
ENABLE_FORWARD_USER_INFO_HEADERS,
FORWARD_SESSION_INFO_HEADER_CHAT_ID,
FORWARD_USER_INFO_HEADER_AUTH_TYPE,
FORWARD_USER_INFO_HEADER_JWT,
FORWARD_USER_INFO_HEADER_JWT_EXPIRES_SECONDS,
@ -161,3 +164,87 @@ def parse_custom_headers(
parsed_headers[key] = quote(value, safe=punctuation + ' \t')
return parsed_headers
async def get_headers_and_cookies(
request: Request,
url,
key=None,
config=None,
metadata: dict | None = None,
user=None,
):
config = config or {}
cookies = getattr(request, 'cookies', {}) if config.get('forward_cookies', False) else {}
headers = {
'Content-Type': 'application/json',
**(
{
# LICENSE covers this Open WebUI upstream metadata identifier.
# Do not alter, remove, obscure, or replace it except as LICENSE permits:
# https://docs.openwebui.com/license.
'HTTP-Referer': 'https://openwebui.com/',
'X-Title': 'Open WebUI',
}
if 'openrouter.ai' in url
else {}
),
}
if ENABLE_FORWARD_USER_INFO_HEADERS and user:
headers = include_user_info_headers(headers, user, request=request)
if metadata and metadata.get('chat_id'):
headers[FORWARD_SESSION_INFO_HEADER_CHAT_ID] = metadata.get('chat_id')
token = None
auth_type = config.get('auth_type')
if auth_type == 'bearer' or auth_type is None:
# Default to bearer if not specified
token = key
elif auth_type == 'none':
token = None
elif auth_type == 'session':
token = request.state.token.credentials
elif auth_type == 'system_oauth':
oauth_token = None
try:
if request.cookies.get('oauth_session_id', None):
oauth_token = await request.app.state.oauth_manager.get_oauth_token(
user.id,
request.cookies.get('oauth_session_id', None),
)
except Exception as e:
log.error(f'Error getting OAuth token: {e}')
if oauth_token:
token = f'{oauth_token.get("access_token", "")}'
elif auth_type in ('azure_ad', 'microsoft_entra_id'):
token = get_microsoft_entra_id_access_token()
if token:
headers['Authorization'] = f'Bearer {token}'
if config.get('headers') and isinstance(config.get('headers'), dict):
custom_headers = await get_custom_headers(config.get('headers'), user, metadata, request=request)
headers.update(custom_headers)
return headers, cookies
def get_microsoft_entra_id_access_token():
"""
Get Microsoft Entra ID access token using DefaultAzureCredential for Azure OpenAI.
Returns the token string or None if authentication fails.
"""
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
try:
token_provider = get_bearer_token_provider(
DefaultAzureCredential(), 'https://cognitiveservices.azure.com/.default'
)
return token_provider()
except Exception as e:
log.error(f'Error getting Microsoft Entra ID access token: {e}')
return None

View file

@ -1,6 +1,7 @@
import asyncio
import logging
from contextlib import AsyncExitStack
from datetime import timedelta
from typing import Any, Optional
log = logging.getLogger(__name__)
@ -135,7 +136,11 @@ class MCPClient:
if not self.session:
raise RuntimeError('MCP client is not connected.')
result = await self.session.call_tool(function_name, function_args)
tool_call_timeout = None
if AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER is not None and AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER > 0:
tool_call_timeout = timedelta(seconds=AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER)
result = await self.session.call_tool(function_name, function_args, read_timeout_seconds=tool_call_timeout)
if not result:
raise Exception('No result returned from MCP tool call.')

View file

@ -115,6 +115,7 @@ from open_webui.utils.misc import (
get_last_user_message_item,
get_message_list,
get_output_text,
get_paired_tool_call_ids,
get_response_error_detail,
get_reasoning_details,
get_system_message,
@ -509,6 +510,7 @@ def get_citation_source_from_tool_result(
},
'document': [],
'metadata': [],
'distances': [],
}
sources_by_file[key]['document'].append(content)
@ -520,6 +522,8 @@ def get_citation_source_from_tool_result(
**({'note_id': note_id} if note_id else {}),
}
)
if 'distance' in chunk:
sources_by_file[key]['distances'].append(chunk['distance'])
# Return all grouped sources as a list
if sources_by_file:
@ -933,6 +937,9 @@ def handle_responses_streaming_event(
error = data.get('response', {}).get('error', {})
return current_output, {'error': error}
elif event_type == 'error':
return current_output, {'error': data}
else:
return current_output, None
@ -1767,19 +1774,10 @@ async def get_image_urls(delta_images, request, metadata, user) -> list[str]:
return image_urls
async def add_file_context(messages: list, chat_id: str, user) -> list:
def add_file_context(messages: list) -> list:
"""
Add file URLs to messages for native function calling.
"""
if not is_saved_chat_id(chat_id):
return messages
chat = await Chats.get_chat_by_id_and_user_id(chat_id, user.id)
if not chat:
return messages
history = chat.chat.get('history', {})
stored_messages = get_message_list(history.get('messages', {}), history.get('currentId'))
def format_file_tag(file):
# Every file reaching here has a url or a chat id, so id is always set.
@ -1792,20 +1790,13 @@ async def add_file_context(messages: list, chat_id: str, user) -> list:
attrs += f' name="{file["name"]}"'
return f'<file {attrs}/>'
# Pair only user-role messages from both lists to avoid misalignment.
# After process_messages_with_output(), assistant messages with tool calls
# are expanded into multiple messages (assistant + tool results), making
# the payload message list longer than the stored message list. A naive
# positional zip() would pair user messages with wrong stored messages,
# causing later images to lose their file context (see #21878).
user_messages = [m for m in messages if m.get('role') == 'user']
stored_user_messages = [m for m in stored_messages if m.get('role') == 'user']
for message, stored_message in zip(user_messages, stored_user_messages):
for message in messages:
if message.get('role') != 'user':
continue
# Chat references carry no url - they are addressed by id via view_chat.
attached_files = [
file
for file in stored_message.get('files', [])
for file in message.get('files', [])
if (file.get('url') and not file.get('url').startswith('data:'))
or (file.get('type') == 'chat' and file.get('id'))
]
@ -2270,6 +2261,7 @@ def strip_reasoning_details(output: list) -> list:
def process_messages_with_output(
messages: list[dict],
reasoning_format: str | None = None,
include_file_context: bool = False,
) -> list[dict]:
"""
Process messages with OR-aligned output items for LLM consumption.
@ -2295,33 +2287,25 @@ def process_messages_with_output(
continue
clean_message = dict(message)
for key in ('id', 'files', 'output', 'model', 'contextSummary', 'context_summary', 'usage'):
for key in ('id', 'output', 'model', 'contextSummary', 'context_summary', 'usage'):
clean_message.pop(key, None)
processed.append(clean_message)
if include_file_context:
add_file_context(processed)
for message in processed:
message.pop('files', None)
return processed
def sanitize_tool_pairs(messages: list[dict]) -> list[dict]:
tool_result_ids = {
message.get('tool_call_id')
for message in messages
if message.get('role') == 'tool' and message.get('tool_call_id')
}
tool_call_ids = {
tool_call.get('id')
for message in messages
for tool_call in (message.get('tool_calls') or [])
if message.get('role') == 'assistant' and tool_call.get('id')
}
paired_ids_by_message = get_paired_tool_call_ids(messages)
sanitized = []
for message in messages:
for message, paired_ids in zip(messages, paired_ids_by_message):
if message.get('role') == 'assistant' and message.get('tool_calls'):
kept = [
tool_call for tool_call in message.get('tool_calls') or [] if tool_call.get('id') in tool_result_ids
]
kept = [tool_call for tool_call in message.get('tool_calls') or [] if tool_call.get('id') in paired_ids]
if kept:
sanitized.append({**message, 'tool_calls': kept})
else:
@ -2330,7 +2314,7 @@ def sanitize_tool_pairs(messages: list[dict]) -> list[dict]:
clean.pop('reasoning_items', None)
if clean.get('content'):
sanitized.append(clean)
elif message.get('role') != 'tool' or message.get('tool_call_id') in tool_call_ids:
elif message.get('role') != 'tool' or message.get('tool_call_id') in paired_ids:
sanitized.append(message)
return sanitized
@ -2438,6 +2422,17 @@ async def process_chat_payload(request, form_data, user, metadata, model):
# which the frontend strips, causing tool calls to be merged into content.
chat_id = metadata.get('chat_id')
user_message_id = metadata.get('user_message_id')
payload_tools = form_data.get('tools', None) # snapshot before filters
chat = await Chats.get_chat_by_id(chat_id) if is_saved_chat_id(chat_id) else None
is_note_chat = bool(chat and (chat.meta or {}).get('internal') is True and (chat.meta or {}).get('type') == 'note')
use_builtin_tools = is_note_chat or (
bool(metadata.get('session_id'))
and metadata.get('params', {}).get('function_calling') != 'legacy'
and (model.get('info', {}).get('meta', {}).get('capabilities') or {}).get('builtin_tools', True)
)
metadata['include_file_context'] = bool(
chat and chat.user_id == user.id and payload_tools is None and use_builtin_tools
)
if is_saved_chat_id(chat_id) and user_message_id:
db_messages = await load_messages_from_db(chat_id, user_message_id)
@ -2474,8 +2469,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
if f.get('url')
],
]
# Strip files field — it's been incorporated into content
message.pop('files', None)
if regeneration_prompt:
form_data['messages'].append({'role': 'user', 'content': regeneration_prompt})
@ -2511,6 +2504,10 @@ async def process_chat_payload(request, form_data, user, metadata, model):
except Exception:
log.exception('Context compaction failed; continuing with full chat history')
metadata['context_start_message_id'] = next(
(message.get('id') for message in form_data.get('messages', []) if message.get('role') != 'system'), None
)
# Process messages with OR-aligned output items for clean LLM messages
for message in form_data.get('messages', []):
output = message.get('output')
@ -2522,6 +2519,7 @@ async def process_chat_payload(request, form_data, user, metadata, model):
form_data['messages'] = process_messages_with_output(
form_data.get('messages', []),
reasoning_format=get_reasoning_format(model),
include_file_context=metadata['include_file_context'],
)
form_data['messages'] = sanitize_tool_pairs(form_data['messages'])
@ -2643,7 +2641,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
form_data['files'] = files
variables = form_data.pop('variables', None)
payload_tools = form_data.get('tools', None) # snapshot before filters
# Process the form_data through the pipeline
try:
@ -2776,12 +2773,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
available_skills = []
terminal_skills = []
view_skill_ids = []
chat = None
if is_saved_chat_id(metadata.get('chat_id')):
chat = await Chats.get_chat_by_id(metadata['chat_id'])
is_note_chat = bool(chat and (chat.meta or {}).get('internal') is True and (chat.meta or {}).get('type') == 'note')
if is_note_chat:
note_id = (chat.meta or {}).get('note_id')
note = await Notes.get_note_by_id(note_id) if note_id else None
@ -2805,12 +2796,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
if note_files:
files = [*(files or []), *note_files]
use_builtin_tools = is_note_chat or (
bool(metadata.get('session_id'))
and metadata.get('params', {}).get('function_calling') != 'legacy'
and (model.get('info', {}).get('meta', {}).get('capabilities') or {}).get('builtin_tools', True)
)
if skill_ids or use_builtin_tools:
from open_webui.models.skills import Skills as SkillsModel
from open_webui.utils.terminals import (
@ -3048,6 +3033,8 @@ async def process_chat_payload(request, form_data, user, metadata, model):
for tool_server in direct_tool_servers:
if tool_server.get('is_terminal') is True and not terminal_capability:
continue
# Copy so the pops below keep metadata intact for sub-agents and approval resumes
tool_server = dict(tool_server)
system_prompt = tool_server.pop('system_prompt', None)
if system_prompt:
form_data['messages'] = add_or_update_system_message(
@ -3079,10 +3066,6 @@ async def process_chat_payload(request, form_data, user, metadata, model):
# Only inject when the request originates from the UI (identified by session_id).
# API callers don't expect hidden tools; they can explicitly request tools via tool_ids.
if use_builtin_tools:
# Add file context to user messages
chat_id = metadata.get('chat_id')
form_data['messages'] = await add_file_context(form_data.get('messages', []), chat_id, user)
if (model.get('info', {}).get('meta', {}).get('builtinTools') or {}).get('knowledge', True):
from html import escape
@ -3544,6 +3527,11 @@ async def drain_approved_tool_calls(request, form_data, user, model, metadata) -
assistant_message = await Chats.get_message_by_id_and_message_id(chat_id, message_id)
if assistant_message:
db_messages.append({k: v for k, v in assistant_message.items() if k in MESSAGE_REPLAY_KEYS})
context_start_message_id = metadata.get('context_start_message_id')
start_index = next(
(index for index, message in enumerate(db_messages) if message.get('id') == context_start_message_id), 0
)
db_messages = db_messages[start_index:]
for message in db_messages:
output = message.get('output')
# reasoning_details can be model/provider-bound, so only replay them
@ -3555,9 +3543,11 @@ async def drain_approved_tool_calls(request, form_data, user, model, metadata) -
):
message['output'] = strip_reasoning_details(output)
system_message = get_system_message(form_data.get('messages', []))
form_data['messages'] = process_messages_with_output(
db_messages,
[system_message, *db_messages] if system_message else db_messages,
reasoning_format=get_reasoning_format(model),
include_file_context=metadata.get('include_file_context', False),
)
form_data['messages'] = sanitize_tool_pairs(form_data['messages'])
@ -3612,7 +3602,7 @@ async def pause_for_tool_approval(chat_id: str, message_id: str, output: list[di
if not has_pending_approval:
item['status'] = 'pending'
has_pending_approval = True
elif item.get('status') == 'in_progress':
elif item.get('status') in {'in_progress', 'completed'}:
item['status'] = 'queued'
await Chats.upsert_message_to_chat_by_id_and_message_id(
@ -4549,7 +4539,7 @@ async def streaming_chat_response_handler(response, ctx):
last_type = output[-1].get('type', '') if output else ''
if last_type == 'message':
if last_type == 'message' and output[-1].get('_tag_type') != content_type:
# Use the output item's own text for tag detection
item = output[-1]
item_text = get_last_text(output)
@ -4679,14 +4669,14 @@ async def streaming_chat_response_handler(response, ctx):
# Strip start and end tags from content
start_tag_pattern = _start_tag_pattern(start_tag)
block_content = re.sub(start_tag_pattern, '', block_content).strip()
block_content = re.sub(start_tag_pattern, '', block_content)
end_tag_pattern = rf'{re.escape(end_tag)}'
end_tag_regex = re.compile(end_tag_pattern, re.DOTALL)
split_content = end_tag_regex.split(block_content, maxsplit=1)
block_content = split_content[0].strip() if split_content else ''
leftover_content = split_content[1].strip() if len(split_content) > 1 else ''
leftover_content = split_content[1].lstrip() if len(split_content) > 1 else ''
if block_content:
# Update the item with final content
@ -5113,8 +5103,8 @@ async def streaming_chat_response_handler(response, ctx):
'data': data,
}
)
# Check for Responses API events (type field starts with "response.")
elif data.get('type', '').startswith('response.'):
# Check for Responses API events
elif data.get('type', '').startswith('response.') or data.get('type', '') == 'error':
response_data_type = data.get('type', '')
response_data_is_delta = response_data_type.endswith('.delta')
output, response_metadata = handle_responses_streaming_event(data, output)
@ -5173,6 +5163,20 @@ async def streaming_chat_response_handler(response, ctx):
response_metadata['usage'] = usage
if response_metadata.get('error'):
log.error(
'Provider returned error (streaming): %s', response_metadata['error']
)
if save_to_chat:
try:
await Chats.upsert_message_to_chat_by_id_and_message_id(
metadata['chat_id'],
metadata['message_id'],
{
'error': {'content': response_metadata['error']},
},
)
except Exception:
pass
await event_emitter(
{
'type': 'chat:completion',

View file

@ -261,6 +261,26 @@ def get_output_text(output: list | None) -> str:
return '\n'.join(texts)
def get_paired_tool_call_ids(messages: list[dict]) -> list[set[str]]:
"""Tool call ids answered within each message's assistant-plus-tool-results block."""
paired_ids_by_message = [set() for _ in messages]
for index, message in enumerate(messages):
if message.get('role') != 'assistant' or not message.get('tool_calls'):
continue
block_end = index + 1
while block_end < len(messages) and messages[block_end].get('role') == 'tool':
block_end += 1
requested_ids = {tool_call.get('id') for tool_call in message['tool_calls'] if tool_call.get('id')}
completed_ids = {tool_message.get('tool_call_id') for tool_message in messages[index + 1 : block_end]}
paired_ids = requested_ids & completed_ids
for block_index in range(index, block_end):
paired_ids_by_message[block_index] = paired_ids
return paired_ids_by_message
def reconcile_tool_pairs(messages: list[dict]) -> list[dict]:
"""Drop unpaired tool_use / tool_result from a reconstructed conversation.
@ -271,22 +291,14 @@ def reconcile_tool_pairs(messages: list[dict]) -> list[dict]:
Well-formed output is unaffected: every id pairs, so nothing is stripped.
"""
completed_tool_call_ids = {
message['tool_call_id'] for message in messages if message.get('role') == 'tool' and message.get('tool_call_id')
}
requested_tool_call_ids = {
tool_call['id']
for message in messages
for tool_call in message.get('tool_calls') or ()
if message.get('role') == 'assistant' and tool_call.get('id')
}
paired_ids_by_message = get_paired_tool_call_ids(messages)
reconciled_messages = []
for message in messages:
for message, paired_ids in zip(messages, paired_ids_by_message):
role = message.get('role')
# Orphan tool result — no assistant ever claimed this call_id.
if role == 'tool' and message.get('tool_call_id') not in requested_tool_call_ids:
# Orphan tool result: not claimed by the assistant heading its tool block.
if role == 'tool' and message.get('tool_call_id') not in paired_ids:
continue
# Non-assistant or no tool_calls — pass through unchanged.
@ -294,10 +306,8 @@ def reconcile_tool_pairs(messages: list[dict]) -> list[dict]:
reconciled_messages.append(message)
continue
# Keep only tool_calls whose id received a tool-role response.
valid_tool_calls = [
tool_call for tool_call in message['tool_calls'] if tool_call.get('id') in completed_tool_call_ids
]
# Keep only tool_calls whose id received an adjacent tool-role response.
valid_tool_calls = [tool_call for tool_call in message['tool_calls'] if tool_call.get('id') in paired_ids]
if valid_tool_calls:
reconciled_messages.append({**message, 'tool_calls': valid_tool_calls})

View file

@ -182,8 +182,18 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
if model:
if custom_model.is_active:
arena_meta = model['info']['meta'] if model.get('arena') else None
model['name'] = custom_model.name
model['info'] = custom_model.model_dump()
if arena_meta:
# Evaluation config owns arena access grants and model_ids
model['info']['meta'].update(
{
key: arena_meta[key]
for key in ('access_grants', 'model_ids', 'filter_mode')
if key in arena_meta
}
)
schema = get_chat_variables_schema(custom_model.params.model_dump().get('system'))
if schema:
model['info'].setdefault('meta', {})['chat_variables_schema'] = schema

View file

@ -268,9 +268,6 @@ def _notification_webhook_content(event: Any) -> tuple[str, str, dict[str, Any],
title = str(data.get('title') or event.message or 'Chat finished')
content = str(data.get('message') or '')
url = str(data.get('url') or '')
chat_id = str(data.get('chat_id') or '')
if chat_id and url.endswith(f'/c/{chat_id}'):
url = f'{url[: -len(f"/c/{chat_id}")].rstrip("/")}/{chat_id}'
body = '\n'.join(part for part in (content, url) if part)
return (
f'**{title}**',
@ -288,9 +285,6 @@ def _notification_webhook_content(event: Any) -> tuple[str, str, dict[str, Any],
title = str(event.message or 'Chat failed')
content = str(data.get('message') or '')
url = str(data.get('url') or '')
chat_id = str(data.get('chat_id') or '')
if chat_id and url.endswith(f'/c/{chat_id}'):
url = f'{url[: -len(f"/c/{chat_id}")].rstrip("/")}/{chat_id}'
body = '\n'.join(part for part in (content, url) if part)
return (
f'**{title}**',

View file

@ -1917,7 +1917,7 @@ class OAuthManager:
detailed_error,
exc_info=True,
)
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
except Exception as e:
detailed_error = _build_oauth_callback_error_message(e)
log.warning(
@ -1926,7 +1926,7 @@ class OAuthManager:
detailed_error,
exc_info=True,
)
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
# Try to get userinfo from the token first, some providers include it there
user_data: UserInfo = token.get('userinfo')
@ -1949,7 +1949,7 @@ class OAuthManager:
user_data = user_data['data']
if not user_data:
log.warning('OAuth callback failed for provider %s, user data is missing', provider)
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
# Extract the "sub" claim, using custom claim if configured
if auth_config.OAUTH_SUB_CLAIM:
@ -1959,7 +1959,7 @@ class OAuthManager:
sub = user_data.get(OAUTH_PROVIDERS[provider].get('sub_claim', 'sub'))
if not sub:
log.warning(f'OAuth callback failed, sub is missing: {user_data}')
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
sub = str(sub)
oauth_data = {}
@ -1994,18 +1994,18 @@ class OAuthManager:
email = primary_email
else:
log.warning('No primary email found in GitHub response')
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
else:
log.warning('Failed to fetch GitHub email')
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
except Exception as e:
log.warning(f'Error fetching GitHub email: {e}')
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
elif ENABLE_OAUTH_EMAIL_FALLBACK:
email = f'{provider}@{sub}.local'
else:
log.warning(f'OAuth callback failed, email is missing: {user_data}')
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
email = email.lower()
# If allowed domains are configured, check if the email domain is in the list
@ -2014,7 +2014,7 @@ class OAuthManager:
and email.split('@')[-1] not in auth_config.OAUTH_ALLOWED_DOMAINS
):
log.warning(f'OAuth callback failed, e-mail domain is not in the list of allowed domains: {user_data}')
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
raise HTTPException(400, detail=ERROR_MESSAGES.OAUTH_LOGIN_FAILED)
# Check if the user exists
user = await Users.get_user_by_oauth_sub(provider, sub, db=db)

View file

@ -379,10 +379,6 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
if 'tools' in openai_payload:
ollama_payload['tools'] = openai_payload['tools']
if 'max_tokens' in openai_payload:
ollama_payload['num_predict'] = openai_payload['max_tokens']
del openai_payload['max_tokens']
# If there are advanced parameters in the payload, format them in Ollama's options field
if openai_payload.get('options'):
# Copied before key deletions below so the caller's options stay intact
@ -430,6 +426,11 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
ollama_options['stop'] = openai_payload.get('stop')
ollama_payload['options'] = ollama_options
if 'max_tokens' in openai_payload:
ollama_options = ollama_payload.get('options', {})
ollama_options['num_predict'] = openai_payload['max_tokens']
ollama_payload['options'] = ollama_options
if 'metadata' in openai_payload:
ollama_payload['metadata'] = openai_payload['metadata']

View file

@ -14,7 +14,7 @@ from open_webui.models.chats import Chat, ChatForm, Chats
from open_webui.models.config import Config
from open_webui.models.users import UserModel, Users
from open_webui.tasks import create_task, has_active_tasks
from open_webui.utils.auth import create_token
from open_webui.utils.auth import VERIFIED_USER_ROLES, create_token
from open_webui.utils.json_codec import JSONCodec
from open_webui.utils.misc import get_message_list
from sqlalchemy import select
@ -84,7 +84,7 @@ async def process_pending_internal_messages(
return
user = await Users.get_user_by_id(user_id)
if not user:
if not user or user.role not in VERIFIED_USER_ROLES:
return
async with get_async_db() as db:
@ -219,6 +219,7 @@ async def process_pending_internal_messages(
history['messages'] = messages
history['currentId'] = assistant_message_id
chat.chat = {**(chat.chat or {}), 'history': history}
chat.current_message_id = assistant_message_id
chat.updated_at = int(time.time())
await db.commit()

View file

@ -11,7 +11,7 @@ from datetime import datetime, timezone
from typing import Literal
from uuid import uuid4
from fastapi import Request
from fastapi import HTTPException, Request
from open_webui.internal.db import get_async_db
from open_webui.models.chat_messages import ChatMessages
from open_webui.models.chats import Chat, ChatForm, Chats
@ -351,6 +351,7 @@ async def execute_due_timer(app, timer_id: str, claim_id: str | None = None) ->
parent.chat = parent_chat
history['currentId'] = assistant_message_id
parent.current_message_id = assistant_message_id
parent.updated_at = int(time.time())
timer_row = await db.get(Chat, timer_id)
if timer_row:
@ -416,7 +417,20 @@ async def execute_due_timer(app, timer_id: str, claim_id: str | None = None) ->
await app.state.CHAT_COMPLETION_HANDLER(request, form_data, user=user)
except Exception as exc:
log.exception(f'Timer {timer_id} completion failed')
await _set_timer_state(timer_id, 'error', timer_error=str(exc)[:500])
error_detail = exc.detail if isinstance(exc, HTTPException) else str(exc)
await _set_timer_state(timer_id, 'error', timer_error=error_detail[:500])
await Chats.upsert_message_to_chat_by_id_and_message_id(
parent_chat_id, assistant_message_id, {'error': {'content': error_detail}, 'done': True}
)
await sio.emit(
'events',
{
'chat_id': parent_chat_id,
'message_id': assistant_message_id,
'data': {'type': 'chat:message:error', 'data': {'error': {'content': error_detail}, 'done': True}},
},
room=f'user:{timer.user_id}',
)
async def _set_timer_state(timer_id: str, status: str, **fields) -> None:

View file

@ -5,7 +5,10 @@ from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import ENABLE_ADMIN_CHAT_ACCESS
from open_webui.models.chats import Chats
from open_webui.models.config import Config
from open_webui.models.users import Users
from open_webui.socket.main import get_event_emitter
from open_webui.utils.json_codec import JSONCodec
@ -25,7 +28,7 @@ async def resolve_tool_call_output(
db: AsyncSession | None = None,
) -> dict:
chat = await Chats.get_chat_by_id(chat_id, db=db)
if not chat or (chat.user_id != user.id and user.role != 'admin'):
if not chat or (chat.user_id != user.id and not (user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS)):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
@ -160,8 +163,17 @@ async def build_tool_approval_resume_payload(chat_id: str, message_id: str, chat
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail='Tool call message model is missing.')
messages = []
if params.get('system'):
messages.append({'role': 'system', 'content': params.get('system')})
system_prompt = params.get('system')
if not system_prompt:
# Mirror the chat UI's system prompt fallback
user = await Users.get_user_by_id(chat.user_id)
ui_settings = (user.settings.ui if user and user.settings else None) or {}
system_prompt = ui_settings.get('system')
if system_prompt is None:
default_interface_settings = await Config.get('ui.default_interface_settings') or {}
system_prompt = default_interface_settings.get('system')
if system_prompt:
messages.append({'role': 'system', 'content': system_prompt})
return {
'stream': params.get('stream_response', True),

View file

@ -101,7 +101,7 @@ from open_webui.tools.builtin import (
write_note,
)
from open_webui.utils.access_control import has_access, has_connection_access, has_permission
from open_webui.utils.chat_id import is_saved_chat_id
from open_webui.utils.chat_id import is_saved_chat_id, is_temporary_chat_id
from open_webui.utils.headers import (
bearer_auth_header,
get_custom_headers,
@ -650,6 +650,7 @@ async def get_builtin_tools(
and config.get('subagents.enable')
and getattr(request.state, 'internal', False) is not True
and getattr(request.state, 'direct', False) is not True
and not is_temporary_chat_id(metadata.get('chat_id'))
):
builtin_functions.extend([delegate_task, timer])

View file

@ -21,7 +21,7 @@ joserfc==1.7.4
requests==2.34.2
regex==2026.5.9
google-re2==1.1.20251105 # bounded compilation and linear-time knowledge searches
aiohttp==3.13.5 # do not update to 3.13.3 - broken
aiohttp==3.14.3 # do not update to 3.13.3 - broken
aiodns==3.6.1 # keep pinned: 4.x pulls pycares 5 (c-ares 1.34.6) which breaks DNS on some hosts (#28013, #28215); opt-in via AIOHTTP_CLIENT_ASYNC_DNS_RESOLVER
aiocache==0.12.3
aiofiles==25.1.0
@ -36,7 +36,7 @@ aiosqlite==0.22.1
psycopg[binary]==3.3.4
alembic==1.18.4
pycrdt==0.13.1
pycrdt==0.14.8
redis==8.0.1
hiredis==3.4.2
@ -67,7 +67,7 @@ lxml==6.1.1
validators==0.35.0
psutil==7.2.2
pillow==12.2.0
pillow==12.3.0
rank-bm25==0.2.2
black==26.5.1

View file

@ -16,7 +16,7 @@ joserfc==1.7.4
requests==2.34.2
regex==2026.5.9
google-re2==1.1.20251105 # bounded compilation and linear-time knowledge searches
aiohttp==3.13.5 # do not update to 3.13.3 - broken
aiohttp==3.14.3 # do not update to 3.13.3 - broken
aiodns==3.6.1 # keep pinned: 4.x pulls pycares 5 (c-ares 1.34.6) which breaks DNS on some hosts (#28013, #28215); opt-in via AIOHTTP_CLIENT_ASYNC_DNS_RESOLVER
aiocache==0.12.3
aiofiles==25.1.0
@ -32,7 +32,7 @@ aiosqlite==0.22.1
psycopg[binary]==3.3.4
alembic==1.18.4
pycrdt==0.13.1
pycrdt==0.14.8
redis==8.0.1
hiredis==3.4.2
@ -85,7 +85,7 @@ psutil==7.2.2
sentencepiece==0.2.1
soundfile==0.13.1
pillow==12.2.0
pillow==12.3.0
opencv-python-headless==4.13.0.92
rapidocr==3.9.2
rank-bm25==0.2.2

View file

@ -22,7 +22,7 @@ dependencies = [
"joserfc==1.7.4",
"requests==2.34.2",
"aiohttp==3.13.5", # do not update to 3.13.3 - broken
"aiohttp==3.14.3", # do not update to 3.13.3 - broken
"aiodns==3.6.1", # keep pinned: 4.x pulls pycares 5 (c-ares 1.34.6) which breaks DNS on some hosts (#28013, #28215); opt-in via AIOHTTP_CLIENT_ASYNC_DNS_RESOLVER
"aiocache==0.12.3",
"aiofiles==25.1.0",
@ -38,7 +38,7 @@ dependencies = [
"psycopg[binary]==3.3.4",
"alembic==1.18.4",
"pycrdt==0.13.1",
"pycrdt==0.14.8",
"redis==8.0.1",
"hiredis==3.4.2",
# "valkey-glide-sync==2.3.1", # optional: install manually if VECTOR_DB=valkey
@ -93,7 +93,7 @@ dependencies = [
"soundfile==0.13.1",
"azure-ai-documentintelligence==1.0.2",
"pillow==12.2.0",
"pillow==12.3.0",
"opencv-python-headless==4.13.0.92",
"rapidocr==3.9.2",
"rank-bm25==0.2.2",

View file

@ -77,9 +77,29 @@
// remove trailing slash from url
url = url.replace(/\/$/, '');
let _headers = null;
if (headers) {
try {
_headers = JSON.parse(headers);
if (typeof _headers !== 'object' || Array.isArray(_headers)) {
_headers = null;
throw new Error('Headers must be a valid JSON object');
}
headers = JSON.stringify(_headers, null, 2);
} catch (error) {
toast.error($i18n.t('Headers must be a valid JSON object'));
return;
}
}
const res = await verifyOllamaConnection(localStorage.token, {
url,
key
key,
config: {
auth_type,
...(_headers ? { headers: _headers } : {})
}
}).catch((error) => {
toast.error(`${error}`);
});
@ -241,6 +261,12 @@
showAdvanced = false;
tags = [];
modelIds = [];
headers = '';
enable = true;
connectionType = 'external';
provider = '';
apiVersion = '';
apiType = '';
};
const init = () => {

View file

@ -169,10 +169,7 @@
toast.error($i18n.t('External Document Loader URL required.'));
return;
}
if (
RAGConfig.CONTENT_EXTRACTION_ENGINE === 'external' &&
RAGConfig.EXTERNAL_DOCUMENT_LOADER_HEADERS
) {
if (RAGConfig.EXTERNAL_DOCUMENT_LOADER_HEADERS) {
try {
const headers = JSON.parse(RAGConfig.EXTERNAL_DOCUMENT_LOADER_HEADERS);
if (headers === null || typeof headers !== 'object' || Array.isArray(headers)) {

View file

@ -824,7 +824,7 @@
{/if}
</div>
<Dropdown align="end">
<Dropdown closeOnSelect align="end">
<Tooltip content={$i18n.t('Actions')}>
<button
class="flex h-8 items-center gap-1.5 rounded-xl bg-transparent px-1.5 text-[0.8125rem] font-normal text-gray-700 transition hover:text-gray-900 dark:text-gray-200 dark:hover:text-gray-100"

View file

@ -579,6 +579,20 @@
/>
</div>
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs text-gray-600 dark:text-gray-400 mb-1">
{$i18n.t('settings.admin.web.tavilySearchDepth.label')}
</div>
<SettingsSelect bind:value={webConfig.TAVILY_SEARCH_DEPTH} className="w-full">
<option value="ultra-fast">{$i18n.t('Ultra-fast')}</option>
<option value="fast">{$i18n.t('Fast')}</option>
<option value="basic">{$i18n.t('Basic')}</option>
<option value="advanced">{$i18n.t('Advanced')}</option>
</SettingsSelect>
</div>
</div>
{:else if webConfig.WEB_SEARCH_ENGINE === 'staan'}
<div class="mb-2.5 flex w-full flex-col">
<div>

View file

@ -167,6 +167,7 @@
return null;
});
if (res) {
automation = res;
toast.success($i18n.t('Automation triggered'));
setTimeout(() => loadRuns(false), 2000);
}

View file

@ -28,6 +28,7 @@
let startDate = '';
let startTime = '';
let endDate = '';
let endDayOffset = 0;
let endTime = '';
let allDay = false;
let location = '';
@ -111,6 +112,12 @@
alertMinutes = 10;
repeatFrequency = '';
}
endDayOffset = endDate
? Math.round(
(dateTimeToNs(endDate, '12:00') - dateTimeToNs(startDate, '12:00')) /
(24 * 60 * 60 * 1000 * NS)
)
: 0;
}
$: if (show) reset();
@ -129,7 +136,12 @@
loading = true;
try {
const startNs = dateTimeToNs(startDate, allDay ? '00:00' : startTime);
let endNs = endDate ? dateTimeToNs(endDate, allDay ? '23:59' : endTime) : undefined;
const shiftedEndDate = endDate
? nsToDateStr(dateTimeToNs(startDate, '12:00') + endDayOffset * 24 * 60 * 60 * 1000 * NS)
: '';
let endNs = shiftedEndDate
? dateTimeToNs(shiftedEndDate, allDay ? '23:59' : endTime)
: undefined;
if (endNs !== undefined && endNs < startNs) {
const duration =
event?.end_at && event.end_at > event.start_at ? event.end_at - event.start_at : 0;

View file

@ -126,7 +126,7 @@
}
}
text = text.replaceAll('{{CLIPBOARD}}', clipboardText.replaceAll('\r\n', '\n'));
text = text.replaceAll('{{CLIPBOARD}}', () => clipboardText.replaceAll('\r\n', '\n'));
}
if (text.includes('{{USER_LOCATION}}')) {

View file

@ -109,6 +109,8 @@
selectedContentIdx = 0;
} else if (newContents.length > contents.length) {
selectedContentIdx = newContents.length - 1;
} else if (selectedContentIdx > newContents.length - 1) {
selectedContentIdx = newContents.length - 1;
}
contents = newContents;

View file

@ -429,6 +429,7 @@
let loadedChatIdProp = '';
let currentDraftKey = '';
// Chat parameters own the approval mode; chats without an override use the user default.
$: toolApprovalMode =
(params?.tool_approval_mode ?? $settings?.params?.tool_approval_mode) === 'ask'
? 'ask'
@ -769,9 +770,8 @@
webSearchEnabled = input.webSearchEnabled ?? false;
imageGenerationEnabled = input.imageGenerationEnabled ?? false;
codeInterpreterEnabled = input.codeInterpreterEnabled ?? false;
if (input.toolApprovalMode) {
await handleToolApprovalModeChange(input.toolApprovalMode);
}
// Ignore approval modes in older drafts. Restoring input must not overwrite chat
// parameters or invoke the change handler, which can save settings and approve tools.
return true;
} catch (e) {
return false;
@ -834,6 +834,7 @@
selectedFilterIds = [];
webSearchEnabled = false;
imageGenerationEnabled = false;
codeInterpreterEnabled = false;
const storageChatInput = sessionStorage.getItem(
`chat-input${chatIdProp ? `-${chatIdProp}` : ''}`
@ -1240,7 +1241,11 @@
const chatEventHandler = async (event, cb) => {
console.log(event);
if (event.chat_id === $chatId) {
// A new chat's title can arrive before its id; the response message already exists.
if (
event.chat_id === $chatId ||
(!$chatId && event?.data?.type === 'chat:title' && history.messages[event.message_id])
) {
await tick();
const type = event?.data?.type ?? null;
if (type === 'chat:reload') {
@ -4101,8 +4106,7 @@
selectedFilterIds,
imageGenerationEnabled,
webSearchEnabled,
codeInterpreterEnabled,
toolApprovalMode
codeInterpreterEnabled
});
const saveDraft = async (draft: any, chatId: string | null = null, debounce = true) => {

View file

@ -238,6 +238,7 @@
}
let chatInputDraft: any;
// Approval mode is persisted in chat parameters and user settings, not input drafts.
$: chatInputDraft = {
prompt,
files: files
@ -254,8 +255,7 @@
selectedFilterIds,
imageGenerationEnabled,
webSearchEnabled,
codeInterpreterEnabled,
toolApprovalMode
codeInterpreterEnabled
};
$: onChange(chatInputDraft);
@ -305,7 +305,7 @@
}
}
text = text.replaceAll('{{CLIPBOARD}}', clipboardText.replaceAll('\r\n', '\n'));
text = text.replaceAll('{{CLIPBOARD}}', () => clipboardText.replaceAll('\r\n', '\n'));
}
if (text.includes('{{USER_LOCATION}}')) {
@ -1452,7 +1452,7 @@
.insertContentAt(range, [
{
type: 'mention',
attrs: props
attrs: { ...props, mentionSuggestionChar: '/' }
},
{ type: 'text', text: ' ' }
])

View file

@ -55,6 +55,7 @@
const onKeyDown = (event: KeyboardEvent) => {
if (!['ArrowUp', 'ArrowDown', 'Enter', 'Tab', 'Escape'].includes(event.key)) return false;
if ((filteredItems ?? []).length === 0) return false;
if (event.key === 'ArrowUp') {
suggestionElement?.selectUp();
@ -89,7 +90,10 @@
}
</script>
<div class={(filteredItems ?? []).length > 0 ? '' : 'hidden'} id="suggestions-container">
<div
class={(filteredItems ?? []).length > 0 ? '' : 'hidden'}
id={(filteredItems ?? []).length > 0 ? 'suggestions-container' : undefined}
>
<DropdownMenu className="w-72 max-w-[calc(100vw-1rem)] overflow-x-hidden font-sans text-xs">
<div class="max-h-60 overflow-y-auto overflow-x-hidden scrollbar-thin">
{#if char === '/'}

View file

@ -370,6 +370,8 @@
parentId: parentId,
childrenIds: [],
files: undefined,
annotation: undefined,
feedbackId: undefined,
content: output !== undefined ? '' : content,
...(output !== undefined ? { output } : {}),
timestamp: Math.floor(Date.now() / 1000) // Unix epoch

View file

@ -1,5 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte';
import { safeLinkUrl } from '$lib/utils';
import CodeBlock from './CodeBlock.svelte';
import Modal from '$lib/components/common/Modal.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
@ -97,7 +98,7 @@
<ul class="mt-1 list-disc pl-4 text-xs">
{#each codeExecution?.result?.files as file}
<li>
<a href={file.url} target="_blank">{file.name}</a>
<a href={safeLinkUrl(file.url)} target="_blank">{file.name}</a>
</li>
{/each}
</ul>

View file

@ -99,14 +99,18 @@
$: pendingToolTokens = tokens.filter(
(t) => t?.attributes?.type === 'tool_calls' && t?.attributes?.status === 'pending'
);
$: hasActiveToolCalls = tokens.some(
(t) =>
t?.attributes?.type === 'tool_calls' &&
t?.attributes?.status !== 'rejected' &&
t?.attributes?.status !== 'failed' &&
t?.attributes?.status !== 'incomplete' &&
t?.attributes?.done !== 'true'
);
$: hasActiveDetails = tokens.some((token) => {
const attributes = token?.attributes;
if (attributes?.type === 'tool_calls') {
return (
attributes.status !== 'rejected' &&
attributes.status !== 'failed' &&
attributes.status !== 'incomplete' &&
attributes.done !== 'true'
);
}
return !messageDone && attributes?.done && attributes.done !== 'true';
});
$: hasRejected = tokens.some(
(t) => t?.attributes?.type === 'tool_calls' && t?.attributes?.status === 'rejected'
);
@ -174,7 +178,7 @@
return detail;
})();
$: prefixText = hasActiveToolCalls ? $i18n.t('Exploring') : $i18n.t('Explored');
$: prefixText = hasActiveDetails ? $i18n.t('Exploring') : $i18n.t('Explored');
</script>
<div {id} class="w-full min-w-0">
@ -199,7 +203,7 @@
>
<div class="flex items-center gap-1.5 min-w-0">
<!-- Status icon -->
{#if hasActiveToolCalls}
{#if hasActiveDetails}
<div>
<Spinner className="size-4" />
</div>
@ -223,7 +227,7 @@
<!-- Summary text -->
<div class="flex-1 line-clamp-1">
<span class="text-gray-600 dark:text-gray-300 {hasActiveToolCalls ? 'shimmer' : ''}"
<span class="text-gray-600 dark:text-gray-300 {hasActiveDetails ? 'shimmer' : ''}"
>{prefixText}</span
>
{#if summaryText}

View file

@ -33,6 +33,7 @@
export let selectionOnly = false;
export let onClick: () => void = () => {};
export let onEdit: () => void = () => {};
$: localizedDescription = resolveLocalizedModelDescription(item.model, $i18n.language);
@ -304,6 +305,7 @@
model={item.model}
{pinModelHandler}
{deleteModelHandler}
{onEdit}
copyLinkHandler={() => {
copyLinkHandler(item.model);
}}

View file

@ -20,6 +20,7 @@
export let pinModelHandler: (modelId: string) => void = () => {};
export let copyLinkHandler: Function = () => {};
export let deleteModelHandler: Function = () => {};
export let onEdit: () => void = () => {};
export let onClose: Function = () => {};
@ -30,6 +31,7 @@
bind:show
align="end"
sideOffset={-2}
contentClass="model-selector-child-menu"
onOpenChange={(state) => {
if (state === false) {
onClose();
@ -61,6 +63,7 @@
showSettings.set({ tab: 'admin:models', state: { id: model?.id ?? null } });
}
show = false;
onEdit();
}}
>
<Pencil className="size-3.5" />

View file

@ -159,8 +159,8 @@
}
};
const handlePointerDown = (e: PointerEvent) => {
if (!show) return;
const handleWindowClick = (e: MouseEvent) => {
if (!show || e.detail === 0) return;
const target = e.target as Node;
if (
(triggerElement && triggerElement.contains(target)) ||
@ -169,6 +169,8 @@
) {
return;
}
e.preventDefault();
e.stopPropagation();
show = false;
document.getElementById(`model-selector-${id}-button`)?.blur();
};
@ -808,6 +810,7 @@
let deleteModelTarget: any = null;
const deleteModelHandler = async (model: any) => {
show = false;
deleteModelTarget = model;
showDeleteConfirm = true;
};
@ -901,7 +904,7 @@
}}
/>
<svelte:window on:pointerdown={handlePointerDown} on:keydown={handleKeydown} />
<svelte:window on:click|capture={handleWindowClick} on:keydown={handleKeydown} />
<div class="relative w-full">
<button
@ -1106,6 +1109,9 @@
onClick={() => {
selectItem(item, index);
}}
onEdit={() => {
show = false;
}}
/>
{/each}
<div style="height: {(filteredItems.length - visibleEnd) * ITEM_HEIGHT}px;" />

View file

@ -221,7 +221,7 @@
{/if}
</div>
<Dropdown align="end">
<Dropdown closeOnSelect align="end">
<Tooltip content={$i18n.t('Actions')}>
<button
class="flex h-7 items-center gap-1.5 rounded-lg bg-transparent px-1.5 text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white"

View file

@ -185,7 +185,7 @@
<div class="min-w-0 flex-1"></div>
{/if}
<Dropdown align="end">
<Dropdown closeOnSelect align="end">
<Tooltip content={$i18n.t('Actions')}>
<button
class="flex h-7 items-center gap-1.5 rounded-lg bg-transparent px-1.5 text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white"

View file

@ -151,7 +151,7 @@
{$i18n.t('and create a new shared link.')}
{:else}
{$i18n.t(
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat."
"Messages you send after creating your link won't be shared. Your link is private until you choose who can view it."
)}
{/if}
</div>

View file

@ -14,6 +14,9 @@
/** Close when clicking outside */
export let closeOnOutsideClick = true;
/** Close action menus after selection. Submenu triggers prevent the default click. */
export let closeOnSelect = false;
/** Called when open/close state changes */
export let onOpenChange: (state: boolean) => void = () => {};
@ -281,11 +284,15 @@
afterOpen();
}
function handleWindowPointerDown(event: PointerEvent) {
if (!show || !closeOnOutsideClick) return;
function handleWindowClick(event: MouseEvent) {
if (!show || !closeOnOutsideClick || event.detail === 0) return;
if (!(event.target instanceof Node)) return;
if (triggerEl?.contains(event.target)) return;
if (contentEl?.contains(event.target)) return;
// Submenu content is portaled outside contentEl.
if (event.target instanceof Element && event.target.closest('[role="menu"]')) return;
event.preventDefault();
event.stopPropagation();
closeDropdown(false);
}
@ -302,10 +309,7 @@
import { onMount, onDestroy } from 'svelte';
let onPointerDown: ((e: PointerEvent) => void) | undefined;
onMount(() => {
onPointerDown = (e) => handleWindowPointerDown(e);
document.addEventListener('pointerdown', onPointerDown, true);
if (visualViewportAware) {
window.visualViewport?.addEventListener('resize', scheduleSettledPositionUpdates);
window.visualViewport?.addEventListener('scroll', schedulePositionUpdate);
@ -314,9 +318,6 @@
onDestroy(() => {
if (positionFrame != null) cancelAnimationFrame(positionFrame);
for (const timer of settleTimers) window.clearTimeout(timer);
if (onPointerDown) {
document.removeEventListener('pointerdown', onPointerDown, true);
}
if (visualViewportAware) {
window.visualViewport?.removeEventListener('resize', scheduleSettledPositionUpdates);
window.visualViewport?.removeEventListener('scroll', schedulePositionUpdate);
@ -325,6 +326,7 @@
</script>
<svelte:window
on:click|capture={handleWindowClick}
on:keydown={handleKeydown}
on:scroll|capture={positionContent}
on:resize={positionContent}
@ -351,7 +353,10 @@
style:max-height={resolvedMaxHeight}
style:overflow-y="auto"
transition:flyAndScale
on:click={(e) => e.stopPropagation()}
on:click={(e) => {
e.stopPropagation();
if (closeOnSelect && !e.defaultPrevented) closeDropdown();
}}
on:pointerdown={(e) => e.stopPropagation()}
>
<slot name="content" />

View file

@ -2,7 +2,7 @@
import { createEventDispatcher, getContext } from 'svelte';
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { formatFileSize } from '$lib/utils';
import { formatFileSize, safeLinkUrl } from '$lib/utils';
import { settings, showFileNavPath } from '$lib/stores';
import FileItemModal from './FileItemModal.svelte';
@ -71,7 +71,7 @@
} else {
window.open(`${WEBUI_API_BASE_URL}/files/${url}/content`, '_blank').focus();
}
} else {
} else if (safeLinkUrl(url)) {
window.open(`${url}`, '_blank').focus();
}
}

View file

@ -4,7 +4,7 @@
import { getContext, onMount, tick } from 'svelte';
import { formatFileSize, getLineCount } from '$lib/utils';
import { formatFileSize, getLineCount, safeLinkUrl } from '$lib/utils';
import { WEBUI_API_BASE_URL } from '$lib/constants';
import { settings } from '$lib/stores';
import { getKnowledgeById } from '$lib/apis/knowledge';
@ -264,7 +264,7 @@
href="#"
class="hover:underline line-clamp-1"
on:click|preventDefault={() => {
if (item.type === 'file' || item.url) {
if (item.type === 'file' || safeLinkUrl(item.url)) {
let fileId = item?.id ?? item?.tempId;
window.open(
item.type === 'file'

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { marked } from 'marked';
import DOMPurify from 'dompurify';
import equal from 'fast-deep-equal';
import { equalEditorJSON } from '$lib/utils/editorJson';
import { showCallOverlay, skills, terminalSkills } from '$lib/stores';
marked.use({
@ -1380,7 +1380,7 @@
}
if (json) {
if (!equal(value, jsonValue)) {
if (!equalEditorJSON(value, jsonValue)) {
editor.commands.setContent(value);
selectTemplate();
}

View file

@ -278,6 +278,7 @@
{/if}
<Dropdown
closeOnSelect
onOpenChange={(state) => {
if (state === false) {
onClose();
@ -501,7 +502,8 @@
<hr class="border-gray-50/30 dark:border-gray-800/30 mx-1 my-0.5" />
<div class="flex max-h-28 overflow-y-auto px-2 py-1">
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
<div class="flex max-h-28 overflow-y-auto px-2 py-1" on:click={(e) => e.preventDefault()}>
<Tags chatId={chat.id} />
</div>
{/if}

View file

@ -45,7 +45,6 @@
export let chatId = '';
export let archived = false;
let dropdown: Dropdown;
let show = false;
let pinned = false;
@ -292,7 +291,7 @@
{/if}
<Dropdown
bind:this={dropdown}
closeOnSelect
bind:show
onOpenChange={(state) => {
if (state) {
@ -368,7 +367,6 @@
draggable="false"
class="flex h-[1.6875rem] gap-2 items-center rounded-xl px-2 text-[0.8125rem] cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-900 w-full"
on:click={() => {
dropdown.close();
renameHandler();
}}
>
@ -380,7 +378,6 @@
draggable="false"
class="flex h-[1.6875rem] gap-2 items-center rounded-xl px-2 text-[0.8125rem] cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-900 w-full"
on:click={() => {
dropdown.close();
markUnreadHandler();
}}
>
@ -394,7 +391,6 @@
draggable="false"
class="flex h-[1.6875rem] gap-2 items-center rounded-xl px-2 text-[0.8125rem] cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-900 w-full"
on:click={() => {
dropdown.close();
pinHandler();
}}
>
@ -412,7 +408,6 @@
draggable="false"
class="flex h-[1.6875rem] gap-2 items-center rounded-xl px-2 text-[0.8125rem] cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-900 w-full"
on:click={() => {
dropdown.close();
cloneChatHandler();
}}
>

View file

@ -26,6 +26,7 @@
</script>
<Dropdown
closeOnSelect
bind:show
{align}
onOpenChange={(state) => {
@ -62,7 +63,6 @@
<button
class="flex h-[1.6875rem] w-full items-center gap-2 rounded-xl px-2 text-[0.8125rem] select-none cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-900"
on:click={() => {
show = false;
onMarkAllRead();
}}
>

View file

@ -835,8 +835,7 @@
}}
on:keydown={(e) => {
if (e.key === 'Enter') {
updateHandler({ name });
edit = false;
e.currentTarget.blur();
}
}}
class="w-full h-full bg-transparent outline-hidden"

View file

@ -8,7 +8,7 @@
import { marked } from 'marked';
import { toast } from 'svelte-sonner';
import equal from 'fast-deep-equal';
import { equalEditorJSON } from '$lib/utils/editorJson';
import { goto, onNavigate } from '$app/navigation';
@ -340,7 +340,7 @@
}
function areContentsEqual(a, b) {
return equal(a, b);
return equalEditorJSON(a, b);
}
function insertNoteVersion(note) {

View file

@ -36,6 +36,7 @@
</script>
<Dropdown
closeOnSelect
bind:show
align="end"
sideOffset={6}
@ -54,8 +55,10 @@
'Format Markdown as you type and paste. Turn off to keep Markdown characters and paste plain text. Existing formatting is preserved.'
)}
>
<!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
<div
class="select-none flex h-[1.6875rem] items-center gap-2 rounded-xl px-2 text-[0.8125rem] hover:bg-gray-50/60 dark:hover:bg-gray-800/60"
on:click={(e) => e.preventDefault()}
>
<Bold className="size-3.5 shrink-0" strokeWidth="2" />
<span class="flex-1">{$i18n.t('Formatting')}</span>
@ -106,7 +109,6 @@
class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[0.8125rem] hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => {
onUploadFiles();
show = false;
}}
>
<CloudArrowUp className="size-3.5" strokeWidth="2" />
@ -155,7 +157,6 @@
class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[0.8125rem] hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => {
onPin();
show = false;
}}
>
{#if isPinned}

View file

@ -26,6 +26,7 @@
</script>
<Dropdown
closeOnSelect
bind:show
onOpenChange={(state) => {
if (state === false) {
@ -62,7 +63,6 @@
class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-xs hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => {
onUpload({ type: 'new_directory' });
show = false;
}}
>
<NewFolderAlt />
@ -134,7 +134,6 @@
class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-xs hover:text-gray-900 dark:hover:text-gray-100"
on:click={() => {
onReset();
show = false;
}}
>
<ArrowUturnLeft strokeWidth="2" />

View file

@ -34,12 +34,10 @@
let editName = '';
let editInput: HTMLInputElement;
let dragOver = false;
let showDropdown = false;
const startRename = () => {
editName = directory.name;
editing = true;
showDropdown = false;
setTimeout(() => editInput?.select(), 0);
};
@ -163,7 +161,7 @@
{#if writeAccess}
<div class="flex items-center">
<Dropdown bind:show={showDropdown} align="end" sideOffset={4}>
<Dropdown closeOnSelect align="end" sideOffset={4}>
<button
class="p-1 rounded-full hover:bg-gray-100 dark:hover:bg-gray-850 transition"
type="button"

View file

@ -584,7 +584,8 @@
<Switch
bind:state={skill.is_active}
on:change={async () => {
toggleSkillById(localStorage.token, skill.id);
await toggleSkillById(localStorage.token, skill.id);
_skills.set(await getSkills(localStorage.token));
}}
/>
</Tooltip>

File diff suppressed because it is too large Load diff

View file

@ -285,6 +285,7 @@
"Base Model (From)": "",
"Base Model is required.": "",
"Base Models": "",
"Basic": "",
"Basic text only": "",
"Basics": "",
"Bearer": "",
@ -1000,6 +1001,7 @@
"Failed to update webhook": "",
"Failed to upload background image.": "",
"Failed to upload file.": "",
"Fast": "",
"Features Permissions": "",
"February": "",
"Feedback": "",
@ -2706,6 +2708,7 @@
"settings.admin.web.sougouSearchApiSk.label": "Sougou Search API SK",
"settings.admin.web.tavilyApiKey.label": "Tavily API Key",
"settings.admin.web.tavilyExtractDepth.label": "Tavily Extract Depth",
"settings.admin.web.tavilySearchDepth.label": "Tavily Search Depth",
"settings.admin.web.timeout.description": "Maximum time to wait while loading web content.",
"settings.admin.web.timeout.label": "Timeout",
"settings.admin.web.title": "Web Search",
@ -3319,6 +3322,7 @@
"Type Hugging Face Resolve (Download) URL": "",
"Type your answer": "",
"Uh-oh! There was an issue with the response.": "",
"Ultra-fast": "",
"Unarchive": "",
"Unarchive Chat": "",
"Unassigned": "",

View file

@ -2,14 +2,14 @@
"-1 for no limit, or a positive integer for a specific limit": "-1 per nessun limite, o un numero intero positivo per un limite specifico",
"(latest)": "(ultima)",
"(leave blank for to use commercial endpoint)": "(lascia vuoto per utilizzare l'endpoint commerciale)",
"[Last] dddd [at] h:mm A": "",
"[Today at] h:mm A": "",
"[Yesterday at] h:mm A": "",
"[Last] dddd [at] h:mm A": "[Ultimo] dddd [alle] h:mm A",
"[Today at] h:mm A": "[Oggi alle] h:mm A",
"[Yesterday at] h:mm A": "[Ieri alle] h:mm A",
"{{ models }}": "{{ models }}",
"{{count}} added lines_one": "",
"{{count}} added lines_many": "",
"{{count}} added lines_other": "",
"{{COUNT}} Available Skills": "",
"{{count}} added lines_one": "{{count}} righe aggiunte",
"{{count}} added lines_many": "{{count}} righe aggiunte",
"{{count}} added lines_other": "{{count}} righe aggiunte",
"{{COUNT}} Available Skills": "{{COUNT}} abilità disponibili",
"{{COUNT}} Available Tools": "{{COUNT}} strumenti disponibili",
"{{COUNT}} characters": "{{COUNT}} caratteri",
"{{COUNT}} document(s) selected.": "{{COUNT}} documento(i) selezionato(i).",
@ -21,48 +21,48 @@
"{{count}} files selected. Only new and modified files will be uploaded. Deleted files will be removed. The folder structure will be mirrored. Continue?_one": "",
"{{count}} files selected. Only new and modified files will be uploaded. Deleted files will be removed. The folder structure will be mirrored. Continue?_many": "",
"{{count}} files selected. Only new and modified files will be uploaded. Deleted files will be removed. The folder structure will be mirrored. Continue?_other": "",
"{{count}} filters_one": "",
"{{count}} filters_many": "",
"{{count}} filters_other": "",
"{{count}} groups_one": "",
"{{count}} groups_many": "",
"{{count}} groups_other": "",
"{{count}} filters_one": "{{count}} filtro",
"{{count}} filters_many": "{{count}} filtri",
"{{count}} filters_other": "{{count}} filtri",
"{{count}} groups_one": "{{count}} gruppo",
"{{count}} groups_many": "{{count}} gruppi",
"{{count}} groups_other": "{{count}} gruppi",
"{{COUNT}} hidden lines": "{{COUNT}} righe nascoste",
"{{COUNT}} members": "{{COUNT}} membri",
"{{count}} of {{total}} accessible_one": "",
"{{count}} of {{total}} accessible_many": "",
"{{count}} of {{total}} accessible_other": "",
"{{COUNT}} pipeline(s) selected": "",
"{{count}} removed lines_one": "",
"{{count}} removed lines_many": "",
"{{count}} removed lines_other": "",
"{{count}} of {{total}} accessible_one": "{{count}} di {{total}} accessibile",
"{{count}} of {{total}} accessible_many": "{{count}} di {{total}} accessibili",
"{{count}} of {{total}} accessible_other": "{{count}} di {{total}} accessibili",
"{{COUNT}} pipeline(s) selected": "{{COUNT}} pipeline selezionate",
"{{count}} removed lines_one": "{{count}} riga rimossa",
"{{count}} removed lines_many": "{{count}} righe rimosse",
"{{count}} removed lines_other": "{{count}} righe rimosse",
"{{COUNT}} Replies": "{{COUNT}} risposte",
"{{COUNT}} Rows": "{{COUNT}} righe",
"{{count}} selected_one": "",
"{{count}} selected_many": "",
"{{count}} selected_other": "",
"{{count}} selected_one": "{{count}} selezionato",
"{{count}} selected_many": "{{count}} selezionati",
"{{count}} selected_other": "{{count}} selezionati",
"{{COUNT}} Sources": "{{COUNT}} fonti",
"{{count}} users_one": "",
"{{count}} users_many": "",
"{{count}} users_other": "",
"{{count}} users_one": "{{count}} utente",
"{{count}} users_many": "{{count}} utenti",
"{{count}} users_other": "{{count}} utenti",
"{{COUNT}} words": "{{COUNT}} parole",
"{{COUNT}}d_time_ago": "",
"{{COUNT}}h_time_ago": "",
"{{COUNT}}m_time_ago": "",
"{{COUNT}}w_time_ago": "",
"{{COUNT}}y_time_ago": "",
"{{label}} contains invalid JSON": "",
"{{label}} must be a JSON object": "",
"{{model}} download has been canceled": "",
"{{modelName}} profile image": "",
"{{NAMES}} reacted with {{REACTION}}": "",
"{{page}} / {{totalPages}} ({{rows}} rows)": "",
"{{percent}}% full": "",
"{{label}} contains invalid JSON": "{{label}} contiene JSON non valido",
"{{label}} must be a JSON object": "{{label}} deve essere un oggetto JSON",
"{{model}} download has been canceled": "download di {{model}} annullato",
"{{modelName}} profile image": "Immagine del profilo di {{modelName}}",
"{{NAMES}} reacted with {{REACTION}}": "{{NAMES}} ha reagito con {{REACTION}}",
"{{page}} / {{totalPages}} ({{rows}} rows)": "{{page}} / {{totalPages}} ({{rows}} righe)",
"{{percent}}% full": "{{percent}}% totale",
"{{user}}'s Chats": "Chat di {{user}}",
"{{webUIName}} Backend Required": "{{webUIName}} Richiesta Backend",
"* (all domains)": "",
"* (all domains)": "* (tutti i domini)",
"*Prompt node ID(s) are required for image generation": "*ID nodo prompt sono necessari per la generazione di immagini",
"*required": "",
"*required": "*obbligatorio",
"1 file": "1 file",
"1 group": "1 gruppo",
"1 hour before": "1 ora prima",
@ -73,38 +73,38 @@
"1m_time_ago": "",
"30 minutes before": "30 minuti prima",
"5 minutes before": "5 minuti prima",
"A chat run failed.": "",
"A chat run finished successfully.": "",
"A collaboration channel where people join as members": "",
"A discussion channel where access is controlled by groups and permissions": "",
"A chat run failed.": "Un'esecuzione della chat è fallita.",
"A chat run finished successfully.": "Un'esecuzione della chat è terminata con successo.",
"A collaboration channel where people join as members": "Un canale di collaborazione dove le persone si uniscono come membri",
"A discussion channel where access is controlled by groups and permissions": "Un canale di discussione dove l'accesso è controllato da gruppi e permessi",
"A new version (v{{LATEST_VERSION}}) is now available.": "Una nuova versione (v{{LATEST_VERSION}}) è ora disponibile.",
"A private conversation between you and selected users": "",
"A private conversation between you and selected users": "Una conversazione privata tra te e gli utenti selezionati",
"a user": "un utente",
"about the Roman Empire": "",
"Accept Autocomplete Generation\nJump to Prompt Variable": "",
"Access": "Accesso",
"Access Control": "Controllo accessi",
"Access Grants": "",
"Access level": "",
"Access List": "",
"Access prohibited": "",
"Access updated": "",
"Access Grants": "Permessi di accesso",
"Access level": "Livello di accesso",
"Access List": "Lista accessi",
"Access prohibited": "Accesso vietato",
"Access updated": "Accesso aggiornato",
"Accessible to all users": "Accessibile a tutti gli utenti",
"Account Activation Pending": "Account in attesa di attivazione",
"Accurate information": "Informazioni accurate",
"Action": "",
"Action": "Azione",
"Actions": "Azioni",
"Activate this command by typing \"/{{COMMAND}}\" to chat input.": "Attiva questo comando digitando \"/{{COMMAND}}\" nell'input della chat.",
"Active": "Attivo",
"Active Users": "Utenti attivi",
"Activity": "",
"Activity": "Attività",
"Add": "Aggiungi",
"Add a model ID": "Aggiungi un ID modello",
"Add a preference, fact, or instruction about you": "",
"Add a short description about what this model does": "Aggiungi una breve descrizione di quello che fa questo modello",
"Add a tag": "Aggiungi un tag",
"Add a tag...": "Aggiungi un tag...",
"Add Access": "",
"Add Access": "Aggiungi accesso",
"Add Arena Model": "Aggiungi modello Arena",
"Add Connection": "Aggiungi connessione",
"Add Content": "Aggiungi contenuto",
@ -115,7 +115,7 @@
"Add Details": "",
"Add durable context for future chats": "",
"Add Files": "Aggiungi dei file",
"Add Image": "",
"Add Image": "Aggiungi immagine",
"Add Knowledge Connection": "",
"Add location": "",
"Add Member": "",
@ -123,8 +123,8 @@
"Add Memory": "Aggiungi memoria",
"Add prompt suggestion": "",
"Add Reaction": "Aggiungi una reazione",
"Add tag": "",
"Add Tag": "Aggiungi un tag",
"Add tag": "Aggiungi tag",
"Add Tag": "Aggiungi tag",
"Add Terminal": "",
"Add Terminal Connection": "",
"Add text content": "Aggiungi contenuto di testo",
@ -937,7 +937,7 @@
"Explain options trading": "",
"Explain options trading in simple terms if I'm familiar with buying and selling stocks.": "",
"Explored": "Utilizzato",
"Exploring": "Utilizzando",
"Exploring": "Utilizzo",
"Export": "Esportazione",
"Export as CSV": "",
"Export as JSON": "",
@ -3069,29 +3069,29 @@
"Share link copied to clipboard.": "",
"Share to Open WebUI Community": "Condividi con la comunità OpenWebUI",
"Share your background and interests": "",
"Shared": "",
"Shared Chats": "",
"Shared with you": "",
"Shared": "Condiviso",
"Shared Chats": "Chat condivise",
"Shared with you": "Condiviso con te",
"Sharing Permissions": "Condivisione dei permessi",
"Shell": "",
"Shorten older messages so this chat can keep going.": "",
"Shorten older messages so this chat can keep going.": "Riduci i messaggi più vecchi in modo che questa chat possa continuare.",
"Show": "Mostra",
"Show \"What's New\" Modal on Login": "Mostra il modulo \"Novità\" al login",
"Show All": "Mostra tutto",
"Show all ({{COUNT}} characters)": "Mostra tutto ({{COUNT}} caratteri)",
"Show Files": "",
"Show Hidden Files": "",
"Show image preview": "",
"Show me a code snippet": "",
"Show me a code snippet of a website's sticky header in CSS and JavaScript.": "",
"Show Files": "Mostra file",
"Show Hidden Files": "Mostra file nascosti",
"Show image preview": "Mostra anteprima immagine",
"Show me a code snippet": "Mostrami un frammento di codice",
"Show me a code snippet of a website's sticky header in CSS and JavaScript.": "Mostrami un frammento di codice di un header fisso di un sito web in CSS e JavaScript.",
"Show Model": "Mostra Modello",
"Show more": "",
"Show or switch the current model.": "",
"Show Shortcuts": "",
"Show the floating quick-action toolbar in chat.": "",
"Show more": "Mostra altro",
"Show or switch the current model.": "Mostra o passa al modello corrente",
"Show Shortcuts": "Mostra scorciatoie",
"Show the floating quick-action toolbar in chat.": "Mostra la barra degli strumenti a comparsa nelle chat",
"Show your support!": "Mostra il tuo supporto!",
"Showcased creativity": "Creatività messa in mostra",
"Sidebar": "",
"Sidebar": "Barra laterale",
"Sign in": "Accedi",
"Sign in to {{WEBUI_NAME}}": "Accedi a {{WEBUI_NAME}}",
"Sign in to {{WEBUI_NAME}} with LDAP": "Accedi a {{WEBUI_NAME}} con LDAP",
@ -3100,9 +3100,9 @@
"Sign up to {{WEBUI_NAME}}": "Registrati a {{WEBUI_NAME}}",
"Signing in to {{WEBUI_NAME}}": "Accedi a {{WEBUI_NAME}}",
"simultaneous sub-agents": "",
"Single": "",
"Single": "Singolo",
"Sink List": "",
"Size": "",
"Size": "Dimensione",
"sk-1234": "sk-1234",
"Skill created successfully": "",
"Skill deleted successfully": "",
@ -3113,7 +3113,7 @@
"Skill Menu": "",
"Skill Name": "",
"Skill updated successfully": "",
"Skills": "",
"Skills": "Competenze",
"Skills Access": "",
"Skills Public Sharing": "",
"Skills Sharing": "",
@ -3126,7 +3126,7 @@
"Sonar Pro": "",
"Sonar Reasoning": "",
"Sonar Reasoning Pro": "",
"Sort": "",
"Sort": "Ordina",
"Source": "Fonte",
"Specific users or groups": "",
"Speech recognition error: {{error}}": "Errore di riconoscimento vocale: {{error}}",
@ -3143,7 +3143,7 @@
"Starting in 1 minute": "",
"Starting kernel...": "",
"Starting now": "",
"Status": "",
"Status": "Stato",
"Status cleared successfully": "",
"Status updated successfully": "",
"Status: check what is running in this chat.": "",
@ -3153,66 +3153,66 @@
"Stop camera": "",
"Stop Download": "",
"Stop Generating": "",
"Storage": "",
"Storage": "Archiviazione",
"Streamable HTTP": "",
"Strikethrough": "",
"Su_day_of_week": "",
"Submit answers": "",
"Submit question": "",
"Submit suggestion": "",
"Subtitle": "",
"Subtitle": "Sottotitolo",
"Success": "Successo",
"Successfully imported {{userCount}} users.": "",
"Suggest a change": "",
"Suggest a change": "Suggerisci modifica",
"Suggested": "Suggerito",
"Suggested prompts": "",
"Summarize this note.": "",
"Suggested prompts": "Prompt suggeriti",
"Summarize this note.": "Riassumi questa nota.",
"Support": "Supporto",
"Support this plugin:": "Supporta questo plugin:",
"Swap": "",
"Switch camera": "",
"Switch to JSON editor": "",
"Switch to visual editor": "",
"Sync": "",
"Sync": "Sincronizzazione",
"Sync a local directory with this knowledge base. Only new and modified files will be uploaded. The directory structure will be mirrored.": "",
"Sync complete: {{added}} added, {{modified}} modified, {{deleted}} deleted, {{unmodified}} unmodified": "",
"Sync Complete!": "",
"Sync Complete!": "Sincronizzazione completata!",
"Sync directory": "Sincronizza directory",
"Sync Failed": "",
"Sync Usage Stats": "",
"Syncing stats...": "",
"Syncing...": "",
"Sync Failed": "Sincronizzazione fallita",
"Sync Usage Stats": "Sincronizza statistiche di utilizzo",
"Syncing stats...": "Sincronizzazione statistiche...",
"Syncing...": "Sincronizzazione...",
"Syncs only chats with updates after your last sync timestamp. Disable to re-sync all chats.": "",
"System": "Sistema",
"System events only": "",
"System events only": "Solo eventi di sistema",
"System Instructions": "Istruzioni di sistema",
"System Prompt": "Prompt di sistema",
"Table": "",
"Tag": "",
"Table": "Tabella",
"Tag": "Tag",
"Tags": "Tag",
"Tail free sampling is used to reduce the impact of less probable tokens from the output. A higher value (e.g., 2.0) will reduce the impact more, while a value of 1.0 disables this setting.": "Il campionamento tail free viene utilizzato per ridurre l'impatto di token meno probabili dall'output. Un valore più alto (ad esempio, 2.0) ridurrà maggiormente l'impatto, mentre un valore di 1.0 disabilita questa impostazione.",
"Talk to Model": "",
"Talk to Model": "Parla con il modello",
"Tap to interrupt": "Tocca per interrompere",
"Target ID": "",
"Target ID for notify": "",
"Task List": "",
"Task List": "Lista attività",
"Tasks": "Attività",
"tasks completed": "",
"tasks completed": "attività completate",
"Tell me a fun fact": "",
"Tell me a random fun fact about the Roman Empire": "",
"Tell us more:": "Raccontaci di più:",
"Temporary": "",
"Temporary": "Temporaneo",
"Temporary Chat": "Chat temporanea",
"Temporary: toggle temporary chat for this new chat.": "",
"Terminal": "",
"Terminal Contexts": "",
"Terminal servers saved": "",
"Temporary: toggle temporary chat for this new chat.": "Temporanea: attiva/disattiva la chat temporanea per questa nuova chat.",
"Terminal": "Terminale",
"Terminal Contexts": "Contesti terminale",
"Terminal servers saved": "Server terminale salvati",
"Test": "",
"Test must pass before a source is created.": "",
"Test notification sent.": "",
"Test Query": "",
"Test returned no results.": "",
"Test succeeded.": "",
"Test succeeded.": "Test superato.",
"Test the source before creating it.": "",
"Test the source before saving it.": "",
"Th_day_of_week": "",
@ -3232,11 +3232,11 @@
"There was an error syncing your stats. Please try again.": "",
"These settings are read from environment variables and cannot be edited here while {{ENV_VAR}} is disabled.": "",
"thinking, output_config": "",
"Thinking...": "Sto pensando...",
"Thinking...": "Ragionamento...",
"This action cannot be undone. Do you wish to continue?": "Questa azione non può essere annullata. Vuoi continuare?",
"This channel was created on {{createdAt}}. This is the very beginning of the {{channelName}} channel.": "Questo canale è stato creato il {{createdAt}}. Questo è l'inizio del canale {{channelName}}.",
"This chat won't appear in history and your messages will not be saved.": "Questa chat non apparirà nella cronologia e i tuoi messaggi non verranno salvati.",
"This folder is empty": "",
"This folder is empty": "Questa cartella è vuota",
"This is a default user permission and will remain enabled.": "",
"This is an experimental feature, it may not function as expected and is subject to change at any time.": "Questa è una funzionalità sperimentale, potrebbe non funzionare come previsto ed è soggetta a modifiche in qualsiasi momento.",
"This knowledge base retrieves from a connected source. Open WebUI can query it, but cannot upload, sync, edit, delete, reset, or reindex its source data.": "",
@ -3254,24 +3254,24 @@
"This will remove all files and directories from this knowledge base. This action cannot be undone.": "",
"This will revoke the current API key.": "",
"Thorough explanation": "Spiegazione dettagliata",
"Thought": "Pensiero",
"Thought for {{DURATION}}": "Pensiero per {{DURATION}}",
"Thought for {{DURATION}} seconds": "Pensiero per {{DURATION}} secondi",
"Thought for less than a second": "Pensiero per meno di un secondo",
"Thought": "Ragionamento",
"Thought for {{DURATION}}": "Ragionamento per {{DURATION}}",
"Thought for {{DURATION}} seconds": "Ragionamento per {{DURATION}} secondi",
"Thought for less than a second": "Ragionamento per meno di un secondo",
"Thread": "Discussione",
"Through": "",
"Thumbs up/down ratings from users on model responses": "",
"Through": "Attraverso",
"Thumbs up/down ratings from users on model responses": "Valutazioni pollici su/giù dagli utenti sulle risposte del modello",
"Tika": "Tika",
"Tika 3.x": "",
"Tika 4.x": "",
"Tika Server URL required.": "L'URL del server Tika è obbligatorio.",
"Tiktoken": "Tiktoken",
"Time": "",
"Timeout": "",
"Timer": "",
"Time": "Tempo",
"Timeout": "Timeout",
"Timer": "Timer",
"Title": "Titolo",
"Title cannot be an empty string.": "Il titolo non può essere una stringa vuota.",
"Title is required": "",
"Title is required": "Il titolo è obbligatorio",
"To access the available model names for downloading,": "Per accedere ai nomi dei modelli disponibili per il download,",
"To access the GGUF models available for downloading,": "Per accedere ai modelli GGUF disponibili per il download,",
"To access the WebUI, please reach out to the administrator. Admins can manage user statuses from the Admin Panel.": "Per accedere a WebUI, contatta l'amministratore. Gli amministratori possono gestire gli stati degli utenti dal pannello di amministrazione.",
@ -3285,7 +3285,7 @@
"To select skills here, add them to the \"Skills\" workspace first.": "",
"To select toolkits here, add them to the \"Tools\" workspace or enable a tool server first.": "",
"Today": "Oggi",
"Today at": "",
"Today at": "Oggi alle",
"Toggle {{COUNT}} sources": "",
"Toggle 1 source": "",
"Toggle Controls": "",
@ -3299,66 +3299,66 @@
"Token": "Token",
"Token activity": "",
"Token counts are estimates and may not reflect actual API usage": "",
"tokens": "",
"Tokens": "",
"tokens": "token",
"Tokens": "Token",
"Too verbose": "Troppo prolisso",
"tool": "",
"tool": "strumento",
"Tool created successfully": "Strumento creato con successo",
"Tool deleted successfully": "Strumento eliminato con successo",
"Tool Description": "Descrizione dello Strumento",
"Tool Description": "Descrizione dello strumento",
"Tool ID": "ID Strumento",
"Tool imported successfully": "Strumento importato con successo",
"tool loops per sub-agent": "",
"Tool Menu": "",
"Tool Name": "Nome dello Strumento",
"Tool Permissions": "",
"Tool Servers": "Server degli Strumento",
"Tool Menu": "Menu strumento",
"Tool Name": "Nome dello strumento",
"Tool Permissions": "Permessi dello strumento",
"Tool Servers": "Server degli strumenti",
"Tool updated successfully": "Strumento aggiornato con successo",
"Tools": "Strumenti",
"Tools Access": "Accesso agli Strumento",
"Tools can execute arbitrary code.": "",
"Tools Access": "Accesso agli strumenti",
"Tools can execute arbitrary code.": "Gli strumenti possono eseguire codice arbitrario.",
"Tools have a function calling system that allows arbitrary code execution.": "Gli strumenti hanno un sistema di chiamata di funzione che consente l'esecuzione di codice arbitrario.",
"Tools Public Sharing": "Condivisione Pubblica degli strumenti",
"Tools Sharing": "",
"Tools Public Sharing": "Condivisione pubblica degli strumenti",
"Tools Sharing": "Condivisione strumenti",
"Top": "",
"Total chats": "",
"Total chats": "Chat totali",
"Transformers": "Transformer",
"Translation key": "",
"Trial License": "",
"Try adjusting your search or filter to find what you are looking for.": "",
"Try Again": "",
"Try adjusting your search or filter to find what you are looking for.": "Prova a regolare la tua ricerca o il filtro per ottenere risultati.",
"Try Again": "Riprova",
"TTS Voice": "Voce TTS",
"Tu_day_of_week": "",
"Type": "Digitare",
"Type a model ref": "",
"Type here...": "",
"Type here...": "Digita qui...",
"Type Hugging Face Resolve (Download) URL": "Digita URL di Risoluzione (Download) di Hugging Face",
"Type your answer": "",
"Type your answer": "Digita la tua risposta",
"Uh-oh! There was an issue with the response.": "Oh-oh! C'è stato un problema con la risposta.",
"Unarchive": "",
"Unarchive Chat": "Disarchivia Chat",
"Unassigned": "",
"Underline": "",
"unknown": "",
"Unknown": "",
"Unknown User": "",
"Unload Model": "",
"Unarchive": "Disarchivia",
"Unarchive Chat": "Disarchivia chat",
"Unassigned": "Non assegnato",
"Underline": "Sottolineato",
"unknown": "sconosciuto",
"Unknown": "Sconosciuto",
"Unknown User": "Utente sconosciuto",
"Unload Model": "Scarica modello",
"Unloads {{FROM_NOW}}": "Scarica {{FROM_NOW}}",
"Unmute": "",
"Unmute": "Rimuovi muto",
"Unpin": "Rimuovi fissato",
"Unpin from Sidebar": "",
"Unread": "",
"Unshare All": "",
"Unshare All Shared Chats": "",
"Unshare Chat": "",
"Unsupported file type.": "",
"Unpin from Sidebar": "Rimuovi dalla barra laterale",
"Unread": "Non letto",
"Unshare All": "Rimuovi condivisione di tutto",
"Unshare All Shared Chats": "Rimuovi condivisione di tutte le chat condivise",
"Unshare Chat": "Rimuovi condivisione chat",
"Unsupported file type.": "Tipo di file non supportato.",
"Untagged": "Non taggato",
"Untitled": "Senza titolo",
"Update": "Aggiorna",
"Update All Models": "",
"Update All Models": "Aggiorna tutti i modelli",
"Update and Copy Link": "Aggiorna e Copia Link",
"Update for the latest features and improvements.": "Aggiorna per le ultime funzionalità e miglioramenti.",
"Update your status": "",
"Update your status": "Aggiorna il tuo stato",
"Updated": "Aggiornato",
"Updated at": "Aggiornato il",
"Updated At": "Aggiornato Il",
@ -3368,90 +3368,90 @@
"Upload a GGUF model": "Carica un modello GGUF",
"Upload Audio": "Carica Audio",
"Upload directory": "Carica cartella",
"Upload failed": "",
"Upload failed for {{failed}} of {{total}} files.": "",
"Upload failed": "Caricamento fallito",
"Upload failed for {{failed}} of {{total}} files.": "Caricamento fallito per {{failed}} di {{total}} file.",
"Upload files": "Carica file",
"Upload Files": "Carica File",
"Upload Folder": "",
"Upload Model": "",
"Upload Photo": "",
"Upload profile image": "",
"Upload Progress": "Avanzamento Caricamento",
"Uploaded files or images": "",
"Uploading": "",
"Uploading {{current}}/{{total}}: {{file}}": "",
"Uploading...": "",
"Upload Files": "Carica file",
"Upload Folder": "Carica cartella",
"Upload Model": "Carica modello",
"Upload Photo": "Carica foto",
"Upload profile image": "Carica immagine profilo",
"Upload Progress": "Avanzamento caricamento",
"Uploaded files or images": "File o immagini caricati",
"Uploading": "Caricamento",
"Uploading {{current}}/{{total}}: {{file}}": "Caricamento {{current}}/{{total}}: {{file}}",
"Uploading...": "Caricamento...",
"URL": "URL",
"URL is required": "",
"URL is required": "L'URL è richiesto",
"URL Mode": "Modalità URL",
"Use": "",
"Use a valid event name or pattern like user.*": "",
"Use default": "",
"Use": "Utilizza",
"Use a valid event name or pattern like user.*": "Utilizza un nome evento valido o un pattern come user.*",
"Use default": "Utilizza predefinito",
"Use device vibration feedback on supported Android devices.": "",
"Use discovered scopes": "",
"Use groups to organize your users and assign permissions.": "",
"Use segmented retrieval for focused and relevant content extraction.": "",
"Use segmented retrieval for focused and relevant context.": "",
"Use segmented retrieval for focused context.": "",
"Use these in model system prompts as {{example}}.": "",
"Use Web Search?": "",
"Use these in model system prompts as {{example}}.": "Usali nei prompt di sistema del modello come {{example}}.",
"Use Web Search?": "Utilizzare la ricerca web?",
"user": "utente",
"User": "Utente",
"User Activity": "",
"User Count": "",
"User Groups": "",
"User Activity": "Attività utente",
"User Count": "Conteggio utenti",
"User Groups": "Gruppi utente",
"User location successfully retrieved.": "Posizione utente recuperata con successo.",
"User menu": "",
"User messages": "",
"User not found.": "",
"User Preview": "",
"User ratings (thumbs up/down)": "",
"User Variables": "",
"User menu": "Menu utente",
"User messages": "Messaggi utente",
"User not found.": "Utente non trovato.",
"User Preview": "Anteprima utente",
"User ratings (thumbs up/down)": "Valutazioni degli utenti (pollice su/giù)",
"User Variables": "Variabili utente",
"User Webhooks": "Webhook Utente",
"Username": "Nome Utente",
"users": "",
"users": "utenti",
"Users": "Utenti",
"Uses DefaultAzureCredential to authenticate": "",
"Uses defaults": "",
"Uses defaults": "Utilizza i valori predefiniti",
"Uses OAuth 2.1 Dynamic Client Registration": "",
"Using Entire Document": "",
"Using Focused Retrieval": "",
"Using Entire Document": "Usando l'intero documento",
"Using Focused Retrieval": "Usando il recupero mirato",
"Using the default arena model with all models. Click the plus button to add custom models.": "Utilizzando il modello di arena predefinito con tutti i modelli. Fai clic sul pulsante più per aggiungere modelli personalizzati.",
"Value": "",
"Value": "Valore",
"Valves": "Abilitazioni",
"Valves updated": "Abilitazioni aggiornate",
"Valves updated successfully": "Abilitazioni aggiornate con successo",
"variable": "variabile",
"Variable key": "",
"Variable keys must be unique.": "",
"Variable keys must use lowercase snake case.": "",
"Variable value": "",
"Vector Field": "",
"Variable key": "Chiave variabile",
"Variable keys must be unique.": "Le chiavi variabili devono essere uniche.",
"Variable keys must use lowercase snake case.": "Le chiavi variabili devono utilizzare il formato snake case in minuscolo.",
"Variable value": "Valore variabile",
"Vector Field": "Campo vettoriale",
"Verify Connection": "Verifica connessione",
"Version {{selectedVersion}} of {{totalVersions}}": "Versione {{selectedVersion}} di {{totalVersions}}",
"Version deleted": "",
"Version deleted": "Versione eliminata",
"Vertical Layout": "Layout verticale",
"Video player": "",
"View channel": "",
"View chat": "",
"View Replies": "Visualizza Risposte",
"View Result from {{NAME}}": "Vedi risultato di {{NAME}}",
"Video player": "Player video",
"View channel": "Vedi canale",
"View chat": "Vedi chat",
"View Replies": "Visualizza risposte",
"View Result from {{NAME}}": "Risultato di {{NAME}}",
"View source: {{name}}": "Vedi fonte: {{name}}",
"View source: {{title}}": "Vedi fonte: {{title}}",
"Viewport Pinned": "Blocca finestra",
"Viewport Unpinned": "Sblocca finestra",
"Visibility": "Visibilità",
"Visible": "",
"Visible to all users": "",
"Visual": "",
"Visible": "Visibile",
"Visible to all users": "Visibile a tutti gli utenti",
"Visual": "Visuale",
"vocabulary for a college entrance exam": "",
"Voice": "Voce",
"Voice Input": "Input vocale",
"Voice mode": "Modalità vocale",
"Wait for the current response to finish before compacting.": "",
"Wait for the current response to finish before forking.": "",
"Waiting for upload": "",
"Waiting for upload...": "",
"Waiting for upload": "Attesa del caricamento",
"Waiting for upload...": "Attesa del caricamento...",
"Warning": "Attenzione",
"Warning:": "Attenzione:",
"Warning: Enabling this will allow users to run scheduled prompts automatically.": "",
@ -3462,31 +3462,31 @@
"Web API": "API Web",
"Web Search": "Ricerca sul Web",
"Webhook": "",
"Webhook deleted": "",
"Webhook Name": "",
"Webhook saved": "",
"Webhooks": "",
"Webhook deleted": "Webhook eliminato",
"Webhook Name": "Nome webhook",
"Webhook saved": "Webhook salvato",
"Webhooks": "Webhook",
"Webpage URLs": "URL delle pagine web",
"WebUI will make requests to \"{{url}}\"": "WebUI farà richieste a \"{{url}}\"",
"WebUI will make requests to \"{{url}}/api/chat\"": "WebUI farà richieste a \"{{url}}/api/chat\"",
"WebUI will make requests to \"{{url}}/chat/completions\"": "WebUI farà richieste a \"{{url}}/chat/completions\"",
"Week": "",
"Week of": "",
"Weekly": "",
"Weekly at": "",
"Welcome to your AI home.": "",
"Week": "Settimana",
"Week of": "Settimana di",
"Weekly": "Settimanale",
"Weekly at": "Settimanale alle",
"Welcome to your AI home.": "Benvenuto nella tua AI.",
"What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.": "",
"What are you trying to achieve?": "Cosa stai cercando di ottenere?",
"What are you working on?": "Su cosa stai lavorando?",
"What is NOT shared:": "",
"What is shared:": "",
"What is NOT shared:": "Cosa NON è condiviso:",
"What is shared:": "Cosa è condiviso:",
"What's New in": "Novità in",
"What's on your mind?": "",
"When": "",
"When": "Quando",
"Whisper (Local)": "Whisper (Locale)",
"Who can share to this group": "",
"Why?": "Perché?",
"Width": "",
"Width": "Larghezza",
"Wikipedia": "",
"Won": "Vinto",
"Working Directory": "",

File diff suppressed because it is too large Load diff

View file

@ -6,8 +6,8 @@
"[Today at] h:mm A": "[Bugün saat] HH:mm",
"[Yesterday at] h:mm A": "[Dün saat] HH:mm",
"{{ models }}": "{{ models }}",
"{{count}} added lines_one": "",
"{{count}} added lines_other": "",
"{{count}} added lines_one": "{{count}} eklenen satır",
"{{count}} added lines_other": "{{count}} eklenen satır",
"{{COUNT}} Available Skills": "{{COUNT}} Yetenek Mevcut",
"{{COUNT}} Available Tools": "{{COUNT}} Kullanılabilir Araç",
"{{COUNT}} characters": "{{COUNT}} karakter",
@ -27,8 +27,8 @@
"{{count}} of {{total}} accessible_one": "Erişilebilir: {{total}} / {{count}}",
"{{count}} of {{total}} accessible_other": "Erişilebilir: {{total}} / {{count}}",
"{{COUNT}} pipeline(s) selected": "{{COUNT}} pipeline seçildi",
"{{count}} removed lines_one": "",
"{{count}} removed lines_other": "",
"{{count}} removed lines_one": "{{count}} silinen satır",
"{{count}} removed lines_other": "{{count}} silinen satır",
"{{COUNT}} Replies": "{{COUNT}} Yanıt",
"{{COUNT}} Rows": "{{COUNT}} satır",
"{{count}} selected_one": "{{count}} seçildi",
@ -71,7 +71,7 @@
"A new version (v{{LATEST_VERSION}}) is now available.": "Yeni bir sürüm (v{{LATEST_VERSION}}) artık mevcut.",
"A private conversation between you and selected users": "Sizinle seçili kullanıcılar arasında özel bir konuşma",
"a user": "bir kullanıcı",
"about the Roman Empire": "",
"about the Roman Empire": "Roma İmparatorluğu hakkında",
"Accept Autocomplete Generation\nJump to Prompt Variable": "Otomatik Tamamlama Oluşturmayı Kabul Et\nPrompt Değişkenine Git",
"Access": "Erişim",
"Access Control": "Erişim Kontrolü",
@ -239,6 +239,7 @@
"Attach Notes": "Not Ekle",
"Attach Webpage": "Web sayfası ekle",
"Attention to detail": "Ayrıntılara dikkat",
"Audio playback failed": "Ses oynatma başarısız oldu",
"Audio player": "Ses oynatıcı",
"August": "Ağustos",
"Auth": "Yetki",
@ -273,11 +274,11 @@
"Azure AI Speech": "Azure AI Konuşma",
"Azure OpenAI": "Azure OpenAI",
"Back": "Geri",
"Background Image": "",
"Background image must be at most 25 megapixels.": "",
"Background image must be at most 5 MiB.": "",
"Background image must be PNG, JPEG, WebP, or GIF.": "",
"Background image preview": "",
"Background Image": "Arka Plan Görseli",
"Background image must be at most 25 megapixels.": "Arka plan görseli en fazla 25 megapiksel olabilir.",
"Background image must be at most 5 MiB.": "Arka plan görseli en fazla 5 MiB olabilir.",
"Background image must be PNG, JPEG, WebP, or GIF.": "Arka plan görseli PNG, JPEG, WebP veya GIF formatında olmalıdır.",
"Background image preview": "Arka plan görseli önizlemesi",
"Background sub-agent finished": "Arka plan alt ajanı tamamlandı",
"background sub-agents": "arka plan alt ajanları",
"Bad Response": "Kötü Yanıt",
@ -344,7 +345,7 @@
"Chat Menu": "Sohbet Menüsü",
"Chat moved successfully": "Sohbet başarıyla taşındı",
"Chat Permissions": "Sohbet İzinleri",
"Chat unarchived.": "",
"Chat unarchived.": "Sohbet arşivden çıkarıldı.",
"Chat unshared successfully.": "Sohbet paylaşımı başarıyla kaldırıldı.",
"Chat Uploads": "Sohbet Yüklemeleri",
"Chat Variables": "Sohbet Değişkenleri",
@ -363,7 +364,6 @@
"Citation": "Alıntı",
"Citations": "Alıntılar",
"Clear": "Temizle",
"Clear filters to reorder": "Yeniden sıralamak için filtreleri temizle",
"Clear Memory": "Belleği Temizle",
"Clear search": "Aramayı temizle",
"Clear status": "Durumu temizle",
@ -450,7 +450,7 @@
"Connection lost. Reconnecting...": "Bağlantı kesildi. Yeniden bağlanılıyor...",
"Connection successful": "Bağlantı başarılı",
"Connection Type": "Bağlantı Tipi",
"Connections managed through the Admin Panel are recommended for everyday use. Direct connections provide convenient access to your own endpoints, but rely on your browser session to keep requests running, making them better suited to testing and temporary use.": "",
"Connections managed through the Admin Panel are recommended for everyday use. Direct connections provide convenient access to your own endpoints, but rely on your browser session to keep requests running, making them better suited to testing and temporary use.": "Yönetim Paneli üzerinden yönetilen bağlantılar günlük kullanım için önerilir. Doğrudan bağlantılar kendi uç noktalarınıza kolay erişim sağlar, ancak isteklerin çalışmaya devam etmesi tarayıcı oturumunuza bağlıdır; bu yüzden test ve geçici kullanım için daha uygundur.",
"Connections saved successfully": "Bağlantılar başarıyla kaydedildi",
"Connections settings updated": "Bağlantı ayarları güncellendi",
"Contact Admin for WebUI Access": "WebUI Erişimi için Yöneticiyle İletişime Geçin",
@ -496,14 +496,14 @@
"Could not check for updates": "Güncellemeler kontrol edilemedi",
"Could not load release notes.": "Sürüm notları yüklenemedi.",
"Could not read file.": "Dosya okunamadı.",
"Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?": "",
"Could you start by asking me about instances when I procrastinate the most and then give me some suggestions to overcome it?": "En çok hangi durumlarda erteleme yaptığımı sorarak başlar mısın, sonra bunu aşmam için bana bazı öneriler verir misin?",
"Couldn't find your language?": "Dilinizi bulamadınız mı?",
"CPU": "İşlemci",
"Create": "Oluştur",
"Create a knowledge base": "Bir bilgi tabanı oluştur",
"Create a model": "Bir model oluştur",
"Create a new note": "Yeni bir not oluştur",
"Create a reusable terminal skill from this chat.": "",
"Create a reusable terminal skill from this chat.": "Bu sohbetten yeniden kullanılabilir bir terminal becerisi oluştur.",
"Create Account": "Hesap Oluştur",
"Create Admin Account": "Yönetici Hesabı Oluştur",
"Create announcements shown to users in the app.": "Uygulamada kullanıcılara gösterilecek duyurular oluşturun.",
@ -514,8 +514,8 @@
"Create new key": "Yeni anahtar oluştur",
"Create Prompt": "Prompt Oluştur",
"Create scheduled prompts that run automatically on a recurring basis.": "Yinelenen şekilde otomatik olarak çalışan zamanlanmış istemler oluşturun.",
"Create skill": "",
"Create skill: create a reusable terminal skill from this chat.": "",
"Create skill": "Beceri oluştur",
"Create skill: create a reusable terminal skill from this chat.": "Beceri oluştur: bu sohbetten yeniden kullanılabilir bir terminal becerisi oluştur.",
"Create your first note from the Create menu.": "Oluştur menüsünden ilk notunuzu oluşturun.",
"Created": "Oluşturuldu",
"Created at": "Oluşturulma tarihi",
@ -618,7 +618,7 @@
"Detected Variables": "Algılanan Değişkenler",
"Dictate": "Dikte Et",
"Didn't fully follow instructions": "Talimatları tam olarak takip etmedi",
"Diff settings": "",
"Diff settings": "Fark ayarları",
"Direct": "Doğrudan",
"Direct Message": "Doğrudan Mesaj",
"Direct Tool Servers": "Doğrudan Araç Sunucuları",
@ -842,7 +842,7 @@
"Enter skill instructions in markdown...": "Yetenek talimatlarını Markdown olarak girin...",
"Enter Sougou Search API sID": "Sougou Search API sID'sini Girin",
"Enter Sougou Search API SK": "Sougou Search API SK'sini Girin",
"Enter Staan API Key": "",
"Enter Staan API Key": "Staan API Anahtarını Girin",
"Enter stop sequence": "Durdurma dizisini girin",
"Enter system prompt": "Sistem promptunu girin",
"Enter system prompt here": "Sistem promptunu buraya girin.",
@ -922,8 +922,8 @@
"Experience": "Deneyim",
"Experimental": "Deneysel",
"Explain": "Açıkla",
"Explain options trading": "",
"Explain options trading in simple terms if I'm familiar with buying and selling stocks.": "",
"Explain options trading": "Opsiyon ticaretini açıkla",
"Explain options trading in simple terms if I'm familiar with buying and selling stocks.": "Hisse alıp satmaya aşinaysam, opsiyon ticaretini basit terimlerle açıklar mısın?",
"Explored": "Keşfedildi",
"Exploring": "Keşfediliyor",
"Export": "Dışa Aktar",
@ -942,8 +942,8 @@
"External Knowledge Source": "Harici Bilgi Kaynağı",
"External vectors must be generated with the same embedding model configured in Open WebUI.": "Harici vektörler, Open WebUI'da yapılandırılan aynı gömme modeliyle oluşturulmalıdır.",
"Extract action items from this note.": "Bu nottan eylem maddelerini çıkar.",
"Extracted text lines": "",
"Extracting text and comparing…": "",
"Extracted text lines": "Çıkarılan metin satırları",
"Extracting text and comparing…": "Metin çıkarılıyor ve karşılaştırılıyor…",
"Failed to add file.": "Dosya eklenemedi.",
"Failed to add members": "Üyeler eklenemedi",
"Failed to archive chat.": "Sohbet arşivlenemedi.",
@ -987,7 +987,7 @@
"Failed to save chat variables": "Sohbet değişkenleri kaydedilemedi",
"Failed to save connections": "Bağlantılar kaydedilemedi",
"Failed to save conversation": "Sohbet kaydedilemedi",
"Failed to save model": "",
"Failed to save model": "Model kaydedilemedi",
"Failed to save models configuration": "Modeller yapılandırması kaydedilemedi",
"Failed to save policy: {{error}}": "İlke kaydedilemedi: {{error}}",
"Failed to save terminal servers": "Terminal sunucuları kaydedilemedi",
@ -997,7 +997,7 @@
"Failed to update settings": "Ayarlar güncellenemedi",
"Failed to update status": "Durum güncellenemedi",
"Failed to update webhook": "Webhook güncellenemedi",
"Failed to upload background image.": "",
"Failed to upload background image.": "Arka plan görseli yüklenemedi.",
"Failed to upload file.": "Dosya yüklenemedi.",
"Features Permissions": "Özellik Yetkileri",
"February": "Şubat",
@ -1014,7 +1014,7 @@
"File content": "Dosya içeriği",
"File content updated successfully.": "Dosya içeriği başarıyla güncellendi.",
"File deleted successfully.": "Dosya başarıyla silindi.",
"File differences": "",
"File differences": "Dosya farkları",
"File Mode": "Dosya Modu",
"File moved.": "Dosya taşındı.",
"File name": "Dosya adı",
@ -1051,15 +1051,15 @@
"Follow up: {{question}}": "Takip sorusu: {{question}}",
"Followed instructions perfectly": "Talimatları mükemmel şekilde takip etti",
"for placeholders": "yer tutucular için",
"for what to do with my kids' art": "",
"for what to do with my kids' art": "çocuklarımın sanat eserleriyle ne yapacağıma dair",
"Fork": "Dallandır",
"Fork chat": "Sohbeti dallandır",
"Fork the current chat branch into a new chat.": "Mevcut sohbet dalını yeni bir sohbete dallandırın.",
"Fork: fork the current chat branch into a new chat.": "Dallandır: Mevcut sohbet dalını yeni bir sohbete dallandırın.",
"Forking chat...": "Sohbet dallandırılıyor...",
"Form": "Form",
"Format Markdown as you type and paste. Turn off to keep Markdown characters and paste plain text. Existing formatting is preserved.": "",
"Formatting": "",
"Format Markdown as you type and paste. Turn off to keep Markdown characters and paste plain text. Existing formatting is preserved.": "Yazarken ve yapıştırırken Markdown'ı biçimlendirir. Markdown karakterlerini korumak ve düz metin yapıştırmak için kapatın. Mevcut biçimlendirme korunur.",
"Formatting": "Biçimlendirme",
"Formatting may be inconsistent from source.": "Biçimlendirme kaynaktan tutarsız olabilir.",
"Forward": "İlet",
"Forward cookies": "Çerezleri ilet",
@ -1099,8 +1099,8 @@
"Get information on {{name}} in the UI": "{{name}} hakkında arayüzde bilgi alın",
"Get started": "Başlayın",
"Get started with {{WEBUI_NAME}}": "{{WEBUI_NAME}} ile başlayın",
"Give me ideas": "",
"give me tips": "",
"Give me ideas": "Fikir ver",
"give me tips": "ipucu ver",
"Global": "Evrensel",
"Good Response": "İyi Yanıt",
"Google": "Google",
@ -1126,8 +1126,8 @@
"Height": "Yükseklik",
"Hello, {{name}}": "Merhaba, {{name}}",
"Help": "Yardım",
"Help me study": "",
"Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.": "",
"Help me study": "Çalışmama yardım et",
"Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.": "Kelime çalışmama yardım et: boşluğu doldurmam için bir cümle yaz, ben de doğru seçeneği bulmaya çalışayım.",
"Help the community discover great models": "Topluluğun harika modelleri keşfetmesine yardımcı olun",
"Help us translate Open WebUI!": "Open WebUI'ı çevirmemize yardım edin!",
"Hex Color": "Onaltılık Renk",
@ -1138,7 +1138,7 @@
"Hide from Sidebar": "Kenar Çubuğundan Gizle",
"Hide Hidden Files": "Gizli Dosyaları Gizle",
"Hide Model": "Modeli Gizle",
"Hide whitespace": "",
"Hide whitespace": "Boşlukları gizle",
"High": "Yüksek",
"History": "Geçmiş",
"Home": "Ana Sayfa",
@ -1158,7 +1158,7 @@
"Identity audit": "Kimlik denetimi",
"Idle only": "Yalnızca boşta",
"Idle Timeout": "Boşta Kalma Zaman Aşımı",
"if I'm familiar with buying and selling stocks": "",
"if I'm familiar with buying and selling stocks": "hisse senedi alım satımına aşinaysam",
"Image": "Görsel",
"Image Generation": "Görüntü Oluşturma",
"Image Max Compression Size": "Görüntü Maksimum Sıkıştırma Boyutu",
@ -1195,7 +1195,7 @@
"Instructions": "Talimatlar",
"Integrations": "Entegrasyonlar",
"Interface Settings Access": "Arayüz Ayarlarına Erişim",
"Invalid background image.": "",
"Invalid background image.": "Geçersiz arka plan görseli.",
"Invalid file format.": "Geçersiz dosya biçimi.",
"Invalid format.": "Geçersiz biçim.",
"Invalid JSON": "Geçersiz JSON",
@ -1310,11 +1310,11 @@
"Mark as unread": "Okunmadı olarak işaretle",
"Markdown": "Markdown",
"Market": "",
"Matching tabs: {{count}}_one": "",
"Matching tabs: {{count}}_other": "",
"Max Snippets": "",
"Matching tabs: {{count}}_one": "Eşleşen sekme: {{count}}",
"Matching tabs: {{count}}_other": "Eşleşen sekmeler: {{count}}",
"Max Snippets": "Maksimum Parça Sayısı",
"Max tokens to retrieve (1024-32768, default 8192)": "Getirilecek maksimum token (1024-32768, varsayılan 8192)",
"Maximum extra snippets returned per result. Set to 0 to disable them.": "",
"Maximum extra snippets returned per result. Set to 0 to disable them.": "Her sonuç için döndürülen maksimum ek parça sayısı. Bunları devre dışı bırakmak için 0 olarak ayarlayın.",
"Maximum number of files per folder is {{max}}.": "Klasör başına maksimum dosya sayısı {{max}}.",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Aynı anda en fazla 3 model indirilebilir. Lütfen daha sonra tekrar deneyin.",
"May": "Mayıs",
@ -1368,7 +1368,7 @@
"Model filesystem path detected. Model shortname is required for update, cannot continue.": "Model dosya sistemi yolu algılandı. Güncelleme için model kısa adı gerekli, devam edilemiyor.",
"Model Filtering": "Model Filtreleme",
"Model ID": "Model ID",
"Model ID cannot contain whitespace.": "",
"Model ID cannot contain whitespace.": "Model ID boşluk içeremez.",
"Model ID is already added": "Model kimliği zaten eklenmiş",
"Model ID is required.": "Model ID'si gereklidir.",
"Model IDs": "Model ID'leri",
@ -1522,7 +1522,7 @@
"No suggestion prompts": "Önerilen prompt yok",
"No Terminal connection configured.": "Yapılandırılmış Terminal bağlantısı yok.",
"No terminal connections configured.": "Yapılandırılmış terminal bağlantısı yok.",
"No text differences": "",
"No text differences": "Metin farkı yok",
"No tool server connections configured.": "Yapılandırılmış araç sunucusu bağlantısı yok.",
"No tools accessible": "Erişilebilir araç yok",
"No tools found": "Araç bulunamadı",
@ -1557,7 +1557,7 @@
"OAuth Server URL": "OAuth Sunucu URL'si",
"OAuth session disconnected": "OAuth oturumu bağlantısı kesildi",
"October": "Ekim",
"of a website's sticky header": "",
"of a website's sticky header": "bir web sitesinin yapışık başlığı için",
"Off": "Kapalı",
"Okay, Let's Go!": "Tamam, Hadi Başlayalım!",
"OLED Dark": "OLED Koyu",
@ -1619,7 +1619,7 @@
"out of": "/",
"Output": "Çıktı",
"OUTPUT": "ÇIKTI",
"Overcome procrastination": "",
"Overcome procrastination": "Erteleme alışkanlığını aş",
"Overview": "Genel Bakış",
"PaddleOCR-vl": "PaddleOCR-vl",
"PaddleOCR-vl API URL required.": "PaddleOCR-vl API URL'si gereklidir.",
@ -1682,7 +1682,7 @@
"Please select a valid JSON file": "Lütfen geçerli bir JSON dosyası seçin",
"Please select at least one user for Direct Message channel.": "Doğrudan Mesaj kanalı için lütfen en az bir kullanıcı seçin.",
"Please wait until all files are uploaded.": "Lütfen tüm dosyalar yüklenene kadar bekleyin.",
"PNG, JPEG, WebP, or GIF. Up to 5 MiB and 25 megapixels.": "",
"PNG, JPEG, WebP, or GIF. Up to 5 MiB and 25 megapixels.": "PNG, JPEG, WebP veya GIF. En fazla 5 MiB ve 25 megapiksel.",
"Policy changes apply to newly provisioned terminals. Refresh matching terminals to apply them to existing terminals.": "Politika değişiklikleri yeni sağlanan terminallere uygulanır. Mevcut terminallere uygulamak için eşleşen terminalleri yenileyin.",
"Policy ID": "İlke Kimliği",
"Policy ID is required": "Politika Kimliği gereklidir",
@ -1766,7 +1766,7 @@
"Refused when it shouldn't have": "Reddedilmemesi gerekirken reddedildi",
"Regenerate": "Tekrar Oluştur",
"Regenerate Response": "Yanıtı Yeniden Oluştur",
"Region and language of the search results, e.g. en-us.": "",
"Region and language of the search results, e.g. en-us.": "Arama sonuçlarının bölge ve dili, örn. en-us.",
"Register Again": "Yeniden Kaydet",
"Register Client": "İstemciyi Kaydet",
"Registered": "Kayıtlı",
@ -1795,7 +1795,7 @@
"Renamed to {{name}}": "{{name}} olarak yeniden adlandırıldı",
"Reorder": "Yeniden Sırala",
"Repeat": "Tekrarla",
"Replace": "",
"Replace": "Değiştir",
"Reply": "Yanıtla",
"Reply in Thread": "Konuya Yanıtla",
"Reply to thread...": "Konuya yanıt ver...",
@ -1894,7 +1894,7 @@
"See readme.md for instructions": "Yönergeler için readme.md dosyasına bakın",
"Select": "Seç",
"Select {{modelName}} model": "{{modelName}} modelini seç",
"Select {{name}}": "",
"Select {{name}}": "{{name}} seç",
"Select a base model (e.g. llama3, gpt-4o)": "Bir temel model seçin (örn. llama3, gpt-4o)",
"Select a conversation to preview": "Bir sohbeti önizlemek için seç",
"Select a engine": "Bir motor seç",
@ -2170,8 +2170,8 @@
"settings.admin.connections.cacheBaseModelList.label": "Temel Model Listesini Önbelleğe Al",
"settings.admin.connections.directConnections.description": "Doğrudan Bağlantılar, kullanıcıların kendi OpenAI uyumlu API uç noktalarına bağlanmasına olanak tanır.",
"settings.admin.connections.directConnections.label": "Doğrudan Bağlantılar",
"settings.admin.connections.directIntegrations.description": "",
"settings.admin.connections.directIntegrations.label": "",
"settings.admin.connections.directIntegrations.description": "Yöneticiler dahil, kendi araç ve terminal bağlantılarını yönetme izni olan kullanıcılar için Entegrasyonlar sekmesini gösterir. Sekmeyi gizlemek mevcut bağlantıları devre dışı bırakmaz.",
"settings.admin.connections.directIntegrations.label": "Doğrudan Entegrasyonlar",
"settings.admin.connections.manageOllamaApiConnections.description": "Ollama'ya erişmede sorun mu yaşıyorsunuz?",
"settings.admin.connections.manageOllamaApiConnections.label": "Ollama API Bağlantılarını Yönet",
"settings.admin.connections.manageOpenaiApiConnections.label": "OpenAI API Bağlantılarını Yönet",
@ -2670,8 +2670,8 @@
"settings.admin.web.kagiSearchApiKey.label": "Kagi Arama API Anahtarı",
"settings.admin.web.language.label": "Dil",
"settings.admin.web.linkupApiKey.label": "Linkup API Anahtarı",
"settings.admin.web.maxContentLength.description": "",
"settings.admin.web.maxContentLength.label": "",
"settings.admin.web.maxContentLength.description": "Her Exa arama sonucu için maksimum karakter sayısı. Sınır yoksa boş bırakın.",
"settings.admin.web.maxContentLength.label": "Maksimum İçerik Uzunluğu",
"settings.admin.web.microsoftWebIqApiBaseUrl.label": "Microsoft Web IQ API Temel URL'si",
"settings.admin.web.microsoftWebIqApiKey.label": "Microsoft Web IQ API Anahtarı",
"settings.admin.web.mojeekSearchApiKey.label": "Mojeek Search API Anahtarı",
@ -3001,7 +3001,7 @@
"settings.personal.personalization.savedMemories.label": "Kaydedilen Hafızalar",
"settings.personal.personalization.sections.memory.title": "Bellek",
"settings.personal.personalization.title": "Kişiselleştirme",
"settings.personal.shortcuts.acceptAutocomplete.label": "",
"settings.personal.shortcuts.acceptAutocomplete.label": "Otomatik Tamamlamayı Kabul Et\\nPrompt Değişkenine Git",
"settings.personal.shortcuts.addPrompt.label": "Özel Prompt Ekle",
"settings.personal.shortcuts.allowToolCall.label": "Araç Çağrısına İzin Ver",
"settings.personal.shortcuts.attachFile.label": "Bilgi Tabanından Dosya Ekle",
@ -3066,8 +3066,8 @@
"Show Files": "Dosyaları Göster",
"Show Hidden Files": "Gizli Dosyaları Göster",
"Show image preview": "Görsel önizlemesini göster",
"Show me a code snippet": "",
"Show me a code snippet of a website's sticky header in CSS and JavaScript.": "",
"Show me a code snippet": "Bir kod parçacığı göster",
"Show me a code snippet of a website's sticky header in CSS and JavaScript.": "Bir web sitesinin yapışık başlığı için CSS ve JavaScript kod parçacığı göster.",
"Show Model": "Modeli Göster",
"Show more": "Daha fazla göster",
"Show or switch the current model.": "Mevcut modeli göster veya değiştir.",
@ -3116,7 +3116,7 @@
"Speech recognition error: {{error}}": "Konuşma tanıma hatası: {{error}}",
"Speech-to-Text Language": "Konuşmadan Metne Dili",
"Sponsor on GitHub": "GitHub'da Destek Ol",
"Staan API Key": "",
"Staan API Key": "Staan API Anahtarı",
"Start a new conversation": "Yeni bir konuşma başlat",
"Start of the channel": "Kanalın başlangıcı",
"Start Tag": "Başlangıç Etiketi",
@ -3152,7 +3152,7 @@
"Summarize this note.": "Bu notu özetle.",
"Support": "Destek",
"Support this plugin:": "Bu eklentiyi destekleyin:",
"Swap": "",
"Swap": "Yer değiştir",
"Switch camera": "Kamerayı değiştir",
"Switch to JSON editor": "JSON düzenleyiciye geç",
"Switch to visual editor": "Görsel düzenleyiciye geç",
@ -3181,8 +3181,8 @@
"Task List": "Görev Listesi",
"Tasks": "Görevler",
"tasks completed": "görev tamamlandı",
"Tell me a fun fact": "",
"Tell me a random fun fact about the Roman Empire": "",
"Tell me a fun fact": "Bana eğlenceli bir bilgi söyle",
"Tell me a random fun fact about the Roman Empire": "Bana Roma İmparatorluğu hakkında rastgele eğlenceli bir bilgi söyle",
"Tell us more:": "Bize daha fazlasını anlat:",
"Temporary": "Geçici",
"Temporary Chat": "Geçici Sohbet",
@ -3318,7 +3318,7 @@
"Type Hugging Face Resolve (Download) URL": "HuggingFace Çözümleme (İndirme) URL'sini Yazın",
"Type your answer": "Cevabınızı yazın",
"Uh-oh! There was an issue with the response.": "Opps! Yanıtla ilgili bir sorun oluştu",
"Unarchive": "",
"Unarchive": "Arşivden Çıkar",
"Unarchive Chat": "Sohbeti Arşivden Çıkar",
"Unassigned": "Atanmamış",
"Underline": "Altı Çizili",
@ -3355,7 +3355,7 @@
"Upload failed for {{failed}} of {{total}} files.": "{{total}} dosyadan {{failed}} tanesinin yüklenmesi başarısız oldu.",
"Upload files": "Dosyaları yükle",
"Upload Files": "Dosyaları Yükle",
"Upload Folder": "",
"Upload Folder": "Klasör Yükle",
"Upload Model": "Model Yükle",
"Upload Photo": "Fotoğraf Yükle",
"Upload profile image": "Profil resmi yükle",
@ -3427,7 +3427,7 @@
"Visible": "Görünür",
"Visible to all users": "Tüm kullanıcılara görünür",
"Visual": "Görsel",
"vocabulary for a college entrance exam": "",
"vocabulary for a college entrance exam": "üniversite sınavı için kelime çalışması",
"Voice": "Ses",
"Voice Input": "Ses Girişi",
"Voice mode": "Sesli mod",
@ -3458,7 +3458,7 @@
"Weekly": "Haftalık",
"Weekly at": "Haftalık:",
"Welcome to your AI home.": "Yapay zeka merkezinize hoş geldiniz.",
"What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.": "",
"What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.": "Çocuklarımın sanat eserleriyle yapabileceğim 5 yaratıcı şey neler? Onları atmak istemiyorum, ama aynı zamanda çok dağınık oluyor.",
"What are you trying to achieve?": "Ne yapmaya çalışıyorsunuz?",
"What are you working on?": "Üzerinde çalıştığınız nedir?",
"What is NOT shared:": "PaylaşılMAYANlar:",

View file

@ -0,0 +1,31 @@
// Editor JSON can contain ProseMirror's null-prototype attributes, which fast-deep-equal
// cannot compare. Keep this scoped to JSON data, with reference checks and early exits.
export function equalEditorJSON(a: unknown, b: unknown): boolean {
if (a === b) return true;
if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false;
if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (!equalEditorJSON(a[i], b[i])) return false;
}
return true;
}
if (Array.isArray(b)) return false;
const left = a as Record<string, unknown>;
const right = b as Record<string, unknown>;
const keys = Object.keys(left);
if (keys.length !== Object.keys(right).length) return false;
for (const key of keys) {
if (
!Object.prototype.hasOwnProperty.call(right, key) ||
!equalEditorJSON(left[key], right[key])
) {
return false;
}
}
return true;
}

331
uv.lock generated
View file

@ -1,5 +1,5 @@
version = 1
revision = 2
revision = 3
requires-python = ">=3.11, <3.13.0"
resolution-markers = [
"python_full_version >= '3.12' and sys_platform == 'win32'",
@ -69,7 +69,7 @@ wheels = [
[[package]]
name = "aiohttp"
version = "3.13.5"
version = "3.14.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohappyeyeballs" },
@ -78,44 +78,47 @@ dependencies = [
{ name = "frozenlist" },
{ name = "multidict" },
{ name = "propcache" },
{ name = "typing-extensions" },
{ name = "yarl" },
]
sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" }
sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/f5/a20c4ac64aeaef1679e25c9983573618ff765d7aa829fa2b84ae7573169e/aiohttp-3.13.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ab7229b6f9b5c1ba4910d6c41a9eb11f543eadb3f384df1b4c293f4e73d44d6", size = 757513, upload-time = "2026-03-31T21:57:02.146Z" },
{ url = "https://files.pythonhosted.org/packages/75/0a/39fa6c6b179b53fcb3e4b3d2b6d6cad0180854eda17060c7218540102bef/aiohttp-3.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f14c50708bb156b3a3ca7230b3d820199d56a48e3af76fa21c2d6087190fe3d", size = 506748, upload-time = "2026-03-31T21:57:04.275Z" },
{ url = "https://files.pythonhosted.org/packages/87/ec/e38ce072e724fd7add6243613f8d1810da084f54175353d25ccf9f9c7e5a/aiohttp-3.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d2f8616f0ff60bd332022279011776c3ac0faa0f1b463f7bb12326fbc97a1c", size = 501673, upload-time = "2026-03-31T21:57:06.208Z" },
{ url = "https://files.pythonhosted.org/packages/ba/ba/3bc7525d7e2beaa11b309a70d48b0d3cfc3c2089ec6a7d0820d59c657053/aiohttp-3.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2567b72e1ffc3ab25510db43f355b29eeada56c0a622e58dcdb19530eb0a3cb", size = 1763757, upload-time = "2026-03-31T21:57:07.882Z" },
{ url = "https://files.pythonhosted.org/packages/5e/ab/e87744cf18f1bd78263aba24924d4953b41086bd3a31d22452378e9028a0/aiohttp-3.13.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fb0540c854ac9c0c5ad495908fdfd3e332d553ec731698c0e29b1877ba0d2ec6", size = 1720152, upload-time = "2026-03-31T21:57:09.946Z" },
{ url = "https://files.pythonhosted.org/packages/6b/f3/ed17a6f2d742af17b50bae2d152315ed1b164b07a5fd5cc1754d99e4dfa5/aiohttp-3.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9883051c6972f58bfc4ebb2116345ee2aa151178e99c3f2b2bbe2af712abd13", size = 1818010, upload-time = "2026-03-31T21:57:12.157Z" },
{ url = "https://files.pythonhosted.org/packages/53/06/ecbc63dc937192e2a5cb46df4d3edb21deb8225535818802f210a6ea5816/aiohttp-3.13.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2294172ce08a82fb7c7273485895de1fa1186cc8294cfeb6aef4af42ad261174", size = 1907251, upload-time = "2026-03-31T21:57:14.023Z" },
{ url = "https://files.pythonhosted.org/packages/7e/a5/0521aa32c1ddf3aa1e71dcc466be0b7db2771907a13f18cddaa45967d97b/aiohttp-3.13.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a807cabd5115fb55af198b98178997a5e0e57dead43eb74a93d9c07d6d4a7dc", size = 1759969, upload-time = "2026-03-31T21:57:16.146Z" },
{ url = "https://files.pythonhosted.org/packages/f6/78/a38f8c9105199dd3b9706745865a8a59d0041b6be0ca0cc4b2ccf1bab374/aiohttp-3.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6d0d932e0f39c02b80744273cd5c388a2d9bc07760a03164f229c8e02662f6", size = 1616871, upload-time = "2026-03-31T21:57:17.856Z" },
{ url = "https://files.pythonhosted.org/packages/6f/41/27392a61ead8ab38072105c71aa44ff891e71653fe53d576a7067da2b4e8/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60869c7ac4aaabe7110f26499f3e6e5696eae98144735b12a9c3d9eae2b51a49", size = 1739844, upload-time = "2026-03-31T21:57:19.679Z" },
{ url = "https://files.pythonhosted.org/packages/6e/55/5564e7ae26d94f3214250009a0b1c65a0c6af4bf88924ccb6fdab901de28/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26d2f8546f1dfa75efa50c3488215a903c0168d253b75fba4210f57ab77a0fb8", size = 1731969, upload-time = "2026-03-31T21:57:22.006Z" },
{ url = "https://files.pythonhosted.org/packages/6d/c5/705a3929149865fc941bcbdd1047b238e4a72bcb215a9b16b9d7a2e8d992/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1162a1492032c82f14271e831c8f4b49f2b6078f4f5fc74de2c912fa225d51d", size = 1795193, upload-time = "2026-03-31T21:57:24.256Z" },
{ url = "https://files.pythonhosted.org/packages/a6/19/edabed62f718d02cff7231ca0db4ef1c72504235bc467f7b67adb1679f48/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8b14eb3262fad0dc2f89c1a43b13727e709504972186ff6a99a3ecaa77102b6c", size = 1606477, upload-time = "2026-03-31T21:57:26.364Z" },
{ url = "https://files.pythonhosted.org/packages/de/fc/76f80ef008675637d88d0b21584596dc27410a990b0918cb1e5776545b5b/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ca9ac61ac6db4eb6c2a0cd1d0f7e1357647b638ccc92f7e9d8d133e71ed3c6ac", size = 1813198, upload-time = "2026-03-31T21:57:28.316Z" },
{ url = "https://files.pythonhosted.org/packages/e5/67/5b3ac26b80adb20ea541c487f73730dc8fa107d632c998f25bbbab98fcda/aiohttp-3.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7996023b2ed59489ae4762256c8516df9820f751cf2c5da8ed2fb20ee50abab3", size = 1752321, upload-time = "2026-03-31T21:57:30.549Z" },
{ url = "https://files.pythonhosted.org/packages/88/06/e4a2e49255ea23fa4feeb5ab092d90240d927c15e47b5b5c48dff5a9ce29/aiohttp-3.13.5-cp311-cp311-win32.whl", hash = "sha256:77dfa48c9f8013271011e51c00f8ada19851f013cde2c48fca1ba5e0caf5bb06", size = 439069, upload-time = "2026-03-31T21:57:32.388Z" },
{ url = "https://files.pythonhosted.org/packages/c0/43/8c7163a596dab4f8be12c190cf467a1e07e4734cf90eebb39f7f5d53fc6a/aiohttp-3.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:d3a4834f221061624b8887090637db9ad4f61752001eae37d56c52fddade2dc8", size = 462859, upload-time = "2026-03-31T21:57:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/be/6f/353954c29e7dcce7cf00280a02c75f30e133c00793c7a2ed3776d7b2f426/aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9", size = 748876, upload-time = "2026-03-31T21:57:36.319Z" },
{ url = "https://files.pythonhosted.org/packages/f5/1b/428a7c64687b3b2e9cd293186695affc0e1e54a445d0361743b231f11066/aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416", size = 499557, upload-time = "2026-03-31T21:57:38.236Z" },
{ url = "https://files.pythonhosted.org/packages/29/47/7be41556bfbb6917069d6a6634bb7dd5e163ba445b783a90d40f5ac7e3a7/aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2", size = 500258, upload-time = "2026-03-31T21:57:39.923Z" },
{ url = "https://files.pythonhosted.org/packages/67/84/c9ecc5828cb0b3695856c07c0a6817a99d51e2473400f705275a2b3d9239/aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4", size = 1749199, upload-time = "2026-03-31T21:57:41.938Z" },
{ url = "https://files.pythonhosted.org/packages/f0/d3/3c6d610e66b495657622edb6ae7c7fd31b2e9086b4ec50b47897ad6042a9/aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9", size = 1721013, upload-time = "2026-03-31T21:57:43.904Z" },
{ url = "https://files.pythonhosted.org/packages/49/a0/24409c12217456df0bae7babe3b014e460b0b38a8e60753d6cb339f6556d/aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5", size = 1781501, upload-time = "2026-03-31T21:57:46.285Z" },
{ url = "https://files.pythonhosted.org/packages/98/9d/b65ec649adc5bccc008b0957a9a9c691070aeac4e41cea18559fef49958b/aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e", size = 1878981, upload-time = "2026-03-31T21:57:48.734Z" },
{ url = "https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1", size = 1767934, upload-time = "2026-03-31T21:57:51.171Z" },
{ url = "https://files.pythonhosted.org/packages/31/04/d3f8211f273356f158e3464e9e45484d3fb8c4ce5eb2f6fe9405c3273983/aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286", size = 1566671, upload-time = "2026-03-31T21:57:53.326Z" },
{ url = "https://files.pythonhosted.org/packages/41/db/073e4ebe00b78e2dfcacff734291651729a62953b48933d765dc513bf798/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9", size = 1705219, upload-time = "2026-03-31T21:57:55.385Z" },
{ url = "https://files.pythonhosted.org/packages/48/45/7dfba71a2f9fd97b15c95c06819de7eb38113d2cdb6319669195a7d64270/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88", size = 1743049, upload-time = "2026-03-31T21:57:57.341Z" },
{ url = "https://files.pythonhosted.org/packages/18/71/901db0061e0f717d226386a7f471bb59b19566f2cae5f0d93874b017271f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3", size = 1749557, upload-time = "2026-03-31T21:57:59.626Z" },
{ url = "https://files.pythonhosted.org/packages/08/d5/41eebd16066e59cd43728fe74bce953d7402f2b4ddfdfef2c0e9f17ca274/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b", size = 1558931, upload-time = "2026-03-31T21:58:01.972Z" },
{ url = "https://files.pythonhosted.org/packages/30/e6/4a799798bf05740e66c3a1161079bda7a3dd8e22ca392481d7a7f9af82a6/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe", size = 1774125, upload-time = "2026-03-31T21:58:04.007Z" },
{ url = "https://files.pythonhosted.org/packages/84/63/7749337c90f92bc2cb18f9560d67aa6258c7060d1397d21529b8004fcf6f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14", size = 1732427, upload-time = "2026-03-31T21:58:06.337Z" },
{ url = "https://files.pythonhosted.org/packages/98/de/cf2f44ff98d307e72fb97d5f5bbae3bfcb442f0ea9790c0bf5c5c2331404/aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3", size = 433534, upload-time = "2026-03-31T21:58:08.712Z" },
{ url = "https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1", size = 460446, upload-time = "2026-03-31T21:58:10.945Z" },
{ url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" },
{ url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" },
{ url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" },
{ url = "https://files.pythonhosted.org/packages/9e/8d/a71c6f2db52ac1ed142b133f7feddaa6b70539c3f4de24d7e226c95b794c/aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", size = 1780613, upload-time = "2026-07-23T01:52:56.948Z" },
{ url = "https://files.pythonhosted.org/packages/a5/11/3dd9b3fb3a170f6ec9011b5291d876a6fab4086714c9e158600edf01b4fd/aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", size = 1737688, upload-time = "2026-07-23T01:52:59.294Z" },
{ url = "https://files.pythonhosted.org/packages/6d/3e/834c26918be7d88068822b40e0db30fca50b5f4fe79104aa16a93f1d74e6/aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", size = 1845742, upload-time = "2026-07-23T01:53:01.641Z" },
{ url = "https://files.pythonhosted.org/packages/cc/c9/49ab8572df7d66bc13d11e31f781292badb04180dd87ba98733066c6aed7/aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", size = 1928412, upload-time = "2026-07-23T01:53:04.018Z" },
{ url = "https://files.pythonhosted.org/packages/a5/b9/2b8f0c0ce09c87a1daf80fd483431b56b1435d3f62789bc86f572e1245de/aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", size = 1786220, upload-time = "2026-07-23T01:53:06.481Z" },
{ url = "https://files.pythonhosted.org/packages/85/00/9c45f81de11710460edfa1dc81317b6e882703b160926c879a9d20da9fcc/aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", size = 1637231, upload-time = "2026-07-23T01:53:10.258Z" },
{ url = "https://files.pythonhosted.org/packages/19/ce/967d628e910756f3539c6107cb7844a1b69440dcb3029a5ee7871b09ab63/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", size = 1753161, upload-time = "2026-07-23T01:53:13.817Z" },
{ url = "https://files.pythonhosted.org/packages/11/b2/0c3d4114f0aee4f580f5b3b4eb71b24d7a23b834ea506a4dfebe76513f35/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", size = 1756356, upload-time = "2026-07-23T01:53:16.211Z" },
{ url = "https://files.pythonhosted.org/packages/63/5d/99e7d91c82f1399d1ae2a854e080bd1493fbc31e5e959dbc4ec33dac3bec/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", size = 1819846, upload-time = "2026-07-23T01:53:18.289Z" },
{ url = "https://files.pythonhosted.org/packages/ad/05/d5e1cb6480eeffd3f901d40a2c5e2d1e7effdc797837da3b490272699f13/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", size = 1628531, upload-time = "2026-07-23T01:53:23.86Z" },
{ url = "https://files.pythonhosted.org/packages/c9/90/b934682bcaefae18a9e04f3dff5b68522ba810906358ae5029b68110ea3b/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", size = 1832712, upload-time = "2026-07-23T01:53:27.551Z" },
{ url = "https://files.pythonhosted.org/packages/21/df/6061679faaf81fac746e7307c7adb71e858071a5d34c27583afefc64f543/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", size = 1775014, upload-time = "2026-07-23T01:53:30.223Z" },
{ url = "https://files.pythonhosted.org/packages/8a/1d/f854878bbc69b88faefe924b619a34a6f59ec05fd387c77690667eaa75eb/aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", size = 456006, upload-time = "2026-07-23T01:53:34.97Z" },
{ url = "https://files.pythonhosted.org/packages/73/0c/2af9d1674baccd1dbd47282a93d660a22e57ef6167c856deb24b4214fbab/aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", size = 481069, upload-time = "2026-07-23T01:53:39.673Z" },
{ url = "https://files.pythonhosted.org/packages/8e/76/88401ff3fc95e85c5fc38d588f36f55e61ecb64343b2bc8d69326f453cc0/aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", size = 453021, upload-time = "2026-07-23T01:53:43.749Z" },
{ url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" },
{ url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" },
{ url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" },
{ url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" },
{ url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" },
{ url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" },
{ url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" },
{ url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" },
{ url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" },
{ url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" },
{ url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" },
{ url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" },
{ url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" },
{ url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" },
{ url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" },
{ url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" },
{ url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" },
{ url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" },
]
[[package]]
@ -1442,6 +1445,36 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" },
]
[[package]]
name = "google-re2"
version = "1.1.20251105"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6b/60/805c654ba53d685513df955ee745f71920fe8e6a284faf0f9b9dc19b659c/google_re2-1.1.20251105.tar.gz", hash = "sha256:1db14a292ee8303b91e91e7c37e05ac17d3c467f29416c79ac70a78be3e65bda", size = 11676, upload-time = "2025-11-05T14:58:07.324Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/4d/203a08dab1bdb5c83b46dd424c01a789ecb5a37dbc80f33d016bd116a9d7/google_re2-1.1.20251105-1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:329efa209ea7baa44f0facf0402fa34e655dc97fdeb10d0b83fc06354f5575fd", size = 483717, upload-time = "2025-11-05T14:57:04.808Z" },
{ url = "https://files.pythonhosted.org/packages/78/88/466026b43ff5c7d740f5ede090992ec63b60d1810ab14fe35dfc00677e0a/google_re2-1.1.20251105-1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:aa2ad5f6f48921ec137a7b7f1b1da903ddef8627a2dc30bc878a9a69d9925719", size = 515547, upload-time = "2025-11-05T14:57:06.013Z" },
{ url = "https://files.pythonhosted.org/packages/f3/6a/c6c9fdb00c98990e4f7a6cd650e209d7b5d2754ca0404b72c69ac9909a69/google_re2-1.1.20251105-1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ac1cb2526cc88f050a0661fc7245ad009ee454bddc541b2e653f1d007585000d", size = 485396, upload-time = "2025-11-05T14:57:07.592Z" },
{ url = "https://files.pythonhosted.org/packages/a2/f6/529c44f607c47f96cfa29c1fe3a690fe75b2fdb48e9b0d6b54e5f0a75e59/google_re2-1.1.20251105-1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:50c7205182ad66c23c07abe8072f720ca2f7d595b61e28fd9b63623614f9afd6", size = 517150, upload-time = "2025-11-05T14:57:09.376Z" },
{ url = "https://files.pythonhosted.org/packages/df/d2/ccc07860e31ab81965c63f9ed4eb69ea0d3449a9b4e1610f71883694bbe8/google_re2-1.1.20251105-1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:4cb5acee61e35772503b8b1db3c592a46b8e6a9bc0ab54d7d6233654ea2bf93d", size = 482807, upload-time = "2025-11-05T14:57:11.057Z" },
{ url = "https://files.pythonhosted.org/packages/bd/43/5fb20d16664457f61670bdd95f39039d43ee8b7732511c688e2f322a4317/google_re2-1.1.20251105-1-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:1617097d63620c2d46bdfc0e48f24f66cd341664fc75718636d234f67473fe7f", size = 508839, upload-time = "2025-11-05T14:57:12.338Z" },
{ url = "https://files.pythonhosted.org/packages/0e/f2/6e470338271e164dd3c5e508876f99aec3ed23bf419c7d54a5672fd5b05f/google_re2-1.1.20251105-1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a5610b26742b90cb1d64ead2b16fe0e3bd7e67add03fd3779cd1b85e401661", size = 573718, upload-time = "2025-11-05T14:57:13.635Z" },
{ url = "https://files.pythonhosted.org/packages/91/21/4566fc344c21cf3c49082d13ddab785994b5e3b8b7fd4631242538f698a2/google_re2-1.1.20251105-1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03156291269f145eccddff63118f2df02d395792f51fc039f09955818943815a", size = 590749, upload-time = "2025-11-05T14:57:14.864Z" },
{ url = "https://files.pythonhosted.org/packages/94/19/5981fb798bb8d08933b815b1fd9e55d179c380b9d8c21a49197b9b7c5967/google_re2-1.1.20251105-1-cp311-cp311-win32.whl", hash = "sha256:54f51762b51dc238eceddf49b56cc2b64594fe72d9328c1c39d615aa990e1f87", size = 434066, upload-time = "2025-11-05T14:57:16.22Z" },
{ url = "https://files.pythonhosted.org/packages/49/e5/f83053a36cfc4762d843748e4f7a9c1141937dcf74cd6fc3f4598292dda3/google_re2-1.1.20251105-1-cp311-cp311-win_amd64.whl", hash = "sha256:f5f856ff5036a8f22b3bad57f376d4e3b97b59b64f311bdb1f83c8dabded2492", size = 491025, upload-time = "2025-11-05T14:57:17.746Z" },
{ url = "https://files.pythonhosted.org/packages/56/be/4315c3b38f42f9a2888fa76260545c98547502f1c35aa63a672d39011b2e/google_re2-1.1.20251105-1-cp311-cp311-win_arm64.whl", hash = "sha256:913864f97de4151eaa8bb7746ca230fd193656501e07fb658ce2cd46d4f6efcc", size = 642194, upload-time = "2025-11-05T14:57:19.374Z" },
{ url = "https://files.pythonhosted.org/packages/67/20/73b487538e9107c2fd96aed737e3f3890dfce3e292622e4ffb2f9c810ee5/google_re2-1.1.20251105-1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:b30f09b4d63249c72e65ccae4cbf6b331b48c22fc7cb439f1d85f347b9d07ceb", size = 485591, upload-time = "2025-11-05T14:57:20.961Z" },
{ url = "https://files.pythonhosted.org/packages/b9/9a/ca3a993bdb5dc6d5b2616b9657b2872a83d1827f8bd3ab50cd629eb751c7/google_re2-1.1.20251105-1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:9a77892c524b8bdf3d47d7cad1cc2ac3a0108bdd65007ef4c02888fa46baf8ee", size = 518780, upload-time = "2025-11-05T14:57:22.18Z" },
{ url = "https://files.pythonhosted.org/packages/df/37/b2e367987371514253ec9e514637f457deaacb7acc1c900814f3a6421e0f/google_re2-1.1.20251105-1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a3ac51b28cbf25c100dfd8849212d878d7005d1d4a7e129a10789043c56b6021", size = 486966, upload-time = "2025-11-05T14:57:24.575Z" },
{ url = "https://files.pythonhosted.org/packages/d9/69/1db6742943c0ac254bfb7d8a37a5d3f73f016a65cfa1f84fe3a0451820f6/google_re2-1.1.20251105-1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:9f7158afc9825ac2654c6561aea94a1f7edb5b5b88e6e3639bb80bb817d102ac", size = 520225, upload-time = "2025-11-05T14:57:26.039Z" },
{ url = "https://files.pythonhosted.org/packages/f4/0a/0747c92dbebe2c09a26bd7386d372b5c5a9926236b4f3d69bb8f15db05cb/google_re2-1.1.20251105-1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:5320da07dc3b7ac7f407514f42ac17d67e771ac7c7562d449571185e6fb601b2", size = 482943, upload-time = "2025-11-05T14:57:27.353Z" },
{ url = "https://files.pythonhosted.org/packages/7f/14/6bfc6838bb6cb561824ac03deeab2bd11d5d9a93505f536c8fa2f6bd46c4/google_re2-1.1.20251105-1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:5a4e5785bc30d52ce655d805b07ad2d8a4905429a5f690ae9c2f1caa76665709", size = 510384, upload-time = "2025-11-05T14:57:29.139Z" },
{ url = "https://files.pythonhosted.org/packages/8a/0a/6add090c917ee39f6f0be753037cafceb3bad904b424efc155fb38082635/google_re2-1.1.20251105-1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b7a3b90f747130310d4b3b8e19ebb845d0d97c1deb63b36f76c7242dacbd736", size = 572446, upload-time = "2025-11-05T14:57:30.495Z" },
{ url = "https://files.pythonhosted.org/packages/0d/1c/8b1ccbeade96a21435d55b5185cd6d9b2ceab5a9af998a4d9099e0540759/google_re2-1.1.20251105-1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:809c5fa5d08279413b29c2e2c5c528e85cd94a0e0fd897db595a0c09eeee2782", size = 591348, upload-time = "2025-11-05T14:57:31.808Z" },
{ url = "https://files.pythonhosted.org/packages/62/cf/7bdd7a1ae7828b613011da808eafec4da3132f43c3be6af5e0bd670ebe8b/google_re2-1.1.20251105-1-cp312-cp312-win32.whl", hash = "sha256:d8424e63a9ec0fe5bde03d97876b2431f8a746af33eb475fa1ae39144bd05b2a", size = 433787, upload-time = "2025-11-05T14:57:33.071Z" },
{ url = "https://files.pythonhosted.org/packages/31/e9/5dd951c35acaabfe87c67228b9af2cdcd7779d9167edbe6b9094b8a8e529/google_re2-1.1.20251105-1-cp312-cp312-win_amd64.whl", hash = "sha256:062313c309f93dfeb6966372f4c446580e98879133ec155522eea8aaf568a5cd", size = 491726, upload-time = "2025-11-05T14:57:34.39Z" },
{ url = "https://files.pythonhosted.org/packages/60/8d/c1afd29fc2cb475fd4c634f3d3c8099c0efb662362c10b27a9eaf11c9357/google_re2-1.1.20251105-1-cp312-cp312-win_arm64.whl", hash = "sha256:558f144b26a9555ae4e9467cc3aa3299a8ce13217f328b21ae326ca0633be19b", size = 642673, upload-time = "2025-11-05T14:57:35.693Z" },
]
[[package]]
name = "google-resumable-media"
version = "2.10.0"
@ -1454,36 +1487,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b0/d8/00c6854ac1512bb9eaf13bd3f8f28222f7674947fc510a4ff7616f2efc80/google_resumable_media-2.10.0-py3-none-any.whl", hash = "sha256:88152884bee37b2bf36a0ab81ad8c7fd12212c9803dd981d77c1b35b02d34e7c", size = 81533, upload-time = "2026-06-03T16:13:12.51Z" },
]
[[package]]
name = "google-re2"
version = "1.1.20251105"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6b/60/805c654ba53d685513df955ee745f71920fe8e6a284faf0f9b9dc19b659c/google_re2-1.1.20251105.tar.gz", hash = "sha256:1db14a292ee8303b91e91e7c37e05ac17d3c467f29416c79ac70a78be3e65bda", size = 11676 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8d/4d/203a08dab1bdb5c83b46dd424c01a789ecb5a37dbc80f33d016bd116a9d7/google_re2-1.1.20251105-1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:329efa209ea7baa44f0facf0402fa34e655dc97fdeb10d0b83fc06354f5575fd", size = 483717 },
{ url = "https://files.pythonhosted.org/packages/78/88/466026b43ff5c7d740f5ede090992ec63b60d1810ab14fe35dfc00677e0a/google_re2-1.1.20251105-1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:aa2ad5f6f48921ec137a7b7f1b1da903ddef8627a2dc30bc878a9a69d9925719", size = 515547 },
{ url = "https://files.pythonhosted.org/packages/f3/6a/c6c9fdb00c98990e4f7a6cd650e209d7b5d2754ca0404b72c69ac9909a69/google_re2-1.1.20251105-1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ac1cb2526cc88f050a0661fc7245ad009ee454bddc541b2e653f1d007585000d", size = 485396 },
{ url = "https://files.pythonhosted.org/packages/a2/f6/529c44f607c47f96cfa29c1fe3a690fe75b2fdb48e9b0d6b54e5f0a75e59/google_re2-1.1.20251105-1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:50c7205182ad66c23c07abe8072f720ca2f7d595b61e28fd9b63623614f9afd6", size = 517150 },
{ url = "https://files.pythonhosted.org/packages/df/d2/ccc07860e31ab81965c63f9ed4eb69ea0d3449a9b4e1610f71883694bbe8/google_re2-1.1.20251105-1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:4cb5acee61e35772503b8b1db3c592a46b8e6a9bc0ab54d7d6233654ea2bf93d", size = 482807 },
{ url = "https://files.pythonhosted.org/packages/bd/43/5fb20d16664457f61670bdd95f39039d43ee8b7732511c688e2f322a4317/google_re2-1.1.20251105-1-cp311-cp311-macosx_15_0_x86_64.whl", hash = "sha256:1617097d63620c2d46bdfc0e48f24f66cd341664fc75718636d234f67473fe7f", size = 508839 },
{ url = "https://files.pythonhosted.org/packages/0e/f2/6e470338271e164dd3c5e508876f99aec3ed23bf419c7d54a5672fd5b05f/google_re2-1.1.20251105-1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18a5610b26742b90cb1d64ead2b16fe0e3bd7e67add03fd3779cd1b85e401661", size = 573718 },
{ url = "https://files.pythonhosted.org/packages/91/21/4566fc344c21cf3c49082d13ddab785994b5e3b8b7fd4631242538f698a2/google_re2-1.1.20251105-1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03156291269f145eccddff63118f2df02d395792f51fc039f09955818943815a", size = 590749 },
{ url = "https://files.pythonhosted.org/packages/94/19/5981fb798bb8d08933b815b1fd9e55d179c380b9d8c21a49197b9b7c5967/google_re2-1.1.20251105-1-cp311-cp311-win32.whl", hash = "sha256:54f51762b51dc238eceddf49b56cc2b64594fe72d9328c1c39d615aa990e1f87", size = 434066 },
{ url = "https://files.pythonhosted.org/packages/49/e5/f83053a36cfc4762d843748e4f7a9c1141937dcf74cd6fc3f4598292dda3/google_re2-1.1.20251105-1-cp311-cp311-win_amd64.whl", hash = "sha256:f5f856ff5036a8f22b3bad57f376d4e3b97b59b64f311bdb1f83c8dabded2492", size = 491025 },
{ url = "https://files.pythonhosted.org/packages/56/be/4315c3b38f42f9a2888fa76260545c98547502f1c35aa63a672d39011b2e/google_re2-1.1.20251105-1-cp311-cp311-win_arm64.whl", hash = "sha256:913864f97de4151eaa8bb7746ca230fd193656501e07fb658ce2cd46d4f6efcc", size = 642194 },
{ url = "https://files.pythonhosted.org/packages/67/20/73b487538e9107c2fd96aed737e3f3890dfce3e292622e4ffb2f9c810ee5/google_re2-1.1.20251105-1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:b30f09b4d63249c72e65ccae4cbf6b331b48c22fc7cb439f1d85f347b9d07ceb", size = 485591 },
{ url = "https://files.pythonhosted.org/packages/b9/9a/ca3a993bdb5dc6d5b2616b9657b2872a83d1827f8bd3ab50cd629eb751c7/google_re2-1.1.20251105-1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:9a77892c524b8bdf3d47d7cad1cc2ac3a0108bdd65007ef4c02888fa46baf8ee", size = 518780 },
{ url = "https://files.pythonhosted.org/packages/df/37/b2e367987371514253ec9e514637f457deaacb7acc1c900814f3a6421e0f/google_re2-1.1.20251105-1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a3ac51b28cbf25c100dfd8849212d878d7005d1d4a7e129a10789043c56b6021", size = 486966 },
{ url = "https://files.pythonhosted.org/packages/d9/69/1db6742943c0ac254bfb7d8a37a5d3f73f016a65cfa1f84fe3a0451820f6/google_re2-1.1.20251105-1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:9f7158afc9825ac2654c6561aea94a1f7edb5b5b88e6e3639bb80bb817d102ac", size = 520225 },
{ url = "https://files.pythonhosted.org/packages/f4/0a/0747c92dbebe2c09a26bd7386d372b5c5a9926236b4f3d69bb8f15db05cb/google_re2-1.1.20251105-1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:5320da07dc3b7ac7f407514f42ac17d67e771ac7c7562d449571185e6fb601b2", size = 482943 },
{ url = "https://files.pythonhosted.org/packages/7f/14/6bfc6838bb6cb561824ac03deeab2bd11d5d9a93505f536c8fa2f6bd46c4/google_re2-1.1.20251105-1-cp312-cp312-macosx_15_0_x86_64.whl", hash = "sha256:5a4e5785bc30d52ce655d805b07ad2d8a4905429a5f690ae9c2f1caa76665709", size = 510384 },
{ url = "https://files.pythonhosted.org/packages/8a/0a/6add090c917ee39f6f0be753037cafceb3bad904b424efc155fb38082635/google_re2-1.1.20251105-1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b7a3b90f747130310d4b3b8e19ebb845d0d97c1deb63b36f76c7242dacbd736", size = 572446 },
{ url = "https://files.pythonhosted.org/packages/0d/1c/8b1ccbeade96a21435d55b5185cd6d9b2ceab5a9af998a4d9099e0540759/google_re2-1.1.20251105-1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:809c5fa5d08279413b29c2e2c5c528e85cd94a0e0fd897db595a0c09eeee2782", size = 591348 },
{ url = "https://files.pythonhosted.org/packages/62/cf/7bdd7a1ae7828b613011da808eafec4da3132f43c3be6af5e0bd670ebe8b/google_re2-1.1.20251105-1-cp312-cp312-win32.whl", hash = "sha256:d8424e63a9ec0fe5bde03d97876b2431f8a746af33eb475fa1ae39144bd05b2a", size = 433787 },
{ url = "https://files.pythonhosted.org/packages/31/e9/5dd951c35acaabfe87c67228b9af2cdcd7779d9167edbe6b9094b8a8e529/google_re2-1.1.20251105-1-cp312-cp312-win_amd64.whl", hash = "sha256:062313c309f93dfeb6966372f4c446580e98879133ec155522eea8aaf568a5cd", size = 491726 },
{ url = "https://files.pythonhosted.org/packages/60/8d/c1afd29fc2cb475fd4c634f3d3c8099c0efb662362c10b27a9eaf11c9357/google_re2-1.1.20251105-1-cp312-cp312-win_arm64.whl", hash = "sha256:558f144b26a9555ae4e9467cc3aa3299a8ce13217f328b21ae326ca0633be19b", size = 642673 },
]
[[package]]
name = "googleapis-common-protos"
version = "1.75.0"
@ -1595,38 +1598,38 @@ wheels = [
[[package]]
name = "hiredis"
version = "3.4.0"
version = "3.4.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1f/e2/1654d65851f39fd94e91a77a5655d09d4b64901fdc594020d8348db697b2/hiredis-3.4.0.tar.gz", hash = "sha256:da19331354433af6a2c54c21f2d70ba084933c0d7d2c43578ec5c5b446674ad5", size = 137169, upload-time = "2026-06-03T16:23:46.226Z" }
sdist = { url = "https://files.pythonhosted.org/packages/38/da/41b341ebed1eb6f1074112936af98bb52880724737887ae9bade9d7ce107/hiredis-3.4.2.tar.gz", hash = "sha256:9a566dc70e9dd84be3550babc56a8e109bb65cafcac635aea027fa425196a7d7", size = 138058, upload-time = "2026-09-22T12:39:20.363Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/d1/09d7323c76d097ff3f6530228d2422c19817b6052716f9a652ecd6e2f68e/hiredis-3.4.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:7f7fc1535f6e1a190089eae46dee25f0c6b72bb221d377be07092803b8208733", size = 138467, upload-time = "2026-06-03T16:22:04.09Z" },
{ url = "https://files.pythonhosted.org/packages/ed/27/c4ebeb0f7ecc8a23d4356efd3ef2b6243ed74d24584d86ff8065fa14a350/hiredis-3.4.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:ed1dba2695f6de009c67d63b39ff978cb43b8a79362f697acedffb7743e50d21", size = 74504, upload-time = "2026-06-03T16:22:04.998Z" },
{ url = "https://files.pythonhosted.org/packages/ae/d7/4f456f36f5c5224bc11a2fad964116a3cc37259d09dd840628aea5fdbf28/hiredis-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3796094f616f72976ff51e4dc1a016e753c0f9af5393b2df96920b6bae1e19b", size = 70080, upload-time = "2026-06-03T16:22:05.76Z" },
{ url = "https://files.pythonhosted.org/packages/04/ba/a16d44b2bd71e72a10673faa94d07cc4e9de90240b65ce2511af0cce065b/hiredis-3.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccc5c660e31d788ca534a20f2ccb7a80b946b960e18ed4e1db950fcac122b405", size = 304968, upload-time = "2026-06-03T16:22:06.614Z" },
{ url = "https://files.pythonhosted.org/packages/4a/3a/78ca23fe899f8da7ee2caf9c502ac1a63da15d521f33a3fc617a7adbf2e0/hiredis-3.4.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3c67f39b112dc35f68d5b59ee111db6121f037d1a60cf3840ecffbb2ec5686b", size = 337465, upload-time = "2026-06-03T16:22:07.622Z" },
{ url = "https://files.pythonhosted.org/packages/ba/11/2df9a12f170e9d61739e7df5f06712141414b2dce2cf385fc1fb6f31a46b/hiredis-3.4.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bca175f02a2b0150ffe7f5dc8bf49c798f34d2c7024d17ace0ec97a7583560e3", size = 348293, upload-time = "2026-06-03T16:22:08.677Z" },
{ url = "https://files.pythonhosted.org/packages/88/07/716ffeb049377d92da6261c5563e554b82336ce3eafb11eb4510c5558be7/hiredis-3.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43004b0b48abc628dda1ac3ac4871e1326c126f8cd9f11164d61934d827d7a3b", size = 310697, upload-time = "2026-06-03T16:22:09.661Z" },
{ url = "https://files.pythonhosted.org/packages/5d/03/ef3697bdee359b4521101bdc16e8e4965a5ebd8634b605fc7cf9c01b6b82/hiredis-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8aaaab18314fd25453b5cf59c8cdca4110e419455bcb4c0737d19d4151513e75", size = 299377, upload-time = "2026-06-03T16:22:10.777Z" },
{ url = "https://files.pythonhosted.org/packages/bc/a7/2a12a2f828c2d611b74dcf2229998c4d2570fe6ed6b4903d6a4c3add84af/hiredis-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5359caad5b57da0bce11d2880f22617ba3710f0866121a924745447848448034", size = 329008, upload-time = "2026-06-03T16:22:11.82Z" },
{ url = "https://files.pythonhosted.org/packages/66/a9/cdfda214af93eeb9f93a83a099d06f26ae5569f188209ddc8a7c977ed446/hiredis-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:44660a91e0fbc803c29b337c1a9194c8d7b4cd3a3868d28f747cbec2df165483", size = 330103, upload-time = "2026-06-03T16:22:12.935Z" },
{ url = "https://files.pythonhosted.org/packages/87/05/cdc7e2e07b56c716426db4644b917b260a4f6fdc8d16cc3bbac4b27d0a17/hiredis-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315009b441a0105a373a9a780ebb1c6f7d9ead88ac6ea5f2a15791353c6f590", size = 309582, upload-time = "2026-06-03T16:22:14.157Z" },
{ url = "https://files.pythonhosted.org/packages/f2/36/304a0e029cb6e44add3b0d664315de25c483f6e8f8e1d413c68de969a3d0/hiredis-3.4.0-cp311-cp311-win32.whl", hash = "sha256:282c4310af72afbe18b07d416459f4febeaeb805a067a7df790136e0e550fcb2", size = 38823, upload-time = "2026-06-03T16:22:15.14Z" },
{ url = "https://files.pythonhosted.org/packages/f0/19/7ea1fdbee1c42cbac140005e66e60a1198548eea04456e17dab5c285e31b/hiredis-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:bb44efa4fa3e3ed7779ad0ade3c08ed5d75ca7a6336893e9a4f2722093b4168a", size = 40040, upload-time = "2026-06-03T16:22:15.886Z" },
{ url = "https://files.pythonhosted.org/packages/20/e4/2122980b75a3fa8980540e2265028c757564ecc4d813b40298d29dd876ea/hiredis-3.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:4404c557fd49bcfe24dff41f1209e4221c76d1607df2fb2dfd39474b5b086dcb", size = 36851, upload-time = "2026-06-03T16:22:16.644Z" },
{ url = "https://files.pythonhosted.org/packages/d2/84/f74deb132d238a0d5a3eb1618bf7558c65230b279421f909a9753231c516/hiredis-3.4.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:9e88048a66dfffec7a3f578f2a2a0fd907c75b5bd85b3c9184f76f0149ea399f", size = 138679, upload-time = "2026-06-03T16:22:17.598Z" },
{ url = "https://files.pythonhosted.org/packages/a2/13/399fe51d399b8d4f5717aa68cb1dafcb8c244b19b1b9b0afaaa526c1be94/hiredis-3.4.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:8b3f1d03046765c0a83558bf1756811101e3947649c7ca22a71d9dc3c92929d1", size = 74657, upload-time = "2026-06-03T16:22:18.819Z" },
{ url = "https://files.pythonhosted.org/packages/a4/cf/6a0bcf454b1642997c4dd007bd89beada43f38b22781afdf475060e427ac/hiredis-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24751054bb11353016d242d09a4a902ecf8f25e3b56fe396cccb6f056fdda016", size = 70115, upload-time = "2026-06-03T16:22:19.649Z" },
{ url = "https://files.pythonhosted.org/packages/98/99/62340215f80e59680c79ae5080c5422311da105870c57bbefc5d87487025/hiredis-3.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:258f820cdd6ee6be39ae6a8ea94a76b8856d34113de6604f63bc81327ef06240", size = 306481, upload-time = "2026-06-03T16:22:20.608Z" },
{ url = "https://files.pythonhosted.org/packages/f1/be/97f349e5bb0dcab0ef28b15523443d9bbe81f8ccbd3dadff56594dfa82fe/hiredis-3.4.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3774461209688790734b5db8934400a4456493fc1a172fb5298cc5d72201aceb", size = 339560, upload-time = "2026-06-03T16:22:21.861Z" },
{ url = "https://files.pythonhosted.org/packages/1e/3f/eb6a9632bcc13a3fbefce5de90090052fb1ae1cd3d57faf687f20149d592/hiredis-3.4.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccdb63363c82ea9cea2d48126bc8e9241437b8b3b36413e967647a17add59643", size = 351549, upload-time = "2026-06-03T16:22:22.969Z" },
{ url = "https://files.pythonhosted.org/packages/1e/8c/440369f727dcb856f3eeda238d6e67781b180feaa831bd28997d8af10c3b/hiredis-3.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:452cff764acb30c106d1e33f1bdf03fa9d4a9b0a9c995d722d4d39c998b40582", size = 313066, upload-time = "2026-06-03T16:22:23.987Z" },
{ url = "https://files.pythonhosted.org/packages/ec/d1/3d76c4d5c46cd2e7b38641f7c8b325e0cab7d49d565ea573256eb3837d0c/hiredis-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb0a139cd52535f3e5a532816b5c36b3aea95817410fbf28ca4a676026347a5", size = 300827, upload-time = "2026-06-03T16:22:25.287Z" },
{ url = "https://files.pythonhosted.org/packages/c5/bc/d112dd9704ae47243a515fb021ec4d0b5a1b8d83a7a3eff3284c0248412d/hiredis-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:163d8c43e2706d23490532ea0de8736fc1493cfa52f0ee65f85b0f074f2fe017", size = 331284, upload-time = "2026-06-03T16:22:26.385Z" },
{ url = "https://files.pythonhosted.org/packages/e9/7b/8a4dc0a15e4658c81a9e79b2c167fbfbf750e0c1c7ef13e00e69d4273ced/hiredis-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4b8f52844cd260d7805eca55c834e3e06b4c0d5b53a4178143b92242c2517c0d", size = 332962, upload-time = "2026-06-03T16:22:27.392Z" },
{ url = "https://files.pythonhosted.org/packages/1d/52/d3d0bb234de8deb4cbd432cdc63d001a6cad1f9c05fe07d2fa652f8cf412/hiredis-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03374d663b0e025e4039757ef5fad02e3ff714f7a01e5b34c88de2a9c91359dc", size = 311698, upload-time = "2026-06-03T16:22:28.442Z" },
{ url = "https://files.pythonhosted.org/packages/04/5b/54a052eccaf901703b57d7c28509e74341fa0da08d770f485345397ea1e5/hiredis-3.4.0-cp312-cp312-win32.whl", hash = "sha256:696e0a2118e1df5ccacf8ecf8abe528cf0c4f1f1d867f64c34579bef77778cdb", size = 38921, upload-time = "2026-06-03T16:22:29.39Z" },
{ url = "https://files.pythonhosted.org/packages/a7/64/6508236eda66765fbe873d1d0a0722e38059302e96dc9915b162ff17b35a/hiredis-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee6b4beb79a71df67af15a8451366babc2687fcac674d5c6eacec4197e4ce8c1", size = 40090, upload-time = "2026-06-03T16:22:30.204Z" },
{ url = "https://files.pythonhosted.org/packages/fb/1c/7333aba1b4b7cef2591b244140aec0f1aad903397bbaa31c1858722b2fe4/hiredis-3.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:14524fdc751e3960d78d848872576b5442b40baae3cac14fbab1ba7ac523891f", size = 36875, upload-time = "2026-06-03T16:22:31.087Z" },
{ url = "https://files.pythonhosted.org/packages/f8/c4/0170f76b9f22d22f58a08f1379f637f9f6ec24ecfb6e3e07adf100e1dbe7/hiredis-3.4.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:01a71476d6e43aa7c1f4fbb8a90acc1b850bd0a86391adf4c2fca8c11b57e7c4", size = 140478, upload-time = "2026-09-22T12:37:33.765Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e3/2b43b9e3301b01f0e9c2249cc1a7a202f1c6ddc8ef592aebe68498ffe541/hiredis-3.4.2-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:be3cb13b3b69371e0ed298ea045b3ceb88ab3aa188049d892933c6119a2847c6", size = 74936, upload-time = "2026-09-22T12:37:34.634Z" },
{ url = "https://files.pythonhosted.org/packages/02/fa/97b2c2ff7dc8ad892931d8a7b3f541dded542abea42da3541ca1f7e5d0e7/hiredis-3.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5808e4319d5a15621b7dbd64853de5c0fb4e14a18104633d27c9c10d1903aab", size = 71835, upload-time = "2026-09-22T12:37:35.601Z" },
{ url = "https://files.pythonhosted.org/packages/0c/b9/9eed5ffd6c07ca46dcd21ad0ba685d9d67f689bac926eeaffd947a989878/hiredis-3.4.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc7275bb05bcb18805fede5838e653511b78962bc773ba2ffaa0af6171f43350", size = 305651, upload-time = "2026-09-22T12:37:36.505Z" },
{ url = "https://files.pythonhosted.org/packages/ef/b8/1486948f578d4703448e43de3e42c01c4dbc043371699502e8527c28cc7c/hiredis-3.4.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:eb027b6a9b362840af05713f1d6c33969d106d93a8677398b35034c9f9c18c76", size = 338045, upload-time = "2026-09-22T12:37:37.527Z" },
{ url = "https://files.pythonhosted.org/packages/e9/5c/983d46f0b17ad4808b217e6e644630cb0f975197677e5621e1e6a8653072/hiredis-3.4.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccff5bb35017adab43a8aeb29183e29e044762fe544b17d86144102527073ae5", size = 348877, upload-time = "2026-09-22T12:37:38.505Z" },
{ url = "https://files.pythonhosted.org/packages/f9/48/0ce8b35726201626967d862693624ed8a9b250f480bf0a921b927daedacc/hiredis-3.4.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1805792e7d7ee0751f2b44653714d214ae53b46be35b0e17b31e8031eef8f43", size = 310927, upload-time = "2026-09-22T12:37:39.48Z" },
{ url = "https://files.pythonhosted.org/packages/e3/88/6b77a6a00d943587a9a8afa23a627d2a772f48ac577ba2c0c2c41484f486/hiredis-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:513df8c538e1fce9b4d4acacdbc869303a3ff107790db50abe305269ec084046", size = 300061, upload-time = "2026-09-22T12:37:40.407Z" },
{ url = "https://files.pythonhosted.org/packages/3c/bc/cce4c248bddf29533888fb6930a2b28417eb00fb1a2a0d903b4793ca9cb1/hiredis-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bdf6f55350eef61f9e55a3e25cfbad5e1652ab5201f9437fd6bc4cbba3d68324", size = 329689, upload-time = "2026-09-22T12:37:41.871Z" },
{ url = "https://files.pythonhosted.org/packages/a4/26/2663c23ab3ffeedbc29194fb475f390c93deccd9124b00db10d2806a029a/hiredis-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b0d4c9aaeaadcc0c20bd58ac194657acb00f730384717c7bfbdd1cee30f13cad", size = 330604, upload-time = "2026-09-22T12:37:42.924Z" },
{ url = "https://files.pythonhosted.org/packages/c8/b6/7dce7f57e98cf35930536ecdd918fd7dbd332ae6476b64414a37245897ef/hiredis-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2d88b2e8c7cf63b52fe67d95a02660312add872697ad7ec2ad994a78ca2fe086", size = 309897, upload-time = "2026-09-22T12:37:44.152Z" },
{ url = "https://files.pythonhosted.org/packages/30/55/1e6e5262993ede40e98743bf2169d3e0b87f5a66ffd87695de43a48042cc/hiredis-3.4.2-cp311-cp311-win32.whl", hash = "sha256:b26e282e82a9f350c6a5858bf54380419d5bfe2a11553f7f235ee18318d49326", size = 38736, upload-time = "2026-09-22T12:37:45.23Z" },
{ url = "https://files.pythonhosted.org/packages/a9/7c/c001696159859de8270c6dea9925a9d57cadff3385f7771906930ed181ab/hiredis-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:2fde1d857f5a88353083bc73e5e1911d2a9a8fb369ac3f8d3bb86d9fe7f9d5e2", size = 40506, upload-time = "2026-09-22T12:37:46.058Z" },
{ url = "https://files.pythonhosted.org/packages/09/9e/397a43a2254be70ec7baeff63fb115477b0a0fb40b3161d5de2f13666573/hiredis-3.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:99977c00ba4c1df76325a11281ceac8b4f6f736235d01344242728835b07cff4", size = 36904, upload-time = "2026-09-22T12:37:47.502Z" },
{ url = "https://files.pythonhosted.org/packages/f4/fb/aee5f09ba3b483700b0fb4556f9c09e752791d255bb677310485e76a3e37/hiredis-3.4.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:eb98b46a781a960bc9044050cc166e38c19b327a7a8c62afee9c78d72d80dd18", size = 140912, upload-time = "2026-09-22T12:37:48.554Z" },
{ url = "https://files.pythonhosted.org/packages/41/0c/d29b76ac581200ebf0e0194edda8c7aef51dd497079d556aabfd44336de7/hiredis-3.4.2-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:05d06f3edcdeb484aa47610fd520c07d637a763d4ab1cd7793550829afe27ccb", size = 75186, upload-time = "2026-09-22T12:37:50.066Z" },
{ url = "https://files.pythonhosted.org/packages/d5/6f/9092acfd69d9a76fecce4723bba624a36d321447f92e88f80296038dfaee/hiredis-3.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ddfdd5006d1cbe2ee961852b90f89d676b44dd8e0eb2f032dc2383c16a54bfc9", size = 71987, upload-time = "2026-09-22T12:37:51.09Z" },
{ url = "https://files.pythonhosted.org/packages/3c/29/65e823bc79be70322dfab5b7bf46bdbac2d029b848950fed9621adac7445/hiredis-3.4.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4cf7924e86c5f9d4e212d9643a99e607008628941e771df015c72cd6dc4d15e", size = 307130, upload-time = "2026-09-22T12:37:52.538Z" },
{ url = "https://files.pythonhosted.org/packages/69/51/f8b21afd788b8da4be6368cec3777b44151c166b054d3b6dd38349b4323b/hiredis-3.4.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:258741a87fb551e58e5e008ffc989e1bc980b26e2156be365a12b7088b2c48c9", size = 340169, upload-time = "2026-09-22T12:37:54.192Z" },
{ url = "https://files.pythonhosted.org/packages/cc/2c/0f535418703886f755fb8edba8c4ae174e02663ec61b1029d248afa7d835/hiredis-3.4.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aa9fef272956109d72a46016f2ca8431d8af36fcf9cd155da53aeba642d201e7", size = 351929, upload-time = "2026-09-22T12:37:55.735Z" },
{ url = "https://files.pythonhosted.org/packages/b1/4c/d4d16acb0c9d4d4741d4d8c8bd72e7b7e881c9a41867a6b7d24748099a57/hiredis-3.4.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:018fdee902038f74b21e18a6d2fe7819bb63bdaec878d9d5f27280005b778ad7", size = 313508, upload-time = "2026-09-22T12:37:56.879Z" },
{ url = "https://files.pythonhosted.org/packages/98/b7/b7ceb4f6975a91e8100da63d53b41ff075a706472f095e442c8e998bd521/hiredis-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d7282fba5602013d11c068c0f6218c28b67c4c80064f0b3882ffaf0290bbfa9", size = 301438, upload-time = "2026-09-22T12:37:57.994Z" },
{ url = "https://files.pythonhosted.org/packages/00/dc/1ae6dca5684631595685482a8478179503e54d3be40097b3e345f2aa4e93/hiredis-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:254c880fbd087527c326ec7672562dde4ac9dfe1c38b2ce923a387858c7a2618", size = 331982, upload-time = "2026-09-22T12:37:59.266Z" },
{ url = "https://files.pythonhosted.org/packages/da/7c/767f89bdded81ba7be1a185f0718d8662b8e8eee1009ab88e9e1decc6bb4/hiredis-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:12f05180d1dbc11647a11c967984873dd8baa7f4cdfc4f1b3eff42983fa80d4a", size = 333346, upload-time = "2026-09-22T12:38:00.337Z" },
{ url = "https://files.pythonhosted.org/packages/3c/38/5715f89fa8d6ca724ae073d92474628525c9751864eda7a3811033a846fa/hiredis-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc446964ce1ae16ca7689b27991dfb769094531e69f3972e2eaaf03f19037a1e", size = 312252, upload-time = "2026-09-22T12:38:01.518Z" },
{ url = "https://files.pythonhosted.org/packages/0c/c0/3f1f58df82e59f740d3b3eb76c14144917e9cc7345695b184e88669a60a6/hiredis-3.4.2-cp312-cp312-win32.whl", hash = "sha256:cdd19191555763455d34d63697becfe480a5bb907a33fe90e5505fadfd7bc9ae", size = 38728, upload-time = "2026-09-22T12:38:02.697Z" },
{ url = "https://files.pythonhosted.org/packages/0b/e2/de4c556ca70124b3f45396ffe2f339a35d80639e1595abc67c3aa09fba4c/hiredis-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:51add939c00482b855b9ef6ea1354d4ea942f0c281f32aec514a94f07c3e2148", size = 40530, upload-time = "2026-09-22T12:38:03.656Z" },
{ url = "https://files.pythonhosted.org/packages/83/c2/cd2deae4d071718303449c376e29ca3b5000489ca7c6e19d1230e4c7c641/hiredis-3.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:9f298b8a2c2af3166a7381c3d9b6a80c3bf2cf38785dbe06bf030882584eb4f8", size = 36929, upload-time = "2026-09-22T12:38:04.553Z" },
]
[[package]]
@ -2883,7 +2886,7 @@ requires-dist = [
{ name = "aiocache", specifier = "==0.12.3" },
{ name = "aiodns", specifier = "==3.6.1" },
{ name = "aiofiles", specifier = "==25.1.0" },
{ name = "aiohttp", specifier = "==3.13.5" },
{ name = "aiohttp", specifier = "==3.14.3" },
{ name = "aiosqlite", specifier = "==0.22.1" },
{ name = "alembic", specifier = "==1.18.4" },
{ name = "anthropic", specifier = "==0.86.0" },
@ -2915,7 +2918,7 @@ requires-dist = [
{ name = "google-cloud-storage", specifier = "==3.9.0" },
{ name = "google-re2", specifier = "==1.1.20251105" },
{ name = "googleapis-common-protos", specifier = "==1.75.0" },
{ name = "hiredis", specifier = "==3.4.0" },
{ name = "hiredis", specifier = "==3.4.2" },
{ name = "httpx", extras = ["brotli", "cli", "http2", "socks", "zstd"], specifier = "==0.28.1" },
{ name = "itsdangerous", specifier = "==2.2.0" },
{ name = "joserfc", specifier = "==1.7.4" },
@ -2939,7 +2942,7 @@ requires-dist = [
{ name = "pandas", specifier = "==3.0.3" },
{ name = "pgvector", marker = "extra == 'all'", specifier = "==0.4.2" },
{ name = "pgvector", marker = "extra == 'postgres'", specifier = "==0.4.2" },
{ name = "pillow", specifier = "==12.2.0" },
{ name = "pillow", specifier = "==12.3.0" },
{ name = "pinecone", marker = "extra == 'all'", specifier = "==6.0.2" },
{ name = "playwright", marker = "extra == 'all'", specifier = "==1.60.0" },
{ name = "psutil", specifier = "==7.2.2" },
@ -2947,7 +2950,7 @@ requires-dist = [
{ name = "psycopg2-binary", marker = "extra == 'all'", specifier = "==2.9.12" },
{ name = "psycopg2-binary", marker = "extra == 'postgres'", specifier = "==2.9.12" },
{ name = "pyarrow", specifier = "==20.0.0" },
{ name = "pycrdt", specifier = "==0.13.1" },
{ name = "pycrdt", specifier = "==0.14.8" },
{ name = "pydantic", specifier = "==2.13.4" },
{ name = "pydub", specifier = "==0.25.1" },
{ name = "pyjwt", extras = ["crypto"], specifier = "==2.13.0" },
@ -3299,39 +3302,33 @@ wheels = [
[[package]]
name = "pillow"
version = "12.2.0"
version = "12.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" },
{ url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" },
{ url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" },
{ url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" },
{ url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" },
{ url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" },
{ url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" },
{ url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" },
{ url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" },
{ url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" },
{ url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" },
{ url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" },
{ url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" },
{ url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" },
{ url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" },
{ url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" },
{ url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" },
{ url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" },
{ url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" },
{ url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" },
{ url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" },
{ url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" },
{ url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" },
{ url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" },
{ url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" },
{ url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" },
{ url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" },
{ url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" },
{ url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" },
{ url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" },
{ url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" },
{ url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" },
{ url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" },
{ url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" },
{ url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" },
{ url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" },
{ url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" },
{ url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" },
{ url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" },
{ url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" },
{ url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" },
{ url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" },
{ url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" },
{ url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" },
{ url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" },
{ url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" },
{ url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" },
{ url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" },
{ url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" },
{ url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" },
{ url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" },
]
[[package]]
@ -3800,46 +3797,46 @@ wheels = [
[[package]]
name = "pycrdt"
version = "0.13.1"
version = "0.14.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/88/e0c1b145f25f02ae0b95ce98cfa653941fc3e13ec95b1c90a62cd717a932/pycrdt-0.13.1.tar.gz", hash = "sha256:3d888fc5dafe17d6f53a1430c5342cd58f0f704cafb6ff3f565e597c50d99934", size = 85504, upload-time = "2026-05-21T12:36:25.545Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c6/9f/540084c927f3ff2d22883abb722c6b9af1630b0ac31d7f4cf4ec4f1342df/pycrdt-0.14.8.tar.gz", hash = "sha256:45867f5ff08006d852d0cbb3e26b581977122b8f77dcd300359a16188b6cc931", size = 98177, upload-time = "2026-09-30T07:59:48.553Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/3c/7d6c850fcbd7cf011c1818cf27c85c04c23a91c20c660c89f40658d51561/pycrdt-0.13.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ab8e797a7e7658a4765acd25ca6c175e33327be4cecf6cbf6299bd13d4b7d5f5", size = 1702440, upload-time = "2026-05-21T12:34:39.488Z" },
{ url = "https://files.pythonhosted.org/packages/fe/ea/2a1a42358ad9a0fbca3a84193ea6ad5c965412e6b9175080a37c53026261/pycrdt-0.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4eb42e3ccc7fa265a181c0f1b322f263ed367f456b5cc2b09981e5830b5d56", size = 948038, upload-time = "2026-05-21T12:34:41.011Z" },
{ url = "https://files.pythonhosted.org/packages/53/34/c2e08ddb3253ff53629ce3db23ec806b669b7015570588d5e526c811aed2/pycrdt-0.13.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc68323d1a3c7b7c7e5c595308b52d6ae6141709724b043cfd333f431d6d259", size = 961904, upload-time = "2026-05-21T12:34:42.528Z" },
{ url = "https://files.pythonhosted.org/packages/6d/96/ce1bd0827887b44cdb4f522dbde524b237bedafcf10de288e18169271248/pycrdt-0.13.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28e3abb9a5c9642cd05a73871f58982d56a56aa86c3373e93c9c690cc187a683", size = 1135191, upload-time = "2026-05-21T12:34:44.391Z" },
{ url = "https://files.pythonhosted.org/packages/f2/71/4dd73ff84e1ed4f05328d09988551bed88c7e10d1bb0e2b0656ee6d4046c/pycrdt-0.13.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e24ff42e99aa67452b4bfec72d09de36425ec2eabc7e78d4c59ea671baef81e", size = 988852, upload-time = "2026-05-21T12:34:45.936Z" },
{ url = "https://files.pythonhosted.org/packages/d2/4c/da70355291570a1c61465791b2ee648457bef9dde62e66b1617349bf5d18/pycrdt-0.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689c3e1c100c86096802bc27356f8b686fea3abbd2e1641ae87783f68dd4081c", size = 957219, upload-time = "2026-05-21T12:34:47.411Z" },
{ url = "https://files.pythonhosted.org/packages/03/d5/0454874f4817b4a759b4bf9731511f452d404d8bf1d4a9a5fc30ab76c2d4/pycrdt-0.13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0f49ad9d702c0ef09f8346e4cb445ca34d0a1760186178acb0fd9c7dcedd5a7d", size = 1057747, upload-time = "2026-05-21T12:34:49.044Z" },
{ url = "https://files.pythonhosted.org/packages/15/b3/ddcb2636e5c5d5efa5b52c87b772fe8708d62c423c5a84bd01b4cead706d/pycrdt-0.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0c07b7d7e6914900c2b878827e772a9f4226248d7dba098965c5bdd9b9aab062", size = 1123471, upload-time = "2026-05-21T12:34:50.477Z" },
{ url = "https://files.pythonhosted.org/packages/57/74/28e63a2e46a7cb4eef0ed9961b02f1f4bb15ef62ee27abe89e43ed5433c0/pycrdt-0.13.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:dd3b5e58ae6fe8acfeceeb10fd23a5778fdb67596c5411a16b5bf51bdab6b277", size = 1236330, upload-time = "2026-05-21T12:34:51.965Z" },
{ url = "https://files.pythonhosted.org/packages/34/5f/8b8fcc48a46a721c4c38ccd30a718a56e4eff282926b9dffe7c817e56aaa/pycrdt-0.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9ab6caa63565d4ba7f109126336ac1c30fbe146b25b16574669013e52c65feb5", size = 1232793, upload-time = "2026-05-21T12:34:53.59Z" },
{ url = "https://files.pythonhosted.org/packages/81/ea/59e0814910e181af50a3f3e20d68e005b5e2598aa6401812168ccedd11b1/pycrdt-0.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23be7d711363b51324cdba90c1e67d8f5c4e37e14eb503c38a9b35de8b329be1", size = 1170011, upload-time = "2026-05-21T12:34:55.508Z" },
{ url = "https://files.pythonhosted.org/packages/57/a9/2c6fa5c160b328b08a21ab1c75caffdfce2a24bf0cf0edfd7c6f329ab1bf/pycrdt-0.13.1-cp311-cp311-win32.whl", hash = "sha256:6926f156e8a0e88c967a5b4bea2af1425054775943fc9e0be08f4c9523fba509", size = 696030, upload-time = "2026-05-21T12:34:57.473Z" },
{ url = "https://files.pythonhosted.org/packages/65/f0/036f521770e156e5250dc6a930516b0acf70482792a563afc9ac2cca133c/pycrdt-0.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a65d09e383debe2a00d13a3e1415cd599037e0dbf3976adca8cc36483eafc1db", size = 756974, upload-time = "2026-05-21T12:34:58.898Z" },
{ url = "https://files.pythonhosted.org/packages/86/47/61ecc2f5a2d2127a00ffeb3eae5e515f7fc07e203883001cd0c5f8e2c319/pycrdt-0.13.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0269fefddef54f529ef360554ec7ac967d01ba22677c65fc155addbb5c4329d1", size = 1700474, upload-time = "2026-05-21T12:35:00.511Z" },
{ url = "https://files.pythonhosted.org/packages/cc/e5/595d8582952be13060556f2faa29bff2b10b9247011a1a5882240fea6f33/pycrdt-0.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:084fd1c3a680764a1717c638d26dd238a953b8a66801ce34d0f4041963c525cf", size = 942672, upload-time = "2026-05-21T12:35:01.979Z" },
{ url = "https://files.pythonhosted.org/packages/e4/0b/429b57dd1e0a1e8eb79ce8bbd97e1ecea8566c1ed7fea08f79bcd54ad0b0/pycrdt-0.13.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8a417723c723764f4621e03df16e7a19e55ed28048b7015afa04cc4e71338", size = 958984, upload-time = "2026-05-21T12:35:03.625Z" },
{ url = "https://files.pythonhosted.org/packages/0f/a8/be64722f870b03cd8b50505567692f5615ebcc40c03b7dc7b1c1efe15547/pycrdt-0.13.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b69026155572547633af419d7e6da728aa53175b9df95b65a4850365ffc4d79", size = 1134053, upload-time = "2026-05-21T12:35:05.622Z" },
{ url = "https://files.pythonhosted.org/packages/e5/44/b5ba4ed6b2be26ded680a0a2115e749275f779c0db93d8bdc20dcf9f5874/pycrdt-0.13.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d5a4f4ff430a5ed71c33680ec69f6d914c12a4332ba67f4566c82688357046b", size = 984448, upload-time = "2026-05-21T12:35:07.104Z" },
{ url = "https://files.pythonhosted.org/packages/00/ec/7363504d395d6785f159830430e24ecb1e9d074e2ce364db39fbc104d94f/pycrdt-0.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:190819bf2eebb9153ae6cf35e62d09a57fd0de15176a5047c2cb27130f57381d", size = 954167, upload-time = "2026-05-21T12:35:08.794Z" },
{ url = "https://files.pythonhosted.org/packages/f8/83/46ab10d688add2a3dce155a3b3062f8cbf8080cd0b87b4e0f04cfeb1170b/pycrdt-0.13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:425541aeb3c6809938685a680b21fecb09b352e7fb8a21a97e0742fa27cb8cb6", size = 1052431, upload-time = "2026-05-21T12:35:10.327Z" },
{ url = "https://files.pythonhosted.org/packages/fd/4d/015157ec60a82cedcf9322b55bb8cc0c31fe15bb0582f0644d7655b04d49/pycrdt-0.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ea52a06fc84bebd719f515ce9a590e8bc5fc58f2a6c262db1cd01d727cf253", size = 1118940, upload-time = "2026-05-21T12:35:12.303Z" },
{ url = "https://files.pythonhosted.org/packages/1e/82/c9cab9cfc73cfa87ad97580822274f3a51aba47f9432b8c95795dae84f7a/pycrdt-0.13.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:06066675f65e228c22ebf5e5bb1e79fe463f89832b48899b092259c206256880", size = 1233250, upload-time = "2026-05-21T12:35:14.013Z" },
{ url = "https://files.pythonhosted.org/packages/9d/64/9d9069e7572c8350206c315cf8431d50b03a8c7af8d8b8a4c19338d99fd5/pycrdt-0.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:29e99ae8c2c7eb31363af386f22221fb5b685710b7e665ac90a5f772c0dc3df5", size = 1226052, upload-time = "2026-05-21T12:35:15.688Z" },
{ url = "https://files.pythonhosted.org/packages/cf/c6/2f2b36043170c511b0284987ecb346fa7e90709712600c9e298219b58bd3/pycrdt-0.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07692df4f1d493fabe7e337004a4cd671978833486f0d1dd481748f1f91350d2", size = 1167999, upload-time = "2026-05-21T12:35:17.532Z" },
{ url = "https://files.pythonhosted.org/packages/a2/14/d1dc34597666a491683a1fd61439f3350180d0bc53101effb465f56698e9/pycrdt-0.13.1-cp312-cp312-win32.whl", hash = "sha256:b4640674e88978cdd12ad531c6b73ac2d72697b854ca04c968113a36cb98c867", size = 693890, upload-time = "2026-05-21T12:35:19.148Z" },
{ url = "https://files.pythonhosted.org/packages/29/d5/2b84f8e3e6aae2632c6ee7351b73fa74093795ba24751ec582773d881ca3/pycrdt-0.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:6502dc5eae7f9489da534fc847d61c7bedcaf014c65fd46dbb4a30ca298594a7", size = 753915, upload-time = "2026-05-21T12:35:21.208Z" },
{ url = "https://files.pythonhosted.org/packages/04/4d/56a44130870221ff86bde308585e079254f0b44bcd4ee5776d924be85d16/pycrdt-0.13.1-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1110cd4460afb0fdae5ff88a8356dbf0cc963904d6fbc222ea68f4f4e6b9dd84", size = 1708730, upload-time = "2026-05-21T12:36:10.465Z" },
{ url = "https://files.pythonhosted.org/packages/ee/85/d8779cfd4d288d4382bb148f13bb02e423983418616577dc1142eddd4073/pycrdt-0.13.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b864bef9c42a7c18c1652f31907b2eda68e18a87bcc33369e342e667c1b9381", size = 947592, upload-time = "2026-05-21T12:36:12.286Z" },
{ url = "https://files.pythonhosted.org/packages/d5/3a/5240398ec6adce9a7807de918c1dfd1325085eb07e1a32533085e9ff15f5/pycrdt-0.13.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:21e1a73cf7b28391b2f52461590f3be4262df811b0648e4faf7a1503abe2c8d0", size = 965927, upload-time = "2026-05-21T12:36:14.434Z" },
{ url = "https://files.pythonhosted.org/packages/f0/d8/7af8f8ee7e82987bf0b3bffb1f10b03715d47f929b4407c9566290c74d8d/pycrdt-0.13.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4fbb82675b0a6130cd7df1fe60c53ef02515d9f79f7c106f1bbb37fe2368239", size = 1132014, upload-time = "2026-05-21T12:36:16.728Z" },
{ url = "https://files.pythonhosted.org/packages/22/95/e56e7b4ffdd12824d0a78f8ebe31d150a806a23aee065dc83ad9b3497875/pycrdt-0.13.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7d2424598c2b960a92ae705b1e213ad65d83b3210774ba08cd1d5c246a294b3", size = 989719, upload-time = "2026-05-21T12:36:19.202Z" },
{ url = "https://files.pythonhosted.org/packages/ce/da/9154643a1d93b32d8cb16cc30a2eaaec3beb791fb4e240c84b2bc11a0d13/pycrdt-0.13.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fc50d94b4adf6f87bcb7ce70beee1a9af2684e9a43bc46c04f7bf909cc5a05", size = 956173, upload-time = "2026-05-21T12:36:21.061Z" },
{ url = "https://files.pythonhosted.org/packages/af/c4/3058ea744cd72c89a982e5c9b5d85dedd6bd8b93602828759708403dd100/pycrdt-0.13.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dfec67ce79abc92d60cf5727dd58a762d6be361b550943d533a1407c73e62b4f", size = 1056620, upload-time = "2026-05-21T12:36:23.112Z" },
{ url = "https://files.pythonhosted.org/packages/62/4c/e725ecfdd367cd21dc9a29569e3d692e5f342731da5fffd8eee6e0f5ed0a/pycrdt-0.14.8-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dc8b17443acbeb1668e9e442e18f5f6bb5cad6e1904c3b9144d512b2489f3499", size = 1939220, upload-time = "2026-09-30T07:57:53.766Z" },
{ url = "https://files.pythonhosted.org/packages/3a/5f/94b91aeec65371846c968b10c2b983e107ff51b1d2a774d03520cac16ffe/pycrdt-0.14.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3f6694066aafdf0e7014af3a89dcc26db75619dd99259a002e6954b04c5e27", size = 1074712, upload-time = "2026-09-30T07:57:55.458Z" },
{ url = "https://files.pythonhosted.org/packages/79/8c/6c9c397ea7fa7ce99c0ce3c947ad3415940568018c703949c2d7e0d4363d/pycrdt-0.14.8-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2564a348d8125d12c4891a724c1baf36601df2da3e0140abf29f40b65c5b4265", size = 1098768, upload-time = "2026-09-30T07:57:57.2Z" },
{ url = "https://files.pythonhosted.org/packages/7f/66/7c06bae084d1ccc6c20e42989e94d8931b8170e42452e47d6ccab16ec69b/pycrdt-0.14.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a55d9be59106b52f5b209e6a796994535f40a0f83af3aae75a5de18656248ebc", size = 1290230, upload-time = "2026-09-30T07:57:59.352Z" },
{ url = "https://files.pythonhosted.org/packages/c4/29/a171029873629ca87f26df37249e0d582c75b22658008676b32832f0053c/pycrdt-0.14.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18bd511f71b6a2a90f3955f1d7cec6dfdba389527ef105fb2598cdd987365c5f", size = 1126302, upload-time = "2026-09-30T07:58:01.01Z" },
{ url = "https://files.pythonhosted.org/packages/c9/ab/5aeef5e966d5fc51dcd87e971fce9d6fa2e03addd876f5e782fe6eb4473a/pycrdt-0.14.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e98fa6226a9f402c00b6a4f0654f7ba032291cfff5c1dc7154dfdf0db74653fe", size = 1081298, upload-time = "2026-09-30T07:58:02.734Z" },
{ url = "https://files.pythonhosted.org/packages/1a/46/44ef9ebb9728216ade30cca1478e76205e5bc2c80577708b1b3fc477d228/pycrdt-0.14.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ddebf695a4cd846f2750fbfaf657233910a1a33daf280aa4cbde5f38a4c1e0e", size = 1212135, upload-time = "2026-09-30T07:58:04.461Z" },
{ url = "https://files.pythonhosted.org/packages/65/2d/280a9c13b830bac82d690984730383bb8ad42ffdbdec2efd769690b9263a/pycrdt-0.14.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cb8f669ec1b8d5c783db4ad94a25da2df010b3ee0fa756e9025530ec57657433", size = 1250917, upload-time = "2026-09-30T07:58:06.104Z" },
{ url = "https://files.pythonhosted.org/packages/5e/82/7af0c55470dd6563c106fac7162b442eb3262d665adbce4318a06bcc4c27/pycrdt-0.14.8-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:fa5a9531cc245253e7400eb684a3de357a6c44d5355cf51ec7aa603536a12b03", size = 1374029, upload-time = "2026-09-30T07:58:07.795Z" },
{ url = "https://files.pythonhosted.org/packages/6e/c2/2abff768d3e63b161a989d6204f91598a1d53ffd9c01c95c0b41db30b73b/pycrdt-0.14.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dab9b0e5eb9abbdf20c9b8c159f16d681512aaf11c2b975c3b2f2f8fca8c7fbf", size = 1379658, upload-time = "2026-09-30T07:58:09.525Z" },
{ url = "https://files.pythonhosted.org/packages/75/9c/6052266979223afd0b78074748d6b6fa1dfd981359e35531fd84a0e2d580/pycrdt-0.14.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fa7bb3e4cb0bc9d6c9d77567b141847aa5da90004d4c8e2b588d8ed1cb933ae2", size = 1309915, upload-time = "2026-09-30T07:58:11.189Z" },
{ url = "https://files.pythonhosted.org/packages/e7/7b/8fd45190f55ac85ecb18695b8d89316c26f7fd13a54ee3ff056bb224868e/pycrdt-0.14.8-cp311-cp311-win32.whl", hash = "sha256:53942c09503d3c4c42f0b2c59b95dc5a80b18982871cdc57d04e7070c3700a5d", size = 828399, upload-time = "2026-09-30T07:58:12.94Z" },
{ url = "https://files.pythonhosted.org/packages/e7/a8/80279dc4eff64d4609aec3dd1036898628a90305f0e0413d661a81640b7f/pycrdt-0.14.8-cp311-cp311-win_amd64.whl", hash = "sha256:180b587d9561eea6858cc503a288b44e8891fd21d8eb3fd02665728c48f6beeb", size = 884067, upload-time = "2026-09-30T07:58:14.741Z" },
{ url = "https://files.pythonhosted.org/packages/ba/dd/f6abc67c12ca906c977685f2fc8532b6b8d84c3b999089d78274fe1422d1/pycrdt-0.14.8-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dcfdb452743ae02a3d1cff83bf7ef3a95de3e054732add89e3fa5ae5bbac3ba7", size = 1932928, upload-time = "2026-09-30T07:58:16.673Z" },
{ url = "https://files.pythonhosted.org/packages/a3/b4/8073c090a2130cb09f455f75a9ed5b5e2e0feb39ca12af88357aca0b3e52/pycrdt-0.14.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0827c9809899f5ceb52572f5421b1344d019de8c13df8e5c7b085bce554c463c", size = 1066250, upload-time = "2026-09-30T07:58:18.435Z" },
{ url = "https://files.pythonhosted.org/packages/2d/ef/0a6347ea10ade0991dc15ed5b138f88253d74e19c2ebea9f806f0888f12e/pycrdt-0.14.8-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:822a7b68ea6274c0df6ba8f4f52e8592d607cf5cc9ab88a91992165e9e3c1b9f", size = 1094538, upload-time = "2026-09-30T07:58:20.362Z" },
{ url = "https://files.pythonhosted.org/packages/b4/d5/446e965ebe08f2f2737d041cd49314386b25bb0bfc9553b48d657f82c192/pycrdt-0.14.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d299bdbe7fb0bc2fc81ca6c734252d757b873b12ac604824129a6238740153a", size = 1281969, upload-time = "2026-09-30T07:58:22.491Z" },
{ url = "https://files.pythonhosted.org/packages/c1/cd/f3b03152dee00f559e54faa0850bffcb817d4a9efb19f98f564bc36d8dbc/pycrdt-0.14.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:60d49df3203cd5e51197ff02898c474871618742d28a0da680831af4cc2ed854", size = 1124068, upload-time = "2026-09-30T07:58:24.289Z" },
{ url = "https://files.pythonhosted.org/packages/da/24/158eea1800e1d12c6c05d2d321fa161da5ec896ce7ee466ac1354b28e2ce/pycrdt-0.14.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0496ade0ec92904f244c04c6791f9585aedab9065359e5f4c770522ea755695e", size = 1073691, upload-time = "2026-09-30T07:58:26.076Z" },
{ url = "https://files.pythonhosted.org/packages/48/d2/f6dc68037c0312c0bfa59fcc2fe8c9522fafb20d6793d6cff947503c41e6/pycrdt-0.14.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f88f775836bd9a6897aaf9020f2df84e59ab9e3d669cd1dd8c111dc2239822d", size = 1205759, upload-time = "2026-09-30T07:58:28.155Z" },
{ url = "https://files.pythonhosted.org/packages/19/01/4f543c17582ee3319952103c1dd6a4c5935119091070ee28bc7a6e83bb52/pycrdt-0.14.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5871a239cbf8e8428aeee9db7250ad01fbde0d3f73761f3fd99b25c12f363d4d", size = 1243621, upload-time = "2026-09-30T07:58:29.933Z" },
{ url = "https://files.pythonhosted.org/packages/e2/06/3676b46449ab54e19c17dc51beba8ae5c80c5a34c0fee9ea84715cabe991/pycrdt-0.14.8-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3d173c730424d777f8d315c8407e7f221d981ce233cc9450731abb347049d09a", size = 1372362, upload-time = "2026-09-30T07:58:31.738Z" },
{ url = "https://files.pythonhosted.org/packages/cb/9a/cfb116e6283dc0ff32559a01be9732db5f579b2752041458751fb8ec2a27/pycrdt-0.14.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:725ed3241d0a748e374f1b8891d42b05b697c7d6cc65932cc6bf2f316146d6e6", size = 1372653, upload-time = "2026-09-30T07:58:33.475Z" },
{ url = "https://files.pythonhosted.org/packages/be/bf/d14c1c8b39302df43342cd6f29027afa03e709cd49bc8ac5ee2135e8d0d7/pycrdt-0.14.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8822a1da5a9252b18465b10203780400babbdf75f3284858652490c6ea2a759b", size = 1299725, upload-time = "2026-09-30T07:58:35.432Z" },
{ url = "https://files.pythonhosted.org/packages/65/52/a4b61c0edfdf136ef3e1c1a9153e08c61ae2e5143e2597a6f9df58bd533d/pycrdt-0.14.8-cp312-cp312-win32.whl", hash = "sha256:29b5689393acb6b9475f2e5ea122e80b5eb1a96c6cbb9f362cfb44cd4279f3e4", size = 822498, upload-time = "2026-09-30T07:58:37.599Z" },
{ url = "https://files.pythonhosted.org/packages/1a/da/f5108de83a48b62ae289802e751e5bd98189e1e425adf98dbd2d403ed719/pycrdt-0.14.8-cp312-cp312-win_amd64.whl", hash = "sha256:ff7c417c59e2bd72bea576323a3042017313eddebc5dd06c153a38a6016b80ba", size = 877355, upload-time = "2026-09-30T07:58:39.42Z" },
{ url = "https://files.pythonhosted.org/packages/95/0c/1c419aea13103e8c6296eea95f9dc09763ee58f98edf707e417a6f0935c1/pycrdt-0.14.8-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:49e4de3e62bfa05404ea6dea5d850a8102a0a73cf02eca434807825c831d2e61", size = 1953141, upload-time = "2026-09-30T07:59:33.769Z" },
{ url = "https://files.pythonhosted.org/packages/45/fd/dafeeeae019969dab168d20b93efe9b62f34aa30acaed841662897f02a41/pycrdt-0.14.8-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e66c0aef0a32f8d8d86cfdfa84d132ab14a651ffdbc4d0c63e6215e5d1fe73ee", size = 1082192, upload-time = "2026-09-30T07:59:35.934Z" },
{ url = "https://files.pythonhosted.org/packages/0f/05/62982574024283afc246f743f760d2df1230fa40c353f5577ffd7733c06a/pycrdt-0.14.8-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25294f8eb74e4006ff4e78588616924e40c9c8ba8f33444154ad4616adcbe041", size = 1103067, upload-time = "2026-09-30T07:59:38.116Z" },
{ url = "https://files.pythonhosted.org/packages/78/63/aa0022807dc71828cba605111814f31fa09ff5a18da0c6a3729a9571fde2/pycrdt-0.14.8-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01480e703423f981bfedfb1fcf32caad37a67c706e5b228e90dade9f97cca33b", size = 1290942, upload-time = "2026-09-30T07:59:40.501Z" },
{ url = "https://files.pythonhosted.org/packages/2e/8c/a59de72336786189d43bf87c2c3532f6a9c19efb323d24a88e425b864dff/pycrdt-0.14.8-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b741a54351db58642fe310f193dd27db6ef67fe75ab5d0b6b0e25d805ecb561", size = 1133036, upload-time = "2026-09-30T07:59:42.521Z" },
{ url = "https://files.pythonhosted.org/packages/e1/95/9c5fefeb1dc14ac654cab67210ce37d09e1ae88a615f79c560d4cc033597/pycrdt-0.14.8-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:507adbe13e15d0ea871349156cc619e7c9797789098c0f70547f5bc21387a9cf", size = 1089400, upload-time = "2026-09-30T07:59:44.564Z" },
{ url = "https://files.pythonhosted.org/packages/1f/40/f7e69b241c492051304d5310d65ab867a7e0d2e359fa5675ee2304ecadbb/pycrdt-0.14.8-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8dabcd216adedee5a6fa5c2175d8a377831eeafe5b68f598939467b3ba51c5b4", size = 1221648, upload-time = "2026-09-30T07:59:46.568Z" },
]
[[package]]