From 1bfa59acbda0b0a60b73c313f64bededa7e85252 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:13:45 +0200 Subject: [PATCH 01/12] fix: keep HTML entities in extracted document text (#29736) Uploading a file whose text contains literal HTML entities stored a rewritten copy of it: ` ` became a non-breaking space, `>` became `>`, and `&nbsp;` was decoded twice down to a bare non-breaking space. That stored text is what gets indexed and what the model reads, so notes, specs and source files reached the model differing from the file that was uploaded. Every loaded document goes through `ftfy.fix_text`, which is there to repair mojibake left by the encoding-detection fallback. Its default configuration also decodes HTML entities, per line and sticky forward: entities are decoded on every line up to the first line holding a literal `<`, then left alone for the rest of the document. The same escape therefore survives or vanishes depending on where it sits in the file. This disables that one behaviour and leaves every other ftfy repair in place. Text from a third-party extraction engine that returns escaped output now keeps those escapes. Guessing whether an escape is markup or content is the bug being fixed. Fixes #29732 --- backend/open_webui/retrieval/loaders/main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/open_webui/retrieval/loaders/main.py b/backend/open_webui/retrieval/loaders/main.py index 13570c37f0..bc50269f3d 100644 --- a/backend/open_webui/retrieval/loaders/main.py +++ b/backend/open_webui/retrieval/loaders/main.py @@ -314,7 +314,11 @@ class Loader: def load(self, filename: str, file_content_type: str, file_path: str) -> list[Document]: loader = self._get_loader(filename, file_content_type, file_path) docs = loader.load() - return [Document(page_content=ftfy.fix_text(doc.page_content), metadata=doc.metadata) for doc in docs] + # ftfy's auto mode unescapes entities on every line before the first literal '<', rewriting the document. + return [ + Document(page_content=ftfy.fix_text(doc.page_content, unescape_html=False), metadata=doc.metadata) + for doc in docs + ] async def aload(self, filename: str, file_content_type: str, file_path: str) -> list[Document]: """ From 68a74da70bdda7dd1cda4f4eac05668509155b14 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:20:41 +0200 Subject: [PATCH 02/12] Fix: mps inference evaluations (#29735) * fix(retrieval): serialize local embedding and reranking on MPS On Apple Silicon the server process is killed outright (SIGSEGV or SIGTRAP, no traceback) partway through answering any question that retrieves from a knowledge base with hybrid search and a local reranking model. The client sees a dropped connection and the answer is lost. Hybrid search fans its queries out concurrently and every task calls the same shared local model on a worker thread. Torch's Metal shader cache is a process-wide singleton whose lookup tables have no lock, so two of those threads racing inside it corrupt the cache and take the process down with it. Guard the local SentenceTransformer and CrossEncoder calls with a shared lock that is only a real lock when the selected device is MPS. CPU and CUDA installs keep the concurrency they have today, and external reranking endpoints are untouched. Reranking several queries on a Mac now runs one at a time, which is the cost of the process staying alive. Verified by driving the real hybrid-search fan-out with 16 concurrent queries: peak simultaneous entries into the local model drops from 16 to 1 on MPS, stays at 16 on CPU, and the returned documents, scores and ordering are byte-identical in every case. Fixes #29722 * fix(evaluations): serialize the leaderboard embedder against retrieval on MPS The leaderboard's tag-similarity search builds its own SentenceTransformer, and on Apple Silicon sentence-transformers places it on the MPS device. It runs on a worker thread, so an admin running a leaderboard search while anyone queries a knowledge base puts two threads into torch's Metal backend at the same time, which kills the server process outright with no traceback. Move the lock added for the retrieval path into env.py, beside the device selection that decides whether MPS is used at all, and take it around the leaderboard's embedding calls as well. Sharing one lock between the two modules is the whole point, since two separate locks would still let a leaderboard search collide with a retrieval query. Only inference is guarded, matching the retrieval path. Model construction stays as it is here and in the retrieval routers. Verified by driving the leaderboard similarity path and retrieval reranking from six threads against one instrumented model: peak simultaneous entries drops from six to one on MPS, and the similarity scores are unchanged. Related to #29722. --- backend/open_webui/env.py | 5 +++++ backend/open_webui/retrieval/utils.py | 25 ++++++++++++++--------- backend/open_webui/routers/evaluations.py | 6 ++++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/backend/open_webui/env.py b/backend/open_webui/env.py index b17df7340a..c3a7da82e2 100644 --- a/backend/open_webui/env.py +++ b/backend/open_webui/env.py @@ -7,7 +7,9 @@ import pkgutil import re import shutil import sys +import threading import traceback +from contextlib import nullcontext from pathlib import Path from typing import Any, Optional from uuid import uuid4 @@ -66,6 +68,9 @@ if sys.platform == 'darwin' and DEVICE_TYPE == 'cpu': except Exception: pass +# Torch MPS inference is not thread-safe and a concurrent call kills the whole process. +MPS_INFERENCE_LOCK = threading.Lock() if DEVICE_TYPE == 'mps' else nullcontext() + #################################### # LOGGING #################################### diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 294309984e..ec7c73770e 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -32,6 +32,7 @@ from open_webui.env import ( BYPASS_RETRIEVAL_ACCESS_CONTROL, ENABLE_FORWARD_USER_INFO_HEADERS, ENABLE_RETRIEVAL_UNSCOPED_COLLECTIONS, + MPS_INFERENCE_LOCK, OFFLINE_MODE, ) from open_webui.models.access_grants import AccessGrants @@ -1116,17 +1117,16 @@ def get_embedding_function( 'SentenceTransformer model name, or configure an external ' 'RAG_EMBEDDING_ENGINE (ollama, openai, azure_openai).' ) - return await asyncio.to_thread( - ( - lambda query, prefix=None: embedding_function.encode( + + def encode(): + with MPS_INFERENCE_LOCK: + return embedding_function.encode( query, batch_size=int(embedding_batch_size), **({'prompt': prefix} if prefix else {}), ).tolist() - ), - query, - prefix, - ) + + return await asyncio.to_thread(encode) return async_embedding_function elif embedding_engine in ['ollama', 'openai', 'azure_openai']: @@ -1250,9 +1250,14 @@ def get_reranking_function(reranking_engine, reranking_model, reranking_function [(query, doc.page_content) for doc in documents], user=user ) else: - return lambda query, documents, user=None: reranking_function.predict( - [(query, doc.page_content) for doc in documents], batch_size=int(reranking_batch_size) - ) + + def predict(query, documents, user=None): + with MPS_INFERENCE_LOCK: + return reranking_function.predict( + [(query, doc.page_content) for doc in documents], batch_size=int(reranking_batch_size) + ) + + return predict # UUIDs, SHA-256 digests, and prefixed variants thereof all fit [A-Za-z0-9_-]. diff --git a/backend/open_webui/routers/evaluations.py b/backend/open_webui/routers/evaluations.py index 8a6bf71f4a..e1c7543eea 100644 --- a/backend/open_webui/routers/evaluations.py +++ b/backend/open_webui/routers/evaluations.py @@ -4,6 +4,7 @@ from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.concurrency import run_in_threadpool from open_webui.constants import ERROR_MESSAGES +from open_webui.env import MPS_INFERENCE_LOCK from open_webui.events import EVENTS, publish_event from open_webui.internal.db import get_async_session from open_webui.models.config import Config @@ -179,8 +180,9 @@ def _compute_similarities(feedbacks: list[LeaderboardFeedbackData], query: str) return {} try: - tag_embeddings = embedding_model.encode(all_tags) - query_embedding = embedding_model.encode([query])[0] + with MPS_INFERENCE_LOCK: + tag_embeddings = embedding_model.encode(all_tags) + query_embedding = embedding_model.encode([query])[0] except Exception as e: log.error(f'Embedding error: {e}') return {} From a3c90e5aacd60100bf20fe301a938f6d1ca61f4f Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:21:09 +0200 Subject: [PATCH 03/12] fix: blank chat messages on iOS home screen apps and in-app browsers (#29734) Chat messages are virtualized with content-visibility: auto, which WebKit paints incorrectly and can leave blank. #26805 skipped that on Safari by looking for the Safari token in the user agent, but iOS in-app browsers, home screen apps and iPadOS desktop-class standalone windows send no such token, so those users still get empty assistant responses. Check the navigator vendor string as well, which every WebKit surface reports regardless of user agent. The user agent check stays, because non-Apple WebKit ports can compile a different vendor string while shipping the same paint bug. That earlier fix also withheld the message-listitem class entirely, and the class doubles as the styling hook the sidebar hover preview reaches through, so hover previews have rendered at full chat spacing and width on Safari since v0.11.0. Only the content-visibility rule is gated now, on its own class, and the hook stays on every message. Safari hover previews become compact like every other engine. Verified across fourteen engine cases: virtualization is off on every Apple WebKit surface, unchanged on Chromium, Firefox and Android, the hover preview overrides apply again on Safari, and the screenshot export still captures every message on both. Refs #26712, #29688 --- src/lib/components/chat/Messages/Message.svelte | 12 ++++++++---- src/lib/components/layout/Navbar/Menu.svelte | 2 +- src/lib/components/layout/Sidebar/ChatMenu.svelte | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/components/chat/Messages/Message.svelte b/src/lib/components/chat/Messages/Message.svelte index 9d1bb0672c..97c8cd69a4 100644 --- a/src/lib/components/chat/Messages/Message.svelte +++ b/src/lib/components/chat/Messages/Message.svelte @@ -49,16 +49,20 @@ export let topPadding = false; export let onInsertToNote: ((content: string) => void) | null = null; - // Safari's content-visibility implementation has paint bugs that leave + // WebKit's content-visibility implementation has paint bugs that leave // on-screen messages blank (#26712), so skip virtualization there - const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + // iOS WebViews and home screen apps carry no Safari token, hence the vendor check + const isWebKit = + navigator.vendor === 'Apple Computer, Inc.' || + /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
{#if history.messages[messageId]} {#if history.messages[messageId].role === 'user'} @@ -154,7 +158,7 @@ /* Browser-native virtualization: skip rendering of off-screen messages without destroying their component trees. Replaces the JS-based culling that caused catastrophic mount/destroy thrashing. */ - .message-listitem { + .message-virtualized { content-visibility: auto; contain-intrinsic-size: auto 150px; } diff --git a/src/lib/components/layout/Navbar/Menu.svelte b/src/lib/components/layout/Navbar/Menu.svelte index f4f03052d2..d862580ab5 100644 --- a/src/lib/components/layout/Navbar/Menu.svelte +++ b/src/lib/components/layout/Navbar/Menu.svelte @@ -105,7 +105,7 @@ document.body.appendChild(clonedElement); // Override content-visibility so html2canvas can capture all messages - clonedElement.querySelectorAll('.message-listitem').forEach((el) => { + clonedElement.querySelectorAll('.message-virtualized').forEach((el) => { el.style.contentVisibility = 'visible'; }); diff --git a/src/lib/components/layout/Sidebar/ChatMenu.svelte b/src/lib/components/layout/Sidebar/ChatMenu.svelte index c591f46e07..17e8cc4403 100644 --- a/src/lib/components/layout/Sidebar/ChatMenu.svelte +++ b/src/lib/components/layout/Sidebar/ChatMenu.svelte @@ -120,7 +120,7 @@ document.body.appendChild(clonedElement); // Override content-visibility so html2canvas can capture all messages - clonedElement.querySelectorAll('.message-listitem').forEach((el) => { + clonedElement.querySelectorAll('.message-virtualized').forEach((el) => { el.style.contentVisibility = 'visible'; }); From ffe6ab9869f6dbcd43dd205cd62c79bcadb5d484 Mon Sep 17 00:00:00 2001 From: G30 <50341825+silentoplayz@users.noreply.github.com> Date: Sun, 6 Sep 2026 15:31:47 -0400 Subject: [PATCH 04/12] fix: stop the note editor date reading as a button (#29708) --- src/lib/components/notes/NoteEditor.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/notes/NoteEditor.svelte b/src/lib/components/notes/NoteEditor.svelte index 45296173fa..d3478eff18 100644 --- a/src/lib/components/notes/NoteEditor.svelte +++ b/src/lib/components/notes/NoteEditor.svelte @@ -1263,7 +1263,7 @@ ${content}
- +
{#if editor}
From 039c4d665e70a32b8ce14804153b59862f69449d Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:19:10 +0200 Subject: [PATCH 05/12] chore: drop python3-dev from the Docker image (#29731) The apt layer of the Docker image shrinks by about 60 MB. python3-dev installs Debian's own interpreter with its headers, and nothing in the image uses it. The image's Python is the /usr/local build from the base image, which ships its own headers, and it is also the interpreter that installs tool and function requirements at runtime, so Debian's headers were never on the include path of anything built in the container. The zlib headers python3-dev pulled in stay through libmariadb-dev, which also brings the OpenSSL headers, so the optional mariadb connector still builds; the only other header package that goes with it is libexpat1-dev, which nothing in the pinned tree builds against. Part of #29721. --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d70244583b..1e5b033853 100644 --- a/Dockerfile +++ b/Dockerfile @@ -128,7 +128,6 @@ RUN apt-get update && \ apt-get install -y --no-install-recommends \ git build-essential pandoc gcc curl jq ca-certificates \ libmariadb-dev \ - python3-dev \ ffmpeg libsm6 libxext6 zstd \ && rm -rf /var/lib/apt/lists/* From 39c1e86e9525b1ac4f32a570139711659c7e6aff Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:19:23 +0200 Subject: [PATCH 06/12] chore: keep OAuth token payloads out of logs (#29709) Two OAuth failure paths interpolated the raw token object into their log message. On the callback path that object is a live credential set, so a provider returning no user data wrote an access token, and usually a refresh token, straight into the application log. Both messages now log without the payload. The token-exchange failure keeps its error level and its client_id binding and reports the provider's error description instead of the raw response body, which by that branch's own condition never contained an access token anyway. The callback failure keeps its warning level and identifies the provider, matching the other failure logs in that handler. --- backend/open_webui/utils/oauth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 96505a6be0..81cdec4267 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -1263,7 +1263,7 @@ class OAuthClientManager: if token and not token.get('access_token'): error_desc = token.get('error_description', token.get('error', 'Unknown error')) error_message = f'Token exchange failed: {error_desc}' - log.error(f'Invalid token response for client_id {client_id}: {token}') + log.error('Invalid token response for client_id %s: %s', client_id, error_desc) token = None if token: @@ -1917,7 +1917,7 @@ class OAuthManager: if provider == 'feishu' and isinstance(user_data, dict) and 'data' in user_data: user_data = user_data['data'] if not user_data: - log.warning(f'OAuth callback failed, user data is missing: {token}') + log.warning('OAuth callback failed for provider %s, user data is missing', provider) raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) # Extract the "sub" claim, using custom claim if configured From 55a6198e267d7d53ba86c3f492a6af9af907f0b7 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:20:13 +0200 Subject: [PATCH 07/12] Gate the model profile image endpoint on model read access (#29700) Any authenticated user could fetch the profile image of a model they have no access to, and could tell an existing model id from an unknown one by whether the response carried the image or the default logo. The endpoint now serves an image only to callers who can see the model itself: the owner, an admin under the admin bypass, or the holder of a read grant, with the same rule applied to arena models defined in config. Everyone else gets the default logo, byte for byte the response an unknown id already returned, so ids can no longer be probed. BYPASS_MODEL_ACCESS_CONTROL is honoured here because it is what decides which models reach a user's model list to begin with. Avatars now fall back to the default logo wherever a viewer meets a model id without holding a grant on it: a model reply in a channel shown to the other members, and the admin analytics and evaluation pages when the admin bypass is switched off. --- backend/open_webui/models/models.py | 8 ++++--- backend/open_webui/routers/models.py | 33 +++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/backend/open_webui/models/models.py b/backend/open_webui/models/models.py index 5094276bf4..e435a492ba 100755 --- a/backend/open_webui/models/models.py +++ b/backend/open_webui/models/models.py @@ -449,11 +449,13 @@ class ModelsTable: return ModelListResponse(items=models, total=total) - async def get_model_meta_by_id(self, id: str, db: AsyncSession | None = None) -> tuple[dict, int | None]: - """Return (meta, updated_at) for a model, skipping access grant resolution.""" + async def get_model_meta_by_id( + self, id: str, db: AsyncSession | None = None + ) -> tuple[dict, str, int | None] | None: + """Return (meta, user_id, updated_at) for a model, skipping access grant resolution.""" try: async with get_async_db_context(db) as db: - result = await db.execute(select(Model.meta, Model.updated_at).filter_by(id=id)) + result = await db.execute(select(Model.meta, Model.user_id, Model.updated_at).filter_by(id=id)) return result.first() except Exception: return None diff --git a/backend/open_webui/routers/models.py b/backend/open_webui/routers/models.py index a0fd97705d..baa8bae604 100644 --- a/backend/open_webui/routers/models.py +++ b/backend/open_webui/routers/models.py @@ -19,7 +19,11 @@ from fastapi import ( from fastapi.responses import RedirectResponse, StreamingResponse from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL from open_webui.constants import ERROR_MESSAGES -from open_webui.env import ENABLE_PROFILE_IMAGE_URL_FORWARDING, PROFILE_IMAGE_ALLOWED_MIME_TYPES +from open_webui.env import ( + BYPASS_MODEL_ACCESS_CONTROL, + ENABLE_PROFILE_IMAGE_URL_FORWARDING, + PROFILE_IMAGE_ALLOWED_MIME_TYPES, +) from open_webui.events import EVENTS, publish_event from open_webui.internal.db import get_async_session from open_webui.models.access_grants import AccessGrants @@ -36,7 +40,7 @@ from open_webui.models.models import ( ModelResponse, Models, ) -from open_webui.utils.access_control import filter_allowed_access_grants, has_permission +from open_webui.utils.access_control import filter_allowed_access_grants, has_access, has_permission from open_webui.utils.access_control.files import has_access_to_file from open_webui.utils.auth import get_admin_user, get_verified_user from open_webui.utils.chat_variables import get_chat_variables_schema @@ -649,18 +653,37 @@ async def get_model_profile_image( profile_image_url = None updated_at = None + bypass_access_control = BYPASS_MODEL_ACCESS_CONTROL or (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL) + # First, check the database for regular models model_meta = await Models.get_model_meta_by_id(id, db=db) if model_meta: - meta, updated_at = model_meta - profile_image_url = (meta or {}).get('profile_image_url') + meta, model_user_id, model_updated_at = model_meta + # Denied callers get the default image rather than an error, so model ids stay unprobeable. + if ( + bypass_access_control + or user.id == model_user_id + or await AccessGrants.has_access( + user_id=user.id, + resource_type='model', + resource_id=id, + permission='read', + db=db, + ) + ): + profile_image_url = (meta or {}).get('profile_image_url') + updated_at = model_updated_at # Fallback: check arena models stored in config (not in the DB) if not profile_image_url: arena_models = await Config.get('evaluation.arena.models', []) or [] for arena_model in arena_models: if arena_model.get('id') == id: - profile_image_url = arena_model.get('meta', {}).get('profile_image_url') + arena_meta = arena_model.get('meta', {}) + if bypass_access_control or await has_access( + user.id, permission='read', access_grants=arena_meta.get('access_grants', []), db=db + ): + profile_image_url = arena_meta.get('profile_image_url') break if profile_image_url: From ca9ec06c7e65f795cc950ed497ca8b93dd3169c0 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:39:05 +0200 Subject: [PATCH 08/12] chore: drop nltk, unused at the pinned versions (#29725) The main and CUDA Docker images get about 21 MB smaller (the nltk package, the punkt_tab data and its zip); slim images, which never downloaded the data, about 6 MB. nltk was in the image for unstructured, which used it to tokenize documents. The Dockerfile download was added for airgapped containers failing on the missing punkt_tab data (#21150; the same request in #16260), the same lookup failed on first use in other setups (#17594, #4642), and the download in start.sh and start_windows.bat came with the Playwright web loader mode and sits in that branch. unstructured 0.22.31, the pinned version, has no nltk references at all and tokenizes with spaCy, nothing else installed requires nltk outside transformers' testing and dev extras, and nothing in the backend imports it, so the pin and both downloads go together. One user-visible consequence: a tool or function that imports nltk inside the container stops working unless it declares nltk in its frontmatter requirements. On an offline instance the package, and any nltk data such as punkt_tab, have to be installed into the image instead. Part of #29721. --- Dockerfile | 2 -- backend/requirements.txt | 1 - backend/start.sh | 1 - backend/start_windows.bat | 2 -- pyproject.toml | 1 - uv.lock | 17 ----------------- 6 files changed, 24 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1e5b033853..e5d7925e94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -148,7 +148,6 @@ RUN set -e; \ python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')"; \ python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \ python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \ - python -c "import nltk; nltk.download('punkt_tab', download_dir='/usr/local/share/nltk_data')"; \ else \ pip3 install 'torch<=2.9.1' torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu --no-cache-dir; \ uv pip install --system -r requirements.txt --no-cache-dir; \ @@ -157,7 +156,6 @@ RUN set -e; \ python -c "import os; from sentence_transformers import SentenceTransformer; SentenceTransformer(os.environ.get('AUXILIARY_EMBEDDING_MODEL', 'TaylorAI/bge-micro-v2'), device='cpu')"; \ python -c "import os; from faster_whisper import WhisperModel; WhisperModel(os.environ['WHISPER_MODEL'], device='cpu', compute_type='int8', download_root=os.environ['WHISPER_MODEL_DIR'])"; \ python -c "import os; import tiktoken; tiktoken.get_encoding(os.environ['TIKTOKEN_ENCODING_NAME'])"; \ - python -c "import nltk; nltk.download('punkt_tab', download_dir='/usr/local/share/nltk_data')"; \ fi; \ fi; \ mkdir -p /app/backend/data; chown -R $UID:$GID /app/backend/data/; \ diff --git a/backend/requirements.txt b/backend/requirements.txt index a8b091c37b..254425a988 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -78,7 +78,6 @@ python-pptx==1.0.2 msoffcrypto-tool==6.0.0 unstructured==0.22.31 -nltk==3.9.4 Markdown==3.10.2 beautifulsoup4==4.14.3 lxml==6.1.1 diff --git a/backend/start.sh b/backend/start.sh index 634f4bcc1b..b80e2fd67f 100755 --- a/backend/start.sh +++ b/backend/start.sh @@ -24,7 +24,6 @@ if [[ "${WEB_LOADER_ENGINE,,}" == "playwright" ]]; then playwright install chromium playwright install-deps chromium fi - python -c "import nltk; nltk.download('punkt_tab')" fi # ── Secret key setup ───────────────────────────────────────────────────────── diff --git a/backend/start_windows.bat b/backend/start_windows.bat index 4a57d34694..22aaaafc76 100644 --- a/backend/start_windows.bat +++ b/backend/start_windows.bat @@ -13,8 +13,6 @@ IF /I "%WEB_LOADER_ENGINE%" == "playwright" ( playwright install chromium playwright install-deps chromium ) - - python -c "import nltk; nltk.download('punkt_tab')" ) SET "KEY_FILE=.webui_secret_key" diff --git a/pyproject.toml b/pyproject.toml index 5b6b3e0749..ce1d71d436 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,6 @@ dependencies = [ "python-docx==1.2.0", "python-pptx==1.0.2", "msoffcrypto-tool==6.0.0", - "nltk==3.9.4", "Markdown==3.10.2", "beautifulsoup4==4.14.3", "lxml==6.1.1", diff --git a/uv.lock b/uv.lock index cff82b6167..62d9b8b266 100644 --- a/uv.lock +++ b/uv.lock @@ -2690,21 +2690,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/93/a7b983643d1253bb223234b5b226e69de6cda02b76cdca7770f684b795f5/ninja-1.13.0-py3-none-win_arm64.whl", hash = "sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9", size = 290806, upload-time = "2025-08-11T15:10:18.018Z" }, ] -[[package]] -name = "nltk" -version = "3.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "joblib" }, - { name = "regex" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864, upload-time = "2026-03-24T06:13:40.641Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" }, -] - [[package]] name = "numba" version = "0.65.1" @@ -3025,7 +3010,6 @@ dependencies = [ { name = "markdown" }, { name = "mcp" }, { name = "msoffcrypto-tool" }, - { name = "nltk" }, { name = "onnxruntime" }, { name = "openai" }, { name = "opencv-python-headless" }, @@ -3165,7 +3149,6 @@ requires-dist = [ { name = "markdown", specifier = "==3.10.2" }, { name = "mcp", specifier = "==1.27.2" }, { name = "msoffcrypto-tool", specifier = "==6.0.0" }, - { name = "nltk", specifier = "==3.9.4" }, { name = "onnxruntime", specifier = "==1.26.0" }, { name = "openai", specifier = "==2.29.0" }, { name = "opencv-python-headless", specifier = "==4.13.0.92" }, From e07e8ed0d4427a6ef3bbbd265a96da4b19ebd86e Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:39:24 +0200 Subject: [PATCH 09/12] chore: drop the Google Drive client and other unimported dependency pins (#29726) The Docker image and a fresh pip install get about 100 MB smaller uncompressed. The removed pins are ones nothing in the backend imports and that no installed package requires, with the one exception of async-timeout, which redis still requires below Python 3.11.3; the resolver installs it there as a transitive dependency, just no longer pinned to 5.0.1 for plain pip installs. google-api-python-client, google-auth-httplib2 and google-auth-oauthlib were added for Google Drive in 2024, but the picker is frontend-only and loads gapi from apis.google.com; together they are about 95 MB on disk. pymongo, the langchain meta package, pymdown-extensions, pytube, APScheduler and RestrictedPython have no importer; the YouTube loader, the scheduler and the tool sandbox are all hand-written in this repository. google-genai stays: nothing imports it either, but the tool-call path carries accommodations written for python-genai callers, so Gemini pipes are expected to find it in the shared environment. uv.lock is regenerated, deletions only. One user-visible consequence: a tool or function that imported one of the removed packages without declaring it in its frontmatter requirements has worked only because the package was preinstalled. Declaring it fixes that where frontmatter installs are enabled and the instance can reach PyPI; an offline instance needs the package installed into the image instead. Part of #29721. --- backend/requirements-min.txt | 5 - backend/requirements.txt | 12 -- pyproject.toml | 11 -- uv.lock | 283 ----------------------------------- 4 files changed, 311 deletions(-) diff --git a/backend/requirements-min.txt b/backend/requirements-min.txt index a7a19a2257..175aa9f904 100644 --- a/backend/requirements-min.txt +++ b/backend/requirements-min.txt @@ -18,7 +18,6 @@ joserfc==1.7.4 requests==2.34.2 aiohttp==3.13.5 # do not update to 3.13.3 - broken -async-timeout aiocache aiofiles starlette-compress==1.7.1 @@ -36,16 +35,12 @@ pycrdt==0.13.1 redis hiredis -APScheduler==3.11.2 -RestrictedPython==8.2 - loguru==0.7.3 asgiref==3.11.1 mcp==1.27.2 openai -langchain==1.2.10 langchain-community==0.4.2 langchain-classic==1.0.7 langchain-text-splitters==1.1.2 diff --git a/backend/requirements.txt b/backend/requirements.txt index 254425a988..306885cec6 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -17,7 +17,6 @@ requests==2.34.2 regex==2026.5.9 # supports a per-search timeout, which `re` does not aiohttp==3.13.5 # 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 -async-timeout==5.0.1 aiocache==0.12.3 aiofiles==25.1.0 starlette-compress==1.7.1 @@ -36,8 +35,6 @@ pycrdt==0.13.1 redis==8.0.1 hiredis==3.4.0 -APScheduler==3.11.2 -RestrictedPython==8.2 pytz==2026.2 loguru==0.7.3 @@ -51,7 +48,6 @@ openai==2.29.0 anthropic==0.86.0 google-genai==1.66.0 -langchain==1.2.10 langchain-community==0.4.2 langchain-classic==1.0.7 langchain-text-splitters==1.1.2 @@ -71,7 +67,6 @@ ftfy==6.3.1 chardet==7.4.3 pypdf==6.7.5 fpdf2==2.8.7 -pymdown-extensions==10.21.3 docx2txt==0.9 python-docx==1.2.0 python-pptx==1.0.2 @@ -101,7 +96,6 @@ faster-whisper==1.2.1 black==26.5.1 youtube-transcript-api==1.2.4 -pytube==15.0.0 pydub==0.25.1 ddgs==9.14.4 @@ -111,16 +105,10 @@ azure-identity==1.25.3 azure-storage-blob==12.29.0 azure-search-documents==12.0.0 -## Google Drive -google-api-python-client==2.197.0 -google-auth-httplib2==0.4.0 -google-auth-oauthlib==1.4.0 - googleapis-common-protos==1.75.0 google-cloud-storage==3.9.0 ## Databases -pymongo==4.17.0 psycopg2-binary==2.9.12 pgvector==0.4.2 diff --git a/pyproject.toml b/pyproject.toml index ce1d71d436..647128b4f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,6 @@ dependencies = [ "requests==2.34.2", "aiohttp==3.13.5", # 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 - "async-timeout==5.0.1", "aiocache==0.12.3", "aiofiles==25.1.0", "starlette-compress==1.7.1", @@ -45,8 +44,6 @@ dependencies = [ # "valkey-glide-sync==2.3.1", # optional: install manually if VECTOR_DB=valkey "pytz==2026.2", - "APScheduler==3.11.2", - "RestrictedPython==8.2", "loguru==0.7.3", "asgiref==3.11.1", @@ -59,7 +56,6 @@ dependencies = [ "anthropic==0.86.0", "google-genai==1.66.0", - "langchain==1.2.10", "langchain-community==0.4.2", "langchain-classic==1.0.7", "langchain-text-splitters==1.1.2", @@ -80,7 +76,6 @@ dependencies = [ "chardet==7.4.3", "pypdf==6.7.5", "fpdf2==2.8.7", - "pymdown-extensions==10.21.3", "docx2txt==0.9", "python-docx==1.2.0", "python-pptx==1.0.2", @@ -109,15 +104,10 @@ dependencies = [ "black==26.5.1", "youtube-transcript-api==1.2.4", - "pytube==15.0.0", "pydub==0.25.1", "ddgs==9.14.4", - "google-api-python-client==2.197.0", - "google-auth-httplib2==0.4.0", - "google-auth-oauthlib==1.4.0", - "googleapis-common-protos==1.75.0", "google-cloud-storage==3.9.0", @@ -152,7 +142,6 @@ unstructured = [ ] all = [ - "pymongo==4.17.0", "psycopg2-binary==2.9.12", "pgvector==0.4.2", "playwright==1.60.0", # Caution: version must match docker-compose.playwright.yaml - Update the docker-compose.yaml if necessary diff --git a/uv.lock b/uv.lock index 62d9b8b266..ffc5934945 100644 --- a/uv.lock +++ b/uv.lock @@ -210,18 +210,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" }, ] -[[package]] -name = "apscheduler" -version = "3.11.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tzlocal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/12/3e4389e5920b4c1763390c6d371162f3784f86f85cd6d6c1bfe68eef14e2/apscheduler-3.11.2.tar.gz", hash = "sha256:2a9966b052ec805f020c8c4c3ae6e6a06e24b1bf19f2e11d91d8cca0473eef41", size = 108683, upload-time = "2025-12-22T00:39:34.884Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/64/2e54428beba8d9992aa478bb8f6de9e4ecaa5f8f513bcfd567ed7fb0262d/apscheduler-3.11.2-py3-none-any.whl", hash = "sha256:ce005177f741409db4e4dd40a7431b76feb856b9dd69d57e0da49d6715bfd26d", size = 64439, upload-time = "2025-12-22T00:39:33.303Z" }, -] - [[package]] name = "argon2-cffi" version = "25.1.0" @@ -1108,15 +1096,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] -[[package]] -name = "dnspython" -version = "2.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, -] - [[package]] name = "docstring-parser" version = "0.18.0" @@ -1439,22 +1418,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/86/40/9bdbb60b03a332bd45acb8703da08bbc27d991d35286b62e42acc86d243a/google_api_core-2.31.0-py3-none-any.whl", hash = "sha256:ef79fb3784c71cbac89cbd03301ba0c8fb8ad2aa95d7f9204dd9628f7adf59ab", size = 173102, upload-time = "2026-06-03T14:51:26.729Z" }, ] -[[package]] -name = "google-api-python-client" -version = "2.197.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-api-core" }, - { name = "google-auth" }, - { name = "google-auth-httplib2" }, - { name = "httplib2" }, - { name = "uritemplate" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/22/09/081d66357118bd260f8f182cb1b2dd5bd32ca88e3714d7c93896cab946fc/google_api_python_client-2.197.0.tar.gz", hash = "sha256:32e03977eda4a66eafc6ae58dc9ec46426b6025636d5ef019c5703013eddd4e5", size = 14707398, upload-time = "2026-05-28T20:23:12.498Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e5/e9cc221fd75230974d4ef45eb72d2261feca3c110d5554215d516bfe6534/google_api_python_client-2.197.0-py3-none-any.whl", hash = "sha256:0f8b89aa75768161dd4f5092d6bcb386c13236b32e0d9a938c02f71342094d14", size = 15287302, upload-time = "2026-05-28T20:23:09.683Z" }, -] - [[package]] name = "google-auth" version = "2.55.0" @@ -1473,32 +1436,6 @@ requests = [ { name = "requests" }, ] -[[package]] -name = "google-auth-httplib2" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-auth" }, - { name = "httplib2" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1c/b3/f192c8bc7e41e0ebdbd95afcae4783417a34b6a6af62d22daf22c3fd38fc/google_auth_httplib2-0.4.0.tar.gz", hash = "sha256:d5b030a204b7a4b4d553ba9ca701b62481ee2b74419325580be70f7d85ffed35", size = 11161, upload-time = "2026-05-07T08:03:46.878Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/be/954c35a62b9e31de66b0a43c225c9b6bb9e0f98d6b1dc110a2308e3644f5/google_auth_httplib2-0.4.0-py3-none-any.whl", hash = "sha256:8e55cfafa3358cba85f6cad4a886138e88e158d71e7e5c9ee5936a5c1507fb91", size = 9529, upload-time = "2026-05-07T08:02:12.375Z" }, -] - -[[package]] -name = "google-auth-oauthlib" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google-auth" }, - { name = "requests-oauthlib" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/70/18/90c7fac516e63cf2058166fce0c88c353647c677b51cc036c09c49bb5cbb/google_auth_oauthlib-1.4.0.tar.gz", hash = "sha256:18b5e28880eb8eba9065c436becdc0ee8e4b59117a73a510679c82f70cd363d2", size = 21675, upload-time = "2026-05-07T08:03:47.816Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/d3/d7dff0d58a9e9244b48044bfb6a898bfcc8ecc42e0031d1bebc695344725/google_auth_oauthlib-1.4.0-py3-none-any.whl", hash = "sha256:251314f213a9ee46a5ae73988e84fd7cca8bb68e7ecf4bfd45940f9e7f51d070", size = 19261, upload-time = "2026-05-07T08:02:13.798Z" }, -] - [[package]] name = "google-cloud-core" version = "2.6.0" @@ -1762,18 +1699,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] -[[package]] -name = "httplib2" -version = "0.31.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyparsing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c1/1f/e86365613582c027dda5ddb64e1010e57a3d53e99ab8a72093fa13d565ec/httplib2-0.31.2.tar.gz", hash = "sha256:385e0869d7397484f4eab426197a4c020b606edd43372492337c0b4010ae5d24", size = 250800, upload-time = "2026-01-23T11:04:44.165Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/90/fd509079dfcab01102c0fdd87f3a9506894bc70afcf9e9785ef6b2b3aff6/httplib2-0.31.2-py3-none-any.whl", hash = "sha256:dbf0c2fa3862acf3c55c078ea9c0bc4481d7dc5117cae71be9514912cf9f8349", size = 91099, upload-time = "2026-01-23T11:04:42.78Z" }, -] - [[package]] name = "httptools" version = "0.8.0" @@ -2079,20 +2004,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/2c/5c160dbdef7123f8cc97fd8ece7e0198627a426a2a49614845e9086feb8d/kubernetes-36.0.2-py2.py3-none-any.whl", hash = "sha256:faf9b5241b58de0c4a5069f2a0ffc8ac06fece7215156cd3d3ba081a78a858b6", size = 4617568, upload-time = "2026-06-01T18:20:28.737Z" }, ] -[[package]] -name = "langchain" -version = "1.2.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "langchain-core" }, - { name = "langgraph" }, - { name = "pydantic" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/16/22/a4d4ac98fc2e393537130bbfba0d71a8113e6f884d96f935923e247397fe/langchain-1.2.10.tar.gz", hash = "sha256:bdcd7218d9c79a413cf15e106e4eb94408ac0963df9333ccd095b9ed43bf3be7", size = 570071, upload-time = "2026-02-10T14:56:49.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/06/c3394327f815fade875724c0f6cff529777c96a1e17fea066deb997f8cf5/langchain-1.2.10-py3-none-any.whl", hash = "sha256:e07a377204451fffaed88276b8193e894893b1003e25c5bca6539288ccca3698", size = 111738, upload-time = "2026-02-10T14:56:47.985Z" }, -] - [[package]] name = "langchain-classic" version = "1.0.7" @@ -2186,62 +2097,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474, upload-time = "2021-05-07T07:54:13.562Z" } -[[package]] -name = "langgraph" -version = "1.0.10" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "langchain-core" }, - { name = "langgraph-checkpoint" }, - { name = "langgraph-prebuilt" }, - { name = "langgraph-sdk" }, - { name = "pydantic" }, - { name = "xxhash" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/92/14df6fefba28c10caf1cb05aa5b8c7bf005838fe32a86d903b6c7cc4018d/langgraph-1.0.10.tar.gz", hash = "sha256:73bd10ee14a8020f31ef07e9cd4c1a70c35cc07b9c2b9cd637509a10d9d51e29", size = 511644, upload-time = "2026-02-27T21:04:38.743Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/60/260e0c04620a37ba8916b712766c341cc5fc685dabc6948c899494bbc2ae/langgraph-1.0.10-py3-none-any.whl", hash = "sha256:7c298bef4f6ea292fcf9824d6088fe41a6727e2904ad6066f240c4095af12247", size = 160920, upload-time = "2026-02-27T21:04:35.932Z" }, -] - -[[package]] -name = "langgraph-checkpoint" -version = "4.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "langchain-core" }, - { name = "ormsgpack" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/47/886af6f886f0bff2273164a45f008694e48a96ff3cd25ff0228f2aa9480e/langgraph_checkpoint-4.1.1.tar.gz", hash = "sha256:6c2bdb530c91f91d7d9c1bd100925d0fc4f498d418c17f3587d1526279482a25", size = 184020, upload-time = "2026-05-22T16:57:38.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/b4/71425e3e38be92611300b9cc5e46a5bf98ab23f5ea8a75b73d02a2f1413c/langgraph_checkpoint-4.1.1-py3-none-any.whl", hash = "sha256:25d29144b082827218e7bc3f1e9b0566a4bb007895cd6cc26f66a8428739f56e", size = 56212, upload-time = "2026-05-22T16:57:37.203Z" }, -] - -[[package]] -name = "langgraph-prebuilt" -version = "1.0.13" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "langchain-core" }, - { name = "langgraph-checkpoint" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b5/a4/f8ac75fa7c503103f0cf7680944e28bbaaef74c19a8d163d7346869cc369/langgraph_prebuilt-1.0.13.tar.gz", hash = "sha256:ad219782a80e1718e7e7794de49e0ae307111d45cbcffab9a52725a66a609456", size = 172913, upload-time = "2026-04-30T01:48:15.742Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/ef/5ada0bef4013ef5ae53a0ca1de5736517f1076a54d313f156ca545ec65d5/langgraph_prebuilt-1.0.13-py3-none-any.whl", hash = "sha256:7055e9fad41fbd3593800aed0aea0a6e974b17f33ed51b80d3d3a031212dd7c0", size = 37214, upload-time = "2026-04-30T01:48:14.507Z" }, -] - -[[package]] -name = "langgraph-sdk" -version = "0.3.15" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, - { name = "orjson" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/af/cdd4d6f3c05b3c1112ed3f12ef830faf15951b21d22cbc622a4becbbe25c/langgraph_sdk-0.3.15.tar.gz", hash = "sha256:29e805003d2c6e296823dd71992610976fd0428cefaa8b3304fd91f2247037de", size = 201924, upload-time = "2026-05-22T16:54:27.678Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/a5/0196d9c05749c25bc198e4909d68c998bc3120297e14944921baf2f4c384/langgraph_sdk-0.3.15-py3-none-any.whl", hash = "sha256:3838773acf7456d158165385d49f48f1e856f28b56ccd99ea139a8f27004815d", size = 98166, upload-time = "2026-05-22T16:54:26.013Z" }, -] - [[package]] name = "langsmith" version = "0.8.18" @@ -2965,10 +2820,8 @@ dependencies = [ { name = "aiosqlite" }, { name = "alembic" }, { name = "anthropic" }, - { name = "apscheduler" }, { name = "argon2-cffi" }, { name = "asgiref" }, - { name = "async-timeout" }, { name = "authlib" }, { name = "azure-ai-documentintelligence" }, { name = "azure-identity" }, @@ -2990,9 +2843,6 @@ dependencies = [ { name = "faster-whisper" }, { name = "fpdf2" }, { name = "ftfy" }, - { name = "google-api-python-client" }, - { name = "google-auth-httplib2" }, - { name = "google-auth-oauthlib" }, { name = "google-cloud-storage" }, { name = "google-genai" }, { name = "googleapis-common-protos" }, @@ -3000,7 +2850,6 @@ dependencies = [ { name = "httpx", extra = ["brotli", "cli", "http2", "socks", "zstd"] }, { name = "itsdangerous" }, { name = "joserfc" }, - { name = "langchain" }, { name = "langchain-classic" }, { name = "langchain-community" }, { name = "langchain-text-splitters" }, @@ -3025,7 +2874,6 @@ dependencies = [ { name = "pydantic" }, { name = "pydub" }, { name = "pyjwt", extra = ["crypto"] }, - { name = "pymdown-extensions" }, { name = "pymysql" }, { name = "pypandoc" }, { name = "pypdf" }, @@ -3034,7 +2882,6 @@ dependencies = [ { name = "python-multipart" }, { name = "python-pptx" }, { name = "python-socketio" }, - { name = "pytube" }, { name = "pytz" }, { name = "pyxlsb" }, { name = "rank-bm25" }, @@ -3042,7 +2889,6 @@ dependencies = [ { name = "redis" }, { name = "regex" }, { name = "requests" }, - { name = "restrictedpython" }, { name = "sentence-transformers" }, { name = "sentencepiece" }, { name = "soundfile" }, @@ -3068,7 +2914,6 @@ all = [ { name = "playwright" }, { name = "psycopg2-binary" }, { name = "pymilvus" }, - { name = "pymongo" }, { name = "qdrant-client" }, { name = "unstructured" }, { name = "weaviate-client" }, @@ -3100,10 +2945,8 @@ requires-dist = [ { name = "aiosqlite", specifier = "==0.22.1" }, { name = "alembic", specifier = "==1.18.4" }, { name = "anthropic", specifier = "==0.86.0" }, - { name = "apscheduler", specifier = "==3.11.2" }, { name = "argon2-cffi", specifier = "==25.1.0" }, { name = "asgiref", specifier = "==3.11.1" }, - { name = "async-timeout", specifier = "==5.0.1" }, { name = "authlib", specifier = "==1.7.2" }, { name = "azure-ai-documentintelligence", specifier = "==1.0.2" }, { name = "azure-identity", specifier = "==1.25.3" }, @@ -3128,9 +2971,6 @@ requires-dist = [ { name = "faster-whisper", specifier = "==1.2.1" }, { name = "fpdf2", specifier = "==2.8.7" }, { name = "ftfy", specifier = "==6.3.1" }, - { name = "google-api-python-client", specifier = "==2.197.0" }, - { name = "google-auth-httplib2", specifier = "==0.4.0" }, - { name = "google-auth-oauthlib", specifier = "==1.4.0" }, { name = "google-cloud-storage", specifier = "==3.9.0" }, { name = "google-genai", specifier = "==1.66.0" }, { name = "googleapis-common-protos", specifier = "==1.75.0" }, @@ -3138,7 +2978,6 @@ requires-dist = [ { name = "httpx", extras = ["brotli", "cli", "http2", "socks", "zstd"], specifier = "==0.28.1" }, { name = "itsdangerous", specifier = "==2.2.0" }, { name = "joserfc", specifier = "==1.7.4" }, - { name = "langchain", specifier = "==1.2.10" }, { name = "langchain-classic", specifier = "==1.0.7" }, { name = "langchain-community", specifier = "==0.4.2" }, { name = "langchain-text-splitters", specifier = "==1.1.2" }, @@ -3171,9 +3010,7 @@ requires-dist = [ { name = "pydantic", specifier = "==2.13.4" }, { name = "pydub", specifier = "==0.25.1" }, { name = "pyjwt", extras = ["crypto"], specifier = "==2.13.0" }, - { name = "pymdown-extensions", specifier = "==10.21.3" }, { name = "pymilvus", marker = "extra == 'all'", specifier = "==2.6.14" }, - { name = "pymongo", marker = "extra == 'all'", specifier = "==4.17.0" }, { name = "pymysql", specifier = "==1.2.0" }, { name = "pypandoc", specifier = "==1.17" }, { name = "pypdf", specifier = "==6.7.5" }, @@ -3182,7 +3019,6 @@ requires-dist = [ { name = "python-multipart", specifier = "==0.0.32" }, { name = "python-pptx", specifier = "==1.0.2" }, { name = "python-socketio", specifier = "==5.16.2" }, - { name = "pytube", specifier = "==15.0.0" }, { name = "pytz", specifier = "==2026.2" }, { name = "pyxlsb", specifier = "==1.0.10" }, { name = "qdrant-client", marker = "extra == 'all'", specifier = "==1.18.0" }, @@ -3191,7 +3027,6 @@ requires-dist = [ { name = "redis", specifier = "==8.0.1" }, { name = "regex", specifier = "==2026.5.9" }, { name = "requests", specifier = "==2.34.2" }, - { name = "restrictedpython", specifier = "==8.2" }, { name = "sentence-transformers", specifier = "==5.5.1" }, { name = "sentencepiece", specifier = "==0.2.1" }, { name = "soundfile", specifier = "==0.13.1" }, @@ -3453,32 +3288,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, ] -[[package]] -name = "ormsgpack" -version = "1.12.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/08/8b68f24b18e69d92238aa8f258218e6dfeacf4381d9d07ab8df303f524a9/ormsgpack-1.12.2-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bd5f4bf04c37888e864f08e740c5a573c4017f6fd6e99fa944c5c935fabf2dd9", size = 378266, upload-time = "2026-01-18T20:55:59.876Z" }, - { url = "https://files.pythonhosted.org/packages/0d/24/29fc13044ecb7c153523ae0a1972269fcd613650d1fa1a9cec1044c6b666/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d5b28b3570e9fed9a5a76528fc7230c3c76333bc214798958e58e9b79cc18a", size = 203035, upload-time = "2026-01-18T20:55:30.59Z" }, - { url = "https://files.pythonhosted.org/packages/ad/c2/00169fb25dd8f9213f5e8a549dfb73e4d592009ebc85fbbcd3e1dcac575b/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3708693412c28f3538fb5a65da93787b6bbab3484f6bc6e935bfb77a62400ae5", size = 210539, upload-time = "2026-01-18T20:55:48.569Z" }, - { url = "https://files.pythonhosted.org/packages/1b/33/543627f323ff3c73091f51d6a20db28a1a33531af30873ea90c5ac95a9b5/ormsgpack-1.12.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43013a3f3e2e902e1d05e72c0f1aeb5bedbb8e09240b51e26792a3c89267e181", size = 212401, upload-time = "2026-01-18T20:56:10.101Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5d/f70e2c3da414f46186659d24745483757bcc9adccb481a6eb93e2b729301/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7c8b1667a72cbba74f0ae7ecf3105a5e01304620ed14528b2cb4320679d2869b", size = 387082, upload-time = "2026-01-18T20:56:12.047Z" }, - { url = "https://files.pythonhosted.org/packages/c0/d6/06e8dc920c7903e051f30934d874d4afccc9bb1c09dcaf0bc03a7de4b343/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:df6961442140193e517303d0b5d7bc2e20e69a879c2d774316125350c4a76b92", size = 482346, upload-time = "2026-01-18T20:56:05.152Z" }, - { url = "https://files.pythonhosted.org/packages/66/c4/f337ac0905eed9c393ef990c54565cd33644918e0a8031fe48c098c71dbf/ormsgpack-1.12.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6a4c34ddef109647c769d69be65fa1de7a6022b02ad45546a69b3216573eb4a", size = 425181, upload-time = "2026-01-18T20:55:37.83Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/6d5758fabef3babdf4bbbc453738cc7de9cd3334e4c38dd5737e27b85653/ormsgpack-1.12.2-cp311-cp311-win_amd64.whl", hash = "sha256:73670ed0375ecc303858e3613f407628dd1fca18fe6ac57b7b7ce66cc7bb006c", size = 117182, upload-time = "2026-01-18T20:55:31.472Z" }, - { url = "https://files.pythonhosted.org/packages/c4/57/17a15549233c37e7fd054c48fe9207492e06b026dbd872b826a0b5f833b6/ormsgpack-1.12.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2be829954434e33601ae5da328cccce3266b098927ca7a30246a0baec2ce7bd", size = 111464, upload-time = "2026-01-18T20:55:38.811Z" }, - { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, - { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, - { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, - { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, - { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, - { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, - { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, -] - [[package]] name = "overrides" version = "7.7.0" @@ -4222,19 +4031,6 @@ crypto = [ { name = "cryptography" }, ] -[[package]] -name = "pymdown-extensions" -version = "10.21.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markdown" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" }, -] - [[package]] name = "pymilvus" version = "2.6.14" @@ -4254,37 +4050,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/0e/2807bf330edb41537caabf483fa4128e429b8fa5bcfa9bf67effcafa722e/pymilvus-2.6.14-py3-none-any.whl", hash = "sha256:01694978c2bbbf4bcc5ef29dc788e480245154a38eae32da7184959ea4b501fb", size = 340221, upload-time = "2026-05-15T14:11:45.052Z" }, ] -[[package]] -name = "pymongo" -version = "4.17.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dnspython" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/64/50be6fbac9c79fe2e4c17401a467da2d8764d82833d83cec325afe5cab32/pymongo-4.17.0.tar.gz", hash = "sha256:70ffa08ba641468cc068cf46c06b34f01a8ce3489f6411309fcb5ceabe6b2fc0", size = 2523370, upload-time = "2026-04-20T16:39:53.524Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/e2/336d86f221cf1b56b2ed9330d4a3b98f9f38f0b37829ae9a9184617d5419/pymongo-4.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4141e6c6a339789b2974efa00ecd9409101672d77a0e3ee2cc3839eedf8ec4df", size = 874668, upload-time = "2026-04-20T16:37:41.39Z" }, - { url = "https://files.pythonhosted.org/packages/34/8e/75d3c6c935d187ab59c61e9c15d9aab3f274b563eaf1706e8cae5f508dec/pymongo-4.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e68c76b84e0c132d9dbf9307f12ff8185702328187a87b9aca8c941303873433", size = 875294, upload-time = "2026-04-20T16:37:43.432Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ec/62e855744489dbcd54fd778aae4d80fa4c4819e8fb228ca0cf6f21a03997/pymongo-4.17.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba2195d4f386f839a52a23ea1cfd60ffaaba78a3d7841db51b7e433001139918", size = 1496233, upload-time = "2026-04-20T16:37:45.518Z" }, - { url = "https://files.pythonhosted.org/packages/82/e8/93e4e5e5ce8fdf8929dabeefe24aafa5ce046028eed0dfa8eeb936e72c49/pymongo-4.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446ff4bfcb6ec2a2e50998c860986a1e992136f998b7f53e7a717fb8aa5a0b9", size = 1522927, upload-time = "2026-04-20T16:37:47.492Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/425dc1d21e0f17bdea0072fc463f662f7fa06d2852af52975c9eced3c07c/pymongo-4.17.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2a0d5ac205728c86e0a02192f1aa5f865b0d7d51f8df6101c01a69a7fc620d72", size = 1583468, upload-time = "2026-04-20T16:37:49.221Z" }, - { url = "https://files.pythonhosted.org/packages/b3/9d/f08b07eeffda1a43c1759f0fa625e88ae12360996eb56d42aad832fa7dff/pymongo-4.17.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:485c8a8eaa4c739f00a331fc73757898ee7c092c214a79e63866ff76aaf282ff", size = 1572787, upload-time = "2026-04-20T16:37:51.061Z" }, - { url = "https://files.pythonhosted.org/packages/e9/c2/6855a07aafa7b894929af23675b6fb9634800ce43122b76a62f6eeb8da2a/pymongo-4.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b2dfcc795f5b9fedbe179a11fdf6051581479d196582a3fe819a92a00e9b9969", size = 1526184, upload-time = "2026-04-20T16:37:53.358Z" }, - { url = "https://files.pythonhosted.org/packages/4e/05/c952bac7db71c1942ea3559fcd308b49754cc5004b455935fb4000d1f37b/pymongo-4.17.0-cp311-cp311-win32.whl", hash = "sha256:c2292144505fb12156b981bd440f3dc994a883da06ac726c0c8692ccdbc1c510", size = 852621, upload-time = "2026-04-20T16:37:55.28Z" }, - { url = "https://files.pythonhosted.org/packages/11/c0/c04da9f4c0c6252404598f4e394b862a58a9e866822a70ae261c8a018fdf/pymongo-4.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:2e190827834fce70ecdf9d46796c6dbc0ce08ea87dc2ff5bc6f3f5579b605cb9", size = 867852, upload-time = "2026-04-20T16:37:57.233Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b2/c7b4870fbeef471e947d3e014676f5910d02e0197074d692ebcf24ec049a/pymongo-4.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:a8f9c40a09bb7d4b9fc8b1da65ecf6efa79bda5cb2756f39d9b6940fac1d19ae", size = 855019, upload-time = "2026-04-20T16:37:58.983Z" }, - { url = "https://files.pythonhosted.org/packages/98/90/60bcb508840135d5ee46b51b1a950f548338aa8145a8366dbe6639ae51ac/pymongo-4.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53ffa94b2340dbf6b055e09a0090618c60482c158ecfc9565642fc996bf0944", size = 930529, upload-time = "2026-04-20T16:38:00.936Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e9/313840f1e52c6dfac47f704428cbfbce59956ebe7633bffc92b03f74f0ad/pymongo-4.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6fe0de9d0f6791abce3471230b32b4817bf89d27b1182b6a550e1ec0fa72aa9a", size = 930665, upload-time = "2026-04-20T16:38:02.915Z" }, - { url = "https://files.pythonhosted.org/packages/78/35/9d3565ea45b1606f635c1e2cd2563c28d66caafdc50f7ad7d979fcd1b363/pymongo-4.17.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e537e95514dae1aaa718f481ec03151a0f0394bcd05f1322896d8fc1330cb729", size = 1762369, upload-time = "2026-04-20T16:38:05.375Z" }, - { url = "https://files.pythonhosted.org/packages/95/ee/149b0d4b1a11c38bff6f14c23d5814c9b0843fd6dc38ad40596bdb1a62d2/pymongo-4.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37a8385c29881b43eab31f584100fa0eaddedd5607adf010147ba1810118be90", size = 1798044, upload-time = "2026-04-20T16:38:07.195Z" }, - { url = "https://files.pythonhosted.org/packages/7b/d4/4cee4a7b8d8f6f0550ef6cd2fea42455c5ed619a220cb6ba4fb40d6a5bc8/pymongo-4.17.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3ee3d241ed77a4fc99ce3cff3b289c3ebce37f61fdd7349d3592c23b82c8784", size = 1878567, upload-time = "2026-04-20T16:38:09.121Z" }, - { url = "https://files.pythonhosted.org/packages/45/ef/7fe366c84952619ee2f69973566c214775e083dd4df465751912153e4b72/pymongo-4.17.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9eb5d63a3c518cb0804ed678f5e2b875af032d89a7cf57a57360322cf6a4d222", size = 1864881, upload-time = "2026-04-20T16:38:10.896Z" }, - { url = "https://files.pythonhosted.org/packages/2f/35/b577d82c6d1be7aee7ac7e249bc86f7847998345042e5f8360de238e177b/pymongo-4.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e97e03fa13327c87e3fdc5656acd01e71817f0c1dc3221cd8f30de136bf4ec3", size = 1800349, upload-time = "2026-04-20T16:38:13.589Z" }, - { url = "https://files.pythonhosted.org/packages/b8/69/dafcf04f66e130ddd91aeb92e7a692480eda46dcd04ec1dbe82c06619e10/pymongo-4.17.0-cp312-cp312-win32.whl", hash = "sha256:6877214bff5f06f6884a9fc8d9016a4a7a5f51f537f5c51ac3a576f93e7dfb32", size = 900518, upload-time = "2026-04-20T16:38:15.541Z" }, - { url = "https://files.pythonhosted.org/packages/11/35/5c9262a459f988b4eb2605f70815240b77a0d4131136c4326d18f1822b89/pymongo-4.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:9828485f72f63c7d802e0ec41f71906f633c2692621ab3af55ca990186b091b1", size = 920335, upload-time = "2026-04-20T16:38:17.665Z" }, - { url = "https://files.pythonhosted.org/packages/8d/da/e9c7265ee176faccf4e52c4797837e794d93569a1046f6b19a4acc36e5ad/pymongo-4.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:1195370a77baf003b59b10e91ecc4706297197f0dd9d29c840cc556dc08f7cee", size = 903289, upload-time = "2026-04-20T16:38:19.33Z" }, -] - [[package]] name = "pymysql" version = "1.2.0" @@ -4303,15 +4068,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/86/e2ffa604eacfbec3f430b1d850e7e04c4101eca1a5828f9ae54bf51dfba4/pypandoc-1.17-py3-none-any.whl", hash = "sha256:01fdbffa61edb9f8e82e8faad6954efcb7b6f8f0634aead4d89e322a00225a67", size = 23554, upload-time = "2026-03-14T22:38:46.007Z" }, ] -[[package]] -name = "pyparsing" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, -] - [[package]] name = "pypdf" version = "6.7.5" @@ -4540,15 +4296,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, ] -[[package]] -name = "pytube" -version = "15.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/e7/16fec46c8d255c4bbc4b185d89c91dc92cdb802836570d8004d0db169c91/pytube-15.0.0.tar.gz", hash = "sha256:076052efe76f390dfa24b1194ff821d4e86c17d41cb5562f3a276a8bcbfc9d1d", size = 67229, upload-time = "2023-05-07T19:39:01.903Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/64/bcf8632ed2b7a36bbf84a0544885ffa1d0b4bcf25cc0903dba66ec5fdad9/pytube-15.0.0-py3-none-any.whl", hash = "sha256:07b9904749e213485780d7eb606e5e5b8e4341aa4dccf699160876da00e12d78", size = 57594, upload-time = "2023-05-07T19:38:59.191Z" }, -] - [[package]] name = "pytz" version = "2026.2" @@ -4799,15 +4546,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] -[[package]] -name = "restrictedpython" -version = "8.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/97/04/7314639eab9edd57b5c8f6e158a558f5a5a65453c37a51fb8f9ecc1d112e/restrictedpython-8.2.tar.gz", hash = "sha256:b835c2ff87d71b76f6d8b129096cc5a1e893bf01839ab802ba47a4129f1bdfe4", size = 449323, upload-time = "2026-05-29T06:27:57.285Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/59/b2778bfafcc37fdfd87c02702bbfc5753c58c28c7eb82999b7b49ec6e0e7/restrictedpython-8.2-py3-none-any.whl", hash = "sha256:6ff69e2fe06674bfe88943f17f2ce8353a9bc743ee37ea7bec38ff8b4851afd1", size = 27176, upload-time = "2026-05-29T06:27:55.651Z" }, -] - [[package]] name = "rich" version = "13.9.4" @@ -5619,18 +5357,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, ] -[[package]] -name = "tzlocal" -version = "5.4.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tzdata", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/55/15e2340963d2bfedcc6042da3911438fd336f8ae96b65bdbe3a29766da0c/tzlocal-5.4.3.tar.gz", hash = "sha256:3a8c9bc18cf47e1dcde252ea0e6a72a6cde320a400b6ac6db1f1f8cccd553c00", size = 30873, upload-time = "2026-06-17T04:17:41.764Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/28/fc144409c71569e928585f8f3c629d80d1ca3ef40175e9222f01588f98c9/tzlocal-5.4.3-py3-none-any.whl", hash = "sha256:24ce97bb58e2a973f7640ec2553ab4e6f6d5a0d0d1aa9dc43bca21d89e1feb82", size = 18039, upload-time = "2026-06-17T04:17:40.027Z" }, -] - [[package]] name = "ujson" version = "5.13.0" @@ -5732,15 +5458,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/21/80/fbf02ec3c566a3e383a5649385096834a2a981832f1432c3a8797b29185a/unstructured_client-0.42.12-py3-none-any.whl", hash = "sha256:fe6f217066a0c308ba7213185524506dbfc3bb9d35df0ab79549291e9728a012", size = 220154, upload-time = "2026-03-25T20:24:20.288Z" }, ] -[[package]] -name = "uritemplate" -version = "4.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, -] - [[package]] name = "urllib3" version = "2.7.0" From 08ca3d859fadc3adfc972c9a597b406797dbc07b Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:39:36 +0200 Subject: [PATCH 10/12] chore: drop the unused Noto Sans variable fonts (#29723) The four Noto Sans variable fonts under open_webui/static/fonts have never been loaded. Removing them makes an installed package 40 MB smaller on disk, the wheel about 24 MB, and the Docker image about 80 MB, because the image currently stores the static directory twice. The PDF generator registers only the static faces through add_font, and the stylesheet that names the variable fonts, pdf-style.css, is read into a variable that nothing ever uses, so no code path can reach them. The frontend never fetches these files either. Only the four @font-face blocks that pointed at the deleted files are removed; the rest of the stylesheet, font stack included, is left exactly as it is. The files were reachable under the public /static mount, so anything outside this repository that hotlinked them gets a 404 from now on. Part of #29721. --- .../open_webui/static/assets/pdf-style.css | 20 ------------------ .../static/fonts/NotoSans-Variable.ttf | Bin 2490816 -> 0 bytes .../static/fonts/NotoSansJP-Variable.ttf | Bin 9586480 -> 0 bytes .../static/fonts/NotoSansKR-Variable.ttf | Bin 10361060 -> 0 bytes .../static/fonts/NotoSansSC-Variable.ttf | Bin 17805476 -> 0 bytes 5 files changed, 20 deletions(-) delete mode 100644 backend/open_webui/static/fonts/NotoSans-Variable.ttf delete mode 100644 backend/open_webui/static/fonts/NotoSansJP-Variable.ttf delete mode 100644 backend/open_webui/static/fonts/NotoSansKR-Variable.ttf delete mode 100644 backend/open_webui/static/fonts/NotoSansSC-Variable.ttf diff --git a/backend/open_webui/static/assets/pdf-style.css b/backend/open_webui/static/assets/pdf-style.css index 644dd58ae6..68c10b9d1a 100644 --- a/backend/open_webui/static/assets/pdf-style.css +++ b/backend/open_webui/static/assets/pdf-style.css @@ -1,24 +1,4 @@ /* HTML and Body */ -@font-face { - font-family: 'NotoSans'; - src: url('fonts/NotoSans-Variable.ttf'); -} - -@font-face { - font-family: 'NotoSansJP'; - src: url('fonts/NotoSansJP-Variable.ttf'); -} - -@font-face { - font-family: 'NotoSansKR'; - src: url('fonts/NotoSansKR-Variable.ttf'); -} - -@font-face { - font-family: 'NotoSansSC'; - src: url('fonts/NotoSansSC-Variable.ttf'); -} - @font-face { font-family: 'NotoSansSC-Regular'; src: url('fonts/NotoSansSC-Regular.ttf'); diff --git a/backend/open_webui/static/fonts/NotoSans-Variable.ttf b/backend/open_webui/static/fonts/NotoSans-Variable.ttf deleted file mode 100644 index f7d0d78ed3a48ef9c16ae6de52cc2e55809f2c61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2490816 zcmeFacbpW(+P7cbJ=lK2nNi0)FbAc zj#&&CQ3NHK2s^*)?wWxW^*QHxKfm+W`>vnMZ+CTdg}d&muBz@`l%bR=!~a5RK=&?P zyY=uE-mQ#=4V5}?NcRE#2j`mSZcj8sbbs4CgQ zD51)$b;^~qB0nLCMauOHcPSMqf8xqxE5lg2NDK~3<_00AHKfi}Kdv3`xA|FFme@{x zy6l}jqF=PVvD}F(3(pE42-W4(R%SX4osy}%ooc6gZ|Li4@AlntgNnW$SykbN@`Gwj ztr>3IICotK)j_E&9qXOOd$)ggvBU~OLuPwpy~?6JFEK|D*juYHPURY%2#sW!p{$T; zhIT76)j=iVG<|JpukQW(cPCqwO6mHACL5PxmW`6KS>AjFNsz8Cqf419EnKuYS9a_ehZf{3RB1I zuu4e+Ql{cuGgWEjsp#VTX^Rvdf*q8#GKdgbJbBSH)oz)R@Bn3G8IorD+`xm?OZwcv zBu2gTv8U4s(hGBZBpn{XDq>&BC(1G2791B#_XG)#rSq3O#B*FquP7tos-7~XoJL3~ zDVu?1r!UHzWt5^8l~gU&1QQKVW7IUYNG(%q)phE2wL?7-)KyV(ou(x01mzCe#c+di zODK~L#Uke9i*T<2d?D{xbBu+6aAxQ$I4{R5Byx|A7)5pbC?;sAb=qJOMNqkzy zC>fN=3UV$jDd==mb>kW8ZG^C-dN`|1`q)qjpv(UHcVz`sc&e6lBpn`B6RfM!#}U=e8kRnesy0@|^l>qx zit$_eIHsDNEOP}_+_+zdQ{jrnb?IY6on=%>ADgNQ*Md44ma3@I<)>|<>f>~HmI|x4 z(#JNQ@KXBN!5AG^d!*U|7!*~AEw&MoOU5j_3!^l_AQE=nI4Qzh{fU4|Ix%t#;m zcvHXhadG0L?N7^9RGIRZB>l4KIHfoar;kf>q|_iaSIt&=YL4nj4tZ*pnyx01=3KQP z2%n_}t0^2!SDgu)gBMKU{|ov5`tVeN7^&hsaTyza~3IOG?Q26 zyl;@tbaf^r=tCLugC2A0{!?K?xH6rR4d?%pDZ@05rkuQEI{8TL5-^$4Poj*|)jYyF zay%)h)1n}+gzC<@tV^6WlS2^C`rdLuzugkkFn%+G3xC>IG8+ z3nsY!$GgSS5=SgO3!TK#JX9>YD2Oe&Ead)KK?+hQxq3Dhm>5_~+GY_9=1yq|8ABG4 z#zNIfHQ>LZwOgo?oKGjMWRO}u*ZZq(*izd|O5Tktb4WpIJC}SHsl}u$Hb`)%#Lzx* zs@)T`hxUX8ToqeO+`-tO589uyNZg_+OFL;ROFipxZ5X;BD4XP-P>m>|kSJ2JKjxb$ zO`bZ7@=2ZYu*j?+@5P);ImE7#Z$~vSI9`NJ690!5%Ggy~bum_1O!;O9){+)DJE&)p zdPxrwyD!2nlPSsElR5tJ-h8girFEsur|!$=Y!Ua*;hMB-1J#8PX|;U9q@N|I=h^(f zFIROeop*w3r(#M=Orid>1N-(2T2Ss6TkD=Tjr-3e=Rc=YG)HLxoz9=*^(6H?$|YVY zeW52hi4-$Fd8&=1G%^lI3FV#zL4Okek{))-4hejw554SE8X3Q?&)77DT+R$izu-?E zlgm8~@tMX&a?jkGIPKn3aa)snfBx&PTA#cxbJyuP{xSbkbW3$BV&mknG!0O zGUf80_G7XBsrv>Hb1q}|6l^B_ts`|4ua&V@`dO`$ev|3TVrhwSCLVCgW3&&5Ep^Xb zh|fq1%UCl<)u$|(5v)Er>GH{FBYm(oV?MJIo&l=Pn)Gosc_IYoR`5)y##4fyIx-94 z$6Bp5_&@O!v!DQ(xh3ENkXgwlCIt=AiD)B@{T-SMd4wNE<=P~o_eV+JSPIo)8C*g5 zK$P`IKu7dSV3ulek0I%AX4WI&x57Py&qMEp9fWsA?}JYWKOg;6V}FZ&20sygCwf3* zvTiaC!taDXjvfW3oJJ>^U_m*;pF_(-1;Trv72ynytD=>l8F9$ZY_74tM_FH)BMJW% z9i_2Z8-(Cw6)x?6@);b|y_5W+K2E?8L{ zvo5fhT?^saD6?xJoR7Mi@Kn^(glHR!xrPv)gEG4o!ZT1jO$g6IWp<4(gcqPSG^S`x zpv}XVqMe{C>$4_kHyBR%7IcIrj14TY{Y1haMJH(@L($2a2)4JTXd-#&R82(cD|LW~ zlvB>9b4(jrQdWpeK*bgik^7|F5RvO*3y6ry{n#lYwwkAL=?B()joT8HdV<>+U8r$e zp^M;5rDBrySsGXByIAAm8`e3Rn3VBcIG=UoZuA08Ov<=a6Rm-+giG*;!RT6zA@#Z( zHWNM?y$zn`Sjzm2Ci*V=EWAOu#C=oaHbvipx5*cuxBdmhjn+fogZDZ90mUDM=yxbS zDMY0`Khi|MLO<3-YoWU|QTm1SDX=MC+jVvk?6jmHI=pF8YNgx(7V~ z_S_;40&AUM=73%d)h zgkfvJq0U*@S8(Mz?JYRiAgjK{mAFZbQ;1?~!Q~zg`v`t>6#EE1eZey|K52NC#@~oy zP{AiXFH7T-uV-ug>Zk)QafYBIF8J8M3v2xA(1^yzb{=ga_>)lDMew_zw2R>PKz)r* zdwIn*KK1fSX#6>7NsYf4#m0g^55>-cPo2FoP?lr*msbwTbKD=L9Rzty=t7!aAXjP3r1FZ(w$-fw_q4C?Gw7KASMr&#O zd{pcU{!CQrOuPB#ptQ4BpJV#IC+z|L6jbU7{wB1c#y=lzr16)cxzL0-<50OD{F~8c z8vi0xd;0 zhl+23KNcOO@g<+p8vj;wjK&u`Nj&f`K&36gr*7VOjlUV4pz)WXc^Y4IqQ;+$PSW@i zCg=E(FYPu}?vTjSr3 zo}=-l4bRp1;tSF?;7ePbuko)%#qQvXFH64yzll=Ck7;7qt>O;&C-a{V(5GN0&xo7Q z=QXkI=!@_MbBw!`szf_gqR&OJZMK5yJQp59=`-2bBl-`N`U}w~(b}3QX=m5bM9D9^ zE{x^gUFbMXEFT@O$%>&9G%;+RjZb9L?pZ#HU4$6^kS+H>48O>prisaQxd&q0lTEt| zG5jc-cF(3P@_fstu0r@V^l44_RrDEnmhkt`=QQEBQEVcF-$Y-~gx^75)P%Vw`z1{n zdu6|@2}^pfXd+TB$pgZYhU5+5f1$5y!lH6Ngx^IaKZw*o-_nFdrA*uxsfJ29AR@Mu zduY!H{W<$XO+;+Z5u1OjiOfg$!0*&W!j8cSjycaRgcQfvI7flO+@l?8LR;DhTjj`kd&1kG z9bhTpcc9B)74w$g(2L+k!pT1e-_5z1`N=-?7L9!#x=~|O?wn0Y02C}kGxz36d`eE~{61g8Zm_k&IOa#9+nrBZR~6?fIt9jwS9QU|fsR z$Ks4>@x>f3N0$KQjjltnWBgo>Z$xR2`1u@*9hSjrjt8P^fVh!I(e-dG;gaWdu#xb0 z&`nT4_>Z zagHU8Co~a>`=ll&Hu$F|iZ8{V(!`|9J2kOO(Wf;r{5AfJCOQXwRui3#(g%d-T=WG^ zbQb!eCMvdk30|f=a-H!c&bSeI3uU|zVq$y73?YWy<8NwW*g7upuwQf~`o1Q*0{uV} z72AIZACV^gDE=|*;`l4{Q%!6!`k5wnF8aA9wiMkB#Ess9ehs7%y#l4r3(=d=J(}pP z=y&iv>5JXySMeV>rvJt1w?gD6biXDhzVxFeCS{gu5W{cc;=2&N7d@=FkbL8@RMbqNBzYJ9pB! zKcf;4WDS-O8-aHJ& z-IeICk#$aDfX2l~5(71|R!9uecsHPfHL`|D4AIE?FELajYtFDY5MIs@k6S5k)g8s~L%5u8a|h&|8J*i!e!u!L~2;n^Bn`o}pM z?>6*YjkgIsPvhN;o(~t0uasq}#w?3UeJ-ZGUPCX@c(Y_aR(8gB#o1UyN6snb8SKOxOW(ut8sgxjWzBFw28*eN1JNAYtd#J z?|QVk#=8Y=q491+TWY-f(N-F78`@gq?Leh`;7&r@YTSuvJB>RSmG%I)J1Vy5gnjNn zrJms3j*6dww-ps10(TH9enXwyaj4h`yhqU98t*z(d=WhQWM%Oo{K6JnGUf~3A8ncDbo<*h+U-(MiIUb9S!7TiZ6`UxO>qRu#)&aS_+Ts{Vx z0JDitJJg$_vCl>4YV7mTc^Z2eIv*AjzazRtV}FI7t+5ZH=V_Aj8rhFbx*FNjO?n#H2Tg`GvR{~tXk?!<8P&+XX|k9`_8F5gjqCv@eU0pmCW~ui zuQypjBYT`l+Cq?h+@zEhWUo2N7$eA@a#HdD*$+!fJwf)NlTtpg8AFogHL~xUWSkLX zUp84$Bl}g!Gc>YanUrfF`@cykAIP3*G6&)u)6bF#jqC*{D{Ex0G%5DNezK>Q6kAm1 zcnm7_kuhW<${3QY&G7_O+6iR8GAZo`vVWYE_5crmOG@4#d*n&6BgkHFQrZ&SZK$*X z$R2G{$^o(`n3QvnebuC-1+w3qY@+d`9!)i}&zh9FH7CtNRBQpV7n^LUk-ghw8rio? zw${jgUQ+UCPkj1$vV%tU^O7AkvX`2adV=iJB|AfZj(ed4U=V(O3pyCavW|F2sV$@< zIPasRBe?kC7Sa%$4^Yw(T>N267mf2FN;w2K3+<+HK0+C@1lLCK55f5urF?>mowraX z!P$lO*0?U(N8@~g_SLu^+E3$ric%-R4Wraca6Ur^YTO7)T?OZJl==#86dj^*cB4Zz zZZUM2#`yvru5sy4TSjP{FHzcCaD8-?#`y{zt#ON^_@dx^jbdZLErE{HINzY-HEv0C zg2wq4&C|H0(1{vn4?0QX(qFbr);QmxQ#3C9bIVkX^F5lcam%99G|pahy2hp7ZkeHR zen9ad!7Y!@(m4Cj*&4S3I!EKuH@3`$1^7~1bfHG}&9^Lq^9dI}xl|8O6a_Aa38 zc2AVD3N|(_pnQUjO$sQJU}NtB$|2a;q=38y8`~F_W7a#-^PMT5IeS+D2p3<^^pvj@YuD#-{xX+G`vG?Vz#o zg@TS6$3#16Z2Y32v&ON|E*cvjDd?(kLTERQjlUFh*Em^d4~>oQ6iEAlW23z^Hhxqf z?F|kEEs*vG8=orZt8rYkpT@?&3i@jt4;`Sf@wI}18Yhen(%AT2!C;LOL5FB;e6V1s z#)+cCG&cTNFkIslLq}+Ae6(Ps#)+Y$G&Vk4Fk0jI=opQS9~X?(IK|O%8haBuUgMNN zCur==Xr9I?iB5z`^b2W|$r}4sbc)6)g-+Gjx1sqOr!+cEW8aQW*EnU+85;Wzbf(5B zi_X&6ThQ4WryM#*V;7)vHBNbSp2n5*=fe`(t^#_t#@>paqj4&u=W6Ub(epG;74&?# zfVSR-F4Z_y(PbL@E_AuZsfJ#tvF}D#Xq@WkN{xLFx=Q2JKrhnR_oAycPEB+TtR-LZ zh08Sd4)k)3Qwv?Eao#}JYwY{bD>P1RRQd+k_oLDuz^Q{uAHd&TvAy^=xZ*qF)8LBj z#gD-iKe}GyN;`RPF(%5i0SXV$5HF?u4hA*NHv@FAy$%^dh{%vDD)&c$+wK{T)s0IrKgFi16pp zLvVy+?kV^ge&JrJ)2|xmP4qX7E8)Lu+cj@i1IsFpXU%>nopvJ z5NU=%<<$&|TTESGG&C9+dB!ATsd0s|!PsbQHtsQY7@ruQ8b28ajNgrvSA8)zunqjj$$Ki>;;BCDvMNv-Py~jP-`~ zg>^W@H(8-#q5h!(p)sNHp$kK+L+e6UWR=Rw&Z?Q!z*}1USjB%<+*$GYif>d}lU+T# zPWIUB8QD)|f1CY#j*-(gr+v=SoK-nD=4{M4n3IY-aX(%ro*l0lZxC-DZy)a(?;9T; z9~mDV9~&PZ&yUZIpC4ZxUl+eNzA;`9zc>D1{JHp>@pt08;y=ZY#0wLVMA<}6Vsc`7 zVt!(2;^M^RiK`MfCGJW*l-QX#PAImXw z+U0b}S(bBA&P_R+at_6L*BUP#FCS0DYsDMJTf{rWd&CEvwDpAewD`REviO?#`uKJ6 zP4TVq9r1_bFT~%9zaRfJeklG+g7-U#a*23iYGOuWQDRx*lEnJN)rn1syAuy%Yj$7o1(Nt>B@8M+ctc%fyq4t|uc}_g zE2$m^qm;2TbieTw`b27}@i@xpXWWnC3G zN}hO?k%XRk;`+jtC$2m3>xpYQ{@#A*#MdXLp7@e9`ULT}9;kWX)5G5${^qCH9>(20 zq`HT3c_03;C;zL$JyX6~vWJwv9;Vd(j{9!g-)rC2{hju0+TWAop8GcL@4WBYeb?-} zdf!zXt>?bC_ch$NdtdAQCH66@$Jv8;;)B}XPH$8X4 zfUzBwn$SMN_*blw+mbi?E%=UrLF7OEF*@j&Mb+q%4(s*DRE#0Uc|}8vbFk-4r$UTv zMUHRh+JpRN*&lw)`x!H7N%IX#TK?1}t0L!&ga7WwDqA#$Rn4j=Uor$^+ePv{uXP5`PCXyGK7MT+n6&W3w5Sbd85t$vC8=2?kx-H$tZmZa=*lf3n z+d4Kax;y$s^vl@HXbrb%^!w=EXuW9tXfpakbYJwlXwB$X(OS{3qqU>oMC(Mqjn<9s zi8hEHjLnHQj2?OXL;RusdjC5ALVuFq#qZ&F^XvF?{Kft} zzkxs6ALIA;2l&(c7Jh4gq(91^?XUGO^E>#R{2Bhm{w01Jzq6n3&-Bmn&-I)7&HO3; zCjWeYsXxvi?@#b=_Am0+_$&QE{sR9jf0p0Kui+2(hxs@8H~K65<$hPcr{Bx3>(BL< z_-FeK{agGi{l@-6f1O{?zskSbZ{=_BZ}89a$NB^PTm6py1%7XTv%k#G^C$Zg{kDEP z|2BV-f4P6D-`DTsul85@-Tmo)P5&CdmOtM=)6ey<@UQo$`nUUo{eJ!k|60Gj-z55v z=;P5RqfbTu8OzU##inQZS;ezTWR=V+6`L5F9Ge=O5}Oo$E&OWuz3}_tx5ICS-w8h( zek1%$WP138@V~;Zhu@9V2)`An7O5T?7a0~A9vKlC8JQRv8yO!N5SbL292poH6v>Ya zjtq&+jLeEmiOdiG9zGgA7Je@LyxGodZ|}4B+vnL|8ihv6>S6V?dRe`#K2~4*9DA>Q zfz!fX>a>jZjP{C7h~`CSL}x~qM=y-7i>{B}7`-XFJ$hHPPqc5ef3#n8K(u#sWaOd9 z!;uFf4@Mq|Er|XSn-{w@wkkR&Iy*WyIxD&)@6+1h2eso22P2`)% zw~?YC}6*Zz}?Bdv>tYcZnvrc3cdimb8td#t^u4&rJHq3|_HH&#&yoz22Gv*cZ zmU|c4rfu0FJIl6h$Mn6;W^uEG?RvAlS>9ats9Dl1+BwPkGVg4F|&;O zoBO+$%CjN8@>Cz zZQc%dk~`5I>~{C=@V0mb-tFF2caS^I9q&$X^SnpA>%52TdUk#9arcmW*gaw=?FQ~X zcfb3i`;&XXJ!n=otC&^IYVKb9T>E@`ncdOuWOuf^*j?>zc6a+L`=I@!{e^uX(%)Wg zU+C5K>Ud?n23{$zo>$eY;#pofuc24XE8``-EHB#&do?`YGrW*D!ZYn2UJ0*~H`Qz5 zmG==QB6r zeC|e_-EJ}G3peI`>H5xBZgJ;pw}kVJThjT~E#>TSOFQ4WWt{Kbvd&(&ob!WQ-r46? zaFda?k#>>xkq+K)Z>ihB+3!|#ess@pesU{02i$Dupqt|ya^ucnH{l#{D?2~CRh(bk zs?M)&HRm_Cy7RkR!#V2KbQ`*jBE2Jh>=pJQd!>EYUS%J#FS38OSKGhXYwTa`i|ya+ z-|eIJCH67C;I#uj#_I9VLeV0?szT2s8-{aJb)v)wW79Cxfc*M7jMYd`4JvmbKm+YdWQ z`w^#s)6jm_X%yQTdpdGXa<{kz?pF6s zr?Jz-Y3ej{nn$0AER8I)f3SZFzZ8Bs{9+_Kk`swX5|PT0Dv_#@zL9?B2sKWg(ncsv zG1bGkPIWXdQWcmbwX@o*M&>7~sro{7H0r5ZkYm{gMuLs2C7-Tk<%kR`jtre=3xs;jDB`VKcqS~4tqxHan4>|uvjg9H5IM@0ctc0zSDq@yUCHcbCFy2=+nI%@jjz6J^p#EZiY|rs%kg(Pe zlQ^cqaTj&9)j>5frl^E*f^v$@uze7ABz3~LE3k2{F;10-5%UX+XSwh16kj)Iq&~vu6=n8}!KuU4^A&7e z2Hl6o&~MOM*x?LOb25bRPx=SfUJm*Qek#67Ul}g;l0JiWOsj8pOf2h*egZ_qp z1#O$@^YlA>v4-@&j2hi&|65Xr&1Jg%#oxu}GIh(8mERt&6>v7XTGdjIpp1v=XY?uH zTg9CIpZ+)=33w{lugDs$vbFI8RPO8 zJJMsEIgh_jBIDdH!lmCxd~79Sn$;V-GQL?&vDNFU0{7 z9SJk1&_0YgW*=2wt{blrMjUex@g<)43@dPRfsSYF<+}8t%NWx}sBY#|;_c;F(tD5a zL8_#=U-h@*JSY03jt1cppX(A9)bBOwej=!Eo4~G>@RLd^Tm02Jq(UK!Yp+tD`RFv2 z71Z+r$}p94o(aLdA8A`*SJUiBJ09VhAou)(`irgh!0)P!`6()PTE?;1i8QT7stI+@ zGQTE#v+5m+sJxKL@!hn|Kycaf7UM~$CeOEDm1}&b zs+hA06G}mE+CiaHVGwb~pta3AP>p6H1>K>4bAk0 z(^Y&UcorSu8Spcg!n0ARIb%dC7=XUg@#N= z_(|>Lc@>5ns00<{w^=D`@LR2cvg53dLIHMdLwJmMU8^csW$^FYXt#6tWz!qD_ANHv9^@OcHj}1| zp&#*#U#4v9L(T_s-kY-TCC_T4`#b$07R*tao9A+$^u4r(o>Of@-O0Zx*RNE~@yE8- z8rAA#zALc#KJ1yN*z-T}B7AI>pbS@%N4_eDuX*%mc@7OVOX3&zE7z2MP=>OvQHE8C zv75dU`0Fi%*C(FzmlA;%qpy4BL(0JiOY+5bu z^Md}JX`dz7sRs7$#UD#xUP#-ullg#k5p(Vp^pT6{3$+>F&t?p$plVnj-KmRJFmyieN|KAD^=HgiM)GZv(xNx+Ih*-&i_2``JeAkp0w-V&vR;E@A%)v zlQv@!1j2eY) zs$}MH*vdC^Qb$8JY5&H2mpPjFVyH1=MPJh6`4!}?*FFuHXUY1jhA}1eTQH~Uz*s8l z5uStGR~>r{H#^X8bMTkUoQOVP_TagGh`(QUi1kS;#y{y7d--VmO;wG)IMf)RmYa1U z#B-kYN*43S&<<#e?`GqhF>DZG-0(O)3bU|BW~`O90`v7uTb|w~e|vtKFP`k%r_Y^d zl6Tot>7UFe-M5RDH8bzFwsDV)Bbj;cX?bC_bwrA>MtpV-_uWjK%)Gi0 zwvq6B#!XpA?dF#XO5rm)OxA0Sg+Lw_xnUenoxqm|25Sy{8b4=# ztOoI}r5f)}vW#)5Ukn2ul4nwZ8kmMPcj~L)o+Vt9wa5gBv6t{3f2;3i=31v1%JEQR z6lsoPoNYjS2!*V8PstsZ8Vm9(rb6-O3Gji4%QAi*o|j}afo%; zHs(j$_&b22%!7mW=|CPM=zrAFJYVXR+APml;{n2{d*&W%C-J!_NcmNicLoIQpff_jo`V8lo#LxN-eHl-o&z_yZqbH<4g z*r5#j54W-o{d;N|KXISTEpI`E-f1LXSz8IRu9Cf?9JC1hrdzFmvXn^`UK-dOyJqIN zrce7l&xgAc@IS^qGt3w}g!%RmeClmAEEvy9p`+<<3Vn=yst>t71I{6Cm~n6%zQUXn zEbD&O3^i5D;2PJBUx??kX9A>GhIGY$YgnbYX9i{coHl!jHj(}%W9K&f@ps0A&iKqm zuC-o)a$f!dt)g3XZ_G0(;O$xgNz{;bAA;} zr_Fa!=CQQxY}#Ws^MLh?)w$%mo;Kmwm>W>pXvI9b9s37_sRvXG>Mv`)z@JF3n$d!5 zH<7l$z3K<*uz~)&0Xs|}UE0~`LD&u4b200-src4bu8SW^Jg(CQj0vgF13w>;`rRr> z9i*=wB<70sB-T9_w$)(M#2_-k}djzaPa~CZBd267)m;T$6so zKAc5fvNmO$sKr>I_ueFpn*+NyHn!5AWj-otxgwr>){WAK; z{i&YmXLVXrdoz1J|5oKWnx0errT&NeRXi(r4&t+E&5UCirEi3SH9|%+&)>|v?R1sr z=yaa{>^TSfU(H#ou+POPo&HJfV|`thd0#EYwdFDnb1q@^VC_j*OEdqgX~_Pv>>;1J zb}7dh*0MG)1*M@xFb2tZBmOSWg1PjyHu!KG#$xvRgFf4aIcP^U*t~`Q-G*mf8&%4B zh4Hiv^AN5DbF&HLU7htbK2BJF@+)hx|IJ^e%Q7BfF5Ql`<3-%V96OkI$bO4F2bn+M zS0`xmu(Yf6|DbJTU3(UNXe}ypfQ-##Zo^vKdX;s05_=uzzB2gdTg2hnYz$&dYRcF$ z1*XF&_*Qibkar~+9wuCneL{I=J;nZfF*Q)&8Fe1zKZY`^qh4>b$0R=df@1eZCDZV| zg=)gHMp?C4=ha~DoXebNE%ltovqRSF?C)3ySzKZ z&}P04xfu8y0AYYo;oGgYymZ;MVR5B_>EeNS0iRokr2y5}2%E=P zCuI$BPd@J{_@a=#H_PNNtedl-D$njgl&KHv=-uR*$G#+emNGY_yfV*?7?o3Zm=^{6 zzvE=zMaH0@ZDjt`0UiTY3?P=;y8tl(bsvdXRGe%ct<&I zqv2gecIpG3Rh0zmPS#DVJ(xqZqAlvvADRZwrl;sL%$wPll4q36A792tL+mLGr5}f& z81`yz^m@A|yUD5XjB}_{z}rim%Yu+Q2G=> z|B*c)o~yw;b)@7;oA9j<`(aJ3pRsd3`>n6ghEs_r&zPU+8z1wYsuSbGue6~{ndF(B zPaC@2pJFWdICX$EV^gal;&6^F z62=v&-=*JDMn&3|$=~Ouj+U{J_jR9f%)6studKQnX5GPYGc}lflBD$+&nqeC0QPRP zSVNfjG2>z|ACUbZp7(0M>IGfZ{?rHXI_%-R6Z$guaV_;J=Vj5Sj7hv>0kb&e_)mE} zRcH#u*!MDNkH4ZD*Saz8rIm4`R}o|!DSF=J$}tssmBHSU9W&g&_6*x*-iR${|4z-|0(-u>mSGi_4B5!Pv?iT>Xj9na>m02;JwHY#yA-_xj(A~W0brDVayI~P&GphRd2>Pn|-8+Sq0u#6GW|H96v<* z5tS!nu=z%f5`k)VDCuQ%e3e+1>d3H`OM^*QN1 z!Mm{!SSx+OdgD9RPiNAZKjt{G=+^|&^JwN^gs$~Ry)>p7lrlAAo$T{muFG7pKH7%3Rb^ac&p64L*^0dx%kp^#FoHZMGOpgibLkv>`eGI5 zIXhJBD;Tfxj0MV3pZP&EYp3cQu$}e-X?y%=xXc?FV}=@EQva(+=Mmay3H@&>&z9-L zna(@68`J<8Vs0UoGPRU8T#BBJjg4SE#JZC{bZYG>>r`16$$Ikw z<{{+8v#hyMlJB8^!9O2keC((`Q;)IU<=-s8_xavR^@Ijc6SANo)Pe+5hblaKni=b8 zyDHSPtjac9V9y4umD{sV6eb<{9;H2L$e!n8*u5_8c-nU>JjY!2)>{!S->Ni9&vmP# z`a6qL@2F({tKU<}JXp_(|No1(T~)UxGf%F7_EP0}#+9&`qge*$&6p22M9Zk^A;$HP z>>n+~50|PDtReeDdF)WyIE&|PUNByjV=t-=b-u!?g#E5&E!G-eIYPVtsMdu_a{o5f zgZEKQthR);QQ5phQ}m&xi^g;QKsXXhNPq|OZhHZ>_s*CzAbAB)}6BUJRQ~} zfA|Tm&sJruI`pA#_@2C@eqWWPf0f|*6tgVmax#B!qsE#k)^~aI!%vtumZVQFVxQ_2 z_L>LL&cYJ*tJ24|%kw6EEc1JFma52HzH4yIT9;W_B=uB;+{Q;&P{%zM=&o*8+(Lz8zeEkaJ}5Od=a!J4Css;7QQt>^tm zzoHmRd6$;Mes&K1B!_214(S2mRnQtBS}T2AgYc3Z3ld+nlqlh4QMtEjTIIZEI(>;N zdC2ul9;cV%PiYcY(v|!(Ws&j(Wl85(G%rb8@~bWiNs!|J!h?Eoe!8AsjyQ3oyrPxT z8ZVOW$uzlEg>WgGlp|BlKna&JWXf{#9>OKf_E1#i`1EqfJ>5@-`x_H>GQG?(;b(CC zCkQVZSIQQg6JDR=I;T;W^FNkD*8l$#f4}GbKkbLhwZ1%uu3(;2+vr2zRjGx*@3#~( z%PM@F;|qb`$x97~DZtaXunbD?Nb$`?>O)w=w*f~{zJLh04}B0sABLOKY(=*+R`}@k z@G4we1b%-b#h#1&6teOU%0hmrTi_m$y!t_BVB`qOl*zLP=TfEwGy;i7`3kQ?=K^my zgZwi0OBy%8PLO&`07<__`kXqYZUaeIko?$FN!>x3H==g|zn>D+SIR2qg2a{ZYoG&g zP4-^`-bDpDzW}b}y5u(v()XTtkMIJ{SuGTPqLf;}v6Pj4w8AlHCcd=QEbRV`)taArH30W56mzeCT9)w{t9cJpx-n z@{saKep5l(T*A`!O-X#YelK8Ksb5fMNmt4}A3O3>xR~Q(C~@Q`W8=WytvRPX3O8~A zpOO7DMLOzZkTBXSU^R*ls5!6-O2|GX_hR?J*QcYOl1@`-0hGUx-(L&b~|(ChixzchQTEu;p~D1b+?R*$W_<(4fL|~@%^aSXDsvv$`{yAY$(1V zHo|6w-=@#0L+Y>&OFfGI0((H(Rj!GBUxs(!ZV(^X3DVZsC%Aq;dsIzWBP5w;f51Gq zp|LvkCC|i4=69(tnd3JPRpuFbF3(cgPnB<@+5fb5@(h*tj_Exn^bFZ+g3ZCcl6*5I z@AbHj+Jrr$uQTsYGGFS--qK5?F_AbIr5=)Xn|XHX9r;Fzxk4lEQl&$-Lw2G*eLFE|BjZ zJ_NqK4AyFRR-pgxo@a>v8uzbCGam(6e+S=ho~y${?Nm5`Z#Y5%aY75Y#`|!-)d|Zx z5bPVI^LKHnudD-uI!HZC${lbRJpysyTT%8AVo*lPA!&x1q)DJ)T?tExOM2|Pg{`MS zzF+(qyvyZhQPpXdZF_%8Po*ahFDc>{fuYc>qx zSkCvtLAV=^!yCaqj_k$!9r8V}Am0N2@9CJihxJ6q{}!xuhZ%=xYx%B2z6H3H^{%|f z|2J(<{r9CggdXDk_2;lRc#p01U5*c?!w>RaBKYPxQz!Pv`u|TD^8T6q9Z|8b9J79v z-?eCHdZ}&fTMRXq!(8_4?gCADf`3plZJ-u|H_^(Pee1lx68Y&`f`nZk6?e6eY@-c*?0Yj zcu#X5d!_6ncI6$8PDAf4TXH=^kOtSK43clKCw&5yGX0l1xljJb{qvK1=|TO)2LA;D zn+E&pnf>?wNM&z6Ah4U*|8I~z^ME3@&e;5aqtbT&Ev&|j&;J|F;d|cy4F#$p?~?yx z(rqI4b&J~QuYB~s?VFkYkoM8;Xt&e-^}pelnKV!NXX-oN0qucpAq_&@4>Pazi-4oW$n}G!+9U$Qo`>=g|!?DnREHBt_Y_5&QcLF*ChR-WWptF z3730{9RzF=R;OZ_PO^z4-?*|(MbPFYp4Mv(QQtRH1P#mXi1 zHZ+0GAZxU^rcl;JSHc#!L4HTZJRe)V3O#x6$?tcb)b#JKvX+qF-HNm4Se$*!w(JMW z_h-3gcaU!?2EskTH{3(bo~i-Q#`?@BYw=E{J;$|pU*)K`RRe)_P!4;W&5dhFV>bKD z*T567mNvT$u1_3R~fzg)z+zZ*io*LMq4Ra8Wb%`?h2A8?G{669UV=HU35v(2M z`xpHUE#J?k4w*Lj%D1k3bIb1sHJ9&qnWqos_se|Nfwjz=cn9$Wd&Tl?jmdlr+mmK- z;|ugC`>cdnvOjH6#%-#Dd7Qb5P+NSWkhNpsB-**Vgz0#x;l?|vV*0oe`~E86DfzCZ za1(1$xo;EP9sKK!6Ou#c_St5>Y=lzEhABD+5V@5&V4nP?sl%9u1W%5DQ~r(u_a z{JIC}5Joxfk@V8_T_q~Ez$U}^9x9u-%k;IAacUV>Y6gDCi$T(&4ESVFW>ITskmfHL zHQAjYjT+`w+N!3?=N*AETfv3wE!5=O7b$bkAP#Mj8qK>?rSmV07$sCb<&*u3e)!OI z)>rLWqn^CZwFjUjb6bA+J4MCWTg7GpVxyX@dr+=1JCvWomg##W&AuGtg#psG5bT{{ z$wKyggcIyN35CT_TB2~VDfWAkI%mo)Z7%J`IoC-y`1@r+dSZW*T_EeGpsph*PYW27 zX}6Q@EotIck}m6S-KN;~PkBh&*8;H}@dE$Kv}>k4S`&`1vR=^sbA9S(1I&MLE@c+5 zm-#GvInoxdrq5}|z>lbllp*kAIhHmSo03luF2~d>s8hNx1^$6`#8-p%qm88fgZhi# z7eV3{CGhtm^$+}-Hal5&sdL6(GIge|nLZiRlNJl=Bt9l}!shZV!O1d6Ig7T%OW0>Q zaYn-$@~QyiVJ+vh0eeWnzshS?LdicE<5uzfoT2LQ4o?Lz=J9<(@Gl1Q4m-*FS!MKq zbs&9{oj<{l^si;CUxKcJ9fTLjSMmzd;ocx$Nux+vSQiEVI>DdHLH@kcmiOm6Z}R-B zycj2wybB2aW|VPLk-P^GRwN&Jhb6W!M-u)Muq#S=b)B)5jPaTJvY(dZ?*sg)zLG!h z%PL7-gYhP9lauu&PsxX8)Tug)olejm8N?SLL|FqL$T_7Jy-UW5&@@!~U7C>C6JHGt zqRn^n*QLsEJcF<^IWLFOj)A%yi@$#Yvw%AAJCDkW06kXH3B3Z;-@*qay?`%K18BPt z>09q|-<>G;v%jFB%<`{P(SJtDN5ccL<2l)$Kl|u5) zO8O^@4kb7 zk(u}V@_T>$$e5TiWvmVQZEdcnDcV2fn0VcR5hazy_;yS*c%I4_6~O#ME6*r-PMw1I zn;1J8T~6T18RTz3{s}BscsfG}`vauC%CHJgz>!m=ymVxu6=Nqc1Ma!RfG*cU&{_-rutH|AV?@-L7!lsFj;e4++nf?NY^Ly7^UaPpY2 zfBPJJG)U)iipVxI6O<3F;?(mHJ5E zK6zczDtexdLp%IbgdaK1@F>R_9^p7cFODYC zM~?7Jc%5*{70fkyabA_Qi$1f~aQ&~IS9q;F8&5u`j&Lqa1zA8PUk@;F}$`$+wAQm+HM;W#Cxmeyi;5I$uzc7Z2Fs25C!w|Gi1 zmS&Mg)_43B#OAP>zjN3Km^sP6$>8!-aKBdi-!LAIo)0|9!m|%cm=-V-}LMUx2E}o ze+#oG+{eG8Hvq1N$COH32zX6pJiRhc$0{vh1WZ$^YI$G;s(J}LsZ=$*y&A{WuvvBT ztv(d;VF&C{szx(d00)$+xtyl&0F<{D<*dcCtTuJ1T^Y!u_CUB6?u8fObNE%MIzH3{ z>R1Px*O?11z-Pd0rfv)vh3mEh>{OTX)Z^J$uPsm>`8Rv&t%92YKdVnY>u-l=fpXOU ziC_1?kAi>ag|=ycUo;lq*tG%v*l-BU0BqF|A8SZ`8&al**t8LC-solc z0)AI2m-6RQo?Ob4i=A@c0`kelwvEZBF>T$Ld>W5|ML;_?z8#3$_+PM>UqLYe+c#+l z&j8^~Y0IWl;XJqswgK0gehi0{YKAX2qn^#McQfkQjCwYAARC%OUznm)i;9p7y?}dK zVAGb^v}HAD1A~CNwp<3+z+LbhP}i16lxh`)D$oju+Y0-(x*MK{PvK{!T63*6zS?>{ zY=vEXnnwq0!)Vl|H4KKCKtE}7J>08Q+X{fq+ui~X!fWs)9961aakxyW_V`45e4_na zSPeJBL+~o#@9mE%)d3&vP#Zb|aXS#V!zv(d2jX^koj;*MIvwW$?beZY>xkcV@_}~i z)D|dTC(74}@^!iq=rf)1kIvm-9GnH01MSxN33wOwDb*zem7ob=-!7D~s|DMY>UJ?~ zhR5J-_+IhrDL}ouj|R%u{Si2~$xQPp=P@>P>t1t^>45Z_3pBall@^cPZ5ef9z8On!$898z@JgyMg-j zIj&S++Pm*%KzQGSO7)uv=fDQo2KZpVkKuq){cTvM)ByVL0Lnj*b{n`}sX>%=&`5Yu zsln8Fa0wv2A?T3)a7?M85f}!fH>@EnhF6pt-U(h(YD67)6yAbwf&Mg-@{golM$#@L zxi*q(qp-&);*Z*`)M)B8nrow}@0iLk7ig0)8v*|sgHMkod@OA__AaHyQOyQq51-1z_b1kcJAkrHDh;Dx4Lr>s!@(~m(?*lA z*<{K+B@bxZskMN#^C?4qJGe=yX$Fjk_mrA`1~i66@TO8TDnb{a{4<_aYG!L#2RoFS zRR*qsw*jA-O}yFjpaAf-InCircvq>p9e_H|-J;aIYS0GAdwvBN2sgrZr54nO3*Zwt zqSV4Lkj}y}uoSS6{K{uS*l-bkxrnkYqRxwE!nv>+UWa2!ok^TCTLEox zCiOgXk5Xq*x3i`IesR{5N-e$)?gxBe@sCO^!B>{lh8}P!;3sF(zGpw7)H#FTH>J*{ zOy`w?1#lt!q}2IO@u?Div6M0{TM3WCH%cwXpO(`;%chkVNt?LZfc-T2wFO$(s> z*W!2A-Uzh8_5GE)VHl9sjYEJwaT8^}X_itqqc@Y@%@ct#+(Mmhxf;GzY9n>qM7wOd z2(b62<4SEN{mrZ4C8ch~r*Gx_*7-pBZpA*gIY1lTc2KF?-{3Pce0NKI;QlQ?@CT*J zK~2CX3P``;JO01{?Z1`tt(r0~H9cX#)oL4#Xxhv06(o#5^g2rfZ_lLUgh2iK|d?-|%(XYY4EXP@`W`S7rQ z7v0mnYOQTE2Wi zJ{sb8zP*1S7}wpMe4l@Du+Hyi0Bh+1^ZlR>rr|YD#far0>*A3g-y>4wG3)U$^&W2) zc~TzC?^D`-%H^L$z=ks{8lz3nC0eOB|3B>vGFn$$z6@f`2 zuaki}c+FaP!yLV#-M94jHW%3D?FcZgw}IP#!&|;&j(opM5Ayx4F39&g>b+zB-)+G! zxP_0rS(gAALEZPqMLv+vk1ANlk;LiIm$y?CX@L)-D7*P24Sypg>bKHfiNd>O3ct;z z=&4Z*gzv8JV{4GKyI63m53ckSrOv73n7A0nB90b2z7n5I80$dj*NnI=zB`JL;C7xv5pKJ+;$I1!X zwjInB#YusUqWGB;|2k2USH$n4q$rBTqNL1#Rd^>#D*8=De{Lqwe(Dz3CQ6$0AkH+N zC~5gyHfamt2T{@mfp*d@6D7S3a+!g#WSAvN#$c2|9h?&-yeY^{rj%fgGhGmczYnTp z9uE4Rrw8_(&K zoYcwH6(>c>&G>S&?sDH1B~Ls2DoWmlcqmG~L?EyE&f}RV`G?`DCH*%1QaU5JuQYR8ni$K(24gM5zEGw!MuVJ`Sqauz8S++!{>rdzS@Ktw`7T=;tckL1 zK#XPSv+PoA7o{Bcm#c*1_#jI88lc_s#8`e47<+lvYXuXm)e4NMLQOQsP%Oa_&}Rku ztXKi$uOfM`I0$nWlB|BD$Qk6NbS{Bp5 z+*RXxHRh|@P7qgh>Q|?)>KjCe5~c1yycFdt>U~AqUy!D3MwCPzzo~IGaoadEvQ?()6?_&C20tQJPN?rG<+2I3-HU2y7AM z8`kwV)N2)j%J@l?)~t`#6YxNkHsqoWxodM-l(y;71#H)jHQBBSu8Go~d~~od8|0~D zGBm?nQ95M-`%LFWqIAhG%C{zFh|=|IJQbx|S5dmtR`(x8>5&!t@JN)N--yzSvG!t} z^%~EI4-$iZdOs7T&p1)~vXAv8XZ>1(Iqpxc`ZMPPa^ezizLf`aFo-!AMBWFJr@_qk zcLni~H~kihGL$tll-v#LFUs)v_!i9nhz__V%E%VDEy}3kxGBo$!FVUi7-AYTk(WbL zgP6u{2iuMFqZY{Lc=9=(e2#A?QEy;Q=*}g%nn+v|M}Vp3~VxaKgYa~S8G zaaaP{oI{&)SqpQ22eHi~wt2)hkJ#oB+dN{MM{M&L!@TXF-aP8fr`~+(&8Oac>dmL# zeCo}o-u(6W8Q1Ynlm+pT0mbl@C<{}7IbU>4lpofLvbY3(6=g|Vc%m##j4UXPMj+;; z#JrT4mlE^REBGkNvVtpz#3S^ zezs~TI99FNj1yojt%?+7bt16#R+mCU;g5k~HE)_T*0t=LYwv+L*AeGB;#^0Z>xgq5 zajqlIb;P-jIM-dkBT?4-U?V4(hxJX-8Fjt$|r#EK@^Sqhk#%A`vEyTNp`Ps^GZYyhMTXIyw zT2Z!#;_SeIV^DzA%X8cEzV5}%VCqp+3#RE}}lCz^Du@g^3Io1;7 z`M4iPL^)9#y)avpUzqb>%3u(v|I2GpPLivWjQwO)i~>11c|nv@0@mcI0-&!`%Rqdm zE{k$HF1Y=4CoC4_3}ZXP+Bq{rl(XSrZqMb$YQ8uzJ&5l-W4b_|E}RkN*Us24%5SWr z--watLCWt}MY&iF13`{2z8B>Zv0q~CT_SInZiBV=hl4_Bh2KQEOnxpe66Fecyz;#$ zSL5KKDAx*NIqr#ao$J?Y;-Dxui1WraoD$_G@!TY?TV=s~+%AT%un^?(4!OERtas*! za+fjRO#}MA%i6gY8&gENpA_WmL45QC*B_FXhpdlB~6XJTx zoIm9_`Sb-JVrc-z@JvT048UVip0@`vzhK^8FgGvLVu&cOg0Wtd*9P)|_FmuQ4ZRqk zzc=?qdD|ErF&y)-6(?~Uk)pgK_wO>J6dIx%#$qvciSnLx^}ZsSqc51Z_YXw*pdks^ zuRoLq`^kq{*ob2wj*lTA=8xPKnICUO@nV8F_?r zyEYncMb&d-g{Zy?*d=O=l-Pg^qVoQm%3pO;V+Lb~sIjW!ov5+5;gqN$sn8W?@J7@) zHfT34+r+Jk^Pm)Qlm>kD-`_!?+CY3y%fXLwFuk!8c$&!zW`oZi4lY zDJK|LCgRIPe3@t?6FJYsn#fG9GqX+RBe;T>qGpMM9$-GQJQX!-3@|TQS)*CYpdq?} z`?C>uw!&a7W;-Qn_QYVk*%@CBa-Jg#SPwZG;2K_w8bSLJtj&nLsEU2KEox4-&6x?s zQ4iCx2IMm*`OiuIa|OW<#*~Zuavj1SqUI)txrrzDBK(NcxGO5}yQz8TKM(EY>4agJ z4RVl&Jmw*fdC75J>g4To&8n%J4H7kmx*ou?5D{Au~6ahJDLH(Aj$8U(E)n-v!GuAfQuvgS}#MB`SuSD&{ zWhchi`KhQ~l7jWzg>iJbChE7W?{Ar(uJJ&dU3ZDvjdj+oEb5|_sNIRVCpqs`2@gf> zogA#SKE<$6)V_tly6Z>Z{dS7lpUeKgiaH=a){8o@9EfAkZBYkz0b~1)zP`I6>X6PD z1lk=+zJ_H0`5)dE+%{qsnER0jMIFVqqltI)C{f3-hQ^#1bu9J9GOlstb3F4m{ufav zOcZruRZ%CAt4Yl7WX3;v8n|ytKCs5V?+@}kwE&pssl+lZ54dkS^F93#E{HmV_-2xu znY2H1yQs5rg8q4~q0VO9vj>2-=g`+2ayDm=sB;TrgsAg0FgNqIiaNg;4vV@VJLr4C zbkP2SJ0KstKcOy6jI_uNwp&;mEzupvMP0-?S=1OeMg1Ww$k$@(FRmc!68c&)8pO3U zCHjcEj5%9Y5nDuEPJ7GSh`NINR%{S;Wjc_Hm2A7RAv%J2SUDMsLCh=3#mY-~EGjSW zs;lC|MJ|-VS0MkZ`e8g4fc3D7xmxur?&E`~tK%X$82joHpug1vK>gMGaa+_i!N>sO zS<@c0w}yPLt%b2TfajvFqn&lk{kl$A3;J5e?L3E2*B=vg1LNAj{A^@yHqI7x(`B&s zHq+M@*2I=M*eB}N%D9L}_$cbOaI6<~dmePdW}E|SaYsTd!%_SJw)wH1sJj@$F6MF9 zIvfG*?c%pBH$=TR}Gk72>&l}WJ zBe4t5L_Hk~Ns$5NKzpZ&<@7|5i_?tpH2s_@h8|cY>e(dVSbBCb4hg>qi+mst=b5AP zRnP^*c>XYc|LhImaI4-h|F23W#$gIap><52Lz|W#y zW}myvI4;-6Adu(FPer{FQ`D<@Q69v0mHJn2h*II*Ec}}8UZy@Ro*4s_i!%gyk z^PH%+D&Q=bn_KKNw?~V5hsU0~Oji29gy^qAWp zcf~9m0PFAx^Yw&yo;1V=Yyh!7W&5YByQkH`n4gm4r@!EZsDCC0@&3s?{J8+!{^v_k zpAo||jv>z&*R#3!8RY1BTyVU4-WX%S{5-!R>Wd&`LQT-t3$}T|JiT}#>dT}k0oK?{ zwt2Z3Z1ak3US&lMFy2>e^NMX={VD3}Bw+quGnUt^@7L?WSl+}&W^k;0(+5kye)5JK zz9r_jWzY%Ju?zP_eHRMW+Pk*k_IJ#|yE~%3CtvUB<9!n__wQGOIr+fa`w)(=K;Ivh z;Uu1m%5xp{BV+m49+N;@ADQFG1Skq_kDP+-xGt&}3&iUYFW)z!dbGv&e5l?B(ZoRs zbifQ80^e+_grNwUV;r{QwrFZhWIOtkn3Fk7?)=}-ynL`%rrCv1$4*oT**CCY{Fc!ZCl zB_`&?yKz&rFy51F@!PqVcec11~Q=n=r5@celU+oYk6wcRdC?r) zmw`2#;i706yMXl^&UWGCEPO5KD^pX9!EwKh?XS>#)G<9u85X32qi&W zS;<2-a+R$rj);~$90Nql!5DM2#9q-NN?{5?0Mfh*r_X0#L6~P7p(-IbdB>P6+y~d{eY4^jYPtXjMz#q-Z>6)2h=) zb^5O{QnZ@fUX!uZsv}zM%pjMwE1? zCbo%IF9C?BUK6Yltv=(f-xV{k5v;uir=QVS{nsPkPW5L0JPO+Hg@66Fv<}?YAtS1zH<*VGNAX-V-Ye8PvR%ij=zyvC33o*66c3EQ zQzLL+r;DO>rf%nkxF%W`w&^k(n{XVAqYL{&7sm1}>-SsgeapJ~b}U$1-)_bUJQl5M zTx3Qiw8vO1#x8KI?)q4?ZXw73j>+9xU?`~5jpKH=v*7sOU62TwQ2}i+8Y^)c>|;F~ zu#ffNz8;go{?p@UTt%d4Jvo;5%m?YWGmL7m=k}WDK`ec~0=elk5wy{Vc>3HGt#1s_M&ImU+ifXb9pTI2O#wz+JeENYMs4D1;W^{z03;eS;a> z;LNCmc3}N}mk`AE-4IL>Z3y`o%Gw^rc!!U`DzG07e<|7s_Ms73z;R^+>wH8f(MBf6 zFQSd=C)(&ts0?l&JqfJKG2tkNI%qH2*qk6I>}DE9Zf;bCezoH(x?sgukU+^Hq}H5WCQ!qRK_;-v1rp;iZ)$Ce(VOv|LLri z>Fgsj$k7aPF{2@fZN_ilcroLxXfv7rnZz-(8kpCa%-PJXU@gu3RkT?Nks6F=);jDF zZFT~%=4Ow_QqbRQ`kQ?ljCoE7ERYjEi>l43fECyQjt6ttzvnQXIqyZA8ym@x5&2Ob z_0R^rL4N110C}8y5zj@N7aOTS{dv_v-FbsC6V#oz14lvqc@OYjwE3}-3>lFhN#L6DzO-M{xu1MO(ml7i2_n)I%3gf5AdfcflDv0Cg9}Mlw);VSbcHJ+wh@jKWN; zzz(qO!Uv))(m+3pQXvQEYf*JHMHdXlBrL=R?86z{z;n@l(2xMBkORe09Zk^%tnnW> z#{a-t{effo59e?TFGXAILqagmi*uqRYN9#%VhY$l79Rk|-o-CPTM`GkfU?oXf8eEP%i|z53Zf?3 zVF+ep6OQ8+-ix+^ysXFwaI$#*c$BHdrzg}?%o@gr*g8Z$l3hJ#KgH<>R9*b78 z&#a0E`d*bEa3;CTI#Glhex8Vi-Sz4h<2ESZMY&DpBK>9bKm-!=mOTj`nlKv z`rvZ{+J<5XTu}WHpT&C-blWTc(3L zTd1>zF>ZM++SbI#joRpr+1Q7BqHSY*+qiEVv23G{ZBwuTXYgFK?IyT?djkx{GMvI| z(ReLW+mQ(s(GFv<3T(UMt!O{`Q4-wuBklf3yFVVq1JQOe#+?~a9-S~1J8?s_UEH@T z2daW&=1;7VpXhhDgAq6?+8#ky3}Op2XM;ZWAIAf{7wrIX9!Q3aU=9vcM^khGxjHZj z3$X)dz;*{Uu+707D30oAgWed0nW7y^f~FvzfWr|e0UqBDe-9qF4u2HwNNKP(j%>gI zT)>~A{Tzg}m@C@RnBZ~k=poULab9t33RZ#o$Ns=e(T?+2bey)1cLMz%=RDy=E->C- z+KP5E4Y=)8LGW01n#ar2zlnCH84lv1XlEOP{?EP>?Hsu~_q%B4Y4bewF4Pz8*Erzu z^0x`1{Z77q=P~qRJMg%8iO1GUZ1)F!TqZY{&x>}2@n6}8N1|QrhdZKO>w@{B@zHDT zdPm$9?FNtAH!h2IvoP+9cB>Cwh<2NKz5P_QJB;HFkHL2h@Hl&SnrQbLh<0DYInf^Q zc>iF#Xb%&EaXnlj+9URfN1T&9S_IBB9;+bFPl7~y>ce!r7wylYSTEYM;&>+7bM~R< zr$l=}J1>ay#WwKsOY+2LEwoo<&``A3<#9!{H+@0=-!j&BS+G;I_lrgQKn^}IM;{sQ z#}A@K&H-cbZip^1z_&C?SJ9QK;4f$>$8kq=HG$|_cJPl$4&$2Wd=5g7xlZ(0mC!=;*g-f4>V~jg$TrdA#6fC&i?v|exFto8 zr=S*QiEflYebG(+@{~DW^!V*C1XuA?^aNdSSM<=2q9wI zDI|K*%%UeVa8z_FDW>3}==N069qw}$Vh0$%e-LJ1J$}Y@yc0bIv86Zz@{y8!q$D3H z8FxzhO1V|^R1MJ$&qQ})fpMm;hLfVFq2ILhpZ1dI>8gYF((e~NLlOKSdd6b-Q}pn! za9;FGj6YL*ycV6;jP%U(ow+Ic;(P2BJxcf+yS_oW}0C89AgUO;-w?(fZSc5~N*JN$h z+=Jinr|7j9UoFN_t0?HJ7Jbzkfn_*?YogaykOI`H{WS(+I@W+OM?}n zH)OmGxok8FZ$xkWQuMEf;GyVEXuApPs44kux)bL_Z_^RMuLX0{VyWmY ziKpc~(fPejo!4gcZ^&ya)_3bzSOWeERO<_1j@kr)`D??vX-oaKLoo-NaRRqN+wBs8 zKHIU@+BHNsjKyN?!g)Lvy?qQg$c0LxcOdo-w?ywa2CUUi6-4h$U!4bw-X$XzfVJ=~ zw{>N|=z2}`ZuLMex)&F{M=)w*ujoDLwgJYDbqPV@onGXpZ97`{R;(FYR8K>8Y_;g0Bo8OxAR(TBzpeHddI zc1-l)%+(0i;>h}p^Am0E?uYM1-?Lftyg>ek(50 zpb*&ZR(lM^9PGk*(C+ORa6rA=)Voc++tj;Fz1!5gO}*RHyFFy*t#q zL%lnjaRRqRze_znd#c|pg%%i!#W;xDqTh>wG+;dU9*BOwJXVYTfc5);xF2x5c%*>) z9&;>u;)(vJA^J1c!848(&sZzZ8;JgbW6;ZjqQA-^`fCg1>Gf05-vr}#(cdzM?`Z4& zG0{J;u0LGD3(-G@z(oPn0CWG5oPA{de_ShiB=JQK!E9_2-DAyr%)eJqd{SS0N@wx$ zS0{a13H&ZTJs(zx&liSP;)~Hld_f(>7u-{PG5d)x)*xILU+m0SD87(T48&NNH|`9q$Is%6=i;pRj4k5hJv^V;7(H-W zeDOz#FF_~qg~kSdiHy&P`a&m(FJUS1B}$EL;!A9yKZrG~80KLsPU5ckk_Z@MlB}Tp zB($G|_LGdqQtTFA(pBP1#u%*$;$QheBs5#mr21M@o|ph%Mt_hm$f49i!a+S@nuhe zo#Nv=Lwq^ji!Xw_QUZM$kvDdgy{tScn}sg9qZvtsxn5 zV2$|lFb8?*KVMn#<>z{VhIk{sg8Ri+h`0+~!E5mqj)$};jN0gqaiERD)GJKA!k+kw z_>mix(GvYJ71S$2y&}{r@>+aF4IRUt~K6&l;V>J&Cw8{3R+ zemgXQ5gM!;(~U8q=D!wWhA}%dL7`YN($j3M+Y+t}07F`%cLaP9| zEBcwUk?p@=UvLuHzuND}>yW>5UO1s&vR~8cTYCgi_xeI_qkdTIu$ab4GJ4u7W0i4s z8r?qW?(7Up=Ijcyt^H*76E~%GfQTCq?PqoTMeRYxgU|fBGwn-uW@aYZd1iRnzhnJk znbtW*%gj5cKQo=Ml-35jh^3IZhE9P%t40DNzH!LNYJ6w*FcTPu!z{y!2ytT>)2*Ub zP>u2DH^%{8^}FbYzIED8AGPB0u-@J{Y5ckE%=DST?QHw($=rX= z8ScEbhZ~jc%3LdA^>gYwZ>{V0aHksWl`+0|Mp^yr!cIQ7pS{+}Wp%eJyS3~T?l9|i z)HeC-%GPb?x?Rej#JTTRjgH1I#z}t~e{6e|HP(*lgji=Bv2Xap?5ECkx4tvZo?*|h z#yZEWzV;pGfc@MFa&Fq0?U>dC+p!-zneA`vM)qzikrT(6U?;b;+nwyQPAs#teava@ z_O#pCEA5rmH2bpkr@hVo-Jat_IIry|))p(j_0~RZ&$WhGOPq>UT>FYYqn*Ys=rp%e znO&UQ&MjjQtDvVd(%x%Nx29QJoQ!ruvu4yR1ZLvXZ1Cy=#cXFRpv*BAhQ$qw7na!g zXha4lFcf#P`E8hE#5Lkrh0Wn++R(V6@!bBQani*w6FZkf6??J-8wHKRltL0L!GXOj zPy*NgDxsz^{qs-3@q*(TJ&lFTQtrT-57wLvc2jGfo1LuAAo`WIXZ7}Hb{6^L`AfJ} z-S}>b@DO*L9mgHuHnXQ#e^@`dL!G)#ervpuEx;8wsXfn%uy$B41KZk>PP5Rc-Xmg0 z#IkGpQ`>zV*Y0n&ak7{(!h^$OFe~k>F0N^P=O(kpx~;8>u5MSg-r0$)_5MQsv`$y+ zUSJ=zGrQARJv9Q{Aop9xbjQ0>toQand#62s{iU4U8r_ZhZhg0>wIVFJJ2Jv>);LG( z3f5w3gt=`ZcunN_Btn#HOW@2pR7z)N-LI?-s)*DupZc(?S9rt>$u(4Ztl*A zF#i$97ss3bblcyLH-G>6(=n#^KaLWgj}~8EGP{8p{wJS~5P@0$7g0xuz)`{~U^4^8 zX(xrf+DYf6v{k1%v$Edq!||}Ola^bq`F(Z_yQle^^E|8SsK#L9D}OS#l`+&hW=*sX zavZAb{N%*9f1-`={F*b>pWEDGoV9Wp=j<%b4UXY=oYDSV9F2kleRtsl@ zdC|OOwhaz4W~TGmiS738SZjJ%u$#dRvl0g~78rTdsQ=Be?jIQq9MPUR$pc5|z;X9; zmIAY#PEV)W?F08@)r=fb$K6kJ@;S$!kKdoS`h4X5d}QSTIYr=jZ>4kAhPnO>98XUM zdJb&=d7ICdfm|Bbonpo&=dN?t+UIO=-Z(qSz;x#q<6C39eVbO(`(rqRjZ?-zCx&C$ zLyQO3Z&pDbl~Ykt`o+HK_#G$kxMnxE8X5x}+X?k2wW^ssQjq7GJYH8ei--G+5M#WX zhkZMrmCqhykFiFuj!&dh+-k-H%40k+`1`_*QjHhHqJ7ZQ&#rCqdU>o za85bHjG|#l%q&smSH`l>F5u5qWz1ua9k|_k?zVM%IM>25Swr2fPCU1h+0(pY z$FmojX(@5d%4TKfJ0qWw#b|EK3p0)R#$10YN_v0#;F!TN&BNvqqh?s@urx+Z<4{dt8GFe9~<*PiMmH6EG$%vQ#kbTOS!GjBR^ zYn#Q5JWfLMpmmCKxMYTF`P^@ezE&Q4l#|eS=zJe8Ru#LXbJi}#dP?jJvbI{6+~P)m zzhcy~M;p1!mgbLU1vA*VVyrbyvsqyFjLYUwbBGzwOkx@#32ehQ%yectW24#KsBX?S z{xox$X^k63DWetfE;W~!lZh2heq`qnp-ezvum60O6}aS_`gGL3wZwEjujEjlIU+31=br+X z5%Gg#86&>D<p#e7Yg9B!*cNBgJi~B)Gd3{StmShpai1foN$&uoxuxNpJTKCunsu${_#_|`@J>5-tOp5q&3!> zmQ``*Iv<_G_7QiITfyxc8WNt&jp+u5tKo_}+j?Oqv-Ubwt>2wL{PF#XL*kgbof_6* zdz`byDPf&(>N)4^CUyqKO>xsZJDI_v>0&!+tmal`r-47a)6W`hce9f?shzLG z{pP&zP&x(cJNGTzqQxzGwM65j5cmPqs{+R%%o;gcdA>`XkuTp<69T`dhsFluE058Sdg2| zv`xQL&>g{EbR;C6xy@ecYR*7&fRWRvU{<#`INj{~ZdnR1!3UlbSY^Uu@N8?5vB9~> zbCQMDPM%xDwAzQL)=2j&R%}8eo%u_68mEjMVI+2oTH_*ut@Gir{Dw2j_&J>zW1Zs0 zQD=a;*RIbqm{2R5z05e`EVu94q1HyfWuNh9a^^d!%*93vqZ@lyQKB7YRQs~H>ZhO5 z8fl%*|E^dctPf6kXW|#-vGd4*Ki0n%v#PW5A0^yL?}S^Mzbs+C=%0)U#soW|ozQst zk8ur!x&sr|Q{zR1cMgga{Gt=7PFKm^zmN_}xgjQ#3jGNH;ndcLG{P~;? zPJ?to=|b$M{siG+cFyoP*7x>%XPN!MdSySdyRd?*IBnfR)-HEWV2zncjW*6UXDiRU z^4Jf|sdfYVl-t1mXs5Q~x#8x*@WjRk`?Pb(AJb{$6f#m}6`q@{2v6yh3QuA^v3{^8 zS+}fI_EM{p^E;3AecilnAN!zv-Du-47Gm0dv!JoZ$!ayRZhTtNcZ|u_)X)2^1Y56- zSbzN#7_qhY%b%mK+dOxTzUGW~n%EodOZKKOYuQ8Xlb`PYV2`xx858XZb^&{$zaWn{ zOKG*S(}?4HA7i~yE}do_v1i+4RD$iT{-VwTXMuIW`qh4DjWR1)9gO`}eft;V2lIqI z%U)p(w}v}Ctw(NYC#SW|&S7mgZ&(F*d~PJ5%Fk=LB=&*IPTkK->H8tRYhyfhLzl|prY@legV;LLXJIfp{c&(Ea(KV8-^ z8i%EdNND7bND!9M93PeMnAU;iEqx4;Lt?I zTl09-Jbc=MmNi;ghX}tD8jDwA`ulSlD~(X|zA@Wx8$m|6(UNF>H@`E(%uCL36Ktk7Q-{O~{2viJ@V~Q@{WB#;@pFL|tgwhg zmgPKjO1cA`)<$oumzj`!%}N*SH=Us*Fs6~n{+66f``qLIUjB0iboHOhKi~WJpUlyA zaij9zZ~dQtHj4evb^f=;|9u96|LF+-|Gqtwef$5u1kRSj!ksx`iHxrPyiP|upB0ny zQr(}&dhT2coH1d5(T7Leo@N}QAJ2K>aZ4{#>>zWj^UD0%>S%rKEOI6pz0Ihz(NF(R zGpCuC1J5l2=j(y+~)v{_u#E33dRx8|%=f<@s|I4j$!jn?8PsI#3EGIlYCFoOe!fiKG zxJA)Fm4x95cqKHOv(X%8SGRw*+qjABHoX3lnltFZ&L*CTZRAYDG|UJ!x)?Ey;4pqU zk=J4W+w#`=r(zHPZ;Da*KbPoN>HqrQUQ6Ni;{Q~v+y6~5Vi?JIts~UX4E^t8|3_?j zjDIS2@TdMO+W%A>kF#yh$?Lp#zO_R9MXa*cKqG@&E5c`==N0UCyaIgCN$6VkTB{qc zaChaby1ZGCCp_*2jN$e*T|t`}-L>{r?x`%QN?Xcl~c?=q(sYxzFd+|EEhv^IsIV(*L0p zaQ}TN;r`q5?=$+JbNBD={oi~>=hv)g+%tYR7MXSV|F*G+cPwX{E@iu68O^>Zc8G+C zk(3gTayYs~#G!;lghc(sJ55m~aGxD`*Xh&NY#Fr`w+Cu-Z=l{`Q#Ug>?ahHkMBrTc zKNaV-@!DKrj*KpW_g4eOoZx)rBye`y^R4^l6zh&V-8^m9H#eC-J4>vD=5_PB^Q*bu ztmVYDYuWqlugr7iN%N|?$e3ho_J^CB&7Kr5Yv8AHrk0A#Pivx04_&)I4LaH+s8@ zsTt|r!fs)^Y;-2L9=Pqd!2i~KYd*ih8*DWV+-JYCvl%;$lum7@mQ%}~X|)P$W0&>2 z(c4-x{jT4&9#alkkL`VSh}nyxnVQ?(ZEOv(a#&d?J?!u8?=9YgiZa92tl3t7zw36h zW^=aPG|;cBxP`eM%B@j91=_F=STn60#zA|ganPE})&V7`(SiP3S!dl|e%H)szwo=n zWCpemiJDs@lhMek9{4#>tUa8+AFyNFvE9aO+bH_h|LK3Lzx^uEc3{-@_n&K9mF#A2 zuE3aSsV*Z6jJv{xCuJDb(Ynq$q08pi^+qcy{hZ5(7QrK0lj zvpdLcpZX;ru8T9fUv(S>|PmCyZKL)@l; z-pMI>3G_^_%%nBL>S671JCLu|_P#(1Zego(U=;2sJEOJAE@VcStIfvD^hM`^o!i)D zW;5rStBmEwa`UcT*GL|g-b`hlH#^u>Z2lRJ@vE7}%xV;g-qyZo*EP>OvCRvCR;;C8 zwqf3l-rBD6*H%VnNkb8+vpPL*O*GL zemkp~#`@MW%nQ+5TFH$Lfu3#KDruE;TQCd#!{eK!tY-mkLo$&+ux6cFZb5sd>$v5^ z<3%XeQ7g6G)vah}bDLW$t;=>bE7Hno6>?b>#2H?jTFe(5H6YfzH7 zY2ELvtJWH8t<}hC;r!?GBWG zsr9Kn*JiK&t62R%?^|}ZsQo1BXFKIzxBqjD|9|}{`grnJ)Er+Ib5tPP| zz7>M+oD|7fNBExpoTZRQBw`cz&igbAMN)6&b4d}rB=Qt%`9%-DFh%Pk67}W5Rr#D! z5#f`$5=1M3*OFpzOW?JoadOAo?VT9#?y-!K9g#1*^Mewelu@#WTkj4IdLT#Ti1)#} zJvjC}Ss}~3THc2tk=tdaoQ%97c}E@ymf^C`OX9s5<-L*PJ(d-{}&lndUs-jDmvoRsRb)ww%G6j3VUTs+~FCu>yN$k~;zH-Wo<#_MB`m$Bgy(!M|+g<~irNr_k zJ&oacjg=VO8W}X~rq@Jz$tABNx)B!@g86 zu6wPRpY4%zW0<+U*3nme@$-0XWGFLyJ;lR&TpcS1Bm2a89-FJ<QuSum3tP)@cK#jX2#tuA{)!dz?BA`D`RimetfZoS6oKjOAz{V zC9kl|y`L!2^g7-kng1-lv$iQ6PmGjPX9`4Ck-6yDdY1$VbRAN zQaAPchce&GJEU3i2Ulg0S7>O*bdO%hP_OH-uaZ4~E5idlH1^&4ld8QB36a~8$0qd)^=`|$ z$URfqC42i`W_szSbbS@uFGC{#_`YZwQz2u%@4pGVb4!+c!Bgv5zUMO6t23=hmf!cw zbkCU9A;x2&!PwIqb0#W7ydKlr#k&4cMnvwL-cfs&P%cIOHoZ%Hz6xoO7crw)$|Uj0 zp*y2N*pvHYwd9O;@A;BZwO+q{N6STL)^(H@vLWh<@rDMSSryDT_hhmcHmjl& z^9`K_uEcrE087nksc1fy`H!>f-AG@Gmd)91Uzw?7ROHQhO~P*7V^R{$YyL;P6f!IF z&isa9Z=TB1$aeGFAC0I+>Pj!D8s6)r{DFW-6jmX%Bv^hO4HLCdQpe(|Tw@nSEp6RJGtuA0m1 z-i(aT?(k-L$6K!-$uO_dih@ZKdQ>j8qGF7WRB$++e z*7Ci$LwdWet@ZTdQ$nq^zs6J7HI4W25oxKpzEnc8I?$^ZePu}0 zEiqoRqN;2te=A9HLM^qSYVeyUvNiJkhE{Pu1jgQUbDei@KC+^kY;LCAILkt+x~077 zz2M=c#+Dj!y{Fs~w6);YqUG3O!?(7%b}t@VeY35C5gAw#&9*iA@ZdI`_S;_ml&%D} z+EGWY?q?VNYDYum$vqy6JMZ}R=eMtArL<*5zD|)BjY}N6%x*Ca?IZ*887bXO_dy)bc%f#L8@QKIUB&1dX6UPqr^#Q3=M<5IPb$j7CTU#s=r zz2C*Lj&Q9b=VM!+Xc^Xy-e8p?ZfLAI+ZZ%JBm_VFdZ>@p;*9&HQ!||QKPm^*7va!l*yGu8BKe?uQjGN!z+%<&UO0F;oJmy9yWcm8CFeSH)~nP!96l^PeSQ5?HxM zu2-qkAa75}-!!Zc5!y3pWl&uLkM5cK9F@ue<4=-Iom>R?PbvBC_ysWSr;^othKLCL zG-+i(Q%W@LoxPDlh#_KM(Zg<0EGYI(eagd+gzSAYHYoCogQ(tZ!jIW+6p0I*=wVNDKQ z69+R7XDy2$2Z{c}#S?vk0!Tfgp+yKLLD7+%>12u+Rv*cIM1k_pmLr90`RM*FKT@`G zn5%`YN6NO!2I^qrk@C$XDh`_vM`h8l`N-s#M1%uY9-01xuzwgTkIdf8w)8;pA8DI> zv&*3LkJ9ZUVgYRWV`4eU7Y$GTF=>T(XcS0)R6gFQNCz%rZ_Gr<`D5mEQYZ!Ow_vk9 zAcOTsW$`LH4K^LE*g;VIu;Cadi=>mo>SH-2TucRvj}<;D4R|5?*h9}XDU+b`*rKPM zqiFkMk8E(6N$|*@ViiMQ2swYoREV@Xc@AV@t8uY2t9v9ickYnkEcv_iw#hEJZq_f93qB~=RbC-co_(f7o@k+r-I;k;fx@z zfk%%QF7}c*u;ciI4L$}J9ywmVnvZ%MFCH&{sy8AIrXMeVu2rRjhmKb~!s06+?Rdr3 zL9q%Qcg~Z-QGnv(^EXmP1dw?GSJ6$`5P3o&a3wB>IVbX0$+MbFcQ9I)YJ>LM961O+G4A62kPka{w{>w7fzO} z61p8>ZWWR}U_4p&yo4Eqyp!vP0xvVb3+J5{<`=o*EEr%Im+p!#U$sj@Wj zXf(_?Rkn3lriMwU=1d&2#sYpiuh$6g~{pIH}7P^#%Fdud)bx?LLYibDNjuFPWJcP>S=5yK0#OS#w&Sh`Am6QbO z=L%=k#m$2k&Xum_qx<*xxk-z-T|=<#+>{*wDitQ3oAG2bTTi@mUY9>ux&}<=4I5qJ z5twy8G0!DUh0W&^XR;_En0MY(;Y0bY;(X>Jry>TnozI>l@}TVO`ARh(wp>seBBIh^-UU+yADxHtLTa{CodYv2lx=N8 z&p+Zq#q)Pf#qi>V$?I#HhN1kT9jK_@dEp|@#L%i>{Y7ai9hDpA#ptZw*a@)kVoZY2 zp@C%=<5!GoWw7I7%3>bH3$lxOlY0cIP;jw)MZGi)W?r1Q#ESZr+b*e7loTq_dntKg zec^nVc`0?WmnI~tFJ%{Y33CCvlrxEpt5JH+0uPM~6_;{ROOgf8Tq@ZJ;DX7Q7R<1A zuwnJ3_1lN4yJ6C$=eG_tqjGXNf1#Txhes|KJR&4hVdLeZM;NF)JaMJ9K|XmQXs#&I zT!}?6?Mg(JEjk6By^=CZjD=w4mGn6Y^(Vn_rC@$TWHDr4nUPItWWn|;bDzFIjew`G zMr2AOq9N|8rrcRv3C~?kn=eFTHCwOdEa3-zVXokV4|ZI$PW4O{z{A(H2{bfRvGJNV z%_fZw#|vp(bpF?3ilk%`th|;{oN;kB$KbVnaieJ#7N16}v?*NT?((Ik*^U7YXL zNQu|3tK%6p9Gt3u-F_$5^?8-sGLzT2Ug!uuXa8pf~_}nc@ioGmfg@7arhkAb|bD* z1RjXJkutSYQvvZeGN*T{%Tan(o(R2H5KBb;c363%XyqVZMFek@ZEuS-z`7fi&oF(X zM8}QF=kBWG;nADC7>Zg4vv10yX{eq?Orf`S4J6Ohu0-7B*GK z&lRHjeRFkUC4tk?`o!s6Dh8{oO|u(NJ&;*#n%^Z;!o$_c1s<+}sIN|$(1hyUmDOp} zM(6^fsXA?@b>b5cTb((#LuCT2y5RY0gKmsXh>us7JU+tYqTijgmFYw8jq1sjo)Hn0 zRxf@5>*|H6w;2&~k$|{!TbI>cx*E3JPMOC?_1CJ~>6NV_BUIkbS?!55!i?Jm)7>-; zyigNu8f7YoGc_?~&4mxcvo&$K0)J?n1P&W(j1wqq5oFXPOdOHL5Vo4ctg4dd;NhC2 zB#U|)?5Ihq;01!=_#%rs5?0luSGJ(zJX4c7wU3Dg<7x_1ZJwAgXV^S6w0+q!hSd(M zYuOrRd>*W-9(DjHQ4!LnLSs!tFBlWMidK|BDn5nU1qtf-C6adI_bmN?P- z46$Mm)k|SI5wmLJGF@~9EJqB{P`ju$E-!@2C1Np7W5Y^BzW}udYU4`X=z7*7hRY#Z zKha5}682i-gyY3)U}RBzPS7$_gt8}qbm6+BaK%x92Mzo0%L)lF4E zZhhjUThV#&XnoSeL6!ubtWTTS!jU1SFYIM=AhtehaR;h@=G2$1X_LpJ-=A2?rmA7t z-N;l2FM{yjjWP8r%i;OEarwMJ2y9cD zp1WJRfgW^0(cOpU54q(qr-4Kc=+OJOK{3hCb;H&MZKW?Z1xgwsOZyZm5HuJn>Jv*~ zM?+kpi^_)FhJ-R3VthjqK1xIRp}|z*rl}#ZA!Sy(G8R@hs&syt5!N;uBJHT1FuiGT zq`P7zBs8(8eqH$(Nr+E0DN33mM6jw!73-D9z}hBl8JkCgSxts1JyIzYHW`gpiWb&1 z#TNt=$?#NDa<&5#!{Vk%+pZZ3V0AM;DkMei(Pq9bi26bEn}rgWA_e9)i(~EDL`0dw ztx2Qj5trFMx@;NfOXwo8cmrOi?0Hhl`=ZPuGcBC_D|W_^B-P>C2_*e#I5wC3mu zZg4|#bBxg~$%XaJ#sve3Sum?PWfHE_B+X-^cHf+qg5?a<-b`&(D8udA{Ch<4crWUQtZ9u(rqj@SwpC}c^Yvp4Ay%~N zitgxBA-h#C<pS%Uuwnpn6Of{@(HDnVE2`F2$77n8N^!e7g#hu++Vx(=j zi(0T8R<+S3mefg*+{Tdhs|>KREkf&-C&9(G$gChLXB*q1a>C_zQ=1`k1l1pF+qH#s zHVsnRqt$Ls8mwxMRoc;eZF;+DYO6X4o@&ouUBwl`o%Vu7?OqJT?G>}?@HklCKJVEY zn+H~OaJ6o&5!Q8x)Lw~xjJ|vHk5k((i3+D{efLP*BNg!plhu242B&sp?-edng z+V9bQkG^~K-y>OTfTub{nIqBBu&_fe^9d4Qd52sTZoe<>kgG?8v0+9H3-xGyWLA$< z2g;84qIN+PZ0<-(H;*t;x+$xRj@sS#_;5$^^d7eh7Ie&A(Q5HQddIU#1TqFicXG8Z zQy#4E6l*=ADB?n=GV*)|vVe4|v_902SkkFm_-l#@9`B4Vw=sF}WM}*|7LM|RnQRS4 zRl*}?UZhiF3^U4!-uI6o4jFU9@j|(V4QtK3=_9cWcmc7#vIIzGQTe&KtI&25tw*c_ zhFKOFP-ntiv(i+TH~|XHYGbdi0RCoH7u1-F;3>1ZvQ^F{TFn~a#e~%`$*fK4N7wnN zS(|5NF=4VfHqp$9fgR?!91BMS8_n_g-3-*;HK%4+C^}eVPAxQx;^7GkHF6{_4i;FL zQZ|(YS__YFi=6~hEqrA+de3jR@O4%N%C{DoaTxWxGc9V(2r6gGEpcVTGzL6riJ!(m z*NwP>!LSiM7L&#yng-e~9^Z<7C!&kbcdBxTkGq7@=G;~Ac$YNBDU1MRH(wBL$A`&B z%<5KVR;QN32E<+-2bOe4s2r$2u&rAw0cs4qbdOuQqcVXjL+Sc#H=763yJM3&xki|S z*e=Zq$4fhPg|MjGXtJ;jFsnN&(QlUky*nrEdfYprVPZ*gva`_rdbIFJk>Yl zr8*pqYxVO5K2#4OMg&p+e_6jEy3?qKMg3D(Tx1%dY=Fra)h5Hn0abJ#+HTE2x?sqj z4QmGd5sqXdFa`;td&)EL^q@%V7plUH@}c?_G1e~>hnejcQb02()($J;A!SgkJC{BS z)(%RHgL2f)9hBz|@^Hk6xPF=vmJLSfdr-cr98At=3yFa}#1ps3jf7`NBB9IEVZo3z zs#9)+v>|n7TXY7Ph87ps^(MjN!z`vx7!#(%^DTFFd~!D zd=ywW5})tz!x){2n@8g3k5FB}AIV*C8yO$BT3OOQm7effrR4*~B{0c4WxaRU3hQic z`t^!M@HZPzJSdBY7#mO35oLmxY?_odaU^WBX%mM~KQ7A_xAg+o43F62lbXm8FwvGU zxfYH0tg~fhUB?q(f;~2$tT4f}QIE32q$het8IqcWVwi(0l-H9gVKL%0W9bwb=?D5lxHiy+Z zQ1-|ehsz#;L`N{OZTehDb}+c@sGTWuFdjNsnh4vS0-=i)0be>J489Ns)17&XKTBT- zYh6@^8`a~HE-JG>CJ!dN7>pj&zkSK2NwcB)=UG>D`XE~Yt6llatD+JKaJ!tg?76Vb z&0uvzB*PRpS7Jf?Z*y}EG!YNQT8~>s9Fe0s}njjY{w1p>pTdXZDEn@Hel% zpcnO5P`sc=qzbd7N2EYB7W8mb#7%E;_DMBpSnH?y!tF0ah85~6rd~3y7(|7&AMp96Hvz-ikk{6Lln**Wvk%1 z5S4l-WfuG`MAZ&T*<&2KN57O!3=(dQ5AAshDK+mS_gifhlC835%cri`ej+Y5-Yd)M{T>7o5e^ z@tq_(Y{qn{=TP~41=FWh(RgqUi!W#nl8FEbmkyx!XfB!Xoj~@4&188*rv$wh$jX?X zg;9`5)|sxL{79n^!Lwd6jn1emTnouGdTec8 zWjG#JS6B&anYy_<6C#Nbmb{`prvP4L3skk}eUr=$Ic_J;gk?Obte-9?8u=3^d<{vk zNn~4bsN4lJ#n!G{@fCneqx5a;7-BG@udk+f0X!GQjNPZ#!9;!j>i1ASTc2vuA7STV z=nJ!B5|^+I>7E@5`{I?y&#;I3?y(=c{@V6mq}4xTKfn3-t-RV}W2`_t_Wl!Z6X^Rx zd*9!9Ec@C)?A6cbeHvl6Vy}I^{JR7TGOB&P{Cjm*FShgRMXwN}{n+8JH@vPI?ZI|_ zv*?wOV*q>m`vrjj&c@yyBXf*FoQb`U;%E#2`y@BQc@J-@RF$&~(Cv+ONTS0nc4-6w_{ z&U}u&)3ENezSe^M+_Iy`g62H_sBOg?LpKg%Z*|PJJ6g_;(S!I!_qr}`b2au}f9ds; zXkORXhF5$$(%FrDW82c$bgLRW=-e@6KKcjt57+EFy90de9B2E@x9d1LU5R1x#b5G7%VkNR#qS2u(7*=JR5XS9sG>l^qVG<^2$(#sV$yhat ztxv#ev}vRvRKWh1BI+L`>9xuz-J7KkeaKK$>@+;uT*>JDG=J)ML#eC3l6hX!8rFZM zPI^rnnfbMb^?Dkw=v#g1NfWrgoA<;kY%Aq^1eT5Z!{Wz&5x0%*GMfIOV#n^vS>G0A zZ`_@~q%pm_A&e}98CyCO;M^CX|3InD-#2s372F-%w_yEYWrHP*s0Pb^VSGKBlLFbK ze#D*RL#^1m^hf6%n)>3eo`Ca6;)2?w(Ywe#^hQBs`jNTYM(H8fAKEQ1JChiH6fX7B z+{1s&d&Wr%IF17C13h1Ol&Ahk%VZtH+3)LE++$+hM{1mQEN<#~UeJ21qT;K|+NH-6 zC+<>{aRj@XEjgY%^KBN3bv(852cA@Zy!?q*ahL6cSbu=e=AYO&>pj**(n)Rl89jx1 zs&rwH{u8Df3QAyWFQ2KldT@jh?}<59E;)zhj=Ha|MfDCA+=niWYy*a(T}yb6;j~@blWd_c<)) zdHsT~g9-ZcdF%fqynW~MAKuHO3(n`S-|MH7&*yL2n@ta$FJ1L1%V|G9cjGJW0rv&R zXpIg;7jou(U>3(-$X~u!O6OjfI`K`Cmv_++`b`wdyO=OzSZZs%n7i`j0g>`zUZ(4t zVfMwm6|HSJ{gO7P-(c&wl$iK~+{w6l)i- zj_OT{A>(q&)GL%AT+Uy*(}uGzmv4N_5ei+A=mr#2+Le?UE&U|Ym6Una4tobe{itsQ zp{K_?bR~V!6}r!QC4J2mx6d2~s#&g7Y2m(n z+=oALhP>BE$?uP9DA&mT_j&4^YYc`(B@|qXE!!nxaj#`8d&MeKUCT;!!wBnod#;aB zNxvSOzl#$LUXL%g(nkBQCzphC-Sw0i*N#6(WZ^+r)n~^2|8da!o#+9Gud3`ri%BmT5XBg};I4FMd z&6EkZg`+63{5EaW5r!~~8MRcm6`{`Su^}`K5y3EAVa&bF9kqlZKu9mI;|&ep%vrw6 z?RVTPSnXlB25uJ5Iq7l`Hz$|fC3lc+&Y64G)E=sG!$0`3DOEyA1DPLHWh~zV40e@q zG6JnCeiG2UwlL@jsS|2&yR#~Fj-Tlos7jqz6B_NR%9wTCPmro|=Nx3aytf2VU*Tx} zme{;kM?^8V;wJpSV)1U}Yl;0bevx; zRHK-pTBvCV@#WRg1${KXvpRkXFTi3|Crsse8H_LjOlEb$G@g4LUWBBX-(l!8fhn^N zGDq60Q|5la1#flHBX4+^c=e1ITYCrEZlCY?&bx8bZEf@qJXf#=!+uuC4K+gP6}vVU zp$*6RH9~b88E4eUf-#c(`P2RN60#;8= z$zvDW$&{MX)t_KPT{Wd^clP0|nt7{Uz3!6M#ua#kBYm}5i@&lGfjU0=)>>Lwoj9sq zMxoS+js19Fq%L9d8!Q^FZql4LN$mr7cv&MVSM{Cv^1Yh&>v!TOeUJLt2tST+#di|Y zza^1n^?c0*37uZA$@)TsQ|k5k*GLeok4^fM=A+lgW$#2YiPy)K?F>*FT; z>Y=mh<0oFgsnq(UiXoS4v_56(ZK;=8pFi^!b;MDh|Hv)ch`D~ss?WXlAVRa#7O0={ z42t!KAr0eM6zi*>vK^uKZpxz1X)Nm9!ZrV<1zZhLIp4TMss?@D>t4RRA#TENE;_3r z?om!B*V$mqKTq@e8xo>-gHPU&P;ij!3pHd{{^B3?H)LBorfs5kyERe%s2FHfysxz3#zgT@lKW^5(?KLtY5A=q9xI14AwGvA^KG z&X%O8S7{xNR-d<#%a*rB7_SB?^j3ZGYePa=Yx499w9&rS(wSdE_i$_J%C{QSS#4C( z`9MT!8LqcU}ODuZb z%8<8Bd}`-N*X_1>FMM>z9c*VRJ|~GJ?R@zM6an%pjQK>!=eDbj-;t>7c5Ol@?)J88 z6FaHiF|fl(H&X&`1WO={>`sPvxIJpZ=K&_EU6(TuMBkarE_vO1In