diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index 5be1ac21b3..420633a0f6 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -11,9 +11,9 @@ body:
## Important Notes
- - **Before submitting a bug report**: Please check the [Issues](https://github.com/open-webui/open-webui/issues) and [Discussions](https://github.com/open-webui/open-webui/discussions) sections to see if a similar issue has already been reported. If unsure, start a discussion first, as this helps us efficiently focus on improving the project. Duplicates may be closed without notice. **Please search for existing issues and discussions.**
+ - **Before submitting a bug report**: Please check the [Issues](https://github.com/open-webui/open-webui/issues) and [Discussions](https://github.com/open-webui/open-webui/discussions) sections to see if a similar issue has already been reported. If unsure, start a discussion first, as this helps us efficiently focus on improving the project. Duplicates may be closed without notice. **Please search for existing issues AND discussions. No matter open or closed.**
- - Check for opened, **but also for (recently) CLOSED issues** as the issue you are trying to report **might already have been fixed!**
+ - Check for opened, **but also for (recently) CLOSED issues** as the issue you are trying to report **might already have been fixed on the dev branch!**
- **Respectful collaboration**: Open WebUI is a volunteer-driven project with a single maintainer and contributors who also have full-time jobs. Please be constructive and respectful in your communication.
@@ -21,6 +21,8 @@ body:
- **Bug Reproducibility**: If a bug cannot be reproduced using a `:main` or `:dev` Docker setup or with `pip install` on Python 3.11, community assistance may be required. In such cases, we will move it to the "[Issues](https://github.com/open-webui/open-webui/discussions/categories/issues)" Discussions section. Your help is appreciated!
+ - **Scope**: If you want to report a SECURITY VULNERABILITY, then do so through our [GitHub security page](https://github.com/open-webui/open-webui/security).
+
- type: checkboxes
id: issue-check
attributes:
@@ -31,6 +33,8 @@ body:
required: true
- label: I have searched for any existing and/or related discussions.
required: true
+ - label: I have also searched in the CLOSED issues AND CLOSED discussions and found no related items (your issue might already be addressed on the development branch!).
+ required: true
- label: I am using the latest version of Open WebUI.
required: true
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml
index 4f159f4faa..05dc6cfa94 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yaml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yaml
@@ -8,11 +8,21 @@ body:
value: |
## Important Notes
### Before submitting
- Please check the open AND closed [Issues](https://github.com/open-webui/open-webui/issues) AND [Discussions](https://github.com/open-webui/open-webui/discussions) to see if a similar request has been posted.
+
+ Please check the **open AND closed** [Issues](https://github.com/open-webui/open-webui/issues) AND [Discussions](https://github.com/open-webui/open-webui/discussions) to see if a similar request has been posted.
It's likely we're already tracking it! If you’re unsure, start a discussion post first.
- If your feature request might impact others in the community, consider opening a discussion instead and evaluate whether and how to implement it.
- This will help us efficiently focus on improving the project.
+ #### Scope
+
+ If your feature request is likely to take more than a quick coding session to implement, test and verify, then open it in the **Ideas** section of the [Discussions](https://github.com/open-webui/open-webui/discussions) instead.
+ **We will close and force move your feature request to the Ideas section, if we believe your feature request is not trivial/quick to implement.**
+ This is to ensure the issues tab is used only for issues, quickly addressable feature requests and tracking tickets by the maintainers.
+ Other feature requests belong in the **Ideas** section of the [Discussions](https://github.com/open-webui/open-webui/discussions).
+
+ If your feature request might impact others in the community, definitely open a discussion instead and evaluate whether and how to implement it.
+
+ This will help us efficiently focus on improving the project.
+
### Collaborate respectfully
We value a **constructive attitude**, so please be mindful of your communication. If negativity is part of your approach, our capacity to engage may be limited. We're here to help if you're **open to learning** and **communicating positively**.
@@ -23,7 +33,6 @@ body:
We appreciate your time and ask that you **respect ours**.
-
### Contributing
If you encounter an issue, we highly encourage you to submit a pull request or fork the project. We actively work to prevent contributor burnout to maintain the quality and continuity of Open WebUI.
@@ -36,14 +45,22 @@ body:
label: Check Existing Issues
description: Please confirm that you've checked for existing similar requests
options:
- - label: I have searched all existing open AND closed issues and discussions for similar requests. I have found none that is comparable to my request.
+ - label: I have searched for all existing **open AND closed** issues and discussions for similar requests. I have found none that is comparable to my request.
+ required: true
+ - type: checkboxes
+ id: feature-scope
+ attributes:
+ label: Verify Feature Scope
+ description: Please confirm the feature's scope is within the described scope
+ options:
+ - label: I have read through and understood the scope definition for feature requests in the Issues section. I believe my feature request meets the definition and belongs in the Issues section instead of the Discussions.
required: true
- type: textarea
id: problem-description
attributes:
label: Problem Description
description: Is your feature request related to a problem? Please provide a clear and concise description of what the problem is.
- placeholder: "Ex. I'm always frustrated when..."
+ placeholder: "Ex. I'm always frustrated when... / Not related to a problem"
validations:
required: true
- type: textarea
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 0ec871f328..75b9359168 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,16 +1,18 @@
# Pull Request Checklist
-### Note to first-time contributors: Please open a discussion post in [Discussions](https://github.com/open-webui/open-webui/discussions) and describe your changes before submitting a pull request.
+### Note to first-time contributors: Please open a discussion post in [Discussions](https://github.com/open-webui/open-webui/discussions) to discuss your idea/fix with the community before creating a pull request, and describe your changes before submitting a pull request.
+
+This is to ensure large feature PRs are discussed with the community first, before starting work on it. If the community does not want this feature or it is not relevant for Open WebUI as a project, it can be identified in the discussion before working on the feature and submitting the PR.
**Before submitting, make sure you've checked the following:**
-- [ ] **Target branch:** Verify that the pull request targets the `dev` branch. Not targeting the `dev` branch may lead to immediate closure of the PR.
-- [ ] **Description:** Provide a concise description of the changes made in this pull request.
+- [ ] **Target branch:** Verify that the pull request targets the `dev` branch. **Not targeting the `dev` branch will lead to immediate closure of the PR.**
+- [ ] **Description:** Provide a concise description of the changes made in this pull request down below.
- [ ] **Changelog:** Ensure a changelog entry following the format of [Keep a Changelog](https://keepachangelog.com/) is added at the bottom of the PR description.
- [ ] **Documentation:** If necessary, update relevant documentation [Open WebUI Docs](https://github.com/open-webui/docs) like environment variables, the tutorials, or other documentation sources.
- [ ] **Dependencies:** Are there any new dependencies? Have you updated the dependency versions in the documentation?
-- [ ] **Testing:** Perform manual tests to verify the implemented fix/feature works as intended AND does not break any other functionality. Take this as an opportunity to make screenshots of the feature/fix and include it in the PR description.
-- [ ] **Agentic AI Code:**: Confirm this Pull Request is **not written by any AI Agent** or has at least gone through additional human review **and** manual testing. If any AI Agent is the co-author of this PR, it may lead to immediate closure of the PR.
+- [ ] **Testing:** Perform manual tests to **verify the implemented fix/feature works as intended AND does not break any other functionality**. Take this as an opportunity to **make screenshots of the feature/fix and include it in the PR description**.
+- [ ] **Agentic AI Code:** Confirm this Pull Request is **not written by any AI Agent** or has at least **gone through additional human review AND manual testing**. If any AI Agent is the co-author of this PR, it may lead to immediate closure of the PR.
- [ ] **Code review:** Have you performed a self-review of your code, addressing any coding standard issues and ensuring adherence to the project's coding standards?
- [ ] **Title Prefix:** To clearly categorize this pull request, prefix the pull request title using one of the following:
- **BREAKING CHANGE**: Significant changes that may affect compatibility
@@ -75,3 +77,6 @@
### Contributor License Agreement
By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](https://github.com/open-webui/open-webui/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT), and I am providing my contributions under its terms.
+
+> [!NOTE]
+> Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.
diff --git a/README.md b/README.md
index 49c0a8d9d3..52a3821aa5 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@ Passionate about open-source AI? [Join our team →](https://careers.openwebui.c

> [!TIP]
-> **Looking for an [Enterprise Plan](https://docs.openwebui.com/enterprise)?** – **[Speak with Our Sales Team Today!](mailto:sales@openwebui.com)**
+> **Looking for an [Enterprise Plan](https://docs.openwebui.com/enterprise)?** – **[Speak with Our Sales Team Today!](https://docs.openwebui.com/enterprise)**
>
> Get **enhanced capabilities**, including **custom theming and branding**, **Service Level Agreement (SLA) support**, **Long-Term Support (LTS) versions**, and **more!**
@@ -65,43 +65,6 @@ For more information, be sure to check out our [Open WebUI Documentation](https:
Want to learn more about Open WebUI's features? Check out our [Open WebUI documentation](https://docs.openwebui.com/features) for a comprehensive overview!
-## Sponsors 🙌
-
-#### Emerald
-
-
-
-
-
-
-
-
-
-
- Tailscale • Connect self-hosted AI to any device with Tailscale
-
-
-
-
-
-
-
-
-
- Warp • The intelligent terminal for developers
-
-
-
-
---
We are incredibly grateful for the generous support of our sponsors. Their contributions help us to maintain and improve our project, ensuring we can continue to deliver quality work to our community. Thank you!
diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py
index f7926abe85..d0a76e0238 100644
--- a/backend/open_webui/config.py
+++ b/backend/open_webui/config.py
@@ -576,19 +576,26 @@ OAUTH_ROLES_CLAIM = PersistentConfig(
os.environ.get("OAUTH_ROLES_CLAIM", "roles"),
)
+SEP = os.environ.get("OAUTH_ROLES_SEPARATOR", ",")
+
OAUTH_ALLOWED_ROLES = PersistentConfig(
"OAUTH_ALLOWED_ROLES",
"oauth.allowed_roles",
[
role.strip()
- for role in os.environ.get("OAUTH_ALLOWED_ROLES", "user,admin").split(",")
+ for role in os.environ.get("OAUTH_ALLOWED_ROLES", f"user{SEP}admin").split(SEP)
+ if role
],
)
OAUTH_ADMIN_ROLES = PersistentConfig(
"OAUTH_ADMIN_ROLES",
"oauth.admin_roles",
- [role.strip() for role in os.environ.get("OAUTH_ADMIN_ROLES", "admin").split(",")],
+ [
+ role.strip()
+ for role in os.environ.get("OAUTH_ADMIN_ROLES", "admin").split(SEP)
+ if role
+ ],
)
OAUTH_ALLOWED_DOMAINS = PersistentConfig(
@@ -3336,6 +3343,10 @@ DEEPGRAM_API_KEY = PersistentConfig(
os.getenv("DEEPGRAM_API_KEY", ""),
)
+# ElevenLabs configuration
+ELEVENLABS_API_BASE_URL = os.getenv(
+ "ELEVENLABS_API_BASE_URL", "https://api.elevenlabs.io"
+)
AUDIO_STT_OPENAI_API_BASE_URL = PersistentConfig(
"AUDIO_STT_OPENAI_API_BASE_URL",
diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py
index 9998af0e73..105fc5c337 100644
--- a/backend/open_webui/main.py
+++ b/backend/open_webui/main.py
@@ -482,9 +482,11 @@ from open_webui.utils.auth import (
)
from open_webui.utils.plugin import install_tool_and_function_dependencies
from open_webui.utils.oauth import (
+ get_oauth_client_info_with_dynamic_client_registration,
+ encrypt_data,
+ decrypt_data,
OAuthManager,
OAuthClientManager,
- decrypt_data,
OAuthClientInformationFull,
)
from open_webui.utils.security_headers import SecurityHeadersMiddleware
@@ -1556,11 +1558,15 @@ async def chat_completion(
log.info("Chat processing was cancelled")
try:
event_emitter = get_event_emitter(metadata)
- await event_emitter(
- {"type": "chat:tasks:cancel"},
+ await asyncio.shield(
+ event_emitter(
+ {"type": "chat:tasks:cancel"},
+ )
)
except Exception as e:
pass
+ finally:
+ raise # re-raise to ensure proper task cancellation handling
except Exception as e:
log.debug(f"Error processing chat payload: {e}")
if metadata.get("chat_id") and metadata.get("message_id"):
@@ -1591,7 +1597,7 @@ async def chat_completion(
finally:
try:
if mcp_clients := metadata.get("mcp_clients"):
- for client in mcp_clients.values():
+ for client in reversed(mcp_clients.values()):
await client.disconnect()
except Exception as e:
log.debug(f"Error cleaning up: {e}")
@@ -1937,6 +1943,7 @@ if len(app.state.config.TOOL_SERVER_CONNECTIONS) > 0:
if tool_server_connection.get("type", "openapi") == "mcp":
server_id = tool_server_connection.get("info", {}).get("id")
auth_type = tool_server_connection.get("auth_type", "none")
+
if server_id and auth_type == "oauth_2.1":
oauth_client_info = tool_server_connection.get("info", {}).get(
"oauth_client_info", ""
@@ -1982,6 +1989,64 @@ except Exception as e:
)
+async def register_client(self, request, client_id: str) -> bool:
+ server_type, server_id = client_id.split(":", 1)
+
+ connection = None
+ connection_idx = None
+
+ for idx, conn in enumerate(request.app.state.config.TOOL_SERVER_CONNECTIONS or []):
+ if conn.get("type", "openapi") == server_type:
+ info = conn.get("info", {})
+ if info.get("id") == server_id:
+ connection = conn
+ connection_idx = idx
+ break
+
+ if connection is None or connection_idx is None:
+ log.warning(
+ f"Unable to locate MCP tool server configuration for client {client_id} during re-registration"
+ )
+ return False
+
+ server_url = connection.get("url")
+ oauth_server_key = (connection.get("config") or {}).get("oauth_server_key")
+
+ try:
+ oauth_client_info = (
+ await get_oauth_client_info_with_dynamic_client_registration(
+ request,
+ client_id,
+ server_url,
+ oauth_server_key,
+ )
+ )
+ except Exception as e:
+ log.error(f"Dynamic client re-registration failed for {client_id}: {e}")
+ return False
+
+ try:
+ request.app.state.config.TOOL_SERVER_CONNECTIONS[connection_idx] = {
+ **connection,
+ "info": {
+ **connection.get("info", {}),
+ "oauth_client_info": encrypt_data(
+ oauth_client_info.model_dump(mode="json")
+ ),
+ },
+ }
+ except Exception as e:
+ log.error(
+ f"Failed to persist updated OAuth client info for tool server {client_id}: {e}"
+ )
+ return False
+
+ oauth_client_manager.remove_client(client_id)
+ oauth_client_manager.add_client(client_id, oauth_client_info)
+ log.info(f"Re-registered OAuth client {client_id} for tool server")
+ return True
+
+
@app.get("/oauth/clients/{client_id}/authorize")
async def oauth_client_authorize(
client_id: str,
@@ -1989,6 +2054,41 @@ async def oauth_client_authorize(
response: Response,
user=Depends(get_verified_user),
):
+ # ensure_valid_client_registration
+ client = oauth_client_manager.get_client(client_id)
+ client_info = oauth_client_manager.get_client_info(client_id)
+ if client is None or client_info is None:
+ raise HTTPException(status.HTTP_404_NOT_FOUND)
+
+ if not await oauth_client_manager._preflight_authorization_url(client, client_info):
+ log.info(
+ "Detected invalid OAuth client %s; attempting re-registration",
+ client_id,
+ )
+
+ registered = await register_client(request, client_id)
+ if not registered:
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail="Failed to re-register OAuth client",
+ )
+
+ client = oauth_client_manager.get_client(client_id)
+ client_info = oauth_client_manager.get_client_info(client_id)
+ if client is None or client_info is None:
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail="OAuth client unavailable after re-registration",
+ )
+
+ if not await oauth_client_manager._preflight_authorization_url(
+ client, client_info
+ ):
+ raise HTTPException(
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+ detail="OAuth client registration is still invalid after re-registration",
+ )
+
return await oauth_client_manager.handle_authorize(request, client_id=client_id)
diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py
index cfcbc004b7..3ae7112f3e 100644
--- a/backend/open_webui/models/chats.py
+++ b/backend/open_webui/models/chats.py
@@ -440,7 +440,10 @@ class ChatTable:
order_by = filter.get("order_by")
direction = filter.get("direction")
- if order_by and direction and getattr(Chat, order_by):
+ if order_by and direction:
+ if not getattr(Chat, order_by, None):
+ raise ValueError("Invalid order_by field")
+
if direction.lower() == "asc":
query = query.order_by(getattr(Chat, order_by).asc())
elif direction.lower() == "desc":
diff --git a/backend/open_webui/models/oauth_sessions.py b/backend/open_webui/models/oauth_sessions.py
index 81ce220384..b0e465dbe7 100644
--- a/backend/open_webui/models/oauth_sessions.py
+++ b/backend/open_webui/models/oauth_sessions.py
@@ -262,5 +262,16 @@ class OAuthSessionTable:
log.error(f"Error deleting OAuth sessions by user ID: {e}")
return False
+ def delete_sessions_by_provider(self, provider: str) -> bool:
+ """Delete all OAuth sessions for a provider"""
+ try:
+ with get_db() as db:
+ db.query(OAuthSession).filter_by(provider=provider).delete()
+ db.commit()
+ return True
+ except Exception as e:
+ log.error(f"Error deleting OAuth sessions by provider {provider}: {e}")
+ return False
+
OAuthSessions = OAuthSessionTable()
diff --git a/backend/open_webui/retrieval/loaders/main.py b/backend/open_webui/retrieval/loaders/main.py
index 2ef1d75e02..e93d443bf6 100644
--- a/backend/open_webui/retrieval/loaders/main.py
+++ b/backend/open_webui/retrieval/loaders/main.py
@@ -272,7 +272,6 @@ class Loader:
loader = TikaLoader(
url=self.kwargs.get("TIKA_SERVER_URL"),
file_path=file_path,
- mime_type=file_content_type,
extract_images=self.kwargs.get("PDF_EXTRACT_IMAGES"),
)
elif (
diff --git a/backend/open_webui/retrieval/loaders/youtube.py b/backend/open_webui/retrieval/loaders/youtube.py
index da17eaef65..cba602ed87 100644
--- a/backend/open_webui/retrieval/loaders/youtube.py
+++ b/backend/open_webui/retrieval/loaders/youtube.py
@@ -83,6 +83,7 @@ class YoutubeLoader:
TranscriptsDisabled,
YouTubeTranscriptApi,
)
+ from youtube_transcript_api.proxies import GenericProxyConfig
except ImportError:
raise ImportError(
'Could not import "youtube_transcript_api" Python package. '
@@ -90,10 +91,9 @@ class YoutubeLoader:
)
if self.proxy_url:
- youtube_proxies = {
- "http": self.proxy_url,
- "https": self.proxy_url,
- }
+ youtube_proxies = GenericProxyConfig(
+ http_url=self.proxy_url, https_url=self.proxy_url
+ )
log.debug(f"Using proxy URL: {self.proxy_url[:14]}...")
else:
youtube_proxies = None
diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py
index 69aee29ac2..da570330b3 100644
--- a/backend/open_webui/retrieval/utils.py
+++ b/backend/open_webui/retrieval/utils.py
@@ -71,6 +71,7 @@ def get_loader(request, url: str):
url,
verify_ssl=request.app.state.config.ENABLE_WEB_LOADER_SSL_VERIFICATION,
requests_per_second=request.app.state.config.WEB_LOADER_CONCURRENT_REQUESTS,
+ trust_env=request.app.state.config.WEB_SEARCH_TRUST_ENV,
)
@@ -668,46 +669,51 @@ def get_sources_from_items(
collection_names.append(f"file-{item['id']}")
elif item.get("type") == "collection":
- if (
- item.get("context") == "full"
- or request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL
+ # Manual Full Mode Toggle for Collection
+ knowledge_base = Knowledges.get_knowledge_by_id(item.get("id"))
+
+ if knowledge_base and (
+ user.role == "admin"
+ or knowledge_base.user_id == user.id
+ or has_access(user.id, "read", knowledge_base.access_control)
):
- # Manual Full Mode Toggle for Collection
- knowledge_base = Knowledges.get_knowledge_by_id(item.get("id"))
-
- if knowledge_base and (
- user.role == "admin"
- or knowledge_base.user_id == user.id
- or has_access(user.id, "read", knowledge_base.access_control)
+ if (
+ item.get("context") == "full"
+ or request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL
):
+ if knowledge_base and (
+ user.role == "admin"
+ or knowledge_base.user_id == user.id
+ or has_access(user.id, "read", knowledge_base.access_control)
+ ):
- file_ids = knowledge_base.data.get("file_ids", [])
+ file_ids = knowledge_base.data.get("file_ids", [])
- documents = []
- metadatas = []
- for file_id in file_ids:
- file_object = Files.get_file_by_id(file_id)
+ documents = []
+ metadatas = []
+ for file_id in file_ids:
+ file_object = Files.get_file_by_id(file_id)
- if file_object:
- documents.append(file_object.data.get("content", ""))
- metadatas.append(
- {
- "file_id": file_id,
- "name": file_object.filename,
- "source": file_object.filename,
- }
- )
+ if file_object:
+ documents.append(file_object.data.get("content", ""))
+ metadatas.append(
+ {
+ "file_id": file_id,
+ "name": file_object.filename,
+ "source": file_object.filename,
+ }
+ )
- query_result = {
- "documents": [documents],
- "metadatas": [metadatas],
- }
- else:
- # Fallback to collection names
- if item.get("legacy"):
- collection_names = item.get("collection_names", [])
+ query_result = {
+ "documents": [documents],
+ "metadatas": [metadatas],
+ }
else:
- collection_names.append(item["id"])
+ # Fallback to collection names
+ if item.get("legacy"):
+ collection_names = item.get("collection_names", [])
+ else:
+ collection_names.append(item["id"])
elif item.get("docs"):
# BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
diff --git a/backend/open_webui/retrieval/web/firecrawl.py b/backend/open_webui/retrieval/web/firecrawl.py
index a85fc51fbd..acad014d70 100644
--- a/backend/open_webui/retrieval/web/firecrawl.py
+++ b/backend/open_webui/retrieval/web/firecrawl.py
@@ -1,11 +1,11 @@
import logging
from typing import Optional, List
-from urllib.parse import urljoin
-import requests
from open_webui.retrieval.web.main import SearchResult, get_filtered_results
from open_webui.env import SRC_LOG_LEVELS
+from firecrawl import Firecrawl
+
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -18,27 +18,18 @@ def search_firecrawl(
filter_list: Optional[List[str]] = None,
) -> List[SearchResult]:
try:
- firecrawl_search_url = urljoin(firecrawl_url, "/v1/search")
- response = requests.post(
- firecrawl_search_url,
- headers={
- "User-Agent": "Open WebUI (https://github.com/open-webui/open-webui) RAG Bot",
- "Authorization": f"Bearer {firecrawl_api_key}",
- },
- json={
- "query": query,
- "limit": count,
- },
+ firecrawl = Firecrawl(api_key=firecrawl_api_key, api_url=firecrawl_url)
+ response = firecrawl.search(
+ query=query, limit=count, ignore_invalid_urls=True, timeout=count * 3
)
- response.raise_for_status()
- results = response.json().get("data", [])
+ results = response.web
if filter_list:
results = get_filtered_results(results, filter_list)
results = [
SearchResult(
- link=result.get("url"),
- title=result.get("title"),
- snippet=result.get("description"),
+ link=result.url,
+ title=result.title,
+ snippet=result.description,
)
for result in results[:count]
]
diff --git a/backend/open_webui/retrieval/web/utils.py b/backend/open_webui/retrieval/web/utils.py
index 61356adb56..74c4a734da 100644
--- a/backend/open_webui/retrieval/web/utils.py
+++ b/backend/open_webui/retrieval/web/utils.py
@@ -4,7 +4,6 @@ import socket
import ssl
import urllib.parse
import urllib.request
-from collections import defaultdict
from datetime import datetime, time, timedelta
from typing import (
Any,
@@ -17,11 +16,12 @@ from typing import (
Union,
Literal,
)
+
+from fastapi.concurrency import run_in_threadpool
import aiohttp
import certifi
import validators
from langchain_community.document_loaders import PlaywrightURLLoader, WebBaseLoader
-from langchain_community.document_loaders.firecrawl import FireCrawlLoader
from langchain_community.document_loaders.base import BaseLoader
from langchain_core.documents import Document
from open_webui.retrieval.loaders.tavily import TavilyLoader
@@ -39,7 +39,9 @@ from open_webui.config import (
EXTERNAL_WEB_LOADER_URL,
EXTERNAL_WEB_LOADER_API_KEY,
)
-from open_webui.env import SRC_LOG_LEVELS, AIOHTTP_CLIENT_SESSION_SSL
+from open_webui.env import SRC_LOG_LEVELS
+
+from firecrawl import Firecrawl
log = logging.getLogger(__name__)
log.setLevel(SRC_LOG_LEVELS["RAG"])
@@ -142,13 +144,13 @@ class RateLimitMixin:
class URLProcessingMixin:
- def _verify_ssl_cert(self, url: str) -> bool:
+ async def _verify_ssl_cert(self, url: str) -> bool:
"""Verify SSL certificate for a URL."""
- return verify_ssl_cert(url)
+ return await run_in_threadpool(verify_ssl_cert, url)
async def _safe_process_url(self, url: str) -> bool:
"""Perform safety checks before processing a URL."""
- if self.verify_ssl and not self._verify_ssl_cert(url):
+ if self.verify_ssl and not await self._verify_ssl_cert(url):
raise ValueError(f"SSL certificate verification failed for {url}")
await self._wait_for_rate_limit()
return True
@@ -189,13 +191,12 @@ class SafeFireCrawlLoader(BaseLoader, RateLimitMixin, URLProcessingMixin):
(uses FIRE_CRAWL_API_KEY environment variable if not provided).
api_url: Base URL for FireCrawl API. Defaults to official API endpoint.
mode: Operation mode selection:
- - 'crawl': Website crawling mode (default)
- - 'scrape': Direct page scraping
+ - 'crawl': Website crawling mode
+ - 'scrape': Direct page scraping (default)
- 'map': Site map generation
proxy: Proxy override settings for the FireCrawl API.
params: The parameters to pass to the Firecrawl API.
- Examples include crawlerOptions.
- For more details, visit: https://github.com/mendableai/firecrawl-py
+ For more details, visit: https://docs.firecrawl.dev/sdks/python#batch-scrape
"""
proxy_server = proxy.get("server") if proxy else None
if trust_env and not proxy_server:
@@ -215,50 +216,84 @@ class SafeFireCrawlLoader(BaseLoader, RateLimitMixin, URLProcessingMixin):
self.api_key = api_key
self.api_url = api_url
self.mode = mode
- self.params = params
+ self.params = params or {}
def lazy_load(self) -> Iterator[Document]:
- """Load documents concurrently using FireCrawl."""
- for url in self.web_paths:
- try:
- self._safe_process_url_sync(url)
- loader = FireCrawlLoader(
- url=url,
- api_key=self.api_key,
- api_url=self.api_url,
- mode=self.mode,
- params=self.params,
+ """Load documents using FireCrawl batch_scrape."""
+ log.debug(
+ "Starting FireCrawl batch scrape for %d URLs, mode: %s, params: %s",
+ len(self.web_paths),
+ self.mode,
+ self.params,
+ )
+ try:
+ firecrawl = Firecrawl(api_key=self.api_key, api_url=self.api_url)
+ result = firecrawl.batch_scrape(
+ self.web_paths,
+ formats=["markdown"],
+ skip_tls_verification=not self.verify_ssl,
+ ignore_invalid_urls=True,
+ remove_base64_images=True,
+ max_age=300000, # 5 minutes https://docs.firecrawl.dev/features/fast-scraping#common-maxage-values
+ wait_timeout=len(self.web_paths) * 3,
+ **self.params,
+ )
+
+ if result.status != "completed":
+ raise RuntimeError(
+ f"FireCrawl batch scrape did not complete successfully. result: {result}"
)
- for document in loader.lazy_load():
- if not document.metadata.get("source"):
- document.metadata["source"] = document.metadata.get("sourceURL")
- yield document
- except Exception as e:
- if self.continue_on_failure:
- log.exception(f"Error loading {url}: {e}")
- continue
+
+ for data in result.data:
+ metadata = data.metadata or {}
+ yield Document(
+ page_content=data.markdown or "",
+ metadata={"source": metadata.url or metadata.source_url or ""},
+ )
+
+ except Exception as e:
+ if self.continue_on_failure:
+ log.exception(f"Error extracting content from URLs: {e}")
+ else:
raise e
async def alazy_load(self):
"""Async version of lazy_load."""
- for url in self.web_paths:
- try:
- await self._safe_process_url(url)
- loader = FireCrawlLoader(
- url=url,
- api_key=self.api_key,
- api_url=self.api_url,
- mode=self.mode,
- params=self.params,
+ log.debug(
+ "Starting FireCrawl batch scrape for %d URLs, mode: %s, params: %s",
+ len(self.web_paths),
+ self.mode,
+ self.params,
+ )
+ try:
+ firecrawl = Firecrawl(api_key=self.api_key, api_url=self.api_url)
+ result = firecrawl.batch_scrape(
+ self.web_paths,
+ formats=["markdown"],
+ skip_tls_verification=not self.verify_ssl,
+ ignore_invalid_urls=True,
+ remove_base64_images=True,
+ max_age=300000, # 5 minutes https://docs.firecrawl.dev/features/fast-scraping#common-maxage-values
+ wait_timeout=len(self.web_paths) * 3,
+ **self.params,
+ )
+
+ if result.status != "completed":
+ raise RuntimeError(
+ f"FireCrawl batch scrape did not complete successfully. result: {result}"
)
- async for document in loader.alazy_load():
- if not document.metadata.get("source"):
- document.metadata["source"] = document.metadata.get("sourceURL")
- yield document
- except Exception as e:
- if self.continue_on_failure:
- log.exception(f"Error loading {url}: {e}")
- continue
+
+ for data in result.data:
+ metadata = data.metadata or {}
+ yield Document(
+ page_content=data.markdown or "",
+ metadata={"source": metadata.url or metadata.source_url or ""},
+ )
+
+ except Exception as e:
+ if self.continue_on_failure:
+ log.exception(f"Error extracting content from URLs: {e}")
+ else:
raise e
diff --git a/backend/open_webui/routers/audio.py b/backend/open_webui/routers/audio.py
index cb7a57b5b7..1213ffbd05 100644
--- a/backend/open_webui/routers/audio.py
+++ b/backend/open_webui/routers/audio.py
@@ -39,13 +39,14 @@ from open_webui.config import (
WHISPER_MODEL_DIR,
CACHE_DIR,
WHISPER_LANGUAGE,
+ ELEVENLABS_API_BASE_URL,
)
from open_webui.constants import ERROR_MESSAGES
from open_webui.env import (
+ ENV,
AIOHTTP_CLIENT_SESSION_SSL,
AIOHTTP_CLIENT_TIMEOUT,
- ENV,
SRC_LOG_LEVELS,
DEVICE_TYPE,
ENABLE_FORWARD_USER_INFO_HEADERS,
@@ -413,7 +414,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
timeout=timeout, trust_env=True
) as session:
async with session.post(
- f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
+ f"{ELEVENLABS_API_BASE_URL}/v1/text-to-speech/{voice_id}",
json={
"text": payload["input"],
"model_id": request.app.state.config.TTS_MODEL,
@@ -1037,7 +1038,7 @@ def get_available_models(request: Request) -> list[dict]:
elif request.app.state.config.TTS_ENGINE == "elevenlabs":
try:
response = requests.get(
- "https://api.elevenlabs.io/v1/models",
+ f"{ELEVENLABS_API_BASE_URL}/v1/models",
headers={
"xi-api-key": request.app.state.config.TTS_API_KEY,
"Content-Type": "application/json",
@@ -1141,7 +1142,7 @@ def get_elevenlabs_voices(api_key: str) -> dict:
try:
# TODO: Add retries
response = requests.get(
- "https://api.elevenlabs.io/v1/voices",
+ f"{ELEVENLABS_API_BASE_URL}/v1/voices",
headers={
"xi-api-key": api_key,
"Content-Type": "application/json",
diff --git a/backend/open_webui/routers/auths.py b/backend/open_webui/routers/auths.py
index e3271250c1..f261673f38 100644
--- a/backend/open_webui/routers/auths.py
+++ b/backend/open_webui/routers/auths.py
@@ -508,6 +508,15 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
user = Auths.authenticate_user(admin_email.lower(), admin_password)
else:
+ password_bytes = form_data.password.encode("utf-8")
+ if len(password_bytes) > 72:
+ # TODO: Implement other hashing algorithms that support longer passwords
+ log.info("Password too long, truncating to 72 bytes for bcrypt")
+ password_bytes = password_bytes[:72]
+
+ # decode safely — ignore incomplete UTF-8 sequences
+ form_data.password = password_bytes.decode("utf-8", errors="ignore")
+
user = Auths.authenticate_user(form_data.email.lower(), form_data.password)
if user:
diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py
index e7fa13d1ff..5c08fded23 100644
--- a/backend/open_webui/routers/configs.py
+++ b/backend/open_webui/routers/configs.py
@@ -1,4 +1,5 @@
import logging
+import copy
from fastapi import APIRouter, Depends, Request, HTTPException
from pydantic import BaseModel, ConfigDict
import aiohttp
@@ -15,6 +16,7 @@ from open_webui.utils.tools import (
set_tool_servers,
)
from open_webui.utils.mcp.client import MCPClient
+from open_webui.models.oauth_sessions import OAuthSessions
from open_webui.env import SRC_LOG_LEVELS
@@ -165,6 +167,21 @@ async def set_tool_servers_config(
form_data: ToolServersConfigForm,
user=Depends(get_admin_user),
):
+ for connection in request.app.state.config.TOOL_SERVER_CONNECTIONS:
+ server_type = connection.get("type", "openapi")
+ auth_type = connection.get("auth_type", "none")
+
+ if auth_type == "oauth_2.1":
+ # Remove existing OAuth clients for tool servers
+ server_id = connection.get("info", {}).get("id")
+ client_key = f"{server_type}:{server_id}"
+
+ try:
+ request.app.state.oauth_client_manager.remove_client(client_key)
+ except:
+ pass
+
+ # Set new tool server connections
request.app.state.config.TOOL_SERVER_CONNECTIONS = [
connection.model_dump() for connection in form_data.TOOL_SERVER_CONNECTIONS
]
@@ -176,6 +193,7 @@ async def set_tool_servers_config(
if server_type == "mcp":
server_id = connection.get("info", {}).get("id")
auth_type = connection.get("auth_type", "none")
+
if auth_type == "oauth_2.1" and server_id:
try:
oauth_client_info = connection.get("info", {}).get(
diff --git a/backend/open_webui/routers/files.py b/backend/open_webui/routers/files.py
index 84d8f841cf..2a5c3e5bb1 100644
--- a/backend/open_webui/routers/files.py
+++ b/backend/open_webui/routers/files.py
@@ -115,6 +115,10 @@ def process_uploaded_file(request, file, file_path, file_item, file_metadata, us
request.app.state.config.CONTENT_EXTRACTION_ENGINE == "external"
):
process_file(request, ProcessFileForm(file_id=file_item.id), user=user)
+ else:
+ raise Exception(
+ f"File type {file.content_type} is not supported for processing"
+ )
else:
log.info(
f"File type {file.content_type} is not provided, but trying to process anyway"
diff --git a/backend/open_webui/routers/openai.py b/backend/open_webui/routers/openai.py
index 8c5e3da736..4f9b297bfb 100644
--- a/backend/open_webui/routers/openai.py
+++ b/backend/open_webui/routers/openai.py
@@ -501,50 +501,55 @@ async def get_all_models(request: Request, user: UserModel) -> dict[str, list]:
return response
return None
- def merge_models_lists(model_lists):
+ def is_supported_openai_models(model_id):
+ if any(
+ name in model_id
+ for name in [
+ "babbage",
+ "dall-e",
+ "davinci",
+ "embedding",
+ "tts",
+ "whisper",
+ ]
+ ):
+ return False
+ return True
+
+ def get_merged_models(model_lists):
log.debug(f"merge_models_lists {model_lists}")
- merged_list = []
+ models = {}
- for idx, models in enumerate(model_lists):
- if models is not None and "error" not in models:
+ for idx, model_list in enumerate(model_lists):
+ if model_list is not None and "error" not in model_list:
+ for model in model_list:
+ model_id = model.get("id") or model.get("name")
- merged_list.extend(
- [
- {
+ if (
+ "api.openai.com"
+ in request.app.state.config.OPENAI_API_BASE_URLS[idx]
+ and not is_supported_openai_models(model_id)
+ ):
+ # Skip unwanted OpenAI models
+ continue
+
+ if model_id and model_id not in models:
+ models[model_id] = {
**model,
- "name": model.get("name", model["id"]),
+ "name": model.get("name", model_id),
"owned_by": "openai",
"openai": model,
"connection_type": model.get("connection_type", "external"),
"urlIdx": idx,
}
- for model in models
- if (model.get("id") or model.get("name"))
- and (
- "api.openai.com"
- not in request.app.state.config.OPENAI_API_BASE_URLS[idx]
- or not any(
- name in model["id"]
- for name in [
- "babbage",
- "dall-e",
- "davinci",
- "embedding",
- "tts",
- "whisper",
- ]
- )
- )
- ]
- )
- return merged_list
+ return models
- models = {"data": merge_models_lists(map(extract_data, responses))}
+ models = get_merged_models(map(extract_data, responses))
log.debug(f"models: {models}")
- request.app.state.OPENAI_MODELS = {model["id"]: model for model in models["data"]}
- return models
+ request.app.state.OPENAI_MODELS = map(extract_data, responses)
+ return {"data": list(models.values())}
@router.get("/models")
diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py
index 47b2c57961..5aa7a4ff40 100644
--- a/backend/open_webui/socket/main.py
+++ b/backend/open_webui/socket/main.py
@@ -18,6 +18,10 @@ from open_webui.utils.redis import (
get_sentinel_url_from_env,
)
+from open_webui.config import (
+ CORS_ALLOW_ORIGIN,
+)
+
from open_webui.env import (
ENABLE_WEBSOCKET_SUPPORT,
WEBSOCKET_MANAGER,
@@ -58,7 +62,7 @@ if WEBSOCKET_MANAGER == "redis":
else:
mgr = socketio.AsyncRedisManager(WEBSOCKET_REDIS_URL)
sio = socketio.AsyncServer(
- cors_allowed_origins=[],
+ cors_allowed_origins=CORS_ALLOW_ORIGIN,
async_mode="asgi",
transports=(["websocket"] if ENABLE_WEBSOCKET_SUPPORT else ["polling"]),
allow_upgrades=ENABLE_WEBSOCKET_SUPPORT,
@@ -67,7 +71,7 @@ if WEBSOCKET_MANAGER == "redis":
)
else:
sio = socketio.AsyncServer(
- cors_allowed_origins=[],
+ cors_allowed_origins=CORS_ALLOW_ORIGIN,
async_mode="asgi",
transports=(["websocket"] if ENABLE_WEBSOCKET_SUPPORT else ["polling"]),
allow_upgrades=ENABLE_WEBSOCKET_SUPPORT,
diff --git a/backend/open_webui/utils/mcp/client.py b/backend/open_webui/utils/mcp/client.py
index 01df38886c..6edfca4f6c 100644
--- a/backend/open_webui/utils/mcp/client.py
+++ b/backend/open_webui/utils/mcp/client.py
@@ -2,6 +2,8 @@ import asyncio
from typing import Optional
from contextlib import AsyncExitStack
+import anyio
+
from mcp import ClientSession
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.streamable_http import streamablehttp_client
@@ -11,26 +13,29 @@ from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAu
class MCPClient:
def __init__(self):
self.session: Optional[ClientSession] = None
- self.exit_stack = AsyncExitStack()
+ self.exit_stack = None
async def connect(self, url: str, headers: Optional[dict] = None):
- try:
- self._streams_context = streamablehttp_client(url, headers=headers)
+ async with AsyncExitStack() as exit_stack:
+ try:
+ self._streams_context = streamablehttp_client(url, headers=headers)
- transport = await self.exit_stack.enter_async_context(self._streams_context)
- read_stream, write_stream, _ = transport
+ transport = await exit_stack.enter_async_context(self._streams_context)
+ read_stream, write_stream, _ = transport
- self._session_context = ClientSession(
- read_stream, write_stream
- ) # pylint: disable=W0201
+ self._session_context = ClientSession(
+ read_stream, write_stream
+ ) # pylint: disable=W0201
- self.session = await self.exit_stack.enter_async_context(
- self._session_context
- )
- await self.session.initialize()
- except Exception as e:
- await self.disconnect()
- raise e
+ self.session = await exit_stack.enter_async_context(
+ self._session_context
+ )
+ with anyio.fail_after(10):
+ await self.session.initialize()
+ self.exit_stack = exit_stack.pop_all()
+ except Exception as e:
+ await asyncio.shield(self.disconnect())
+ raise e
async def list_tool_specs(self) -> Optional[dict]:
if not self.session:
diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py
index dd42612eee..6a68be82e3 100644
--- a/backend/open_webui/utils/middleware.py
+++ b/backend/open_webui/utils/middleware.py
@@ -1545,7 +1545,6 @@ async def process_chat_response(
): # Only update titles and tags for non-temp chats
if (
TASKS.TITLE_GENERATION in tasks
- and tasks[TASKS.TITLE_GENERATION]
):
user_message = get_last_user_message(messages)
if user_message and len(user_message) > 100:
@@ -2349,7 +2348,9 @@ async def process_chat_response(
)
if data:
- if "event" in data:
+ if "event" in data and not getattr(
+ request.state, "direct", False
+ ):
await event_emitter(data.get("event", {}))
if "selected_model_id" in data:
diff --git a/backend/open_webui/utils/models.py b/backend/open_webui/utils/models.py
index 587e2a2c7d..6661fadb9e 100644
--- a/backend/open_webui/utils/models.py
+++ b/backend/open_webui/utils/models.py
@@ -166,13 +166,18 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
action_ids = []
filter_ids = []
- if "info" in model and "meta" in model["info"]:
- action_ids.extend(
- model["info"]["meta"].get("actionIds", [])
- )
- filter_ids.extend(
- model["info"]["meta"].get("filterIds", [])
- )
+ if "info" in model:
+ if "meta" in model["info"]:
+ action_ids.extend(
+ model["info"]["meta"].get("actionIds", [])
+ )
+ filter_ids.extend(
+ model["info"]["meta"].get("filterIds", [])
+ )
+
+ if "params" in model["info"]:
+ # Remove params to avoid exposing sensitive info
+ del model["info"]["params"]
model["action_ids"] = action_ids
model["filter_ids"] = filter_ids
@@ -182,22 +187,40 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
elif custom_model.is_active and (
custom_model.id not in [model["id"] for model in models]
):
+ # Custom model based on a base model
owned_by = "openai"
pipe = None
+ for m in models:
+ if (
+ custom_model.base_model_id == m["id"]
+ or custom_model.base_model_id == m["id"].split(":")[0]
+ ):
+ owned_by = m.get("owned_by", "unknown")
+ if "pipe" in m:
+ pipe = m["pipe"]
+ break
+
+ model = {
+ "id": f"{custom_model.id}",
+ "name": custom_model.name,
+ "object": "model",
+ "created": custom_model.created_at,
+ "owned_by": owned_by,
+ "preset": True,
+ **({"pipe": pipe} if pipe is not None else {}),
+ }
+
+ info = custom_model.model_dump()
+ if "params" in info:
+ # Remove params to avoid exposing sensitive info
+ del info["params"]
+
+ model["info"] = info
+
action_ids = []
filter_ids = []
- for model in models:
- if (
- custom_model.base_model_id == model["id"]
- or custom_model.base_model_id == model["id"].split(":")[0]
- ):
- owned_by = model.get("owned_by", "unknown owner")
- if "pipe" in model:
- pipe = model["pipe"]
- break
-
if custom_model.meta:
meta = custom_model.meta.model_dump()
@@ -207,20 +230,10 @@ async def get_all_models(request, refresh: bool = False, user: UserModel = None)
if "filterIds" in meta:
filter_ids.extend(meta["filterIds"])
- models.append(
- {
- "id": f"{custom_model.id}",
- "name": custom_model.name,
- "object": "model",
- "created": custom_model.created_at,
- "owned_by": owned_by,
- "info": custom_model.model_dump(),
- "preset": True,
- **({"pipe": pipe} if pipe is not None else {}),
- "action_ids": action_ids,
- "filter_ids": filter_ids,
- }
- )
+ model["action_ids"] = action_ids
+ model["filter_ids"] = filter_ids
+
+ models.append(model)
# Process action_ids to get the actions
def get_action_items_from_module(function, module):
diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py
index e0bf7582c6..6889f377bc 100644
--- a/backend/open_webui/utils/oauth.py
+++ b/backend/open_webui/utils/oauth.py
@@ -1,4 +1,5 @@
import base64
+import copy
import hashlib
import logging
import mimetypes
@@ -74,6 +75,8 @@ from mcp.shared.auth import (
OAuthMetadata,
)
+from authlib.oauth2.rfc6749.errors import OAuth2Error
+
class OAuthClientInformationFull(OAuthClientMetadata):
issuer: Optional[str] = None # URL of the OAuth server that issued this client
@@ -150,6 +153,37 @@ def decrypt_data(data: str):
raise
+def _build_oauth_callback_error_message(e: Exception) -> str:
+ """
+ Produce a user-facing callback error string with actionable context.
+ Keeps the message short and strips newlines for safe redirect usage.
+ """
+ if isinstance(e, OAuth2Error):
+ parts = [p for p in [e.error, e.description] if p]
+ detail = " - ".join(parts)
+ elif isinstance(e, HTTPException):
+ detail = e.detail if isinstance(e.detail, str) else str(e.detail)
+ elif isinstance(e, aiohttp.ClientResponseError):
+ detail = f"Upstream provider returned {e.status}: {e.message}"
+ elif isinstance(e, aiohttp.ClientError):
+ detail = str(e)
+ elif isinstance(e, KeyError):
+ missing = str(e).strip("'")
+ if missing.lower() == "state":
+ detail = "Missing state parameter in callback (session may have expired)"
+ else:
+ detail = f"Missing expected key '{missing}' in OAuth response"
+ else:
+ detail = str(e)
+
+ detail = detail.replace("\n", " ").strip()
+ if not detail:
+ detail = e.__class__.__name__
+
+ message = f"OAuth callback failed: {detail}"
+ return message[:197] + "..." if len(message) > 200 else message
+
+
def is_in_blocked_groups(group_name: str, groups: list) -> bool:
"""
Check if a group name matches any blocked pattern.
@@ -371,6 +405,82 @@ class OAuthClientManager:
if client_id in self.clients:
del self.clients[client_id]
log.info(f"Removed OAuth client {client_id}")
+
+ if hasattr(self.oauth, "_clients"):
+ if client_id in self.oauth._clients:
+ self.oauth._clients.pop(client_id, None)
+
+ if hasattr(self.oauth, "_registry"):
+ if client_id in self.oauth._registry:
+ self.oauth._registry.pop(client_id, None)
+
+ return True
+
+ async def _preflight_authorization_url(
+ self, client, client_info: OAuthClientInformationFull
+ ) -> bool:
+ # TODO: Replace this logic with a more robust OAuth client registration validation
+ # Only perform preflight checks for Starlette OAuth clients
+ if not hasattr(client, "create_authorization_url"):
+ return True
+
+ redirect_uri = None
+ if client_info.redirect_uris:
+ redirect_uri = str(client_info.redirect_uris[0])
+
+ try:
+ auth_data = await client.create_authorization_url(redirect_uri=redirect_uri)
+ authorization_url = auth_data.get("url")
+
+ if not authorization_url:
+ return True
+ except Exception as e:
+ log.debug(
+ f"Skipping OAuth preflight for client {client_info.client_id}: {e}",
+ )
+ return True
+
+ try:
+ async with aiohttp.ClientSession(trust_env=True) as session:
+ async with session.get(
+ authorization_url,
+ allow_redirects=False,
+ ssl=AIOHTTP_CLIENT_SESSION_SSL,
+ ) as resp:
+ if resp.status < 400:
+ return True
+ response_text = await resp.text()
+
+ error = None
+ error_description = ""
+
+ content_type = resp.headers.get("content-type", "")
+ if "application/json" in content_type:
+ try:
+ payload = json.loads(response_text)
+ error = payload.get("error")
+ error_description = payload.get("error_description", "")
+ except:
+ pass
+ else:
+ error_description = response_text
+
+ error_message = f"{error or ''} {error_description or ''}".lower()
+
+ if any(
+ keyword in error_message
+ for keyword in ("invalid_client", "invalid client", "client id")
+ ):
+ log.warning(
+ f"OAuth client preflight detected invalid registration for {client_info.client_id}: {error} {error_description}"
+ )
+
+ return False
+ except Exception as e:
+ log.debug(
+ f"Skipping OAuth preflight network check for client {client_info.client_id}: {e}"
+ )
+
return True
def get_client(self, client_id):
@@ -561,7 +671,6 @@ class OAuthClientManager:
client = self.get_client(client_id)
if client is None:
raise HTTPException(404)
-
client_info = self.get_client_info(client_id)
if client_info is None:
raise HTTPException(404)
@@ -569,7 +678,8 @@ class OAuthClientManager:
redirect_uri = (
client_info.redirect_uris[0] if client_info.redirect_uris else None
)
- return await client.authorize_redirect(request, str(redirect_uri))
+ redirect_uri_str = str(redirect_uri) if redirect_uri else None
+ return await client.authorize_redirect(request, redirect_uri_str)
async def handle_callback(self, request, client_id: str, user_id: str, response):
client = self.get_client(client_id)
@@ -621,8 +731,14 @@ class OAuthClientManager:
error_message = "Failed to obtain OAuth token"
log.warning(error_message)
except Exception as e:
- error_message = "OAuth callback error"
- log.warning(f"OAuth callback error: {e}")
+ error_message = _build_oauth_callback_error_message(e)
+ log.warning(
+ "OAuth callback error for user_id=%s client_id=%s: %s",
+ user_id,
+ client_id,
+ error_message,
+ exc_info=True,
+ )
redirect_url = (
str(request.app.state.config.WEBUI_URL or request.base_url)
@@ -630,7 +746,9 @@ class OAuthClientManager:
if error_message:
log.debug(error_message)
- redirect_url = f"{redirect_url}/?error={error_message}"
+ redirect_url = (
+ f"{redirect_url}/?error={urllib.parse.quote_plus(error_message)}"
+ )
return RedirectResponse(url=redirect_url, headers=response.headers)
response = RedirectResponse(url=redirect_url, headers=response.headers)
@@ -1104,7 +1222,13 @@ class OAuthManager:
try:
token = await client.authorize_access_token(request)
except Exception as e:
- log.warning(f"OAuth callback error: {e}")
+ detailed_error = _build_oauth_callback_error_message(e)
+ log.warning(
+ "OAuth callback error during authorize_access_token for provider %s: %s",
+ provider,
+ detailed_error,
+ exc_info=True,
+ )
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
# Try to get userinfo from the token first, some providers include it there
diff --git a/backend/open_webui/utils/payload.py b/backend/open_webui/utils/payload.py
index 4a431dcab3..bf372e0e76 100644
--- a/backend/open_webui/utils/payload.py
+++ b/backend/open_webui/utils/payload.py
@@ -297,6 +297,10 @@ def convert_payload_openai_to_ollama(openai_payload: dict) -> dict:
if "tools" in openai_payload:
ollama_payload["tools"] = openai_payload["tools"]
+ if "max_tokens" in openai_payload:
+ ollama_payload["num_predict"] = openai_payload["max_tokens"]
+ del openai_payload["max_tokens"]
+
# If there are advanced parameters in the payload, format them in Ollama's options field
if openai_payload.get("options"):
ollama_payload["options"] = openai_payload["options"]
diff --git a/backend/requirements.txt b/backend/requirements.txt
index 8876f5d8ac..0fdcb618fd 100644
--- a/backend/requirements.txt
+++ b/backend/requirements.txt
@@ -5,7 +5,7 @@ python-multipart==0.0.20
itsdangerous==2.2.0
python-socketio==5.13.0
-python-jose==3.4.0
+python-jose==3.5.0
cryptography
bcrypt==5.0.0
argon2-cffi==25.1.0
@@ -63,7 +63,7 @@ fpdf2==2.8.2
pymdown-extensions==10.14.2
docx2txt==0.8
python-pptx==1.0.2
-unstructured==0.16.17
+unstructured==0.18.15
nltk==3.9.1
Markdown==3.9
pypandoc==1.15
@@ -133,7 +133,7 @@ pytest-docker~=3.1.1
ldap3==2.9.1
## Firecrawl
-firecrawl-py==1.12.0
+firecrawl-py==4.5.0
## Trace
opentelemetry-api==1.37.0
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 74249febd9..349734a939 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -11,8 +11,6 @@ services:
open-webui:
build:
context: .
- args:
- OLLAMA_BASE_URL: '/ollama'
dockerfile: Dockerfile
image: ghcr.io/open-webui/open-webui:${WEBUI_DOCKER_TAG-main}
container_name: open-webui
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
index ec8a79bbce..5b37c8f8c8 100644
--- a/docs/CONTRIBUTING.md
+++ b/docs/CONTRIBUTING.md
@@ -24,6 +24,10 @@ Noticed something off? Have an idea? Check our [Issues tab](https://github.com/o
> - **Template Compliance:** Please be aware that failure to follow the provided issue template, or not providing the requested information at all, will likely result in your issue being closed without further consideration. This approach is critical for maintaining the manageability and integrity of issue tracking.
> - **Detail is Key:** To ensure your issue is understood and can be effectively addressed, it's imperative to include comprehensive details. Descriptions should be clear, including steps to reproduce, expected outcomes, and actual results. Lack of sufficient detail may hinder our ability to resolve your issue.
+> [!WARNING]
+> Reporting vulnerabilities is not wanted through Issues!
+> Instead, [use the security reporting functionality](https://github.com/open-webui/open-webui/security) and ensure you comply with the outlined requirements.
+
### 🧭 Scope of Support
We've noticed an uptick in issues not directly related to Open WebUI but rather to the environment it's run in, especially Docker setups. While we strive to support Docker deployment, understanding Docker fundamentals is crucial for a smooth experience.
@@ -32,6 +36,8 @@ We've noticed an uptick in issues not directly related to Open WebUI but rather
- **Advanced Configurations**: Setting up reverse proxies for HTTPS and managing Docker deployments requires foundational knowledge. There are numerous online resources available to learn these skills. Ensuring you have this knowledge will greatly enhance your experience with Open WebUI and similar projects.
+- **Check the documentation and help improve it**: [Our documentation](https://docs.openwebui.com) has ever growing troubleshooting guides and detailed installation tutorials. Please verify if it is of help to your issue and help expand it by submitting issues and PRs on our [Docs Repository](https://github.com/open-webui/docs).
+
## 💡 Contributing
Looking to contribute? Great! Here's how you can help:
@@ -46,9 +52,15 @@ We welcome pull requests. Before submitting one, please:
4. Write clear, descriptive commit messages.
5. It's essential to complete your pull request in a timely manner. We move fast, and having PRs hang around too long is not feasible. If you can't get it done within a reasonable time frame, we may have to close it to keep the project moving forward.
+> [!NOTE]
+> The Pull Request Template has various requirements outlined. Go through the PR-checklist one by one and ensure you completed all steps before submitting your PR for review (you can open it as draft otherwise!).
+
### 📚 Documentation & Tutorials
-Help us make Open WebUI more accessible by improving documentation, writing tutorials, or creating guides on setting up and optimizing the web UI.
+Help us make Open WebUI more accessible by improving the documentation, writing tutorials, or creating guides on setting up and optimizing the Web UI.
+
+Help expand our documentation by submitting issues and PRs on our [Docs Repository](https://github.com/open-webui/docs).
+We welcome tutorials, guides and other documentation improvements!
### 🌐 Translations and Internationalization
@@ -62,9 +74,12 @@ To add a new language:
- Copy the American English translation file(s) (from `en-US` directory in `src/lib/i18n/locale`) to this new directory and update the string values in JSON format according to your language. Make sure to preserve the structure of the JSON object.
- Add the language code and its respective title to languages file at `src/lib/i18n/locales/languages.json`.
+> [!NOTE]
+> When adding new translations, do so in a standalone PR! Feature PRs or PRs fixing a bug should not contain translation updates. Always keep the scope of a PR narrow.
+
### 🤔 Questions & Feedback
-Got questions or feedback? Join our [Discord community](https://discord.gg/5rJgQTnV4s) or open an issue. We're here to help!
+Got questions or feedback? Join our [Discord community](https://discord.gg/5rJgQTnV4s) or open an issue or discussion. We're here to help!
## 🙏 Thank You!
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
index f08c465471..a9463269df 100644
--- a/docs/SECURITY.md
+++ b/docs/SECURITY.md
@@ -44,8 +44,9 @@ We appreciate the community's interest in identifying potential vulnerabilities.
> - Screenshots/videos demonstrating the exploit (supplementary to written steps)
>
> **Failure to provide a reproducible PoC may lead to closure of the report**
-> We will notify you, if we struggle to reproduce the exploit using your PoC to allow you to improve your PoC
-> However, if we repeatedly cannot reproduce the exploit using the PoC, the report may be closed
+>
+> We will notify you, if we struggle to reproduce the exploit using your PoC to allow you to improve your PoC.
+> However, if we repeatedly cannot reproduce the exploit using the PoC, the report may be closed.
5. **Required Patch or Actionable Remediation Plan Submission**: Along with the PoC, reporters must provide a patch or some actionable steps to remediate the identified vulnerability. This helps us evaluate and implement fixes rapidly.
@@ -88,11 +89,21 @@ We appreciate the community's interest in identifying potential vulnerabilities.
**Non-compliant submissions will be closed, and repeat extreme violators may be banned.** Our goal is to foster a constructive reporting environment where quality submissions promote better security for all users.
+## Where to report the vulnerability
+
If you want to report a vulnerability and can meet the outlined requirements, [open a vulnerability report here](https://github.com/open-webui/open-webui/security/advisories/new).
+If you feel like you are not able to follow ALL outlined requirements for vulnerability-specific reasons, still do report it, we will check every report either way.
## Product Security And For Non-Vulnerability Security Concerns:
-If your concern does not meet the vulnerability requirements outlined above, such as:
+If your concern does not meet the vulnerability requirements outlined above, is not a vulnerability, **but is still related to security concerns**, then use the following channels instead:
+
+- **Documentation issues/improvement ideas:** Open an issue on our [Documentation Repository](https://github.com/open-webui/docs)
+- **Feature requests:** Create a discussion in [GitHub Discussions - Ideas](https://github.com/open-webui/open-webui/discussions/) to discuss with the community if this feature request is wanted by multiple people
+- **Configuration help:** Ask the community for help and guidance on our [Discord Server](https://discord.gg/5rJgQTnV4s) or on [Reddit](https://www.reddit.com/r/OpenWebUI/)
+- **General issues:** Use our [Issue Tracker](https://github.com/open-webui/open-webui/issues)
+
+**Examples of non-vulnerability, still security related concerns:**
- Suggestions for better default configuration values
- Security hardening recommendations
@@ -102,12 +113,7 @@ If your concern does not meet the vulnerability requirements outlined above, suc
- Feature requests for optional security enhancements (2FA, audit logging, etc.)
- General security questions about production deployment
-**then use one of the following channels instead:**
-
-- **Documentation issues/improvement ideas:** Open an issue on our [Documentation Repository](https://github.com/open-webui/docs)
-- **Feature requests:** Create a discussion in [GitHub Discussions - Ideas](https://github.com/open-webui/open-webui/discussions/) to discuss with the community if this feature request is wanted by multiple people
-- **Configuration help:** Ask the community for help and guidance on our [Discord Server](https://discord.gg/5rJgQTnV4s) or on [Reddit](https://www.reddit.com/r/OpenWebUI/)
-- **General issues:** Use our [Issue Tracker](https://github.com/open-webui/open-webui/issues)
+Please use the adequate channel for your specific issue - e.g. best-practice guidance or additional documentation needs into the Documentation Repository, and feature requests into the Main Repository as an issue or discussion.
We regularly audit our internal processes and system architecture for vulnerabilities using a combination of automated and manual testing techniques. We are also planning to implement SAST and SCA scans in our project soon.
@@ -115,4 +121,4 @@ For any other immediate concerns, please create an issue in our [issue tracker](
---
-_Last updated on **2025-10-12**._
+_Last updated on **2025-10-17**._
diff --git a/package-lock.json b/package-lock.json
index 4b3183e318..a584046549 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -103,8 +103,8 @@
"devDependencies": {
"@sveltejs/adapter-auto": "3.2.2",
"@sveltejs/adapter-static": "^3.0.2",
- "@sveltejs/kit": "^2.5.20",
- "@sveltejs/vite-plugin-svelte": "^3.1.1",
+ "@sveltejs/kit": "^2.5.27",
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/postcss": "^4.0.0",
"@tailwindcss/typography": "^0.5.13",
@@ -114,14 +114,14 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-cypress": "^3.4.0",
- "eslint-plugin-svelte": "^2.43.0",
+ "eslint-plugin-svelte": "^2.45.1",
"i18next-parser": "^9.0.1",
"postcss": "^8.4.31",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.6",
"sass-embedded": "^1.81.0",
- "svelte": "^4.2.18",
- "svelte-check": "^3.8.5",
+ "svelte": "^5.0.0",
+ "svelte-check": "^4.0.0",
"svelte-confetti": "^1.3.2",
"tailwindcss": "^4.0.0",
"tslib": "^2.4.1",
@@ -155,18 +155,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@antfu/install-pkg": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.0.0.tgz",
@@ -1997,16 +1985,23 @@
"license": "MIT"
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -2017,18 +2012,11 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
@@ -2210,23 +2198,6 @@
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
"integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg=="
},
- "node_modules/@melt-ui/svelte": {
- "version": "0.76.2",
- "resolved": "https://registry.npmjs.org/@melt-ui/svelte/-/svelte-0.76.2.tgz",
- "integrity": "sha512-7SbOa11tXUS95T3fReL+dwDs5FyJtCEqrqG3inRziDws346SYLsxOQ6HmX+4BkIsQh1R8U3XNa+EMmdMt38lMA==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/core": "^1.3.1",
- "@floating-ui/dom": "^1.4.5",
- "@internationalized/date": "^3.5.0",
- "dequal": "^2.0.3",
- "focus-trap": "^7.5.2",
- "nanoid": "^5.0.4"
- },
- "peerDependencies": {
- "svelte": ">=3 <5"
- }
- },
"node_modules/@mermaid-js/parser": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.2.tgz",
@@ -2948,42 +2919,89 @@
"license": "LIL"
},
"node_modules/@sveltejs/vite-plugin-svelte": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.1.tgz",
- "integrity": "sha512-rimpFEAboBBHIlzISibg94iP09k/KYdHgVhJlcsTfn7KMBhc70jFX/GRWkRdFCc2fdnk+4+Bdfej23cMDnJS6A==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-4.0.4.tgz",
+ "integrity": "sha512-0ba1RQ/PHen5FGpdSrW7Y3fAMQjrXantECALeOiOdBdzR5+5vPP6HVZRLmZaQL+W8m++o+haIAKq5qT+MiZ7VA==",
+ "license": "MIT",
"dependencies": {
- "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0",
- "debug": "^4.3.4",
+ "@sveltejs/vite-plugin-svelte-inspector": "^3.0.0-next.0||^3.0.0",
+ "debug": "^4.3.7",
"deepmerge": "^4.3.1",
"kleur": "^4.1.5",
- "magic-string": "^0.30.10",
- "svelte-hmr": "^0.16.0",
- "vitefu": "^0.2.5"
+ "magic-string": "^0.30.12",
+ "vitefu": "^1.0.3"
},
"engines": {
- "node": "^18.0.0 || >=20"
+ "node": "^18.0.0 || ^20.0.0 || >=22"
},
"peerDependencies": {
- "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "svelte": "^5.0.0-next.96 || ^5.0.0",
"vite": "^5.0.0"
}
},
"node_modules/@sveltejs/vite-plugin-svelte-inspector": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz",
- "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==",
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-3.0.1.tgz",
+ "integrity": "sha512-2CKypmj1sM4GE7HjllT7UKmo4Q6L5xFRd7VMGEWhYnZ+wc6AUVU01IBd7yUi6WnFndEwWoMNOd6e8UjoN0nbvQ==",
+ "license": "MIT",
"dependencies": {
- "debug": "^4.3.4"
+ "debug": "^4.3.7"
},
"engines": {
- "node": "^18.0.0 || >=20"
+ "node": "^18.0.0 || ^20.0.0 || >=22"
},
"peerDependencies": {
- "@sveltejs/vite-plugin-svelte": "^3.0.0",
- "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "@sveltejs/vite-plugin-svelte": "^4.0.0-next.0||^4.0.0",
+ "svelte": "^5.0.0-next.96 || ^5.0.0",
"vite": "^5.0.0"
}
},
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sveltejs/vite-plugin-svelte/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
"node_modules/@swc/helpers": {
"version": "0.5.17",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
@@ -4198,12 +4216,6 @@
"undici-types": "~5.26.4"
}
},
- "node_modules/@types/pug": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
- "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
- "dev": true
- },
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
@@ -4803,11 +4815,12 @@
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/aria-query": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
- "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
- "dependencies": {
- "dequal": "^2.0.3"
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
}
},
"node_modules/asn1": {
@@ -4895,11 +4908,12 @@
"dev": true
},
"node_modules/axobject-query": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz",
- "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==",
- "dependencies": {
- "dequal": "^2.0.3"
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
}
},
"node_modules/balanced-match": {
@@ -4990,6 +5004,23 @@
"svelte": "^4.0.0 || ^5.0.0-next.118"
}
},
+ "node_modules/bits-ui/node_modules/@melt-ui/svelte": {
+ "version": "0.76.2",
+ "resolved": "https://registry.npmjs.org/@melt-ui/svelte/-/svelte-0.76.2.tgz",
+ "integrity": "sha512-7SbOa11tXUS95T3fReL+dwDs5FyJtCEqrqG3inRziDws346SYLsxOQ6HmX+4BkIsQh1R8U3XNa+EMmdMt38lMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.3.1",
+ "@floating-ui/dom": "^1.4.5",
+ "@internationalized/date": "^3.5.0",
+ "dequal": "^2.0.3",
+ "focus-trap": "^7.5.2",
+ "nanoid": "^5.0.4"
+ },
+ "peerDependencies": {
+ "svelte": ">=3 <5"
+ }
+ },
"node_modules/bl": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
@@ -5702,24 +5733,13 @@
"integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==",
"dev": true
},
- "node_modules/code-red": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
- "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@types/estree": "^1.0.1",
- "acorn": "^8.10.0",
- "estree-walker": "^3.0.3",
- "periscopic": "^3.1.0"
- }
- },
- "node_modules/code-red/node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dependencies": {
- "@types/estree": "^1.0.0"
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/codedent": {
@@ -5981,18 +6001,6 @@
"url": "https://github.com/sponsors/fb55"
}
},
- "node_modules/css-tree": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
- "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
- "dependencies": {
- "mdn-data": "2.0.30",
- "source-map-js": "^1.0.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
@@ -6815,15 +6823,6 @@
"node": ">=6"
}
},
- "node_modules/detect-indent": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
- "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -7116,12 +7115,6 @@
"node": ">= 0.4"
}
},
- "node_modules/es6-promise": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
- "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
- "dev": true
- },
"node_modules/esbuild": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
@@ -7278,22 +7271,23 @@
}
},
"node_modules/eslint-plugin-svelte": {
- "version": "2.43.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.43.0.tgz",
- "integrity": "sha512-REkxQWvg2pp7QVLxQNa+dJ97xUqRe7Y2JJbSWkHSuszu0VcblZtXkPBPckkivk99y5CdLw4slqfPylL2d/X4jQ==",
+ "version": "2.46.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.46.1.tgz",
+ "integrity": "sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@jridgewell/sourcemap-codec": "^1.4.15",
"eslint-compat-utils": "^0.5.1",
"esutils": "^2.0.3",
- "known-css-properties": "^0.34.0",
+ "known-css-properties": "^0.35.0",
"postcss": "^8.4.38",
"postcss-load-config": "^3.1.4",
"postcss-safe-parser": "^6.0.0",
"postcss-selector-parser": "^6.1.0",
"semver": "^7.6.2",
- "svelte-eslint-parser": "^0.41.0"
+ "svelte-eslint-parser": "^0.43.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
@@ -7303,7 +7297,7 @@
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0",
- "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191"
+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"svelte": {
@@ -7410,6 +7404,15 @@
"node": ">=0.10"
}
},
+ "node_modules/esrap": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.1.tgz",
+ "integrity": "sha512-ebTT9B6lOtZGMgJ3o5r12wBacHctG7oEWazIda8UlPfA3HD/Wrv8FdXoVo73vzdpwCxNyXjPauyN2bbJzMkB9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ }
+ },
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@@ -9021,10 +9024,11 @@
}
},
"node_modules/known-css-properties": {
- "version": "0.34.0",
- "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz",
- "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==",
- "dev": true
+ "version": "0.35.0",
+ "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz",
+ "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/kokoro-js": {
"version": "1.1.1",
@@ -9658,11 +9662,12 @@
"license": "ISC"
},
"node_modules/magic-string": {
- "version": "0.30.11",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
- "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
+ "@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/markdown-it": {
@@ -9738,11 +9743,6 @@
"node": ">= 0.4"
}
},
- "node_modules/mdn-data": {
- "version": "2.0.30",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
- "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
- },
"node_modules/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
@@ -9857,15 +9857,6 @@
"node": ">=6"
}
},
- "node_modules/min-indent": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
- "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -9961,18 +9952,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/mkdirp": {
- "version": "0.5.6",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
- "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
- "dev": true,
- "dependencies": {
- "minimist": "^1.2.6"
- },
- "bin": {
- "mkdirp": "bin/cmd.js"
- }
- },
"node_modules/mktemp": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/mktemp/-/mktemp-0.4.0.tgz",
@@ -10461,32 +10440,6 @@
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"devOptional": true
},
- "node_modules/periscopic": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
- "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
- "dependencies": {
- "@types/estree": "^1.0.0",
- "estree-walker": "^3.0.0",
- "is-reference": "^3.0.0"
- }
- },
- "node_modules/periscopic/node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
- },
- "node_modules/periscopic/node_modules/is-reference": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
- "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
- "dependencies": {
- "@types/estree": "*"
- }
- },
"node_modules/phonemizer": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/phonemizer/-/phonemizer-1.2.1.tgz",
@@ -10684,6 +10637,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "license": "MIT",
"engines": {
"node": ">=12.0"
},
@@ -11581,73 +11535,6 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
- "node_modules/sander": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
- "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
- "dev": true,
- "dependencies": {
- "es6-promise": "^3.1.2",
- "graceful-fs": "^4.1.3",
- "mkdirp": "^0.5.1",
- "rimraf": "^2.5.2"
- }
- },
- "node_modules/sander/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/sander/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/sander/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/sander/node_modules/rimraf": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
- "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
- "dev": true,
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- }
- },
"node_modules/sass-embedded": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.81.0.tgz",
@@ -12231,21 +12118,6 @@
"node": ">=10.0.0"
}
},
- "node_modules/sorcery": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
- "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.14",
- "buffer-crc32": "^0.2.5",
- "minimist": "^1.2.0",
- "sander": "^0.5.0"
- },
- "bin": {
- "sorcery": "bin/sorcery"
- }
- },
"node_modules/sort-keys": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz",
@@ -12456,18 +12328,6 @@
"node": ">=6"
}
},
- "node_modules/strip-indent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
- "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
- "dev": true,
- "dependencies": {
- "min-indent": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -12527,47 +12387,115 @@
}
},
"node_modules/svelte": {
- "version": "4.2.19",
- "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz",
- "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==",
+ "version": "5.42.2",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.42.2.tgz",
+ "integrity": "sha512-iSry5jsBHispVczyt9UrBX/1qu3HQ/UyKPAIjqlvlu3o/eUvc+kpyMyRS2O4HLLx4MvLurLGIUOyyP11pyD59g==",
+ "license": "MIT",
"dependencies": {
- "@ampproject/remapping": "^2.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.15",
- "@jridgewell/trace-mapping": "^0.3.18",
- "@types/estree": "^1.0.1",
- "acorn": "^8.9.0",
- "aria-query": "^5.3.0",
- "axobject-query": "^4.0.0",
- "code-red": "^1.0.3",
- "css-tree": "^2.3.1",
- "estree-walker": "^3.0.3",
- "is-reference": "^3.0.1",
+ "@jridgewell/remapping": "^2.3.4",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@sveltejs/acorn-typescript": "^1.0.5",
+ "@types/estree": "^1.0.5",
+ "acorn": "^8.12.1",
+ "aria-query": "^5.3.1",
+ "axobject-query": "^4.1.0",
+ "clsx": "^2.1.1",
+ "esm-env": "^1.2.1",
+ "esrap": "^2.1.0",
+ "is-reference": "^3.0.3",
"locate-character": "^3.0.0",
- "magic-string": "^0.30.4",
- "periscopic": "^3.1.0"
+ "magic-string": "^0.30.11",
+ "zimmerframe": "^1.1.2"
},
"engines": {
- "node": ">=16"
+ "node": ">=18"
}
},
"node_modules/svelte-check": {
- "version": "3.8.5",
- "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.5.tgz",
- "integrity": "sha512-3OGGgr9+bJ/+1nbPgsvulkLC48xBsqsgtc8Wam281H4G9F5v3mYGa2bHRsPuwHC5brKl4AxJH95QF73kmfihGQ==",
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.3.tgz",
+ "integrity": "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
- "@jridgewell/trace-mapping": "^0.3.17",
- "chokidar": "^3.4.1",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "chokidar": "^4.0.1",
+ "fdir": "^6.2.0",
"picocolors": "^1.0.0",
- "sade": "^1.7.4",
- "svelte-preprocess": "^5.1.3",
- "typescript": "^5.0.3"
+ "sade": "^1.7.4"
},
"bin": {
"svelte-check": "bin/svelte-check"
},
+ "engines": {
+ "node": ">= 18.0.0"
+ },
"peerDependencies": {
- "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
+ "svelte": "^4.0.0 || ^5.0.0-next.0",
+ "typescript": ">=5.0.0"
+ }
+ },
+ "node_modules/svelte-check/node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/svelte-check/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/svelte-check/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/svelte-check/node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
}
},
"node_modules/svelte-confetti": {
@@ -12580,10 +12508,11 @@
}
},
"node_modules/svelte-eslint-parser": {
- "version": "0.41.0",
- "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.41.0.tgz",
- "integrity": "sha512-L6f4hOL+AbgfBIB52Z310pg1d2QjRqm7wy3kI1W6hhdhX5bvu7+f0R6w4ykp5HoDdzq+vGhIJmsisaiJDGmVfA==",
+ "version": "0.43.0",
+ "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz",
+ "integrity": "sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
@@ -12598,7 +12527,7 @@
"url": "https://github.com/sponsors/ota-meshi"
},
"peerDependencies": {
- "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191"
+ "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"svelte": {
@@ -12606,80 +12535,6 @@
}
}
},
- "node_modules/svelte-hmr": {
- "version": "0.16.0",
- "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
- "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==",
- "engines": {
- "node": "^12.20 || ^14.13.1 || >= 16"
- },
- "peerDependencies": {
- "svelte": "^3.19.0 || ^4.0.0"
- }
- },
- "node_modules/svelte-preprocess": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.3.tgz",
- "integrity": "sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==",
- "dev": true,
- "hasInstallScript": true,
- "dependencies": {
- "@types/pug": "^2.0.6",
- "detect-indent": "^6.1.0",
- "magic-string": "^0.30.5",
- "sorcery": "^0.11.0",
- "strip-indent": "^3.0.0"
- },
- "engines": {
- "node": ">= 16.0.0",
- "pnpm": "^8.0.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.10.2",
- "coffeescript": "^2.5.1",
- "less": "^3.11.3 || ^4.0.0",
- "postcss": "^7 || ^8",
- "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
- "pug": "^3.0.0",
- "sass": "^1.26.8",
- "stylus": "^0.55.0",
- "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
- "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0",
- "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
- },
- "peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
- "coffeescript": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "postcss-load-config": {
- "optional": true
- },
- "pug": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "typescript": {
- "optional": true
- }
- }
- },
"node_modules/svelte-sonner": {
"version": "0.3.28",
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-0.3.28.tgz",
@@ -12688,20 +12543,19 @@
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1"
}
},
- "node_modules/svelte/node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
+ "node_modules/svelte/node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
},
"node_modules/svelte/node_modules/is-reference": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
- "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
+ "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
+ "license": "MIT",
"dependencies": {
- "@types/estree": "*"
+ "@types/estree": "^1.0.6"
}
},
"node_modules/svg-pathdata": {
@@ -14213,11 +14067,17 @@
}
},
"node_modules/vitefu": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz",
- "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==",
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz",
+ "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==",
+ "license": "MIT",
+ "workspaces": [
+ "tests/deps/*",
+ "tests/projects/*",
+ "tests/projects/workspace/packages/*"
+ ],
"peerDependencies": {
- "vite": "^3.0.0 || ^4.0.0 || ^5.0.0"
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0"
},
"peerDependenciesMeta": {
"vite": {
@@ -14945,6 +14805,12 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zimmerframe": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
+ "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
+ "license": "MIT"
}
}
}
diff --git a/package.json b/package.json
index 6d0aa8f5d5..fc09323156 100644
--- a/package.json
+++ b/package.json
@@ -24,8 +24,8 @@
"devDependencies": {
"@sveltejs/adapter-auto": "3.2.2",
"@sveltejs/adapter-static": "^3.0.2",
- "@sveltejs/kit": "^2.5.20",
- "@sveltejs/vite-plugin-svelte": "^3.1.1",
+ "@sveltejs/kit": "^2.5.27",
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/postcss": "^4.0.0",
"@tailwindcss/typography": "^0.5.13",
@@ -35,14 +35,14 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-cypress": "^3.4.0",
- "eslint-plugin-svelte": "^2.43.0",
+ "eslint-plugin-svelte": "^2.45.1",
"i18next-parser": "^9.0.1",
"postcss": "^8.4.31",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.6",
"sass-embedded": "^1.81.0",
- "svelte": "^4.2.18",
- "svelte-check": "^3.8.5",
+ "svelte": "^5.0.0",
+ "svelte-check": "^4.0.0",
"svelte-confetti": "^1.3.2",
"tailwindcss": "^4.0.0",
"tslib": "^2.4.1",
diff --git a/pyproject.toml b/pyproject.toml
index cd5a08fba2..87e88a1b06 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,7 @@ dependencies = [
"itsdangerous==2.2.0",
"python-socketio==5.13.0",
- "python-jose==3.4.0",
+ "python-jose==3.5.0",
"cryptography",
"bcrypt==5.0.0",
"argon2-cffi==25.1.0",
@@ -73,7 +73,7 @@ dependencies = [
"pymdown-extensions==10.14.2",
"docx2txt==0.8",
"python-pptx==1.0.2",
- "unstructured==0.16.17",
+ "unstructured==0.18.15",
"nltk==3.9.1",
"Markdown==3.9",
"pypandoc==1.15",
@@ -151,9 +151,7 @@ all = [
"oracledb==3.2.0",
"colbert-ai==0.2.21",
-
- "firecrawl-py==1.12.0",
- "tencentcloud-sdk-python==3.0.1336",
+ "firecrawl-py==4.5.0",
]
[project.scripts]
diff --git a/src/app.css b/src/app.css
index 49bcebbd97..f4e3225d3b 100644
--- a/src/app.css
+++ b/src/app.css
@@ -129,8 +129,8 @@ li p {
}
::-webkit-scrollbar {
- height: 0.8rem;
- width: 0.8rem;
+ height: 0.45rem;
+ width: 0.45rem;
}
::-webkit-scrollbar-track {
@@ -152,6 +152,14 @@ select {
-webkit-appearance: none;
}
+.dark select:not([class*='bg-transparent']) {
+ @apply bg-gray-900 text-gray-300;
+}
+
+.dark select option {
+ @apply bg-gray-850 text-white;
+}
+
@keyframes shimmer {
0% {
background-position: 200% 0;
diff --git a/src/app.html b/src/app.html
index 6c1c362005..9333dc8ba3 100644
--- a/src/app.html
+++ b/src/app.html
@@ -23,12 +23,7 @@
href="/static/apple-touch-icon.png"
crossorigin="use-credentials"
/>
-
+
@@ -785,8 +777,8 @@
}
}}
>
- {$i18n.t('Self-Hosted')}
- {$i18n.t('minerU managed (Cloud API)')}
+ {$i18n.t('local')}
+ {$i18n.t('cloud')}
@@ -802,15 +794,12 @@
/>
-
- {#if RAGConfig.MINERU_API_MODE === 'cloud'}
-
-
-
- {/if}
+
+
+
diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte
index d8e1d60080..850b49b58f 100644
--- a/src/lib/components/admin/Settings/Models.svelte
+++ b/src/lib/components/admin/Settings/Models.svelte
@@ -313,7 +313,7 @@
{#if models.length > 0}
- {#each filteredModels as model, modelIdx (model.id)}
+ {#each filteredModels as model, modelIdx (`${model.id}-${modelIdx}`)}
-
-
-
- <${''}style>
- body {
- background-color: white; /* Ensure the iframe has a white background */
- }
-
- ${cssContent}
- ${''}style>
-
-
- ${htmlContent}
-
- <${''}script>
- ${jsContent}
- ${''}script>
-
-