From 40c450e6e54303ead5473476f0c2519cd928ae9e Mon Sep 17 00:00:00 2001 From: Taylor Wilsdon Date: Sat, 18 Oct 2025 13:43:51 -0400 Subject: [PATCH 01/45] Add more granular information to oauth failure messages --- backend/open_webui/utils/oauth.py | 55 ++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index e0bf7582c6..4fa493b1d5 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -74,6 +74,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 +152,37 @@ def decrypt_data(data: str): raise +def _build_oauth_callback_error_message(exc: 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(exc, OAuth2Error): + parts = [p for p in [exc.error, exc.description] if p] + detail = " - ".join(parts) + elif isinstance(exc, HTTPException): + detail = exc.detail if isinstance(exc.detail, str) else str(exc.detail) + elif isinstance(exc, aiohttp.ClientResponseError): + detail = f"Upstream provider returned {exc.status}: {exc.message}" + elif isinstance(exc, aiohttp.ClientError): + detail = str(exc) + elif isinstance(exc, KeyError): + missing = str(exc).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(exc) + + detail = detail.replace("\n", " ").strip() + if not detail: + detail = exc.__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. @@ -621,8 +654,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 +669,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 +1145,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 From c107a3799f3695e57c303544dc93cadbdeb91c0a Mon Sep 17 00:00:00 2001 From: Taylor Wilsdon Date: Sat, 18 Oct 2025 14:00:46 -0400 Subject: [PATCH 02/45] Added a targeted utility to wipe all OAuth sessions for a provider so the cleanup can remove stale access tokens across every user when a connection is updated --- backend/open_webui/models/oauth_sessions.py | 11 +++++ backend/open_webui/routers/configs.py | 51 ++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) 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/routers/configs.py b/backend/open_webui/routers/configs.py index e7fa13d1ff..e8e876eac7 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,12 +167,59 @@ async def set_tool_servers_config( form_data: ToolServersConfigForm, user=Depends(get_admin_user), ): - request.app.state.config.TOOL_SERVER_CONNECTIONS = [ + old_connections = copy.deepcopy( + request.app.state.config.TOOL_SERVER_CONNECTIONS or [] + ) + + new_connections = [ connection.model_dump() for connection in form_data.TOOL_SERVER_CONNECTIONS ] + old_mcp_connections = { + conn.get("info", {}).get("id"): conn + for conn in old_connections + if conn.get("type") == "mcp" + } + new_mcp_connections = { + conn.get("info", {}).get("id"): conn + for conn in new_connections + if conn.get("type") == "mcp" + } + + purge_oauth_clients = set() + + for server_id, old_conn in old_mcp_connections.items(): + if not server_id: + continue + + old_auth_type = old_conn.get("auth_type", "none") + new_conn = new_mcp_connections.get(server_id) + + if new_conn is None: + if old_auth_type == "oauth_2.1": + purge_oauth_clients.add(server_id) + continue + + new_auth_type = new_conn.get("auth_type", "none") + + if old_auth_type == "oauth_2.1": + if ( + new_auth_type != "oauth_2.1" + or old_conn.get("url") != new_conn.get("url") + or old_conn.get("info", {}).get("oauth_client_info") + != new_conn.get("info", {}).get("oauth_client_info") + ): + purge_oauth_clients.add(server_id) + + request.app.state.config.TOOL_SERVER_CONNECTIONS = new_connections + await set_tool_servers(request) + for server_id in purge_oauth_clients: + client_key = f"mcp:{server_id}" + request.app.state.oauth_client_manager.remove_client(client_key) + OAuthSessions.delete_sessions_by_provider(client_key) + for connection in request.app.state.config.TOOL_SERVER_CONNECTIONS: server_type = connection.get("type", "openapi") if server_type == "mcp": From d49fb9c01038dc580b2229ac2554727506357372 Mon Sep 17 00:00:00 2001 From: Taylor Wilsdon Date: Sat, 18 Oct 2025 14:16:10 -0400 Subject: [PATCH 03/45] complete cleanup of oauth clients --- backend/open_webui/utils/oauth.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 4fa493b1d5..85f694cb13 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -401,8 +401,19 @@ class OAuthClientManager: return self.clients[client_id] def remove_client(self, client_id): + removed = False if client_id in self.clients: del self.clients[client_id] + removed = True + if hasattr(self.oauth, "_clients"): + if client_id in self.oauth._clients: + self.oauth._clients.pop(client_id, None) + removed = True + if hasattr(self.oauth, "_registry"): + if client_id in self.oauth._registry: + self.oauth._registry.pop(client_id, None) + removed = True + if removed: log.info(f"Removed OAuth client {client_id}") return True From ecbf74dbea59534cebdd3ce1a40749fcfd68133e Mon Sep 17 00:00:00 2001 From: Taylor Wilsdon Date: Sat, 18 Oct 2025 16:53:44 -0400 Subject: [PATCH 04/45] Added a preflight authorize check that automatically re-registers MCP OAuth clients when the stored client ID no longer exists on the server, so the browser flow never hits the stale-ID failure --- backend/open_webui/utils/oauth.py | 206 +++++++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 85f694cb13..d24a379ede 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 @@ -417,6 +418,205 @@ class OAuthClientManager: log.info(f"Removed OAuth client {client_id}") return True + def _find_mcp_connection(self, request, client_id: str): + try: + connections = request.app.state.config.TOOL_SERVER_CONNECTIONS or [] + except Exception: + connections = [] + + normalized_client_id = client_id.split(":")[-1] + + for idx, connection in enumerate(connections): + if not isinstance(connection, dict): + continue + if connection.get("type") != "mcp": + continue + + info = connection.get("info") or {} + server_id = info.get("id") + if not server_id: + continue + + normalized_server_id = server_id.split(":")[-1] + if normalized_server_id == normalized_client_id: + return idx, connection + + return None, None + + async def _preflight_authorization_url( + self, client, client_info: OAuthClientInformationFull + ) -> bool: + # 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) + authorize_url = auth_data.get("url") + if not authorize_url: + return True + except Exception as e: + log.debug( + "Skipping OAuth preflight for client %s: %s", + client_info.client_id, + e, + ) + return True + + try: + async with aiohttp.ClientSession(trust_env=True) as session: + async with session.get( + authorize_url, + allow_redirects=False, + ssl=AIOHTTP_CLIENT_SESSION_SSL, + ) as resp: + if resp.status < 400: + return True + + body_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(body_text) + error = payload.get("error") + error_description = payload.get( + "error_description", "" + ) + except json.JSONDecodeError: + error = None + error_description = "" + else: + error_description = body_text + + combined = f"{error or ''} {error_description}".lower() + if "invalid_client" in combined or "invalid client" in combined or "client id" in combined: + log.warning( + "OAuth client preflight detected invalid registration for %s: %s %s", + client_info.client_id, + error, + error_description, + ) + return False + except Exception as e: + log.debug( + "Skipping OAuth preflight network check for client %s: %s", + client_info.client_id, + e, + ) + + return True + + async def _re_register_client(self, request, client_id: str) -> bool: + idx, connection = self._find_mcp_connection(request, client_id) + if idx is None or connection is None: + log.warning( + "Unable to locate MCP tool server configuration for client %s during re-registration", + client_id, + ) + 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( + "Dynamic client re-registration failed for %s: %s", + client_id, + e, + ) + return False + + encrypted_info = encrypt_data(oauth_client_info.model_dump(mode="json")) + + updated_connections = copy.deepcopy( + request.app.state.config.TOOL_SERVER_CONNECTIONS or [] + ) + if idx >= len(updated_connections): + log.error( + "MCP tool server index %s out of range during OAuth client re-registration for %s", + idx, + client_id, + ) + return False + + updated_connection = copy.deepcopy(connection) + updated_connection.setdefault("info", {}) + updated_connection["info"]["oauth_client_info"] = encrypted_info + updated_connections[idx] = updated_connection + + try: + request.app.state.config.TOOL_SERVER_CONNECTIONS = updated_connections + except Exception as e: + log.error( + "Failed to persist updated OAuth client info for %s: %s", + client_id, + e, + ) + return False + + self.remove_client(client_id) + self.add_client(client_id, oauth_client_info) + OAuthSessions.delete_sessions_by_provider(client_id) + + log.info("Re-registered OAuth client %s for MCP tool server", client_id) + return True + + async def _ensure_valid_client_registration( + self, request, client_id: str + ) -> None: + if not client_id.startswith("mcp:"): + return + + client = self.get_client(client_id) + client_info = self.get_client_info(client_id) + if client is None or client_info is None: + raise HTTPException(status.HTTP_404_NOT_FOUND) + + is_valid = await self._preflight_authorization_url(client, client_info) + if is_valid: + return + + log.info( + "Detected invalid OAuth client %s; attempting re-registration", + client_id, + ) + re_registered = await self._re_register_client(request, client_id) + if not re_registered: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Failed to re-register OAuth client", + ) + + client = self.get_client(client_id) + client_info = self.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 self._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", + ) + def get_client(self, client_id): client = self.clients.get(client_id) return client["client"] if client else None @@ -602,10 +802,11 @@ class OAuthClientManager: return None async def handle_authorize(self, request, client_id: str) -> RedirectResponse: + await self._ensure_valid_client_registration(request, client_id) + 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) @@ -613,7 +814,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) From 4b7403496795fd7476243e7b6a48292c0cfe1b4b Mon Sep 17 00:00:00 2001 From: Taylor Wilsdon Date: Sun, 19 Oct 2025 16:58:09 -0400 Subject: [PATCH 05/45] black fmt --- backend/open_webui/utils/oauth.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index d24a379ede..34fb441679 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -486,9 +486,7 @@ class OAuthClientManager: try: payload = json.loads(body_text) error = payload.get("error") - error_description = payload.get( - "error_description", "" - ) + error_description = payload.get("error_description", "") except json.JSONDecodeError: error = None error_description = "" @@ -496,7 +494,11 @@ class OAuthClientManager: error_description = body_text combined = f"{error or ''} {error_description}".lower() - if "invalid_client" in combined or "invalid client" in combined or "client id" in combined: + if ( + "invalid_client" in combined + or "invalid client" in combined + or "client id" in combined + ): log.warning( "OAuth client preflight detected invalid registration for %s: %s %s", client_info.client_id, @@ -577,9 +579,7 @@ class OAuthClientManager: log.info("Re-registered OAuth client %s for MCP tool server", client_id) return True - async def _ensure_valid_client_registration( - self, request, client_id: str - ) -> None: + async def _ensure_valid_client_registration(self, request, client_id: str) -> None: if not client_id.startswith("mcp:"): return From 02a2683eb0865a273af5d7539a8681433ee14ba9 Mon Sep 17 00:00:00 2001 From: silentoplayz Date: Thu, 23 Oct 2025 05:40:31 -0400 Subject: [PATCH 06/45] fix: display correct keys for international keyboards Updates the ShortcutsModal to dynamically display the correct physical keys for users with non-US keyboard layouts. The `ShortcutItem` component now uses `navigator.keyboard.getLayoutMap()` to resolve `KeyboardEvent.code` values (e.g., "Slash") to the character they produce on the user's active keyboard layout (e.g., "-"). This ensures the displayed shortcuts match the keys the user needs to press. A fallback is included for older browsers that do not support this API. --- src/lib/components/chat/ShortcutItem.svelte | 37 +++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/lib/components/chat/ShortcutItem.svelte b/src/lib/components/chat/ShortcutItem.svelte index 494631a984..aaa4dd7de5 100644 --- a/src/lib/components/chat/ShortcutItem.svelte +++ b/src/lib/components/chat/ShortcutItem.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index cfa4680d01..7610a30d78 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -4,6 +4,7 @@ import { PaneGroup, Pane, PaneResizer } from 'paneforge'; import { getContext, onDestroy, onMount, tick } from 'svelte'; + import { fade } from 'svelte/transition'; const i18n: Writable = getContext('i18n'); import { goto } from '$app/navigation'; @@ -34,6 +35,7 @@ showOverview, chatTitle, showArtifacts, + artifactContents, tools, toolServers, functions, @@ -48,9 +50,9 @@ createMessagesList, getPromptVariables, processDetails, - removeAllDetails + removeAllDetails, + getCodeBlockContents } from '$lib/utils'; - import { createNewChat, getAllTags, @@ -75,8 +77,8 @@ import { getTools } from '$lib/apis/tools'; import { uploadFile } from '$lib/apis/files'; import { createOpenAITextStream } from '$lib/apis/streaming'; - - import { fade } from 'svelte/transition'; + import { getFunctions } from '$lib/apis/functions'; + import { updateFolderById } from '$lib/apis/folders'; import Banner from '../common/Banner.svelte'; import MessageInput from '$lib/components/chat/MessageInput.svelte'; @@ -89,9 +91,7 @@ import Spinner from '../common/Spinner.svelte'; import Tooltip from '../common/Tooltip.svelte'; import Sidebar from '../icons/Sidebar.svelte'; - import { getFunctions } from '$lib/apis/functions'; import Image from '../common/Image.svelte'; - import { updateFolderById } from '$lib/apis/folders'; export let chatIdProp = ''; @@ -819,6 +819,63 @@ } }; + $: if (history) { + getContents(); + } else { + artifactContents.set([]); + } + + const getContents = () => { + const messages = history ? createMessagesList(history, history.currentId) : []; + let contents = []; + messages.forEach((message) => { + if (message?.role !== 'user' && message?.content) { + const { + codeBlocks: codeBlocks, + html: htmlContent, + css: cssContent, + js: jsContent + } = getCodeBlockContents(message.content); + + if (htmlContent || cssContent || jsContent) { + const renderedContent = ` + + + + + + <${''}style> + body { + background-color: white; /* Ensure the iframe has a white background */ + } + + ${cssContent} + + + + ${htmlContent} + + <${''}script> + ${jsContent} + + + + `; + contents = [...contents, { type: 'iframe', content: renderedContent }]; + } else { + // Check for SVG content + for (const block of codeBlocks) { + if (block.lang === 'svg' || (block.lang === 'xml' && block.code.includes('{$i18n.t('Settings')} --> - {#if $mobile} + {#if $mobile && ($user?.role === 'admin' || ($user?.permissions.chat?.controls ?? true))} {$i18n.t('Overview')} - { - await showControls.set(true); - await showArtifacts.set(true); - await showOverview.set(false); - await showEmbeds.set(false); - }} - > - -
{$i18n.t('Artifacts')}
-
+ {#if ($artifactContents ?? []).length > 0} + { + await showControls.set(true); + await showArtifacts.set(true); + await showOverview.set(false); + await showEmbeds.set(false); + }} + > + +
{$i18n.t('Artifacts')}
+
+ {/if}
diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index de37963adb..5fb3109062 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -80,8 +80,10 @@ export const showOverview = writable(false); export const showArtifacts = writable(false); export const showCallOverlay = writable(false); -export const embed = writable(null); export const artifactCode = writable(null); +export const artifactContents = writable(null); + +export const embed = writable(null); export const temporaryChatEnabled = writable(false); export const scrollPaginationEnabled = writable(false); diff --git a/src/lib/utils/index.ts b/src/lib/utils/index.ts index 28ddbb32eb..f9fb4236f2 100644 --- a/src/lib/utils/index.ts +++ b/src/lib/utils/index.ts @@ -348,7 +348,7 @@ export const compressImage = async (imageUrl, maxWidth, maxHeight) => { context.drawImage(img, 0, 0, width, height); // Get compressed image URL - const mimeType = imageUrl.match(/^data:([^;]+);/)?.[1]; + const mimeType = imageUrl.match(/^data:([^;]+);/)?.[1]; const compressedUrl = canvas.toDataURL(mimeType); resolve(compressedUrl); }; @@ -1625,3 +1625,63 @@ export const renderVegaVisualization = async (spec: string, i18n?: any) => { const svg = await view.toSVG(); return svg; }; + +export const getCodeBlockContents = (content: string): object => { + const codeBlockContents = content.match(/```[\s\S]*?```/g); + + let codeBlocks = []; + + let htmlContent = ''; + let cssContent = ''; + let jsContent = ''; + + if (codeBlockContents) { + codeBlockContents.forEach((block) => { + const lang = block.split('\n')[0].replace('```', '').trim().toLowerCase(); + const code = block.replace(/```[\s\S]*?\n/, '').replace(/```$/, ''); + codeBlocks.push({ lang, code }); + }); + + codeBlocks.forEach((block) => { + const { lang, code } = block; + + if (lang === 'html') { + htmlContent += code + '\n'; + } else if (lang === 'css') { + cssContent += code + '\n'; + } else if (lang === 'javascript' || lang === 'js') { + jsContent += code + '\n'; + } + }); + } else { + const inlineHtml = content.match(/[\s\S]*?<\/html>/gi); + const inlineCss = content.match(/ \ No newline at end of file + From 20cd9e9461e8f7f551a476dfe9a87040b2d4b9cf Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 26 Oct 2025 21:06:32 -0700 Subject: [PATCH 27/45] chore: svelte5 bump --- package-lock.json | 686 +++++++----------- package.json | 10 +- src/lib/components/chat/Chat.svelte | 16 +- .../chat/Messages/Markdown/HTMLToken.svelte | 14 +- .../Markdown/MarkdownInlineTokens.svelte | 7 +- .../Messages/Markdown/MarkdownTokens.svelte | 7 +- .../layout/Sidebar/ChannelItem.svelte | 4 +- .../layout/Sidebar/RecursiveFolder.svelte | 4 +- src/lib/components/workspace/Models.svelte | 4 +- 9 files changed, 319 insertions(+), 433 deletions(-) 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/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 7610a30d78..cb7ad60c4d 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -1139,7 +1139,7 @@ }); } }; - const chatCompletedHandler = async (chatId, modelId, responseMessageId, messages) => { + const chatCompletedHandler = async (_chatId, modelId, responseMessageId, messages) => { const res = await chatCompleted(localStorage.token, { model: modelId, messages: messages.map((m) => ({ @@ -1153,7 +1153,7 @@ })), filter_ids: selectedFilterIds.length > 0 ? selectedFilterIds : undefined, model_item: $models.find((m) => m.id === modelId), - chat_id: chatId, + chat_id: _chatId, session_id: $socket?.id, id: responseMessageId }).catch((error) => { @@ -1181,9 +1181,9 @@ await tick(); - if ($chatId == chatId) { + if ($chatId == _chatId) { if (!$temporaryChatEnabled) { - chat = await updateChatById(localStorage.token, chatId, { + chat = await updateChatById(localStorage.token, _chatId, { models: selectedModels, messages: messages, history: history, @@ -1199,7 +1199,7 @@ taskIds = null; }; - const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => { + const chatActionHandler = async (_chatId, actionId, modelId, responseMessageId, event = null) => { const messages = createMessagesList(history, responseMessageId); const res = await chatAction(localStorage.token, actionId, { @@ -1214,7 +1214,7 @@ })), ...(event ? { event: event } : {}), model_item: $models.find((m) => m.id === modelId), - chat_id: chatId, + chat_id: _chatId, session_id: $socket?.id, id: responseMessageId }).catch((error) => { @@ -1236,9 +1236,9 @@ } } - if ($chatId == chatId) { + if ($chatId == _chatId) { if (!$temporaryChatEnabled) { - chat = await updateChatById(localStorage.token, chatId, { + chat = await updateChatById(localStorage.token, _chatId, { models: selectedModels, messages: messages, history: history, diff --git a/src/lib/components/chat/Messages/Markdown/HTMLToken.svelte b/src/lib/components/chat/Messages/Markdown/HTMLToken.svelte index 13bee6e111..b66a0b01fd 100644 --- a/src/lib/components/chat/Messages/Markdown/HTMLToken.svelte +++ b/src/lib/components/chat/Messages/Markdown/HTMLToken.svelte @@ -79,7 +79,12 @@ title="Embedded content" frameborder="0" sandbox - onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';" + on:load={(e) => { + try { + e.currentTarget.style.height = + e.currentTarget.contentWindow.document.body.scrollHeight + 20 + 'px'; + } catch {} + }} > {:else} {token.text} @@ -116,7 +121,12 @@ referrerpolicy="strict-origin-when-cross-origin" allowfullscreen width="100%" - onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';" + on:load={(e) => { + try { + e.currentTarget.style.height = + e.currentTarget.contentWindow.document.body.scrollHeight + 20 + 'px'; + } catch {} + }} > {/if} {:else if token.text.includes(` { + try { + e.currentTarget.style.height = + e.currentTarget.contentWindow.document.body.scrollHeight + 20 + 'px'; + } catch {} + }} > {:else if token.type === 'mention'} diff --git a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte index e568d92bac..f989151a5c 100644 --- a/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte +++ b/src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte @@ -321,7 +321,12 @@ title={token.fileId} width="100%" frameborder="0" - onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';" + on:load={(e) => { + try { + e.currentTarget.style.height = + e.currentTarget.contentWindow.document.body.scrollHeight + 20 + 'px'; + } catch {} + }} > {:else if token.type === 'paragraph'}

diff --git a/src/lib/components/layout/Sidebar/ChannelItem.svelte b/src/lib/components/layout/Sidebar/ChannelItem.svelte index 2d853931b5..4ea884cf9d 100644 --- a/src/lib/components/layout/Sidebar/ChannelItem.svelte +++ b/src/lib/components/layout/Sidebar/ChannelItem.svelte @@ -78,7 +78,7 @@ {#if $user?.role === 'admin'} - - + {/if} diff --git a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte index 72a9f007fe..be8343c90d 100644 --- a/src/lib/components/layout/Sidebar/RecursiveFolder.svelte +++ b/src/lib/components/layout/Sidebar/RecursiveFolder.svelte @@ -470,7 +470,7 @@ >

-
- + {/each} {:else} From c57332130586785bf87667dd4b3729f93dd9853f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 26 Oct 2025 21:11:05 -0700 Subject: [PATCH 28/45] refac --- src/lib/components/layout/Sidebar/PinnedModelItem.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/layout/Sidebar/PinnedModelItem.svelte b/src/lib/components/layout/Sidebar/PinnedModelItem.svelte index 1f58548b52..86f176c7a3 100644 --- a/src/lib/components/layout/Sidebar/PinnedModelItem.svelte +++ b/src/lib/components/layout/Sidebar/PinnedModelItem.svelte @@ -19,7 +19,7 @@ {#if model} -
+
Date: Sun, 26 Oct 2025 22:11:47 -0700 Subject: [PATCH 30/45] refac/fix: marker api key validation --- src/lib/components/admin/Settings/Documents.svelte | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/lib/components/admin/Settings/Documents.svelte b/src/lib/components/admin/Settings/Documents.svelte index 3d3277d299..35e0484916 100644 --- a/src/lib/components/admin/Settings/Documents.svelte +++ b/src/lib/components/admin/Settings/Documents.svelte @@ -171,14 +171,6 @@ return; } - if ( - RAGConfig.CONTENT_EXTRACTION_ENGINE === 'datalab_marker' && - !RAGConfig.DATALAB_MARKER_API_KEY - ) { - toast.error($i18n.t('Datalab Marker API Key required.')); - return; - } - if ( RAGConfig.CONTENT_EXTRACTION_ENGINE === 'datalab_marker' && RAGConfig.DATALAB_MARKER_ADDITIONAL_CONFIG && From 46a3f7de5ebc24bfdc8aad02c4a114ac69777447 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 26 Oct 2025 22:12:34 -0700 Subject: [PATCH 31/45] refac: rm rich text highlight --- src/lib/components/common/RichTextInput.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte index a0a1996dfa..d01e0f007f 100644 --- a/src/lib/components/common/RichTextInput.svelte +++ b/src/lib/components/common/RichTextInput.svelte @@ -695,7 +695,6 @@ CodeBlockLowlight.configure({ lowlight }), - Highlight, Typography, TableKit.configure({ table: { resizable: true } From f47214314b5a2c9c52b50b7409ee4fde279c4731 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sun, 26 Oct 2025 22:20:57 -0700 Subject: [PATCH 32/45] refac --- src/lib/components/workspace/Models.svelte | 7 +++ .../workspace/Models/Knowledge.svelte | 2 +- .../workspace/Models/ModelMenu.svelte | 57 ++++++++++++------- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/src/lib/components/workspace/Models.svelte b/src/lib/components/workspace/Models.svelte index 1241969e80..a2d7258b65 100644 --- a/src/lib/components/workspace/Models.svelte +++ b/src/lib/components/workspace/Models.svelte @@ -411,6 +411,8 @@ {#if (filteredModels ?? []).length !== 0}
{#each filteredModels as model (model.id)} + +
{ + goto( + `/workspace/models/edit?id=${encodeURIComponent(model.id)}` + ); + }} shareHandler={() => { shareModelHandler(model); }} diff --git a/src/lib/components/workspace/Models/Knowledge.svelte b/src/lib/components/workspace/Models/Knowledge.svelte index a5bc2a45dc..11f5dabd84 100644 --- a/src/lib/components/workspace/Models/Knowledge.svelte +++ b/src/lib/components/workspace/Models/Knowledge.svelte @@ -18,7 +18,7 @@ let loaded = false; let filesInputElement = null; - let inputFiles = []; + let inputFiles = null; const uploadFileHandler = async (file, fullContext: boolean = false) => { if ($user?.role !== 'admin' && !($user?.permissions?.chat?.file_upload ?? true)) { diff --git a/src/lib/components/workspace/Models/ModelMenu.svelte b/src/lib/components/workspace/Models/ModelMenu.svelte index a17b9258f3..a7a3469231 100644 --- a/src/lib/components/workspace/Models/ModelMenu.svelte +++ b/src/lib/components/workspace/Models/ModelMenu.svelte @@ -22,6 +22,7 @@ export let user; export let model; + export let editHandler: Function; export let shareHandler: Function; export let cloneHandler: Function; export let exportHandler: Function; @@ -61,6 +62,16 @@ align="start" transition={flyAndScale} > + { + editHandler(); + }} + > + +
{$i18n.t('Edit')}
+
+ { @@ -113,6 +124,19 @@
+ { + cloneHandler(); + }} + > + + +
{$i18n.t('Clone')}
+
+ +
+ { @@ -124,6 +148,17 @@
{$i18n.t('Copy Link')}
+ { + exportHandler(); + }} + > + + +
{$i18n.t('Export')}
+
+ {#if $config?.features.enable_community_sharing} {/if} - { - cloneHandler(); - }} - > - - -
{$i18n.t('Clone')}
-
- - { - exportHandler(); - }} - > - - -
{$i18n.t('Export')}
-
-
Date: Mon, 27 Oct 2025 09:11:31 +0100 Subject: [PATCH 33/45] add danish translation --- src/lib/i18n/locales/da-DK/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/i18n/locales/da-DK/translation.json b/src/lib/i18n/locales/da-DK/translation.json index 40d040e8e0..02a4dc4dcb 100644 --- a/src/lib/i18n/locales/da-DK/translation.json +++ b/src/lib/i18n/locales/da-DK/translation.json @@ -1049,7 +1049,7 @@ "New Folder": "Ny mappe", "New Function": "Ny funktion", "New Knowledge": "", - "New Model": "", + "New Model": "Ny model", "New Note": "Ny note", "New Password": "Ny adgangskode", "New Prompt": "", From 5e178824886366b632b31a89251beb4eea441cdf Mon Sep 17 00:00:00 2001 From: Wang Weixuan Date: Tue, 28 Oct 2025 04:58:00 +0800 Subject: [PATCH 34/45] fix: use trusted env in web search loader Signed-off-by: Wang Weixuan --- backend/open_webui/retrieval/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index 08dcde34da..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, ) From b72443004da66e266b7b22bff5394bf47a0a244f Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 27 Oct 2025 15:06:53 -0700 Subject: [PATCH 35/45] refac/chore: svelte 5 migration --- src/lib/components/common/RichTextInput/suggestions.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/components/common/RichTextInput/suggestions.ts b/src/lib/components/common/RichTextInput/suggestions.ts index a87b95abf9..0667e83060 100644 --- a/src/lib/components/common/RichTextInput/suggestions.ts +++ b/src/lib/components/common/RichTextInput/suggestions.ts @@ -1,3 +1,4 @@ +import { mount, unmount } from 'svelte'; import tippy from 'tippy.js'; export function getSuggestionRenderer(Component: any, ComponentProps = {}) { @@ -15,7 +16,7 @@ export function getSuggestionRenderer(Component: any, ComponentProps = {}) { document.body.appendChild(container); // mount Svelte component - component = new Component({ + component = mount(Component, { target: container, props: { char: props?.text, @@ -104,7 +105,12 @@ export function getSuggestionRenderer(Component: any, ComponentProps = {}) { popup?.destroy(); popup = null; - component?.$destroy(); + try { + unmount(component); + } catch (e) { + console.error('Error unmounting component:', e); + } + component = null; if (container?.parentNode) container.parentNode.removeChild(container); From 182e4138bf94c87c51d484a4e30ba6491285171b Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 27 Oct 2025 15:07:00 -0700 Subject: [PATCH 36/45] refac: styling --- src/lib/components/common/FileItemModal.svelte | 4 ++-- src/lib/components/notes/NoteEditor/Chat.svelte | 6 +++--- src/lib/components/notes/NoteEditor/Controls.svelte | 6 +++--- src/lib/components/notes/NotePanel.svelte | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/components/common/FileItemModal.svelte b/src/lib/components/common/FileItemModal.svelte index 52bf75127d..51afbde1af 100644 --- a/src/lib/components/common/FileItemModal.svelte +++ b/src/lib/components/common/FileItemModal.svelte @@ -161,7 +161,7 @@
{#if edit} -
+
{:else if isPDF}
-
+
@@ -377,7 +377,7 @@ Based on the user's instruction, update and enhance the existing notes or select
{#if selectedContent} -
+
{selectedContent?.text} diff --git a/src/lib/components/notes/NoteEditor/Controls.svelte b/src/lib/components/notes/NoteEditor/Controls.svelte index df988c28d9..675056ee41 100644 --- a/src/lib/components/notes/NoteEditor/Controls.svelte +++ b/src/lib/components/notes/NoteEditor/Controls.svelte @@ -17,7 +17,7 @@ }; -
+
-
+
{#if files.length > 0} -
{$i18n.t('Files')}
+
{$i18n.t('Files')}
{#each files.filter((file) => file.type !== 'image') as file, fileIdx} diff --git a/src/lib/components/notes/NotePanel.svelte b/src/lib/components/notes/NotePanel.svelte index 676d86b83d..96e687854e 100644 --- a/src/lib/components/notes/NotePanel.svelte +++ b/src/lib/components/notes/NotePanel.svelte @@ -99,7 +99,7 @@ {#if show}
From 92aafd6c0644c8b095ef8693633be053176a7576 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 27 Oct 2025 15:31:25 -0700 Subject: [PATCH 37/45] refac --- backend/open_webui/routers/configs.py | 62 +++++++-------------------- backend/open_webui/utils/oauth.py | 37 ++++++++-------- 2 files changed, 32 insertions(+), 67 deletions(-) diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index e8e876eac7..43ef73f29b 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -167,59 +167,27 @@ async def set_tool_servers_config( form_data: ToolServersConfigForm, user=Depends(get_admin_user), ): - old_connections = copy.deepcopy( - request.app.state.config.TOOL_SERVER_CONNECTIONS or [] - ) + mcp_server_ids = [ + conn.get("info", {}).get("id") + for conn in form_data.TOOL_SERVER_CONNECTIONS + if conn.get("type") == "mcp" + ] - new_connections = [ + for server_id in mcp_server_ids: + # Remove existing OAuth clients for MCP tool servers that are no longer present + client_key = f"mcp:{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 ] - old_mcp_connections = { - conn.get("info", {}).get("id"): conn - for conn in old_connections - if conn.get("type") == "mcp" - } - new_mcp_connections = { - conn.get("info", {}).get("id"): conn - for conn in new_connections - if conn.get("type") == "mcp" - } - - purge_oauth_clients = set() - - for server_id, old_conn in old_mcp_connections.items(): - if not server_id: - continue - - old_auth_type = old_conn.get("auth_type", "none") - new_conn = new_mcp_connections.get(server_id) - - if new_conn is None: - if old_auth_type == "oauth_2.1": - purge_oauth_clients.add(server_id) - continue - - new_auth_type = new_conn.get("auth_type", "none") - - if old_auth_type == "oauth_2.1": - if ( - new_auth_type != "oauth_2.1" - or old_conn.get("url") != new_conn.get("url") - or old_conn.get("info", {}).get("oauth_client_info") - != new_conn.get("info", {}).get("oauth_client_info") - ): - purge_oauth_clients.add(server_id) - - request.app.state.config.TOOL_SERVER_CONNECTIONS = new_connections - await set_tool_servers(request) - for server_id in purge_oauth_clients: - client_key = f"mcp:{server_id}" - request.app.state.oauth_client_manager.remove_client(client_key) - OAuthSessions.delete_sessions_by_provider(client_key) - for connection in request.app.state.config.TOOL_SERVER_CONNECTIONS: server_type = connection.get("type", "openapi") if server_type == "mcp": diff --git a/backend/open_webui/utils/oauth.py b/backend/open_webui/utils/oauth.py index 34fb441679..30939eb20a 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -153,32 +153,32 @@ def decrypt_data(data: str): raise -def _build_oauth_callback_error_message(exc: Exception) -> str: +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(exc, OAuth2Error): - parts = [p for p in [exc.error, exc.description] if p] + if isinstance(e, OAuth2Error): + parts = [p for p in [e.error, e.description] if p] detail = " - ".join(parts) - elif isinstance(exc, HTTPException): - detail = exc.detail if isinstance(exc.detail, str) else str(exc.detail) - elif isinstance(exc, aiohttp.ClientResponseError): - detail = f"Upstream provider returned {exc.status}: {exc.message}" - elif isinstance(exc, aiohttp.ClientError): - detail = str(exc) - elif isinstance(exc, KeyError): - missing = str(exc).strip("'") + 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(exc) + detail = str(e) detail = detail.replace("\n", " ").strip() if not detail: - detail = exc.__class__.__name__ + detail = e.__class__.__name__ message = f"OAuth callback failed: {detail}" return message[:197] + "..." if len(message) > 200 else message @@ -402,20 +402,18 @@ class OAuthClientManager: return self.clients[client_id] def remove_client(self, client_id): - removed = False if client_id in self.clients: del self.clients[client_id] - removed = True + 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) - removed = True + if hasattr(self.oauth, "_registry"): if client_id in self.oauth._registry: self.oauth._registry.pop(client_id, None) - removed = True - if removed: - log.info(f"Removed OAuth client {client_id}") + return True def _find_mcp_connection(self, request, client_id: str): @@ -574,7 +572,6 @@ class OAuthClientManager: self.remove_client(client_id) self.add_client(client_id, oauth_client_info) - OAuthSessions.delete_sessions_by_provider(client_id) log.info("Re-registered OAuth client %s for MCP tool server", client_id) return True From c8b2313362ce42476fc5775b6cfa7399b53f58f1 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 27 Oct 2025 15:38:59 -0700 Subject: [PATCH 38/45] refac --- backend/open_webui/main.py | 1 + backend/open_webui/routers/configs.py | 25 +++++++++++++------------ backend/open_webui/utils/oauth.py | 4 +++- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 1405a43061..da89fd7de4 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1941,6 +1941,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", "" diff --git a/backend/open_webui/routers/configs.py b/backend/open_webui/routers/configs.py index 43ef73f29b..5c08fded23 100644 --- a/backend/open_webui/routers/configs.py +++ b/backend/open_webui/routers/configs.py @@ -167,19 +167,19 @@ async def set_tool_servers_config( form_data: ToolServersConfigForm, user=Depends(get_admin_user), ): - mcp_server_ids = [ - conn.get("info", {}).get("id") - for conn in form_data.TOOL_SERVER_CONNECTIONS - if conn.get("type") == "mcp" - ] + for connection in request.app.state.config.TOOL_SERVER_CONNECTIONS: + server_type = connection.get("type", "openapi") + auth_type = connection.get("auth_type", "none") - for server_id in mcp_server_ids: - # Remove existing OAuth clients for MCP tool servers that are no longer present - client_key = f"mcp:{server_id}" - try: - request.app.state.oauth_client_manager.remove_client(client_key) - except: - pass + 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 = [ @@ -193,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/utils/oauth.py b/backend/open_webui/utils/oauth.py index 30939eb20a..03f7337774 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -582,6 +582,7 @@ class OAuthClientManager: client = self.get_client(client_id) client_info = self.get_client_info(client_id) + if client is None or client_info is None: raise HTTPException(status.HTTP_404_NOT_FOUND) @@ -593,6 +594,7 @@ class OAuthClientManager: "Detected invalid OAuth client %s; attempting re-registration", client_id, ) + re_registered = await self._re_register_client(request, client_id) if not re_registered: raise HTTPException( @@ -799,7 +801,7 @@ class OAuthClientManager: return None async def handle_authorize(self, request, client_id: str) -> RedirectResponse: - await self._ensure_valid_client_registration(request, client_id) + # await self._ensure_valid_client_registration(request, client_id) client = self.get_client(client_id) if client is None: From cbcab062eba85678d8bd6966cd85da1b6d50b16b Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 27 Oct 2025 16:46:04 -0700 Subject: [PATCH 39/45] refac --- backend/open_webui/main.py | 97 ++++++++++++++++- backend/open_webui/utils/oauth.py | 175 ++++-------------------------- 2 files changed, 116 insertions(+), 156 deletions(-) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index da89fd7de4..14ee4dc870 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 @@ -1987,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, @@ -1994,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, + ) + + re_registered = await register_client(request, client_id) + if not re_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/utils/oauth.py b/backend/open_webui/utils/oauth.py index 03f7337774..6889f377bc 100644 --- a/backend/open_webui/utils/oauth.py +++ b/backend/open_webui/utils/oauth.py @@ -416,34 +416,10 @@ class OAuthClientManager: return True - def _find_mcp_connection(self, request, client_id: str): - try: - connections = request.app.state.config.TOOL_SERVER_CONNECTIONS or [] - except Exception: - connections = [] - - normalized_client_id = client_id.split(":")[-1] - - for idx, connection in enumerate(connections): - if not isinstance(connection, dict): - continue - if connection.get("type") != "mcp": - continue - - info = connection.get("info") or {} - server_id = info.get("id") - if not server_id: - continue - - normalized_server_id = server_id.split(":")[-1] - if normalized_server_id == normalized_client_id: - return idx, connection - - return None, None - 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 @@ -454,168 +430,59 @@ class OAuthClientManager: try: auth_data = await client.create_authorization_url(redirect_uri=redirect_uri) - authorize_url = auth_data.get("url") - if not authorize_url: + authorization_url = auth_data.get("url") + + if not authorization_url: return True except Exception as e: log.debug( - "Skipping OAuth preflight for client %s: %s", - client_info.client_id, - e, + 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( - authorize_url, + authorization_url, allow_redirects=False, ssl=AIOHTTP_CLIENT_SESSION_SSL, ) as resp: if resp.status < 400: return True + response_text = await resp.text() - body_text = await resp.text() error = None error_description = "" - content_type = resp.headers.get("content-type", "") + content_type = resp.headers.get("content-type", "") if "application/json" in content_type: try: - payload = json.loads(body_text) + payload = json.loads(response_text) error = payload.get("error") error_description = payload.get("error_description", "") - except json.JSONDecodeError: - error = None - error_description = "" + except: + pass else: - error_description = body_text + error_description = response_text - combined = f"{error or ''} {error_description}".lower() - if ( - "invalid_client" in combined - or "invalid client" in combined - or "client id" in combined + 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( - "OAuth client preflight detected invalid registration for %s: %s %s", - client_info.client_id, - error, - error_description, + f"OAuth client preflight detected invalid registration for {client_info.client_id}: {error} {error_description}" ) + return False except Exception as e: log.debug( - "Skipping OAuth preflight network check for client %s: %s", - client_info.client_id, - e, + f"Skipping OAuth preflight network check for client {client_info.client_id}: {e}" ) return True - async def _re_register_client(self, request, client_id: str) -> bool: - idx, connection = self._find_mcp_connection(request, client_id) - if idx is None or connection is None: - log.warning( - "Unable to locate MCP tool server configuration for client %s during re-registration", - client_id, - ) - 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( - "Dynamic client re-registration failed for %s: %s", - client_id, - e, - ) - return False - - encrypted_info = encrypt_data(oauth_client_info.model_dump(mode="json")) - - updated_connections = copy.deepcopy( - request.app.state.config.TOOL_SERVER_CONNECTIONS or [] - ) - if idx >= len(updated_connections): - log.error( - "MCP tool server index %s out of range during OAuth client re-registration for %s", - idx, - client_id, - ) - return False - - updated_connection = copy.deepcopy(connection) - updated_connection.setdefault("info", {}) - updated_connection["info"]["oauth_client_info"] = encrypted_info - updated_connections[idx] = updated_connection - - try: - request.app.state.config.TOOL_SERVER_CONNECTIONS = updated_connections - except Exception as e: - log.error( - "Failed to persist updated OAuth client info for %s: %s", - client_id, - e, - ) - return False - - self.remove_client(client_id) - self.add_client(client_id, oauth_client_info) - - log.info("Re-registered OAuth client %s for MCP tool server", client_id) - return True - - async def _ensure_valid_client_registration(self, request, client_id: str) -> None: - if not client_id.startswith("mcp:"): - return - - client = self.get_client(client_id) - client_info = self.get_client_info(client_id) - - if client is None or client_info is None: - raise HTTPException(status.HTTP_404_NOT_FOUND) - - is_valid = await self._preflight_authorization_url(client, client_info) - if is_valid: - return - - log.info( - "Detected invalid OAuth client %s; attempting re-registration", - client_id, - ) - - re_registered = await self._re_register_client(request, client_id) - if not re_registered: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail="Failed to re-register OAuth client", - ) - - client = self.get_client(client_id) - client_info = self.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 self._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", - ) - def get_client(self, client_id): client = self.clients.get(client_id) return client["client"] if client else None @@ -801,8 +668,6 @@ class OAuthClientManager: return None async def handle_authorize(self, request, client_id: str) -> RedirectResponse: - # await self._ensure_valid_client_registration(request, client_id) - client = self.get_client(client_id) if client is None: raise HTTPException(404) From b9bbf2258173301ba3b9be11a5b170ef6189a991 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 27 Oct 2025 16:46:49 -0700 Subject: [PATCH 40/45] refac --- backend/open_webui/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 14ee4dc870..105fc5c337 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -2066,8 +2066,8 @@ async def oauth_client_authorize( client_id, ) - re_registered = await register_client(request, client_id) - if not re_registered: + 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", From a776dbd01d851ddc5f8f45771f9d18ef1d558946 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 27 Oct 2025 23:42:00 -0700 Subject: [PATCH 41/45] refac --- .../chat/Messages/Markdown/MarkdownInlineTokens.svelte | 2 +- .../Messages/Markdown/MarkdownInlineTokens/TextToken.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte b/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte index 4abcbd3246..85ba8740c1 100644 --- a/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte +++ b/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte @@ -24,7 +24,7 @@ export let onSourceClick: Function = () => {}; -{#each tokens as token} +{#each tokens as token, tokenIdx (tokenIdx)} {#if token.type === 'escape'} {unescapeHtml(token.text)} {:else if token.type === 'html'} diff --git a/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens/TextToken.svelte b/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens/TextToken.svelte index d5ae387afe..e7efd10064 100644 --- a/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens/TextToken.svelte +++ b/src/lib/components/chat/Messages/Markdown/MarkdownInlineTokens/TextToken.svelte @@ -13,7 +13,7 @@ {:else} {#each texts as text} - {text} + {text}{' '} {/each} {/if} From 76bde402feab78ffb6e5c57ba724afdf854a53f1 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 27 Oct 2025 23:49:26 -0700 Subject: [PATCH 42/45] refac --- src/lib/components/workspace/Prompts.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/components/workspace/Prompts.svelte b/src/lib/components/workspace/Prompts.svelte index 079ce85bdb..8cac0d5298 100644 --- a/src/lib/components/workspace/Prompts.svelte +++ b/src/lib/components/workspace/Prompts.svelte @@ -33,7 +33,7 @@ let promptsImportInputElement: HTMLInputElement; let loaded = false; - let importFiles = ''; + let importFiles = null; let query = ''; let prompts = []; From e986488ab5951bd3a5a0cc8d13286a2e420bb0d7 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 28 Oct 2025 00:06:52 -0700 Subject: [PATCH 43/45] enh: ELEVENLABS_API_BASE_URL env var --- backend/open_webui/config.py | 4 ++++ backend/open_webui/routers/audio.py | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/config.py b/backend/open_webui/config.py index c794065974..d0a76e0238 100644 --- a/backend/open_webui/config.py +++ b/backend/open_webui/config.py @@ -3343,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/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", From f524a6a8e7d10754d21b81a59fbf75f4cfa63213 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 28 Oct 2025 00:34:53 -0700 Subject: [PATCH 44/45] refac/fix: kb image upload handling --- backend/open_webui/routers/files.py | 4 ++++ src/lib/apis/files/index.ts | 4 ++++ .../workspace/Knowledge/KnowledgeBase.svelte | 15 ++++++++------- 3 files changed, 16 insertions(+), 7 deletions(-) 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/src/lib/apis/files/index.ts b/src/lib/apis/files/index.ts index 6a1763edb8..8351393e3c 100644 --- a/src/lib/apis/files/index.ts +++ b/src/lib/apis/files/index.ts @@ -63,6 +63,10 @@ export const uploadFile = async (token: string, file: File, metadata?: object | console.error(data.error); res.error = data.error; } + + if (res?.data) { + res.data = data; + } } } } diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte index 3c494e7609..0054eb2964 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte @@ -184,12 +184,6 @@ if (uploadedFile) { console.log(uploadedFile); - - if (uploadedFile.error) { - console.warn('File upload warning:', uploadedFile.error); - toast.warning(uploadedFile.error); - } - knowledge.files = knowledge.files.map((item) => { if (item.itemId === tempItemId) { item.id = uploadedFile.id; @@ -199,7 +193,14 @@ delete item.itemId; return item; }); - await addFileHandler(uploadedFile.id); + + if (uploadedFile.error) { + console.warn('File upload warning:', uploadedFile.error); + toast.warning(uploadedFile.error); + knowledge.files = knowledge.files.filter((file) => file.id !== uploadedFile.id); + } else { + await addFileHandler(uploadedFile.id); + } } else { toast.error($i18n.t('Failed to upload file.')); } From aaef7538b2406cc9f971978d58fc70d52636796f Mon Sep 17 00:00:00 2001 From: Stoyan Zlatev Date: Tue, 28 Oct 2025 09:53:35 +0200 Subject: [PATCH 45/45] Reformat --- .../workspace/Knowledge/KnowledgeBase.svelte | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte index c1a95dae21..34122f3558 100644 --- a/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte +++ b/src/lib/components/workspace/Knowledge/KnowledgeBase.svelte @@ -199,12 +199,12 @@ toast.warning(uploadedFile.error); knowledge.files = knowledge.files.filter((file) => file.id !== uploadedFile.id); } else { - if (syncMode) { - await syncFileHandler(uploadedFile.id); - } else { - await addFileHandler(uploadedFile.id); - } - } + if (syncMode) { + await syncFileHandler(uploadedFile.id); + } else { + await addFileHandler(uploadedFile.id); + } + } } else { toast.error($i18n.t('Failed to upload file.')); }