From 69589807fdab2ba71fd506a3e73c42442e75c037 Mon Sep 17 00:00:00 2001
From: yyh <92089059+lyzno1@users.noreply.github.com>
Date: Wed, 31 Dec 2025 08:32:55 +0800
Subject: [PATCH 01/14] refactor: Replace direct `process.env.NODE_ENV` checks
with `IS_PROD` and `IS_DEV` constants. (#30383)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Asuka Minato
---
.../components/base/date-and-time-picker/utils/dayjs.ts | 3 ++-
web/app/components/base/error-boundary/index.tsx | 9 +++++----
web/app/components/base/ga/index.tsx | 4 ++--
web/app/components/base/zendesk/index.tsx | 4 ++--
web/app/components/header/github-star/index.tsx | 3 ++-
web/service/use-common.ts | 3 ++-
6 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/web/app/components/base/date-and-time-picker/utils/dayjs.ts b/web/app/components/base/date-and-time-picker/utils/dayjs.ts
index b37808209c..23307895a7 100644
--- a/web/app/components/base/date-and-time-picker/utils/dayjs.ts
+++ b/web/app/components/base/date-and-time-picker/utils/dayjs.ts
@@ -3,6 +3,7 @@ import type { Day } from '../types'
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
+import { IS_PROD } from '@/config'
import tz from '@/utils/timezone.json'
dayjs.extend(utc)
@@ -131,7 +132,7 @@ export type ToDayjsOptions = {
}
const warnParseFailure = (value: string) => {
- if (process.env.NODE_ENV !== 'production')
+ if (!IS_PROD)
console.warn('[TimePicker] Failed to parse time value', value)
}
diff --git a/web/app/components/base/error-boundary/index.tsx b/web/app/components/base/error-boundary/index.tsx
index b041bba4d6..9cb4b70cf5 100644
--- a/web/app/components/base/error-boundary/index.tsx
+++ b/web/app/components/base/error-boundary/index.tsx
@@ -4,6 +4,7 @@ import { RiAlertLine, RiBugLine } from '@remixicon/react'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import Button from '@/app/components/base/button'
+import { IS_DEV } from '@/config'
import { cn } from '@/utils/classnames'
type ErrorBoundaryState = {
@@ -54,7 +55,7 @@ class ErrorBoundaryInner extends React.Component<
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
- if (process.env.NODE_ENV === 'development') {
+ if (IS_DEV) {
console.error('ErrorBoundary caught an error:', error)
console.error('Error Info:', errorInfo)
}
@@ -262,13 +263,13 @@ export function withErrorBoundary
(
// Simple error fallback component
export const ErrorFallback: React.FC<{
error: Error
- resetErrorBoundary: () => void
-}> = ({ error, resetErrorBoundary }) => {
+ resetErrorBoundaryAction: () => void
+}> = ({ error, resetErrorBoundaryAction }) => {
return (
Oops! Something went wrong
{error.message}
-
diff --git a/web/app/components/base/ga/index.tsx b/web/app/components/base/ga/index.tsx
index eb991092e0..03475b3bb4 100644
--- a/web/app/components/base/ga/index.tsx
+++ b/web/app/components/base/ga/index.tsx
@@ -3,7 +3,7 @@ import type { FC } from 'react'
import { headers } from 'next/headers'
import Script from 'next/script'
import * as React from 'react'
-import { IS_CE_EDITION } from '@/config'
+import { IS_CE_EDITION, IS_PROD } from '@/config'
export enum GaType {
admin = 'admin',
@@ -32,7 +32,7 @@ const GA: FC = ({
if (IS_CE_EDITION)
return null
- const cspHeader = process.env.NODE_ENV === 'production'
+ const cspHeader = IS_PROD
? (headers() as unknown as UnsafeUnwrappedHeaders).get('content-security-policy')
: null
const nonce = extractNonceFromCSP(cspHeader)
diff --git a/web/app/components/base/zendesk/index.tsx b/web/app/components/base/zendesk/index.tsx
index dc6ce1e655..e12a128a02 100644
--- a/web/app/components/base/zendesk/index.tsx
+++ b/web/app/components/base/zendesk/index.tsx
@@ -1,13 +1,13 @@
import { headers } from 'next/headers'
import Script from 'next/script'
import { memo } from 'react'
-import { IS_CE_EDITION, ZENDESK_WIDGET_KEY } from '@/config'
+import { IS_CE_EDITION, IS_PROD, ZENDESK_WIDGET_KEY } from '@/config'
const Zendesk = async () => {
if (IS_CE_EDITION || !ZENDESK_WIDGET_KEY)
return null
- const nonce = process.env.NODE_ENV === 'production' ? (await headers()).get('x-nonce') ?? '' : ''
+ const nonce = IS_PROD ? (await headers()).get('x-nonce') ?? '' : ''
return (
<>
diff --git a/web/app/components/header/github-star/index.tsx b/web/app/components/header/github-star/index.tsx
index 01f30fba31..e91bdcca2c 100644
--- a/web/app/components/header/github-star/index.tsx
+++ b/web/app/components/header/github-star/index.tsx
@@ -3,6 +3,7 @@ import type { FC } from 'react'
import type { GithubRepo } from '@/models/common'
import { RiLoader2Line } from '@remixicon/react'
import { useQuery } from '@tanstack/react-query'
+import { IS_DEV } from '@/config'
const defaultData = {
stargazers_count: 110918,
@@ -21,7 +22,7 @@ const GithubStar: FC<{ className: string }> = (props) => {
const { isFetching, isError, data } = useQuery({
queryKey: ['github-star'],
queryFn: getStar,
- enabled: process.env.NODE_ENV !== 'development',
+ enabled: !IS_DEV,
retry: false,
placeholderData: defaultData,
})
diff --git a/web/service/use-common.ts b/web/service/use-common.ts
index 77bb2a11fa..a1edb041c0 100644
--- a/web/service/use-common.ts
+++ b/web/service/use-common.ts
@@ -23,6 +23,7 @@ import type {
} from '@/models/common'
import type { RETRIEVE_METHOD } from '@/types/app'
import { useMutation, useQuery } from '@tanstack/react-query'
+import { IS_DEV } from '@/config'
import { get, post } from './base'
import { useInvalid } from './use-base'
@@ -85,7 +86,7 @@ export const useUserProfile = () => {
profile,
meta: {
currentVersion: response.headers.get('x-version'),
- currentEnv: process.env.NODE_ENV === 'development'
+ currentEnv: IS_DEV
? 'DEVELOPMENT'
: response.headers.get('x-env'),
},
From 3a59ae96177139507786f8885624b44ba360c22b Mon Sep 17 00:00:00 2001
From: zyssyz123 <916125788@qq.com>
Date: Wed, 31 Dec 2025 10:10:58 +0800
Subject: [PATCH 02/14] feat: add oauth_new_user flag for frontend when user
oauth login (#30370)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: hj24
---
api/controllers/console/auth/oauth.py | 13 ++++++++----
.../controllers/console/auth/test_oauth.py | 20 ++++++++++---------
2 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/api/controllers/console/auth/oauth.py b/api/controllers/console/auth/oauth.py
index 7ad1e56373..c20e83d36f 100644
--- a/api/controllers/console/auth/oauth.py
+++ b/api/controllers/console/auth/oauth.py
@@ -124,7 +124,7 @@ class OAuthCallback(Resource):
return redirect(f"{dify_config.CONSOLE_WEB_URL}/signin/invite-settings?invite_token={invite_token}")
try:
- account = _generate_account(provider, user_info)
+ account, oauth_new_user = _generate_account(provider, user_info)
except AccountNotFoundError:
return redirect(f"{dify_config.CONSOLE_WEB_URL}/signin?message=Account not found.")
except (WorkSpaceNotFoundError, WorkSpaceNotAllowedCreateError):
@@ -159,7 +159,10 @@ class OAuthCallback(Resource):
ip_address=extract_remote_ip(request),
)
- response = redirect(f"{dify_config.CONSOLE_WEB_URL}")
+ base_url = dify_config.CONSOLE_WEB_URL
+ query_char = "&" if "?" in base_url else "?"
+ target_url = f"{base_url}{query_char}oauth_new_user={str(oauth_new_user).lower()}"
+ response = redirect(target_url)
set_access_token_to_cookie(request, response, token_pair.access_token)
set_refresh_token_to_cookie(request, response, token_pair.refresh_token)
@@ -177,9 +180,10 @@ def _get_account_by_openid_or_email(provider: str, user_info: OAuthUserInfo) ->
return account
-def _generate_account(provider: str, user_info: OAuthUserInfo):
+def _generate_account(provider: str, user_info: OAuthUserInfo) -> tuple[Account, bool]:
# Get account by openid or email.
account = _get_account_by_openid_or_email(provider, user_info)
+ oauth_new_user = False
if account:
tenants = TenantService.get_join_tenants(account)
@@ -193,6 +197,7 @@ def _generate_account(provider: str, user_info: OAuthUserInfo):
tenant_was_created.send(new_tenant)
if not account:
+ oauth_new_user = True
if not FeatureService.get_system_features().is_allow_register:
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(user_info.email):
raise AccountRegisterError(
@@ -220,4 +225,4 @@ def _generate_account(provider: str, user_info: OAuthUserInfo):
# Link account
AccountService.link_account_integrate(provider, user_info.id, account)
- return account
+ return account, oauth_new_user
diff --git a/api/tests/unit_tests/controllers/console/auth/test_oauth.py b/api/tests/unit_tests/controllers/console/auth/test_oauth.py
index 399caf8c4d..3ddfcdb832 100644
--- a/api/tests/unit_tests/controllers/console/auth/test_oauth.py
+++ b/api/tests/unit_tests/controllers/console/auth/test_oauth.py
@@ -171,7 +171,7 @@ class TestOAuthCallback:
):
mock_config.CONSOLE_WEB_URL = "http://localhost:3000"
mock_get_providers.return_value = {"github": oauth_setup["provider"]}
- mock_generate_account.return_value = oauth_setup["account"]
+ mock_generate_account.return_value = (oauth_setup["account"], True)
mock_account_service.login.return_value = oauth_setup["token_pair"]
with app.test_request_context("/auth/oauth/github/callback?code=test_code"):
@@ -179,7 +179,7 @@ class TestOAuthCallback:
oauth_setup["provider"].get_access_token.assert_called_once_with("test_code")
oauth_setup["provider"].get_user_info.assert_called_once_with("access_token")
- mock_redirect.assert_called_once_with("http://localhost:3000")
+ mock_redirect.assert_called_once_with("http://localhost:3000?oauth_new_user=true")
@pytest.mark.parametrize(
("exception", "expected_error"),
@@ -223,7 +223,7 @@ class TestOAuthCallback:
# This documents actual behavior. See test_defensive_check_for_closed_account_status for details
(
AccountStatus.CLOSED.value,
- "http://localhost:3000",
+ "http://localhost:3000?oauth_new_user=false",
),
],
)
@@ -260,7 +260,7 @@ class TestOAuthCallback:
account = MagicMock()
account.status = account_status
account.id = "123"
- mock_generate_account.return_value = account
+ mock_generate_account.return_value = (account, False)
# Mock login for CLOSED status
mock_token_pair = MagicMock()
@@ -296,7 +296,7 @@ class TestOAuthCallback:
mock_account = MagicMock()
mock_account.status = AccountStatus.PENDING
- mock_generate_account.return_value = mock_account
+ mock_generate_account.return_value = (mock_account, False)
mock_token_pair = MagicMock()
mock_token_pair.access_token = "jwt_access_token"
@@ -360,7 +360,7 @@ class TestOAuthCallback:
closed_account.status = AccountStatus.CLOSED
closed_account.id = "123"
closed_account.name = "Closed Account"
- mock_generate_account.return_value = closed_account
+ mock_generate_account.return_value = (closed_account, False)
# Mock successful login (current behavior)
mock_token_pair = MagicMock()
@@ -374,7 +374,7 @@ class TestOAuthCallback:
resource.get("github")
# Verify current behavior: login succeeds (this is NOT ideal)
- mock_redirect.assert_called_once_with("http://localhost:3000")
+ mock_redirect.assert_called_once_with("http://localhost:3000?oauth_new_user=false")
mock_account_service.login.assert_called_once()
# Document expected behavior in comments:
@@ -458,8 +458,9 @@ class TestAccountGeneration:
with pytest.raises(AccountRegisterError):
_generate_account("github", user_info)
else:
- result = _generate_account("github", user_info)
+ result, oauth_new_user = _generate_account("github", user_info)
assert result == mock_account
+ assert oauth_new_user == should_create
if should_create:
mock_register_service.register.assert_called_once_with(
@@ -490,9 +491,10 @@ class TestAccountGeneration:
mock_tenant_service.create_tenant.return_value = mock_new_tenant
with app.test_request_context(headers={"Accept-Language": "en-US,en;q=0.9"}):
- result = _generate_account("github", user_info)
+ result, oauth_new_user = _generate_account("github", user_info)
assert result == mock_account
+ assert oauth_new_user is False
mock_tenant_service.create_tenant.assert_called_once_with("Test User's Workspace")
mock_tenant_service.create_tenant_member.assert_called_once_with(
mock_new_tenant, mock_account, role="owner"
From de53c78125a76abb97deb2c17eba40d95d796ebb Mon Sep 17 00:00:00 2001
From: quicksand
Date: Wed, 31 Dec 2025 10:11:25 +0800
Subject: [PATCH 03/14] fix(web): template creation permission for app
templates (#30367)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: 非法操作
---
.../app/create-app-dialog/app-list/index.spec.tsx | 8 +-------
.../components/app/create-app-dialog/app-list/index.tsx | 5 +----
2 files changed, 2 insertions(+), 11 deletions(-)
diff --git a/web/app/components/app/create-app-dialog/app-list/index.spec.tsx b/web/app/components/app/create-app-dialog/app-list/index.spec.tsx
index d873b4243e..e0f459ee75 100644
--- a/web/app/components/app/create-app-dialog/app-list/index.spec.tsx
+++ b/web/app/components/app/create-app-dialog/app-list/index.spec.tsx
@@ -14,13 +14,6 @@ vi.mock('ahooks', () => ({
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({ isCurrentWorkspaceEditor: true }),
}))
-vi.mock('use-context-selector', async () => {
- const actual = await vi.importActual('use-context-selector')
- return {
- ...actual,
- useContext: () => ({ hasEditPermission: true }),
- }
-})
vi.mock('nuqs', () => ({
useQueryState: () => ['Recommended', vi.fn()],
}))
@@ -119,6 +112,7 @@ describe('Apps', () => {
fireEvent.click(screen.getAllByTestId('app-card')[0])
expect(screen.getByTestId('create-from-template-modal')).toBeInTheDocument()
})
+
it('shows no template message when list is empty', () => {
mockUseExploreAppList.mockReturnValueOnce({
data: { allList: [], categories: [] },
diff --git a/web/app/components/app/create-app-dialog/app-list/index.tsx b/web/app/components/app/create-app-dialog/app-list/index.tsx
index 6a30df0dd9..b967ba7d55 100644
--- a/web/app/components/app/create-app-dialog/app-list/index.tsx
+++ b/web/app/components/app/create-app-dialog/app-list/index.tsx
@@ -8,7 +8,6 @@ import { useRouter } from 'next/navigation'
import * as React from 'react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
-import { useContext } from 'use-context-selector'
import AppTypeSelector from '@/app/components/app/type-selector'
import { trackEvent } from '@/app/components/base/amplitude'
import Divider from '@/app/components/base/divider'
@@ -19,7 +18,6 @@ import CreateAppModal from '@/app/components/explore/create-app-modal'
import { usePluginDependencies } from '@/app/components/workflow/plugin-dependency/hooks'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
-import ExploreContext from '@/context/explore-context'
import { DSLImportMode } from '@/models/app'
import { importDSL } from '@/service/apps'
import { fetchAppDetail } from '@/service/explore'
@@ -47,7 +45,6 @@ const Apps = ({
const { t } = useTranslation()
const { isCurrentWorkspaceEditor } = useAppContext()
const { push } = useRouter()
- const { hasEditPermission } = useContext(ExploreContext)
const allCategoriesEn = AppCategories.RECOMMENDED
const [keywords, setKeywords] = useState('')
@@ -214,7 +211,7 @@ const Apps = ({
{
setCurrApp(app)
setIsShowCreateModal(true)
From fb5edd0bf65191fa0c6841795213c8c5d9c9b089 Mon Sep 17 00:00:00 2001
From: Asuka Minato
Date: Wed, 31 Dec 2025 11:24:35 +0900
Subject: [PATCH 04/14] =?UTF-8?q?refactor:=20split=20changes=20for=20api/s?=
=?UTF-8?q?ervices/tools/api=5Ftools=5Fmanage=5Fservi=E2=80=A6=20(#29899)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
api/.ruff.toml | 6 +++++-
api/core/tools/utils/parser.py | 2 +-
.../tools/api_tools_manage_service.py | 20 ++++++++-----------
3 files changed, 14 insertions(+), 14 deletions(-)
diff --git a/api/.ruff.toml b/api/.ruff.toml
index 7206f7fa0f..8db0cbcb21 100644
--- a/api/.ruff.toml
+++ b/api/.ruff.toml
@@ -1,4 +1,8 @@
-exclude = ["migrations/*"]
+exclude = [
+ "migrations/*",
+ ".git",
+ ".git/**",
+]
line-length = 120
[format]
diff --git a/api/core/tools/utils/parser.py b/api/core/tools/utils/parser.py
index 3486182192..584975de05 100644
--- a/api/core/tools/utils/parser.py
+++ b/api/core/tools/utils/parser.py
@@ -378,7 +378,7 @@ class ApiBasedToolSchemaParser:
@staticmethod
def auto_parse_to_tool_bundle(
content: str, extra_info: dict | None = None, warning: dict | None = None
- ) -> tuple[list[ApiToolBundle], str]:
+ ) -> tuple[list[ApiToolBundle], ApiProviderSchemaType]:
"""
auto parse to tool bundle
diff --git a/api/services/tools/api_tools_manage_service.py b/api/services/tools/api_tools_manage_service.py
index 250d29f335..c32157919b 100644
--- a/api/services/tools/api_tools_manage_service.py
+++ b/api/services/tools/api_tools_manage_service.py
@@ -85,7 +85,9 @@ class ApiToolManageService:
raise ValueError(f"invalid schema: {str(e)}")
@staticmethod
- def convert_schema_to_tool_bundles(schema: str, extra_info: dict | None = None) -> tuple[list[ApiToolBundle], str]:
+ def convert_schema_to_tool_bundles(
+ schema: str, extra_info: dict | None = None
+ ) -> tuple[list[ApiToolBundle], ApiProviderSchemaType]:
"""
convert schema to tool bundles
@@ -103,7 +105,7 @@ class ApiToolManageService:
provider_name: str,
icon: dict,
credentials: dict,
- schema_type: str,
+ schema_type: ApiProviderSchemaType,
schema: str,
privacy_policy: str,
custom_disclaimer: str,
@@ -112,9 +114,6 @@ class ApiToolManageService:
"""
create api tool provider
"""
- if schema_type not in [member.value for member in ApiProviderSchemaType]:
- raise ValueError(f"invalid schema type {schema}")
-
provider_name = provider_name.strip()
# check if the provider exists
@@ -241,18 +240,15 @@ class ApiToolManageService:
original_provider: str,
icon: dict,
credentials: dict,
- schema_type: str,
+ _schema_type: ApiProviderSchemaType,
schema: str,
- privacy_policy: str,
+ privacy_policy: str | None,
custom_disclaimer: str,
labels: list[str],
):
"""
update api tool provider
"""
- if schema_type not in [member.value for member in ApiProviderSchemaType]:
- raise ValueError(f"invalid schema type {schema}")
-
provider_name = provider_name.strip()
# check if the provider exists
@@ -277,7 +273,7 @@ class ApiToolManageService:
provider.icon = json.dumps(icon)
provider.schema = schema
provider.description = extra_info.get("description", "")
- provider.schema_type_str = ApiProviderSchemaType.OPENAPI
+ provider.schema_type_str = schema_type
provider.tools_str = json.dumps(jsonable_encoder(tool_bundles))
provider.privacy_policy = privacy_policy
provider.custom_disclaimer = custom_disclaimer
@@ -356,7 +352,7 @@ class ApiToolManageService:
tool_name: str,
credentials: dict,
parameters: dict,
- schema_type: str,
+ schema_type: ApiProviderSchemaType,
schema: str,
):
"""
From e6f3528bb05c18598457fa1fe504dc77d9b48726 Mon Sep 17 00:00:00 2001
From: Jasonfish
Date: Wed, 31 Dec 2025 10:26:28 +0800
Subject: [PATCH 05/14] fix: Incorrect REDIS ssl variable used for Celery
causing Celery unable to start (#29605)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
---
api/extensions/ext_celery.py | 3 +--
api/schedule/queue_monitor_task.py | 5 +++++
.../unit_tests/extensions/test_celery_ssl.py | 15 +++++----------
3 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/api/extensions/ext_celery.py b/api/extensions/ext_celery.py
index 5cf4984709..764df5d178 100644
--- a/api/extensions/ext_celery.py
+++ b/api/extensions/ext_celery.py
@@ -12,9 +12,8 @@ from dify_app import DifyApp
def _get_celery_ssl_options() -> dict[str, Any] | None:
"""Get SSL configuration for Celery broker/backend connections."""
- # Use REDIS_USE_SSL for consistency with the main Redis client
# Only apply SSL if we're using Redis as broker/backend
- if not dify_config.REDIS_USE_SSL:
+ if not dify_config.BROKER_USE_SSL:
return None
# Check if Celery is actually using Redis
diff --git a/api/schedule/queue_monitor_task.py b/api/schedule/queue_monitor_task.py
index db610df290..77d6b5a138 100644
--- a/api/schedule/queue_monitor_task.py
+++ b/api/schedule/queue_monitor_task.py
@@ -16,6 +16,11 @@ celery_redis = Redis(
port=redis_config.get("port") or 6379,
password=redis_config.get("password") or None,
db=int(redis_config.get("virtual_host")) if redis_config.get("virtual_host") else 1,
+ ssl=bool(dify_config.BROKER_USE_SSL),
+ ssl_ca_certs=dify_config.REDIS_SSL_CA_CERTS if dify_config.BROKER_USE_SSL else None,
+ ssl_cert_reqs=getattr(dify_config, "REDIS_SSL_CERT_REQS", None) if dify_config.BROKER_USE_SSL else None,
+ ssl_certfile=getattr(dify_config, "REDIS_SSL_CERTFILE", None) if dify_config.BROKER_USE_SSL else None,
+ ssl_keyfile=getattr(dify_config, "REDIS_SSL_KEYFILE", None) if dify_config.BROKER_USE_SSL else None,
)
logger = logging.getLogger(__name__)
diff --git a/api/tests/unit_tests/extensions/test_celery_ssl.py b/api/tests/unit_tests/extensions/test_celery_ssl.py
index fc7a090ef9..d3a4d69f07 100644
--- a/api/tests/unit_tests/extensions/test_celery_ssl.py
+++ b/api/tests/unit_tests/extensions/test_celery_ssl.py
@@ -8,11 +8,12 @@ class TestCelerySSLConfiguration:
"""Test suite for Celery SSL configuration."""
def test_get_celery_ssl_options_when_ssl_disabled(self):
- """Test SSL options when REDIS_USE_SSL is False."""
- mock_config = MagicMock()
- mock_config.REDIS_USE_SSL = False
+ """Test SSL options when BROKER_USE_SSL is False."""
+ from configs import DifyConfig
- with patch("extensions.ext_celery.dify_config", mock_config):
+ dify_config = DifyConfig(CELERY_BROKER_URL="redis://localhost:6379/0")
+
+ with patch("extensions.ext_celery.dify_config", dify_config):
from extensions.ext_celery import _get_celery_ssl_options
result = _get_celery_ssl_options()
@@ -21,7 +22,6 @@ class TestCelerySSLConfiguration:
def test_get_celery_ssl_options_when_broker_not_redis(self):
"""Test SSL options when broker is not Redis."""
mock_config = MagicMock()
- mock_config.REDIS_USE_SSL = True
mock_config.CELERY_BROKER_URL = "amqp://localhost:5672"
with patch("extensions.ext_celery.dify_config", mock_config):
@@ -33,7 +33,6 @@ class TestCelerySSLConfiguration:
def test_get_celery_ssl_options_with_cert_none(self):
"""Test SSL options with CERT_NONE requirement."""
mock_config = MagicMock()
- mock_config.REDIS_USE_SSL = True
mock_config.CELERY_BROKER_URL = "redis://localhost:6379/0"
mock_config.REDIS_SSL_CERT_REQS = "CERT_NONE"
mock_config.REDIS_SSL_CA_CERTS = None
@@ -53,7 +52,6 @@ class TestCelerySSLConfiguration:
def test_get_celery_ssl_options_with_cert_required(self):
"""Test SSL options with CERT_REQUIRED and certificates."""
mock_config = MagicMock()
- mock_config.REDIS_USE_SSL = True
mock_config.CELERY_BROKER_URL = "rediss://localhost:6380/0"
mock_config.REDIS_SSL_CERT_REQS = "CERT_REQUIRED"
mock_config.REDIS_SSL_CA_CERTS = "/path/to/ca.crt"
@@ -73,7 +71,6 @@ class TestCelerySSLConfiguration:
def test_get_celery_ssl_options_with_cert_optional(self):
"""Test SSL options with CERT_OPTIONAL requirement."""
mock_config = MagicMock()
- mock_config.REDIS_USE_SSL = True
mock_config.CELERY_BROKER_URL = "redis://localhost:6379/0"
mock_config.REDIS_SSL_CERT_REQS = "CERT_OPTIONAL"
mock_config.REDIS_SSL_CA_CERTS = "/path/to/ca.crt"
@@ -91,7 +88,6 @@ class TestCelerySSLConfiguration:
def test_get_celery_ssl_options_with_invalid_cert_reqs(self):
"""Test SSL options with invalid cert requirement defaults to CERT_NONE."""
mock_config = MagicMock()
- mock_config.REDIS_USE_SSL = True
mock_config.CELERY_BROKER_URL = "redis://localhost:6379/0"
mock_config.REDIS_SSL_CERT_REQS = "INVALID_VALUE"
mock_config.REDIS_SSL_CA_CERTS = None
@@ -108,7 +104,6 @@ class TestCelerySSLConfiguration:
def test_celery_init_applies_ssl_to_broker_and_backend(self):
"""Test that SSL options are applied to both broker and backend when using Redis."""
mock_config = MagicMock()
- mock_config.REDIS_USE_SSL = True
mock_config.CELERY_BROKER_URL = "redis://localhost:6379/0"
mock_config.CELERY_BACKEND = "redis"
mock_config.CELERY_RESULT_BACKEND = "redis://localhost:6379/0"
From 925168383b931086d74184894813c602d67dae22 Mon Sep 17 00:00:00 2001
From: lif <1835304752@qq.com>
Date: Wed, 31 Dec 2025 10:28:14 +0800
Subject: [PATCH 06/14] fix: keyword search now matches both content and
keywords fields (#29619)
Co-authored-by: Claude
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
.../console/datasets/datasets_segments.py | 28 +++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/api/controllers/console/datasets/datasets_segments.py b/api/controllers/console/datasets/datasets_segments.py
index e73abc2555..5a536af6d2 100644
--- a/api/controllers/console/datasets/datasets_segments.py
+++ b/api/controllers/console/datasets/datasets_segments.py
@@ -3,10 +3,12 @@ import uuid
from flask import request
from flask_restx import Resource, marshal
from pydantic import BaseModel, Field
-from sqlalchemy import select
+from sqlalchemy import String, cast, func, or_, select
+from sqlalchemy.dialects.postgresql import JSONB
from werkzeug.exceptions import Forbidden, NotFound
import services
+from configs import dify_config
from controllers.common.schema import register_schema_models
from controllers.console import console_ns
from controllers.console.app.error import ProviderNotInitializeError
@@ -143,7 +145,29 @@ class DatasetDocumentSegmentListApi(Resource):
query = query.where(DocumentSegment.hit_count >= hit_count_gte)
if keyword:
- query = query.where(DocumentSegment.content.ilike(f"%{keyword}%"))
+ # Search in both content and keywords fields
+ # Use database-specific methods for JSON array search
+ if dify_config.SQLALCHEMY_DATABASE_URI_SCHEME == "postgresql":
+ # PostgreSQL: Use jsonb_array_elements_text to properly handle Unicode/Chinese text
+ keywords_condition = func.array_to_string(
+ func.array(
+ select(func.jsonb_array_elements_text(cast(DocumentSegment.keywords, JSONB)))
+ .correlate(DocumentSegment)
+ .scalar_subquery()
+ ),
+ ",",
+ ).ilike(f"%{keyword}%")
+ else:
+ # MySQL: Cast JSON to string for pattern matching
+ # MySQL stores Chinese text directly in JSON without Unicode escaping
+ keywords_condition = cast(DocumentSegment.keywords, String).ilike(f"%{keyword}%")
+
+ query = query.where(
+ or_(
+ DocumentSegment.content.ilike(f"%{keyword}%"),
+ keywords_condition,
+ )
+ )
if args.enabled.lower() != "all":
if args.enabled.lower() == "true":
From 9007109a6bf97ddd1766e13313a36d16b6d11182 Mon Sep 17 00:00:00 2001
From: wangxiaolei
Date: Wed, 31 Dec 2025 10:30:15 +0800
Subject: [PATCH 07/14] fix: [xxx](xxx) render as xxx](xxx) (#30392)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
api/core/rag/cleaner/clean_processor.py | 44 ++--
api/core/tools/utils/text_processing_utils.py | 6 +
.../unit_tests/core/rag/cleaner/__init__.py | 0
.../core/rag/cleaner/test_clean_processor.py | 213 ++++++++++++++++++
.../unit_tests/utils/test_text_processing.py | 5 +
5 files changed, 255 insertions(+), 13 deletions(-)
create mode 100644 api/tests/unit_tests/core/rag/cleaner/__init__.py
create mode 100644 api/tests/unit_tests/core/rag/cleaner/test_clean_processor.py
diff --git a/api/core/rag/cleaner/clean_processor.py b/api/core/rag/cleaner/clean_processor.py
index 9cb009035b..e182c35b99 100644
--- a/api/core/rag/cleaner/clean_processor.py
+++ b/api/core/rag/cleaner/clean_processor.py
@@ -27,26 +27,44 @@ class CleanProcessor:
pattern = r"([a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+)"
text = re.sub(pattern, "", text)
- # Remove URL but keep Markdown image URLs
- # First, temporarily replace Markdown image URLs with a placeholder
- markdown_image_pattern = r"!\[.*?\]\((https?://[^\s)]+)\)"
- placeholders: list[str] = []
+ # Remove URL but keep Markdown image URLs and link URLs
+ # Replace the ENTIRE markdown link/image with a single placeholder to protect
+ # the link text (which might also be a URL) from being removed
+ markdown_link_pattern = r"\[([^\]]*)\]\((https?://[^)]+)\)"
+ markdown_image_pattern = r"!\[.*?\]\((https?://[^)]+)\)"
+ placeholders: list[tuple[str, str, str]] = [] # (type, text, url)
- def replace_with_placeholder(match, placeholders=placeholders):
+ def replace_markdown_with_placeholder(match, placeholders=placeholders):
+ link_type = "link"
+ link_text = match.group(1)
+ url = match.group(2)
+ placeholder = f"__MARKDOWN_PLACEHOLDER_{len(placeholders)}__"
+ placeholders.append((link_type, link_text, url))
+ return placeholder
+
+ def replace_image_with_placeholder(match, placeholders=placeholders):
+ link_type = "image"
url = match.group(1)
- placeholder = f"__MARKDOWN_IMAGE_URL_{len(placeholders)}__"
- placeholders.append(url)
- return f""
+ placeholder = f"__MARKDOWN_PLACEHOLDER_{len(placeholders)}__"
+ placeholders.append((link_type, "image", url))
+ return placeholder
- text = re.sub(markdown_image_pattern, replace_with_placeholder, text)
+ # Protect markdown links first
+ text = re.sub(markdown_link_pattern, replace_markdown_with_placeholder, text)
+ # Then protect markdown images
+ text = re.sub(markdown_image_pattern, replace_image_with_placeholder, text)
# Now remove all remaining URLs
- url_pattern = r"https?://[^\s)]+"
+ url_pattern = r"https?://\S+"
text = re.sub(url_pattern, "", text)
- # Finally, restore the Markdown image URLs
- for i, url in enumerate(placeholders):
- text = text.replace(f"__MARKDOWN_IMAGE_URL_{i}__", url)
+ # Restore the Markdown links and images
+ for i, (link_type, text_or_alt, url) in enumerate(placeholders):
+ placeholder = f"__MARKDOWN_PLACEHOLDER_{i}__"
+ if link_type == "link":
+ text = text.replace(placeholder, f"[{text_or_alt}]({url})")
+ else: # image
+ text = text.replace(placeholder, f"")
return text
def filter_string(self, text):
diff --git a/api/core/tools/utils/text_processing_utils.py b/api/core/tools/utils/text_processing_utils.py
index 0f9a91a111..4bfaa5e49b 100644
--- a/api/core/tools/utils/text_processing_utils.py
+++ b/api/core/tools/utils/text_processing_utils.py
@@ -4,6 +4,7 @@ import re
def remove_leading_symbols(text: str) -> str:
"""
Remove leading punctuation or symbols from the given text.
+ Preserves markdown links like [text](url) at the start.
Args:
text (str): The input text to process.
@@ -11,6 +12,11 @@ def remove_leading_symbols(text: str) -> str:
Returns:
str: The text with leading punctuation or symbols removed.
"""
+ # Check if text starts with a markdown link - preserve it
+ markdown_link_pattern = r"^\[([^\]]+)\]\((https?://[^)]+)\)"
+ if re.match(markdown_link_pattern, text):
+ return text
+
# Match Unicode ranges for punctuation and symbols
# FIXME this pattern is confused quick fix for #11868 maybe refactor it later
pattern = r'^[\[\]\u2000-\u2025\u2027-\u206F\u2E00-\u2E7F\u3000-\u300F\u3011-\u303F"#$%&\'()*+,./:;<=>?@^_`~]+'
diff --git a/api/tests/unit_tests/core/rag/cleaner/__init__.py b/api/tests/unit_tests/core/rag/cleaner/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/api/tests/unit_tests/core/rag/cleaner/test_clean_processor.py b/api/tests/unit_tests/core/rag/cleaner/test_clean_processor.py
new file mode 100644
index 0000000000..65ee62b8dd
--- /dev/null
+++ b/api/tests/unit_tests/core/rag/cleaner/test_clean_processor.py
@@ -0,0 +1,213 @@
+from core.rag.cleaner.clean_processor import CleanProcessor
+
+
+class TestCleanProcessor:
+ """Test cases for CleanProcessor.clean method."""
+
+ def test_clean_default_removal_of_invalid_symbols(self):
+ """Test default cleaning removes invalid symbols."""
+ # Test <| replacement
+ assert CleanProcessor.clean("text<|with<|invalid", None) == "text replacement
+ assert CleanProcessor.clean("text|>with|>invalid", None) == "text>with>invalid"
+
+ # Test removal of control characters
+ text_with_control = "normal\x00text\x1fwith\x07control\x7fchars"
+ expected = "normaltextwithcontrolchars"
+ assert CleanProcessor.clean(text_with_control, None) == expected
+
+ # Test U+FFFE removal
+ text_with_ufffe = "normal\ufffepadding"
+ expected = "normalpadding"
+ assert CleanProcessor.clean(text_with_ufffe, None) == expected
+
+ def test_clean_with_none_process_rule(self):
+ """Test cleaning with None process_rule - only default cleaning applied."""
+ text = "Hello<|World\x00"
+ expected = "Hello becomes >, control chars and U+FFFE are removed
+ assert CleanProcessor.clean(text, None) == "<<>>"
+
+ def test_clean_multiple_markdown_links_preserved(self):
+ """Test multiple markdown links are all preserved."""
+ process_rule = {"rules": {"pre_processing_rules": [{"id": "remove_urls_emails", "enabled": True}]}}
+
+ text = "[One](https://one.com) [Two](http://two.org) [Three](https://three.net)"
+ expected = "[One](https://one.com) [Two](http://two.org) [Three](https://three.net)"
+ assert CleanProcessor.clean(text, process_rule) == expected
+
+ def test_clean_markdown_link_text_as_url(self):
+ """Test markdown link where the link text itself is a URL."""
+ process_rule = {"rules": {"pre_processing_rules": [{"id": "remove_urls_emails", "enabled": True}]}}
+
+ # Link text that looks like URL should be preserved
+ text = "[https://text-url.com](https://actual-url.com)"
+ expected = "[https://text-url.com](https://actual-url.com)"
+ assert CleanProcessor.clean(text, process_rule) == expected
+
+ # Text URL without markdown should be removed
+ text = "https://text-url.com https://actual-url.com"
+ expected = " "
+ assert CleanProcessor.clean(text, process_rule) == expected
+
+ def test_clean_complex_markdown_link_content(self):
+ """Test markdown links with complex content - known limitation with brackets in link text."""
+ process_rule = {"rules": {"pre_processing_rules": [{"id": "remove_urls_emails", "enabled": True}]}}
+
+ # Note: The regex pattern [^\]]* cannot handle ] within link text
+ # This is a known limitation - the pattern stops at the first ]
+ text = "[Text with [brackets] and (parens)](https://example.com)"
+ # Actual behavior: only matches up to first ], URL gets removed
+ expected = "[Text with [brackets] and (parens)]("
+ assert CleanProcessor.clean(text, process_rule) == expected
+
+ # Test that properly formatted markdown links work
+ text = "[Text with (parens) and symbols](https://example.com)"
+ expected = "[Text with (parens) and symbols](https://example.com)"
+ assert CleanProcessor.clean(text, process_rule) == expected
diff --git a/api/tests/unit_tests/utils/test_text_processing.py b/api/tests/unit_tests/utils/test_text_processing.py
index 11e017464a..bf61162a66 100644
--- a/api/tests/unit_tests/utils/test_text_processing.py
+++ b/api/tests/unit_tests/utils/test_text_processing.py
@@ -15,6 +15,11 @@ from core.tools.utils.text_processing_utils import remove_leading_symbols
("", ""),
(" ", " "),
("【测试】", "【测试】"),
+ # Markdown link preservation - should be preserved if text starts with a markdown link
+ ("[Google](https://google.com) is a search engine", "[Google](https://google.com) is a search engine"),
+ ("[Example](http://example.com) some text", "[Example](http://example.com) some text"),
+ # Leading symbols before markdown link are removed, including the opening bracket [
+ ("@[Test](https://example.com)", "Test](https://example.com)"),
],
)
def test_remove_leading_symbols(input_text, expected_output):
From 64dc98e60703ec4f52cdc5685d97f796363153d5 Mon Sep 17 00:00:00 2001
From: Sai
Date: Wed, 31 Dec 2025 10:45:43 +0800
Subject: [PATCH 08/14] fix: workflow incorrectly marked as completed while
nodes are still executing (#30251)
Co-authored-by: sai <>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
---
.../graph_traversal/skip_propagator.py | 1 +
.../graph_engine/graph_traversal/__init__.py | 1 +
.../graph_traversal/test_skip_propagator.py | 307 ++++++++++++++++++
3 files changed, 309 insertions(+)
create mode 100644 api/tests/unit_tests/core/workflow/graph_engine/graph_traversal/__init__.py
create mode 100644 api/tests/unit_tests/core/workflow/graph_engine/graph_traversal/test_skip_propagator.py
diff --git a/api/core/workflow/graph_engine/graph_traversal/skip_propagator.py b/api/core/workflow/graph_engine/graph_traversal/skip_propagator.py
index 78f8ecdcdf..b9c9243963 100644
--- a/api/core/workflow/graph_engine/graph_traversal/skip_propagator.py
+++ b/api/core/workflow/graph_engine/graph_traversal/skip_propagator.py
@@ -60,6 +60,7 @@ class SkipPropagator:
if edge_states["has_taken"]:
# Enqueue node
self._state_manager.enqueue_node(downstream_node_id)
+ self._state_manager.start_execution(downstream_node_id)
return
# All edges are skipped, propagate skip to this node
diff --git a/api/tests/unit_tests/core/workflow/graph_engine/graph_traversal/__init__.py b/api/tests/unit_tests/core/workflow/graph_engine/graph_traversal/__init__.py
new file mode 100644
index 0000000000..cf8811dc2b
--- /dev/null
+++ b/api/tests/unit_tests/core/workflow/graph_engine/graph_traversal/__init__.py
@@ -0,0 +1 @@
+"""Tests for graph traversal components."""
diff --git a/api/tests/unit_tests/core/workflow/graph_engine/graph_traversal/test_skip_propagator.py b/api/tests/unit_tests/core/workflow/graph_engine/graph_traversal/test_skip_propagator.py
new file mode 100644
index 0000000000..0019020ede
--- /dev/null
+++ b/api/tests/unit_tests/core/workflow/graph_engine/graph_traversal/test_skip_propagator.py
@@ -0,0 +1,307 @@
+"""Unit tests for skip propagator."""
+
+from unittest.mock import MagicMock, create_autospec
+
+from core.workflow.graph import Edge, Graph
+from core.workflow.graph_engine.graph_state_manager import GraphStateManager
+from core.workflow.graph_engine.graph_traversal.skip_propagator import SkipPropagator
+
+
+class TestSkipPropagator:
+ """Test suite for SkipPropagator."""
+
+ def test_propagate_skip_from_edge_with_unknown_edges_stops_processing(self) -> None:
+ """When there are unknown incoming edges, propagation should stop."""
+ # Arrange
+ mock_graph = create_autospec(Graph)
+ mock_state_manager = create_autospec(GraphStateManager)
+
+ # Create a mock edge
+ mock_edge = MagicMock(spec=Edge)
+ mock_edge.id = "edge_1"
+ mock_edge.head = "node_2"
+
+ # Setup graph edges dict
+ mock_graph.edges = {"edge_1": mock_edge}
+
+ # Setup incoming edges
+ incoming_edges = [MagicMock(spec=Edge), MagicMock(spec=Edge)]
+ mock_graph.get_incoming_edges.return_value = incoming_edges
+
+ # Setup state manager to return has_unknown=True
+ mock_state_manager.analyze_edge_states.return_value = {
+ "has_unknown": True,
+ "has_taken": False,
+ "all_skipped": False,
+ }
+
+ propagator = SkipPropagator(mock_graph, mock_state_manager)
+
+ # Act
+ propagator.propagate_skip_from_edge("edge_1")
+
+ # Assert
+ mock_graph.get_incoming_edges.assert_called_once_with("node_2")
+ mock_state_manager.analyze_edge_states.assert_called_once_with(incoming_edges)
+ # Should not call any other state manager methods
+ mock_state_manager.enqueue_node.assert_not_called()
+ mock_state_manager.start_execution.assert_not_called()
+ mock_state_manager.mark_node_skipped.assert_not_called()
+
+ def test_propagate_skip_from_edge_with_taken_edge_enqueues_node(self) -> None:
+ """When there is at least one taken edge, node should be enqueued."""
+ # Arrange
+ mock_graph = create_autospec(Graph)
+ mock_state_manager = create_autospec(GraphStateManager)
+
+ # Create a mock edge
+ mock_edge = MagicMock(spec=Edge)
+ mock_edge.id = "edge_1"
+ mock_edge.head = "node_2"
+
+ mock_graph.edges = {"edge_1": mock_edge}
+ incoming_edges = [MagicMock(spec=Edge)]
+ mock_graph.get_incoming_edges.return_value = incoming_edges
+
+ # Setup state manager to return has_taken=True
+ mock_state_manager.analyze_edge_states.return_value = {
+ "has_unknown": False,
+ "has_taken": True,
+ "all_skipped": False,
+ }
+
+ propagator = SkipPropagator(mock_graph, mock_state_manager)
+
+ # Act
+ propagator.propagate_skip_from_edge("edge_1")
+
+ # Assert
+ mock_state_manager.enqueue_node.assert_called_once_with("node_2")
+ mock_state_manager.start_execution.assert_called_once_with("node_2")
+ mock_state_manager.mark_node_skipped.assert_not_called()
+
+ def test_propagate_skip_from_edge_with_all_skipped_propagates_to_node(self) -> None:
+ """When all incoming edges are skipped, should propagate skip to node."""
+ # Arrange
+ mock_graph = create_autospec(Graph)
+ mock_state_manager = create_autospec(GraphStateManager)
+
+ # Create a mock edge
+ mock_edge = MagicMock(spec=Edge)
+ mock_edge.id = "edge_1"
+ mock_edge.head = "node_2"
+
+ mock_graph.edges = {"edge_1": mock_edge}
+ incoming_edges = [MagicMock(spec=Edge)]
+ mock_graph.get_incoming_edges.return_value = incoming_edges
+
+ # Setup state manager to return all_skipped=True
+ mock_state_manager.analyze_edge_states.return_value = {
+ "has_unknown": False,
+ "has_taken": False,
+ "all_skipped": True,
+ }
+
+ propagator = SkipPropagator(mock_graph, mock_state_manager)
+
+ # Act
+ propagator.propagate_skip_from_edge("edge_1")
+
+ # Assert
+ mock_state_manager.mark_node_skipped.assert_called_once_with("node_2")
+ mock_state_manager.enqueue_node.assert_not_called()
+ mock_state_manager.start_execution.assert_not_called()
+
+ def test_propagate_skip_to_node_marks_node_and_outgoing_edges_skipped(self) -> None:
+ """_propagate_skip_to_node should mark node and all outgoing edges as skipped."""
+ # Arrange
+ mock_graph = create_autospec(Graph)
+ mock_state_manager = create_autospec(GraphStateManager)
+
+ # Create outgoing edges
+ edge1 = MagicMock(spec=Edge)
+ edge1.id = "edge_2"
+ edge1.head = "node_downstream_1" # Set head for propagate_skip_from_edge
+
+ edge2 = MagicMock(spec=Edge)
+ edge2.id = "edge_3"
+ edge2.head = "node_downstream_2"
+
+ # Setup graph edges dict for propagate_skip_from_edge
+ mock_graph.edges = {"edge_2": edge1, "edge_3": edge2}
+ mock_graph.get_outgoing_edges.return_value = [edge1, edge2]
+
+ # Setup get_incoming_edges to return empty list to stop recursion
+ mock_graph.get_incoming_edges.return_value = []
+
+ propagator = SkipPropagator(mock_graph, mock_state_manager)
+
+ # Use mock to call private method
+ # Act
+ propagator._propagate_skip_to_node("node_1")
+
+ # Assert
+ mock_state_manager.mark_node_skipped.assert_called_once_with("node_1")
+ mock_state_manager.mark_edge_skipped.assert_any_call("edge_2")
+ mock_state_manager.mark_edge_skipped.assert_any_call("edge_3")
+ assert mock_state_manager.mark_edge_skipped.call_count == 2
+ # Should recursively propagate from each edge
+ # Since propagate_skip_from_edge is called, we need to verify it was called
+ # But we can't directly verify due to recursion. We'll trust the logic.
+
+ def test_skip_branch_paths_marks_unselected_edges_and_propagates(self) -> None:
+ """skip_branch_paths should mark all unselected edges as skipped and propagate."""
+ # Arrange
+ mock_graph = create_autospec(Graph)
+ mock_state_manager = create_autospec(GraphStateManager)
+
+ # Create unselected edges
+ edge1 = MagicMock(spec=Edge)
+ edge1.id = "edge_1"
+ edge1.head = "node_downstream_1"
+
+ edge2 = MagicMock(spec=Edge)
+ edge2.id = "edge_2"
+ edge2.head = "node_downstream_2"
+
+ unselected_edges = [edge1, edge2]
+
+ # Setup graph edges dict
+ mock_graph.edges = {"edge_1": edge1, "edge_2": edge2}
+ # Setup get_incoming_edges to return empty list to stop recursion
+ mock_graph.get_incoming_edges.return_value = []
+
+ propagator = SkipPropagator(mock_graph, mock_state_manager)
+
+ # Act
+ propagator.skip_branch_paths(unselected_edges)
+
+ # Assert
+ mock_state_manager.mark_edge_skipped.assert_any_call("edge_1")
+ mock_state_manager.mark_edge_skipped.assert_any_call("edge_2")
+ assert mock_state_manager.mark_edge_skipped.call_count == 2
+ # propagate_skip_from_edge should be called for each edge
+ # We can't directly verify due to the mock, but the logic is covered
+
+ def test_propagate_skip_from_edge_recursively_propagates_through_graph(self) -> None:
+ """Skip propagation should recursively propagate through the graph."""
+ # Arrange
+ mock_graph = create_autospec(Graph)
+ mock_state_manager = create_autospec(GraphStateManager)
+
+ # Create edge chain: edge_1 -> node_2 -> edge_3 -> node_4
+ edge1 = MagicMock(spec=Edge)
+ edge1.id = "edge_1"
+ edge1.head = "node_2"
+
+ edge3 = MagicMock(spec=Edge)
+ edge3.id = "edge_3"
+ edge3.head = "node_4"
+
+ mock_graph.edges = {"edge_1": edge1, "edge_3": edge3}
+
+ # Setup get_incoming_edges to return different values based on node
+ def get_incoming_edges_side_effect(node_id):
+ if node_id == "node_2":
+ return [edge1]
+ elif node_id == "node_4":
+ return [edge3]
+ return []
+
+ mock_graph.get_incoming_edges.side_effect = get_incoming_edges_side_effect
+
+ # Setup get_outgoing_edges to return different values based on node
+ def get_outgoing_edges_side_effect(node_id):
+ if node_id == "node_2":
+ return [edge3]
+ elif node_id == "node_4":
+ return [] # No outgoing edges, stops recursion
+ return []
+
+ mock_graph.get_outgoing_edges.side_effect = get_outgoing_edges_side_effect
+
+ # Setup state manager to return all_skipped for both nodes
+ mock_state_manager.analyze_edge_states.return_value = {
+ "has_unknown": False,
+ "has_taken": False,
+ "all_skipped": True,
+ }
+
+ propagator = SkipPropagator(mock_graph, mock_state_manager)
+
+ # Act
+ propagator.propagate_skip_from_edge("edge_1")
+
+ # Assert
+ # Should mark node_2 as skipped
+ mock_state_manager.mark_node_skipped.assert_any_call("node_2")
+ # Should mark edge_3 as skipped
+ mock_state_manager.mark_edge_skipped.assert_any_call("edge_3")
+ # Should propagate to node_4
+ mock_state_manager.mark_node_skipped.assert_any_call("node_4")
+ assert mock_state_manager.mark_node_skipped.call_count == 2
+
+ def test_propagate_skip_from_edge_with_mixed_edge_states_handles_correctly(self) -> None:
+ """Test with mixed edge states (some unknown, some taken, some skipped)."""
+ # Arrange
+ mock_graph = create_autospec(Graph)
+ mock_state_manager = create_autospec(GraphStateManager)
+
+ mock_edge = MagicMock(spec=Edge)
+ mock_edge.id = "edge_1"
+ mock_edge.head = "node_2"
+
+ mock_graph.edges = {"edge_1": mock_edge}
+ incoming_edges = [MagicMock(spec=Edge), MagicMock(spec=Edge), MagicMock(spec=Edge)]
+ mock_graph.get_incoming_edges.return_value = incoming_edges
+
+ # Test 1: has_unknown=True, has_taken=False, all_skipped=False
+ mock_state_manager.analyze_edge_states.return_value = {
+ "has_unknown": True,
+ "has_taken": False,
+ "all_skipped": False,
+ }
+
+ propagator = SkipPropagator(mock_graph, mock_state_manager)
+
+ # Act
+ propagator.propagate_skip_from_edge("edge_1")
+
+ # Assert - should stop processing
+ mock_state_manager.enqueue_node.assert_not_called()
+ mock_state_manager.mark_node_skipped.assert_not_called()
+
+ # Reset mocks for next test
+ mock_state_manager.reset_mock()
+ mock_graph.reset_mock()
+
+ # Test 2: has_unknown=False, has_taken=True, all_skipped=False
+ mock_state_manager.analyze_edge_states.return_value = {
+ "has_unknown": False,
+ "has_taken": True,
+ "all_skipped": False,
+ }
+
+ # Act
+ propagator.propagate_skip_from_edge("edge_1")
+
+ # Assert - should enqueue node
+ mock_state_manager.enqueue_node.assert_called_once_with("node_2")
+ mock_state_manager.start_execution.assert_called_once_with("node_2")
+
+ # Reset mocks for next test
+ mock_state_manager.reset_mock()
+ mock_graph.reset_mock()
+
+ # Test 3: has_unknown=False, has_taken=False, all_skipped=True
+ mock_state_manager.analyze_edge_states.return_value = {
+ "has_unknown": False,
+ "has_taken": False,
+ "all_skipped": True,
+ }
+
+ # Act
+ propagator.propagate_skip_from_edge("edge_1")
+
+ # Assert - should propagate skip
+ mock_state_manager.mark_node_skipped.assert_called_once_with("node_2")
From 2aaaa4bd34689e8b62b893f50d254db363dd8f0d Mon Sep 17 00:00:00 2001
From: yyh <92089059+lyzno1@users.noreply.github.com>
Date: Wed, 31 Dec 2025 11:13:22 +0800
Subject: [PATCH 09/14] feat(web): migrate from es-toolkit/compat to native
es-toolkit (#30244) (#30246)
---
web/__mocks__/provider-context.ts | 3 ++-
.../[appId]/overview/time-range-picker/date-picker.tsx | 2 +-
web/app/(shareLayout)/webapp-reset-password/page.tsx | 2 +-
.../webapp-signin/components/mail-and-code-auth.tsx | 2 +-
.../webapp-signin/components/mail-and-password-auth.tsx | 2 +-
.../(commonLayout)/account-page/email-change-modal.tsx | 2 +-
.../app/annotation/batch-add-annotation-modal/index.tsx | 2 +-
.../app/configuration/base/operation-btn/index.tsx | 2 +-
.../app/configuration/config-prompt/simple-prompt-input.tsx | 2 +-
.../app/configuration/config/agent/prompt-editor.tsx | 2 +-
.../dataset-config/params-config/weighted-score.tsx | 2 +-
.../configuration/dataset-config/settings-modal/index.tsx | 2 +-
.../debug/debug-with-multiple-model/context.tsx | 2 +-
.../debug-with-multiple-model/text-generation-item.tsx | 3 ++-
web/app/components/app/configuration/debug/hooks.tsx | 2 +-
web/app/components/app/configuration/debug/index.tsx | 3 ++-
.../app/configuration/hooks/use-advanced-prompt-config.ts | 2 +-
web/app/components/app/configuration/index.tsx | 3 ++-
.../app/configuration/tools/external-data-tool-modal.tsx | 2 +-
web/app/components/app/create-from-dsl-modal/index.tsx | 2 +-
web/app/components/app/duplicate-modal/index.tsx | 2 +-
web/app/components/app/log/index.tsx | 2 +-
web/app/components/app/log/list.tsx | 3 ++-
.../apikey-info-panel/apikey-info-panel.test-utils.tsx | 2 +-
web/app/components/app/switch-app-modal/index.tsx | 2 +-
web/app/components/app/workflow-log/index.tsx | 2 +-
web/app/components/base/agent-log-modal/detail.tsx | 3 ++-
web/app/components/base/app-icon-picker/index.tsx | 2 +-
web/app/components/base/chat/chat-with-history/context.tsx | 2 +-
web/app/components/base/chat/chat-with-history/hooks.tsx | 2 +-
web/app/components/base/chat/chat/hooks.ts | 3 ++-
web/app/components/base/chat/embedded-chatbot/context.tsx | 2 +-
web/app/components/base/chat/embedded-chatbot/hooks.tsx | 2 +-
web/app/components/base/emoji-picker/index.tsx | 2 +-
.../new-feature-panel/conversation-opener/modal.tsx | 2 +-
.../moderation/moderation-setting-modal.tsx | 2 +-
web/app/components/base/file-uploader/hooks.ts | 2 +-
web/app/components/base/file-uploader/pdf-preview.tsx | 2 +-
web/app/components/base/file-uploader/store.tsx | 2 +-
web/app/components/base/fullscreen-modal/index.tsx | 2 +-
web/app/components/base/image-uploader/image-preview.tsx | 2 +-
web/app/components/base/input/index.tsx | 2 +-
web/app/components/base/modal/index.tsx | 2 +-
web/app/components/base/modal/modal.tsx | 2 +-
web/app/components/base/pagination/pagination.tsx | 2 +-
.../context-block/context-block-replacement-block.tsx | 2 +-
.../base/prompt-editor/plugins/context-block/index.tsx | 2 +-
.../history-block/history-block-replacement-block.tsx | 2 +-
.../base/prompt-editor/plugins/history-block/index.tsx | 2 +-
web/app/components/base/radio-card/index.tsx | 2 +-
web/app/components/base/tag-management/panel.tsx | 2 +-
web/app/components/base/tag-management/tag-remove-modal.tsx | 2 +-
web/app/components/base/toast/index.spec.tsx | 2 +-
web/app/components/base/toast/index.tsx | 2 +-
.../components/base/with-input-validation/index.spec.tsx | 2 +-
.../common/document-status-with-action/index-failed.tsx | 2 +-
.../create-options/create-from-dsl-modal/index.tsx | 2 +-
web/app/components/datasets/create/step-two/index.tsx | 2 +-
.../datasets/documents/detail/batch-modal/index.tsx | 2 +-
.../detail/completed/common/full-screen-drawer.tsx | 2 +-
.../detail/completed/common/regeneration-modal.tsx | 2 +-
.../datasets/documents/detail/completed/index.tsx | 2 +-
.../documents/detail/settings/pipeline-settings/index.tsx | 2 +-
web/app/components/datasets/documents/list.tsx | 3 ++-
web/app/components/datasets/documents/operations.tsx | 2 +-
.../datasets/metadata/metadata-dataset/create-content.tsx | 2 +-
web/app/components/datasets/rename-modal/index.tsx | 2 +-
web/app/components/explore/create-app-modal/index.tsx | 2 +-
.../account-setting/api-based-extension-page/modal.tsx | 2 +-
.../data-source-page/data-source-notion/index.tsx | 2 +-
.../account-setting/data-source-page/panel/config-item.tsx | 2 +-
.../members-page/edit-workspace-modal/index.tsx | 2 +-
.../account-setting/members-page/invite-modal/index.tsx | 2 +-
.../account-setting/members-page/invited-modal/index.tsx | 2 +-
.../members-page/transfer-ownership-modal/index.tsx | 2 +-
web/app/components/header/account-setting/menu-dialog.tsx | 2 +-
web/app/components/header/app-selector/index.tsx | 2 +-
web/app/components/plugins/base/deprecation-notice.tsx | 2 +-
web/app/components/plugins/marketplace/context.tsx | 3 ++-
.../subscription-list/edit/apikey-edit-modal.tsx | 2 +-
.../subscription-list/edit/manual-edit-modal.tsx | 2 +-
.../subscription-list/edit/oauth-edit-modal.tsx | 2 +-
web/app/components/plugins/plugin-page/context.tsx | 2 +-
web/app/components/plugins/plugin-page/empty/index.tsx | 2 +-
web/app/components/plugins/plugin-page/index.tsx | 2 +-
.../plugins/plugin-page/install-plugin-dropdown.tsx | 2 +-
.../panel/input-field/field-list/field-list-container.tsx | 2 +-
.../components/publish-as-knowledge-pipeline-modal.tsx | 2 +-
web/app/components/tools/labels/selector.tsx | 2 +-
web/app/components/tools/mcp/modal.tsx | 2 +-
.../tools/setting/build-in/config-credentials.tsx | 2 +-
.../components/tools/workflow-tool/confirm-modal/index.tsx | 2 +-
web/app/components/workflow-app/hooks/use-workflow-run.ts | 2 +-
.../workflow/block-selector/market-place-plugin/list.tsx | 2 +-
web/app/components/workflow/custom-edge.tsx | 2 +-
web/app/components/workflow/dsl-export-confirm-modal.tsx | 2 +-
web/app/components/workflow/hooks-store/store.ts | 4 +---
web/app/components/workflow/hooks/use-nodes-layout.ts | 2 +-
web/app/components/workflow/index.tsx | 2 +-
.../workflow/nodes/_base/components/agent-strategy.tsx | 2 +-
.../nodes/_base/components/editor/code-editor/index.tsx | 2 +-
.../workflow/nodes/_base/components/file-type-item.tsx | 2 +-
.../nodes/_base/components/input-support-select-var.tsx | 2 +-
.../workflow/nodes/_base/components/next-step/index.tsx | 2 +-
.../workflow/nodes/_base/components/next-step/operator.tsx | 2 +-
.../nodes/_base/components/panel-operator/change-block.tsx | 2 +-
.../workflow/nodes/_base/components/variable/utils.ts | 3 ++-
.../_base/components/variable/var-reference-picker.tsx | 2 +-
.../nodes/_base/components/variable/var-reference-vars.tsx | 2 +-
.../variable/variable-label/base/variable-label.tsx | 2 +-
.../workflow/nodes/_base/hooks/use-one-step-run.ts | 3 ++-
.../workflow/nodes/assigner/components/var-list/index.tsx | 2 +-
.../nodes/if-else/components/condition-number-input.tsx | 2 +-
.../workflow/nodes/if-else/components/condition-wrap.tsx | 2 +-
web/app/components/workflow/nodes/iteration/use-config.ts | 2 +-
.../metadata/condition-list/condition-value-method.tsx | 2 +-
.../components/metadata/metadata-filter/index.tsx | 2 +-
.../workflow/nodes/knowledge-retrieval/use-config.ts | 2 +-
.../components/workflow/nodes/knowledge-retrieval/utils.ts | 6 ++----
.../json-schema-config-modal/visual-editor/context.tsx | 2 +-
.../json-schema-config-modal/visual-editor/hooks.ts | 2 +-
.../workflow/nodes/llm/use-single-run-form-params.ts | 2 +-
.../nodes/loop/components/condition-number-input.tsx | 2 +-
.../nodes/parameter-extractor/use-single-run-form-params.ts | 2 +-
.../nodes/question-classifier/components/class-list.tsx | 2 +-
.../nodes/question-classifier/use-single-run-form-params.ts | 2 +-
.../components/workflow/nodes/start/components/var-item.tsx | 2 +-
.../workflow/nodes/tool/components/input-var-list.tsx | 2 +-
.../nodes/variable-assigner/components/var-list/index.tsx | 2 +-
.../note-editor/plugins/link-editor-plugin/component.tsx | 2 +-
.../note-editor/plugins/link-editor-plugin/hooks.ts | 2 +-
.../panel/chat-variable-panel/components/variable-item.tsx | 2 +-
.../panel/debug-and-preview/conversation-variable-modal.tsx | 3 ++-
.../components/workflow/panel/debug-and-preview/index.tsx | 3 ++-
web/app/components/workflow/panel/env-panel/env-item.tsx | 2 +-
.../workflow/panel/global-variable-panel/item.tsx | 2 +-
.../components/workflow/run/utils/format-log/agent/index.ts | 2 +-
web/app/components/workflow/run/utils/format-log/index.ts | 2 +-
.../workflow/run/utils/format-log/iteration/index.spec.ts | 2 +-
.../workflow/run/utils/format-log/loop/index.spec.ts | 2 +-
web/app/components/workflow/utils/elk-layout.ts | 2 +-
web/app/components/workflow/utils/workflow-init.ts | 4 +---
web/app/components/workflow/workflow-history-store.tsx | 2 +-
web/app/education-apply/education-apply-page.tsx | 2 +-
web/app/reset-password/page.tsx | 2 +-
web/app/signin/components/mail-and-password-auth.tsx | 2 +-
web/app/signin/invite-settings/page.tsx | 2 +-
web/app/signup/components/input-mail.tsx | 2 +-
web/context/app-context.tsx | 2 +-
web/context/datasets-context.tsx | 2 +-
web/context/debug-configuration.ts | 2 +-
web/context/explore-context.ts | 2 +-
web/context/mitt-context.tsx | 2 +-
web/context/modal-context.tsx | 2 +-
web/context/provider-context.tsx | 2 +-
web/i18n-config/i18next-config.ts | 2 +-
web/package.json | 1 -
web/pnpm-lock.yaml | 3 ---
web/service/use-plugins.ts | 2 +-
web/utils/index.ts | 2 +-
160 files changed, 172 insertions(+), 169 deletions(-)
diff --git a/web/__mocks__/provider-context.ts b/web/__mocks__/provider-context.ts
index c69a2ad1d2..373c2f86d3 100644
--- a/web/__mocks__/provider-context.ts
+++ b/web/__mocks__/provider-context.ts
@@ -1,6 +1,7 @@
import type { Plan, UsagePlanInfo } from '@/app/components/billing/type'
import type { ProviderContextState } from '@/context/provider-context'
-import { merge, noop } from 'es-toolkit/compat'
+import { merge } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { defaultPlan } from '@/app/components/billing/config'
// Avoid being mocked in tests
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx
index 5f72e7df63..368c3dcfc3 100644
--- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx
+++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/time-range-picker/date-picker.tsx
@@ -4,7 +4,7 @@ import type { FC } from 'react'
import type { TriggerProps } from '@/app/components/base/date-and-time-picker/types'
import { RiCalendarLine } from '@remixicon/react'
import dayjs from 'dayjs'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useCallback } from 'react'
import Picker from '@/app/components/base/date-and-time-picker/date-picker'
diff --git a/web/app/(shareLayout)/webapp-reset-password/page.tsx b/web/app/(shareLayout)/webapp-reset-password/page.tsx
index ec75e15a00..9b9a853cdd 100644
--- a/web/app/(shareLayout)/webapp-reset-password/page.tsx
+++ b/web/app/(shareLayout)/webapp-reset-password/page.tsx
@@ -1,6 +1,6 @@
'use client'
import { RiArrowLeftLine, RiLockPasswordLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { useState } from 'react'
diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx
index f79911099f..5aa9d9f141 100644
--- a/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx
+++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-code-auth.tsx
@@ -1,4 +1,4 @@
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useRouter, useSearchParams } from 'next/navigation'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx
index ae70675e7a..23ac83e76c 100644
--- a/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx
+++ b/web/app/(shareLayout)/webapp-signin/components/mail-and-password-auth.tsx
@@ -1,5 +1,5 @@
'use client'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { useCallback, useState } from 'react'
diff --git a/web/app/account/(commonLayout)/account-page/email-change-modal.tsx b/web/app/account/(commonLayout)/account-page/email-change-modal.tsx
index e74ca9ed41..87ca6a689c 100644
--- a/web/app/account/(commonLayout)/account-page/email-change-modal.tsx
+++ b/web/app/account/(commonLayout)/account-page/email-change-modal.tsx
@@ -1,6 +1,6 @@
import type { ResponseError } from '@/service/fetch'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useRouter } from 'next/navigation'
import * as React from 'react'
import { useState } from 'react'
diff --git a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx
index ab487d9b91..be1518b708 100644
--- a/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx
+++ b/web/app/components/app/annotation/batch-add-annotation-modal/index.tsx
@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/app/configuration/base/operation-btn/index.tsx b/web/app/components/app/configuration/base/operation-btn/index.tsx
index d78d9e81cc..d33b632071 100644
--- a/web/app/components/app/configuration/base/operation-btn/index.tsx
+++ b/web/app/components/app/configuration/base/operation-btn/index.tsx
@@ -4,7 +4,7 @@ import {
RiAddLine,
RiEditLine,
} from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
diff --git a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx
index c5c63279f6..bc94f87838 100644
--- a/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx
+++ b/web/app/components/app/configuration/config-prompt/simple-prompt-input.tsx
@@ -4,7 +4,7 @@ import type { ExternalDataTool } from '@/models/common'
import type { PromptVariable } from '@/models/debug'
import type { GenRes } from '@/service/debug'
import { useBoolean } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import * as React from 'react'
import { useState } from 'react'
diff --git a/web/app/components/app/configuration/config/agent/prompt-editor.tsx b/web/app/components/app/configuration/config/agent/prompt-editor.tsx
index 49a80aed6d..e9e3b60859 100644
--- a/web/app/components/app/configuration/config/agent/prompt-editor.tsx
+++ b/web/app/components/app/configuration/config/agent/prompt-editor.tsx
@@ -2,7 +2,7 @@
import type { FC } from 'react'
import type { ExternalDataTool } from '@/models/common'
import copy from 'copy-to-clipboard'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
diff --git a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx
index 9522d1263a..40beef52e8 100644
--- a/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx
+++ b/web/app/components/app/configuration/dataset-config/params-config/weighted-score.tsx
@@ -1,4 +1,4 @@
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import Slider from '@/app/components/base/slider'
diff --git a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx
index 557eef3ed5..32ca4f99cd 100644
--- a/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx
+++ b/web/app/components/app/configuration/dataset-config/settings-modal/index.tsx
@@ -3,7 +3,7 @@ import type { Member } from '@/models/common'
import type { DataSet } from '@/models/datasets'
import type { RetrievalConfig } from '@/types/app'
import { RiCloseLine } from '@remixicon/react'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx
index 3f0381074f..38f803f8ab 100644
--- a/web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx
+++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/context.tsx
@@ -1,7 +1,7 @@
'use client'
import type { ModelAndParameter } from '../types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { createContext, useContext } from 'use-context-selector'
export type DebugWithMultipleModelContextType = {
diff --git a/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx b/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx
index 9113f782d9..d7918e7ad6 100644
--- a/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx
+++ b/web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx
@@ -4,7 +4,8 @@ import type {
OnSend,
TextGenerationConfig,
} from '@/app/components/base/text-generation/types'
-import { cloneDeep, noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
+import { cloneDeep } from 'es-toolkit/object'
import { memo } from 'react'
import TextGeneration from '@/app/components/app/text-generate/item'
import { TransferMethod } from '@/app/components/base/chat/types'
diff --git a/web/app/components/app/configuration/debug/hooks.tsx b/web/app/components/app/configuration/debug/hooks.tsx
index e66185e284..e5dba3640d 100644
--- a/web/app/components/app/configuration/debug/hooks.tsx
+++ b/web/app/components/app/configuration/debug/hooks.tsx
@@ -6,7 +6,7 @@ import type {
ChatConfig,
ChatItem,
} from '@/app/components/base/chat/types'
-import { cloneDeep } from 'es-toolkit/compat'
+import { cloneDeep } from 'es-toolkit/object'
import {
useCallback,
useRef,
diff --git a/web/app/components/app/configuration/debug/index.tsx b/web/app/components/app/configuration/debug/index.tsx
index 7144d38470..b97bd68c5d 100644
--- a/web/app/components/app/configuration/debug/index.tsx
+++ b/web/app/components/app/configuration/debug/index.tsx
@@ -11,7 +11,8 @@ import {
RiSparklingFill,
} from '@remixicon/react'
import { useBoolean } from 'ahooks'
-import { cloneDeep, noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
+import { cloneDeep } from 'es-toolkit/object'
import { produce, setAutoFreeze } from 'immer'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
diff --git a/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts b/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts
index 3e8f7c5b3a..55b44653c9 100644
--- a/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts
+++ b/web/app/components/app/configuration/hooks/use-advanced-prompt-config.ts
@@ -1,6 +1,6 @@
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { ChatPromptConfig, CompletionPromptConfig, ConversationHistoriesRole, PromptItem } from '@/models/debug'
-import { clone } from 'es-toolkit/compat'
+import { clone } from 'es-toolkit/object'
import { produce } from 'immer'
import { useState } from 'react'
import { checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock, PRE_PROMPT_PLACEHOLDER_TEXT } from '@/app/components/base/prompt-editor/constants'
diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx
index 8a53d9b328..919b7c355a 100644
--- a/web/app/components/app/configuration/index.tsx
+++ b/web/app/components/app/configuration/index.tsx
@@ -20,7 +20,8 @@ import type {
import type { ModelConfig as BackendModelConfig, UserInputFormItem, VisionSettings } from '@/types/app'
import { CodeBracketIcon } from '@heroicons/react/20/solid'
import { useBoolean, useGetState } from 'ahooks'
-import { clone, isEqual } from 'es-toolkit/compat'
+import { clone } from 'es-toolkit/object'
+import { isEqual } from 'es-toolkit/predicate'
import { produce } from 'immer'
import { usePathname } from 'next/navigation'
import * as React from 'react'
diff --git a/web/app/components/app/configuration/tools/external-data-tool-modal.tsx b/web/app/components/app/configuration/tools/external-data-tool-modal.tsx
index 57145cc223..71827c4e0d 100644
--- a/web/app/components/app/configuration/tools/external-data-tool-modal.tsx
+++ b/web/app/components/app/configuration/tools/external-data-tool-modal.tsx
@@ -3,7 +3,7 @@ import type {
CodeBasedExtensionItem,
ExternalDataTool,
} from '@/models/common'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx
index 05873b85a7..838e9cc03f 100644
--- a/web/app/components/app/create-from-dsl-modal/index.tsx
+++ b/web/app/components/app/create-from-dsl-modal/index.tsx
@@ -3,7 +3,7 @@
import type { MouseEventHandler } from 'react'
import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react'
import { useDebounceFn, useKeyPress } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useRouter } from 'next/navigation'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/app/duplicate-modal/index.tsx b/web/app/components/app/duplicate-modal/index.tsx
index 315d871178..7d5b122f69 100644
--- a/web/app/components/app/duplicate-modal/index.tsx
+++ b/web/app/components/app/duplicate-modal/index.tsx
@@ -1,7 +1,7 @@
'use client'
import type { AppIconType } from '@/types/app'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/app/log/index.tsx b/web/app/components/app/log/index.tsx
index d282d61d4e..e96c9ce0c9 100644
--- a/web/app/components/app/log/index.tsx
+++ b/web/app/components/app/log/index.tsx
@@ -3,7 +3,7 @@ import type { FC } from 'react'
import type { App } from '@/types/app'
import { useDebounce } from 'ahooks'
import dayjs from 'dayjs'
-import { omit } from 'es-toolkit/compat'
+import { omit } from 'es-toolkit/object'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import * as React from 'react'
import { useCallback, useEffect, useState } from 'react'
diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx
index c260568582..a17177bf7e 100644
--- a/web/app/components/app/log/list.tsx
+++ b/web/app/components/app/log/list.tsx
@@ -12,7 +12,8 @@ import { RiCloseLine, RiEditFill } from '@remixicon/react'
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
-import { get, noop } from 'es-toolkit/compat'
+import { get } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
diff --git a/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx b/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx
index 5cffa1143b..17857ec702 100644
--- a/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx
+++ b/web/app/components/app/overview/apikey-info-panel/apikey-info-panel.test-utils.tsx
@@ -2,7 +2,7 @@ import type { RenderOptions } from '@testing-library/react'
import type { Mock, MockedFunction } from 'vitest'
import type { ModalContextState } from '@/context/modal-context'
import { fireEvent, render } from '@testing-library/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { defaultPlan } from '@/app/components/billing/config'
import { useModalContext as actualUseModalContext } from '@/context/modal-context'
diff --git a/web/app/components/app/switch-app-modal/index.tsx b/web/app/components/app/switch-app-modal/index.tsx
index 259f8da0b0..30d7877ed0 100644
--- a/web/app/components/app/switch-app-modal/index.tsx
+++ b/web/app/components/app/switch-app-modal/index.tsx
@@ -2,7 +2,7 @@
import type { App } from '@/types/app'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/app/workflow-log/index.tsx b/web/app/components/app/workflow-log/index.tsx
index ae7b08320e..79ae2ed83c 100644
--- a/web/app/components/app/workflow-log/index.tsx
+++ b/web/app/components/app/workflow-log/index.tsx
@@ -5,7 +5,7 @@ import { useDebounce } from 'ahooks'
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
-import { omit } from 'es-toolkit/compat'
+import { omit } from 'es-toolkit/object'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/base/agent-log-modal/detail.tsx b/web/app/components/base/agent-log-modal/detail.tsx
index fff2c3920d..a82a3207b1 100644
--- a/web/app/components/base/agent-log-modal/detail.tsx
+++ b/web/app/components/base/agent-log-modal/detail.tsx
@@ -2,7 +2,8 @@
import type { FC } from 'react'
import type { IChatItem } from '@/app/components/base/chat/chat/type'
import type { AgentIteration, AgentLogDetailResponse } from '@/models/log'
-import { flatten, uniq } from 'es-toolkit/compat'
+import { uniq } from 'es-toolkit/array'
+import { flatten } from 'es-toolkit/compat'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/base/app-icon-picker/index.tsx b/web/app/components/base/app-icon-picker/index.tsx
index 9b9c642d51..4dfad1f6eb 100644
--- a/web/app/components/base/app-icon-picker/index.tsx
+++ b/web/app/components/base/app-icon-picker/index.tsx
@@ -3,7 +3,7 @@ import type { Area } from 'react-easy-crop'
import type { OnImageInput } from './ImageInput'
import type { AppIconType, ImageFile } from '@/types/app'
import { RiImageCircleAiLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { DISABLE_UPLOAD_IMAGE_AS_ICON } from '@/config'
diff --git a/web/app/components/base/chat/chat-with-history/context.tsx b/web/app/components/base/chat/chat-with-history/context.tsx
index d1496f8278..49dd06ca52 100644
--- a/web/app/components/base/chat/chat-with-history/context.tsx
+++ b/web/app/components/base/chat/chat-with-history/context.tsx
@@ -14,7 +14,7 @@ import type {
AppMeta,
ConversationItem,
} from '@/models/share'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { createContext, useContext } from 'use-context-selector'
export type ChatWithHistoryContextValue = {
diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx
index 154729ded0..5ff8e61ff6 100644
--- a/web/app/components/base/chat/chat-with-history/hooks.tsx
+++ b/web/app/components/base/chat/chat-with-history/hooks.tsx
@@ -10,7 +10,7 @@ import type {
ConversationItem,
} from '@/models/share'
import { useLocalStorageState } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import {
useCallback,
diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts
index 50704d9a0d..9b8a9b11dc 100644
--- a/web/app/components/base/chat/chat/hooks.ts
+++ b/web/app/components/base/chat/chat/hooks.ts
@@ -8,7 +8,8 @@ import type { InputForm } from './type'
import type AudioPlayer from '@/app/components/base/audio-btn/audio'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import type { Annotation } from '@/models/log'
-import { noop, uniqBy } from 'es-toolkit/compat'
+import { uniqBy } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce, setAutoFreeze } from 'immer'
import { useParams, usePathname } from 'next/navigation'
import {
diff --git a/web/app/components/base/chat/embedded-chatbot/context.tsx b/web/app/components/base/chat/embedded-chatbot/context.tsx
index 97d3dd53cf..d690c28dd3 100644
--- a/web/app/components/base/chat/embedded-chatbot/context.tsx
+++ b/web/app/components/base/chat/embedded-chatbot/context.tsx
@@ -13,7 +13,7 @@ import type {
AppMeta,
ConversationItem,
} from '@/models/share'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { createContext, useContext } from 'use-context-selector'
export type EmbeddedChatbotContextValue = {
diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx
index 7a7cf4ffd3..803e905837 100644
--- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx
+++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx
@@ -9,7 +9,7 @@ import type {
ConversationItem,
} from '@/models/share'
import { useLocalStorageState } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import {
useCallback,
diff --git a/web/app/components/base/emoji-picker/index.tsx b/web/app/components/base/emoji-picker/index.tsx
index 99e1cfb7b4..9356efbfeb 100644
--- a/web/app/components/base/emoji-picker/index.tsx
+++ b/web/app/components/base/emoji-picker/index.tsx
@@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx
index 6eb31d7476..79520134a4 100644
--- a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx
+++ b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx
@@ -3,7 +3,7 @@ import type { InputVar } from '@/app/components/workflow/types'
import type { PromptVariable } from '@/models/debug'
import { RiAddLine, RiAsterisk, RiCloseLine, RiDeleteBinLine, RiDraggable } from '@remixicon/react'
import { useBoolean } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
diff --git a/web/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx b/web/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx
index c352913e30..59b62d0bfd 100644
--- a/web/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx
+++ b/web/app/components/base/features/new-feature-panel/moderation/moderation-setting-modal.tsx
@@ -2,7 +2,7 @@ import type { ChangeEvent, FC } from 'react'
import type { CodeBasedExtensionItem } from '@/models/common'
import type { ModerationConfig, ModerationContentConfig } from '@/models/debug'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
diff --git a/web/app/components/base/file-uploader/hooks.ts b/web/app/components/base/file-uploader/hooks.ts
index 95a775f3bb..14e46548d8 100644
--- a/web/app/components/base/file-uploader/hooks.ts
+++ b/web/app/components/base/file-uploader/hooks.ts
@@ -2,7 +2,7 @@ import type { ClipboardEvent } from 'react'
import type { FileEntity } from './types'
import type { FileUpload } from '@/app/components/base/features/types'
import type { FileUploadConfigResponse } from '@/models/common'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import { useParams } from 'next/navigation'
import {
diff --git a/web/app/components/base/file-uploader/pdf-preview.tsx b/web/app/components/base/file-uploader/pdf-preview.tsx
index 25644d024e..aab8bcd9d1 100644
--- a/web/app/components/base/file-uploader/pdf-preview.tsx
+++ b/web/app/components/base/file-uploader/pdf-preview.tsx
@@ -1,6 +1,6 @@
import type { FC } from 'react'
import { RiCloseLine, RiZoomInLine, RiZoomOutLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { t } from 'i18next'
import * as React from 'react'
import { useState } from 'react'
diff --git a/web/app/components/base/file-uploader/store.tsx b/web/app/components/base/file-uploader/store.tsx
index 2172733f20..24015df5cf 100644
--- a/web/app/components/base/file-uploader/store.tsx
+++ b/web/app/components/base/file-uploader/store.tsx
@@ -1,7 +1,7 @@
import type {
FileEntity,
} from './types'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import {
createContext,
useContext,
diff --git a/web/app/components/base/fullscreen-modal/index.tsx b/web/app/components/base/fullscreen-modal/index.tsx
index cad91b2452..fb2d2fa79b 100644
--- a/web/app/components/base/fullscreen-modal/index.tsx
+++ b/web/app/components/base/fullscreen-modal/index.tsx
@@ -1,6 +1,6 @@
import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react'
import { RiCloseLargeLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { cn } from '@/utils/classnames'
type IModal = {
diff --git a/web/app/components/base/image-uploader/image-preview.tsx b/web/app/components/base/image-uploader/image-preview.tsx
index 794152804e..b6a07c60aa 100644
--- a/web/app/components/base/image-uploader/image-preview.tsx
+++ b/web/app/components/base/image-uploader/image-preview.tsx
@@ -1,6 +1,6 @@
import type { FC } from 'react'
import { RiAddBoxLine, RiCloseLine, RiDownloadCloud2Line, RiFileCopyLine, RiZoomInLine, RiZoomOutLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { t } from 'i18next'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
diff --git a/web/app/components/base/input/index.tsx b/web/app/components/base/input/index.tsx
index b529702a65..27adbb3973 100644
--- a/web/app/components/base/input/index.tsx
+++ b/web/app/components/base/input/index.tsx
@@ -2,7 +2,7 @@ import type { VariantProps } from 'class-variance-authority'
import type { ChangeEventHandler, CSSProperties, FocusEventHandler } from 'react'
import { RiCloseCircleFill, RiErrorWarningLine, RiSearchLine } from '@remixicon/react'
import { cva } from 'class-variance-authority'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
diff --git a/web/app/components/base/modal/index.tsx b/web/app/components/base/modal/index.tsx
index 7270af1c77..e38254b27a 100644
--- a/web/app/components/base/modal/index.tsx
+++ b/web/app/components/base/modal/index.tsx
@@ -1,6 +1,6 @@
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { Fragment } from 'react'
import { cn } from '@/utils/classnames'
// https://headlessui.com/react/dialog
diff --git a/web/app/components/base/modal/modal.tsx b/web/app/components/base/modal/modal.tsx
index 1e606f125a..a24ca1765e 100644
--- a/web/app/components/base/modal/modal.tsx
+++ b/web/app/components/base/modal/modal.tsx
@@ -1,6 +1,6 @@
import type { ButtonProps } from '@/app/components/base/button'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
diff --git a/web/app/components/base/pagination/pagination.tsx b/web/app/components/base/pagination/pagination.tsx
index dafe0e4ab9..0eb06b594c 100644
--- a/web/app/components/base/pagination/pagination.tsx
+++ b/web/app/components/base/pagination/pagination.tsx
@@ -4,7 +4,7 @@ import type {
IPaginationProps,
PageButtonProps,
} from './type'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { cn } from '@/utils/classnames'
import usePagination from './hook'
diff --git a/web/app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx
index c4a246c40d..36e0d7f17b 100644
--- a/web/app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx
+++ b/web/app/components/base/prompt-editor/plugins/context-block/context-block-replacement-block.tsx
@@ -1,7 +1,7 @@
import type { ContextBlockType } from '../../types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { mergeRegister } from '@lexical/utils'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { $applyNodeReplacement } from 'lexical'
import {
memo,
diff --git a/web/app/components/base/prompt-editor/plugins/context-block/index.tsx b/web/app/components/base/prompt-editor/plugins/context-block/index.tsx
index ce3ed4c210..e3382c011d 100644
--- a/web/app/components/base/prompt-editor/plugins/context-block/index.tsx
+++ b/web/app/components/base/prompt-editor/plugins/context-block/index.tsx
@@ -1,7 +1,7 @@
import type { ContextBlockType } from '../../types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { mergeRegister } from '@lexical/utils'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import {
$insertNodes,
COMMAND_PRIORITY_EDITOR,
diff --git a/web/app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx b/web/app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx
index f62fb6886b..759e654c02 100644
--- a/web/app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx
+++ b/web/app/components/base/prompt-editor/plugins/history-block/history-block-replacement-block.tsx
@@ -1,7 +1,7 @@
import type { HistoryBlockType } from '../../types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { mergeRegister } from '@lexical/utils'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { $applyNodeReplacement } from 'lexical'
import {
useCallback,
diff --git a/web/app/components/base/prompt-editor/plugins/history-block/index.tsx b/web/app/components/base/prompt-editor/plugins/history-block/index.tsx
index dc75fc230d..a1d788c8cd 100644
--- a/web/app/components/base/prompt-editor/plugins/history-block/index.tsx
+++ b/web/app/components/base/prompt-editor/plugins/history-block/index.tsx
@@ -1,7 +1,7 @@
import type { HistoryBlockType } from '../../types'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { mergeRegister } from '@lexical/utils'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import {
$insertNodes,
COMMAND_PRIORITY_EDITOR,
diff --git a/web/app/components/base/radio-card/index.tsx b/web/app/components/base/radio-card/index.tsx
index 678d5b6dee..c349d08a96 100644
--- a/web/app/components/base/radio-card/index.tsx
+++ b/web/app/components/base/radio-card/index.tsx
@@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { cn } from '@/utils/classnames'
diff --git a/web/app/components/base/tag-management/panel.tsx b/web/app/components/base/tag-management/panel.tsx
index 50f9298963..adf750580f 100644
--- a/web/app/components/base/tag-management/panel.tsx
+++ b/web/app/components/base/tag-management/panel.tsx
@@ -3,7 +3,7 @@ import type { HtmlContentProps } from '@/app/components/base/popover'
import type { Tag } from '@/app/components/base/tag-management/constant'
import { RiAddLine, RiPriceTag3Line } from '@remixicon/react'
import { useUnmount } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/base/tag-management/tag-remove-modal.tsx b/web/app/components/base/tag-management/tag-remove-modal.tsx
index a0fdf056d5..6ce52e9dd6 100644
--- a/web/app/components/base/tag-management/tag-remove-modal.tsx
+++ b/web/app/components/base/tag-management/tag-remove-modal.tsx
@@ -2,7 +2,7 @@
import type { Tag } from '@/app/components/base/tag-management/constant'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
diff --git a/web/app/components/base/toast/index.spec.tsx b/web/app/components/base/toast/index.spec.tsx
index 59314063dd..cc5a1e7c6d 100644
--- a/web/app/components/base/toast/index.spec.tsx
+++ b/web/app/components/base/toast/index.spec.tsx
@@ -1,6 +1,6 @@
import type { ReactNode } from 'react'
import { act, render, screen, waitFor } from '@testing-library/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import Toast, { ToastProvider, useToastContext } from '.'
diff --git a/web/app/components/base/toast/index.tsx b/web/app/components/base/toast/index.tsx
index cf9e1cd909..0ab636efc1 100644
--- a/web/app/components/base/toast/index.tsx
+++ b/web/app/components/base/toast/index.tsx
@@ -7,7 +7,7 @@ import {
RiErrorWarningFill,
RiInformation2Fill,
} from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useState } from 'react'
import { createRoot } from 'react-dom/client'
diff --git a/web/app/components/base/with-input-validation/index.spec.tsx b/web/app/components/base/with-input-validation/index.spec.tsx
index b2e67ce056..daf3fd9a74 100644
--- a/web/app/components/base/with-input-validation/index.spec.tsx
+++ b/web/app/components/base/with-input-validation/index.spec.tsx
@@ -1,5 +1,5 @@
import { render, screen } from '@testing-library/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { z } from 'zod'
import withValidation from '.'
diff --git a/web/app/components/datasets/common/document-status-with-action/index-failed.tsx b/web/app/components/datasets/common/document-status-with-action/index-failed.tsx
index a97b74cf2e..b691f4e8c5 100644
--- a/web/app/components/datasets/common/document-status-with-action/index-failed.tsx
+++ b/web/app/components/datasets/common/document-status-with-action/index-failed.tsx
@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import type { IndexingStatusResponse } from '@/models/datasets'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useReducer } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx
index 38481f757f..2d187010b8 100644
--- a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx
+++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/index.tsx
@@ -1,6 +1,6 @@
'use client'
import { useDebounceFn, useKeyPress } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useRouter } from 'next/navigation'
import { useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx
index ecc517ed48..51b5c15178 100644
--- a/web/app/components/datasets/create/step-two/index.tsx
+++ b/web/app/components/datasets/create/step-two/index.tsx
@@ -9,7 +9,7 @@ import {
RiArrowLeftLine,
RiSearchEyeLine,
} from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Image from 'next/image'
import Link from 'next/link'
import { useCallback, useEffect, useMemo, useState } from 'react'
diff --git a/web/app/components/datasets/documents/detail/batch-modal/index.tsx b/web/app/components/datasets/documents/detail/batch-modal/index.tsx
index 080b631bae..8917d85ee7 100644
--- a/web/app/components/datasets/documents/detail/batch-modal/index.tsx
+++ b/web/app/components/datasets/documents/detail/batch-modal/index.tsx
@@ -2,7 +2,7 @@
import type { FC } from 'react'
import type { ChunkingMode, FileItem } from '@/models/datasets'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx
index 73b5cbdef9..de817b8d07 100644
--- a/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx
+++ b/web/app/components/datasets/documents/detail/completed/common/full-screen-drawer.tsx
@@ -1,4 +1,4 @@
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { cn } from '@/utils/classnames'
import Drawer from './drawer'
diff --git a/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx b/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx
index 11f8b9fc65..23c1e826b7 100644
--- a/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx
+++ b/web/app/components/datasets/documents/detail/completed/common/regeneration-modal.tsx
@@ -1,7 +1,7 @@
import type { FC } from 'react'
import { RiLoader2Line } from '@remixicon/react'
import { useCountDown } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/datasets/documents/detail/completed/index.tsx b/web/app/components/datasets/documents/detail/completed/index.tsx
index 531603a1cf..40c70e34f6 100644
--- a/web/app/components/datasets/documents/detail/completed/index.tsx
+++ b/web/app/components/datasets/documents/detail/completed/index.tsx
@@ -4,7 +4,7 @@ import type { Item } from '@/app/components/base/select'
import type { FileEntity } from '@/app/components/datasets/common/image-uploader/types'
import type { ChildChunkDetail, SegmentDetailModel, SegmentUpdater } from '@/models/datasets'
import { useDebounceFn } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { usePathname } from 'next/navigation'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
diff --git a/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx b/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx
index 76c9e2da96..08e13765e5 100644
--- a/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx
+++ b/web/app/components/datasets/documents/detail/settings/pipeline-settings/index.tsx
@@ -1,7 +1,7 @@
import type { NotionPage } from '@/models/common'
import type { CrawlResultItem, CustomFile, FileIndexingEstimateResponse } from '@/models/datasets'
import type { OnlineDriveFile, PublishedPipelineRunPreviewResponse } from '@/models/pipeline'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useRouter } from 'next/navigation'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx
index 0e039bba1a..5fd6cd3a70 100644
--- a/web/app/components/datasets/documents/list.tsx
+++ b/web/app/components/datasets/documents/list.tsx
@@ -9,7 +9,8 @@ import {
RiGlobalLine,
} from '@remixicon/react'
import { useBoolean } from 'ahooks'
-import { pick, uniq } from 'es-toolkit/compat'
+import { uniq } from 'es-toolkit/array'
+import { pick } from 'es-toolkit/object'
import { useRouter } from 'next/navigation'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
diff --git a/web/app/components/datasets/documents/operations.tsx b/web/app/components/datasets/documents/operations.tsx
index a873fcd96d..93afec6f8e 100644
--- a/web/app/components/datasets/documents/operations.tsx
+++ b/web/app/components/datasets/documents/operations.tsx
@@ -11,7 +11,7 @@ import {
RiPlayCircleLine,
} from '@remixicon/react'
import { useBoolean, useDebounceFn } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useRouter } from 'next/navigation'
import * as React from 'react'
import { useCallback, useState } from 'react'
diff --git a/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx b/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx
index b130267236..ee1b9cbcdc 100644
--- a/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx
+++ b/web/app/components/datasets/metadata/metadata-dataset/create-content.tsx
@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import { RiArrowLeftLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/datasets/rename-modal/index.tsx b/web/app/components/datasets/rename-modal/index.tsx
index 589b5a918e..106e35c4ac 100644
--- a/web/app/components/datasets/rename-modal/index.tsx
+++ b/web/app/components/datasets/rename-modal/index.tsx
@@ -4,7 +4,7 @@ import type { MouseEventHandler } from 'react'
import type { AppIconSelection } from '../../base/app-icon-picker'
import type { DataSet } from '@/models/datasets'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
diff --git a/web/app/components/explore/create-app-modal/index.tsx b/web/app/components/explore/create-app-modal/index.tsx
index 1137ae9e74..9bffcc6c69 100644
--- a/web/app/components/explore/create-app-modal/index.tsx
+++ b/web/app/components/explore/create-app-modal/index.tsx
@@ -2,7 +2,7 @@
import type { AppIconType } from '@/types/app'
import { RiCloseLine, RiCommandLine, RiCornerDownLeftLine } from '@remixicon/react'
import { useDebounceFn, useKeyPress } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/header/account-setting/api-based-extension-page/modal.tsx b/web/app/components/header/account-setting/api-based-extension-page/modal.tsx
index e12ecbe756..d857b5fe1f 100644
--- a/web/app/components/header/account-setting/api-based-extension-page/modal.tsx
+++ b/web/app/components/header/account-setting/api-based-extension-page/modal.tsx
@@ -1,6 +1,6 @@
import type { FC } from 'react'
import type { ApiBasedExtension } from '@/models/common'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
diff --git a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx
index 8f6120c826..0959383f29 100644
--- a/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx
+++ b/web/app/components/header/account-setting/data-source-page/data-source-notion/index.tsx
@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import type { DataSourceNotion as TDataSourceNotion } from '@/models/common'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx
index 06c311ef56..f62c5e147d 100644
--- a/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx
+++ b/web/app/components/header/account-setting/data-source-page/panel/config-item.tsx
@@ -3,7 +3,7 @@ import type { FC } from 'react'
import {
RiDeleteBinLine,
} from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { cn } from '@/utils/classnames'
diff --git a/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx b/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx
index 411c437a75..76f04382bd 100644
--- a/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx
+++ b/web/app/components/header/account-setting/members-page/edit-workspace-modal/index.tsx
@@ -1,6 +1,6 @@
'use client'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx
index 964d25e1cb..2d8d138af5 100644
--- a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx
+++ b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx
@@ -3,7 +3,7 @@ import type { RoleKey } from './role-selector'
import type { InvitationResult } from '@/models/common'
import { RiCloseLine, RiErrorWarningFill } from '@remixicon/react'
import { useBoolean } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ReactMultiEmail } from 'react-multi-email'
diff --git a/web/app/components/header/account-setting/members-page/invited-modal/index.tsx b/web/app/components/header/account-setting/members-page/invited-modal/index.tsx
index 3f756ab10d..389db4a42d 100644
--- a/web/app/components/header/account-setting/members-page/invited-modal/index.tsx
+++ b/web/app/components/header/account-setting/members-page/invited-modal/index.tsx
@@ -2,7 +2,7 @@ import type { InvitationResult } from '@/models/common'
import { XMarkIcon } from '@heroicons/react/24/outline'
import { CheckCircleIcon } from '@heroicons/react/24/solid'
import { RiQuestionLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
diff --git a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx
index 1d54167458..be7220da5e 100644
--- a/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx
+++ b/web/app/components/header/account-setting/members-page/transfer-ownership-modal/index.tsx
@@ -1,5 +1,5 @@
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
diff --git a/web/app/components/header/account-setting/menu-dialog.tsx b/web/app/components/header/account-setting/menu-dialog.tsx
index cc5adbc18f..847634ed83 100644
--- a/web/app/components/header/account-setting/menu-dialog.tsx
+++ b/web/app/components/header/account-setting/menu-dialog.tsx
@@ -1,6 +1,6 @@
import type { ReactNode } from 'react'
import { Dialog, DialogPanel, Transition, TransitionChild } from '@headlessui/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { Fragment, useCallback, useEffect } from 'react'
import { cn } from '@/utils/classnames'
diff --git a/web/app/components/header/app-selector/index.tsx b/web/app/components/header/app-selector/index.tsx
index c1b9cd1b83..13677ef7ab 100644
--- a/web/app/components/header/app-selector/index.tsx
+++ b/web/app/components/header/app-selector/index.tsx
@@ -2,7 +2,7 @@
import type { AppDetailResponse } from '@/models/app'
import { Menu, MenuButton, MenuItem, MenuItems, Transition } from '@headlessui/react'
import { ChevronDownIcon, PlusIcon } from '@heroicons/react/24/solid'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useRouter } from 'next/navigation'
import { Fragment, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/plugins/base/deprecation-notice.tsx b/web/app/components/plugins/base/deprecation-notice.tsx
index 7e32133045..c2ddfa6975 100644
--- a/web/app/components/plugins/base/deprecation-notice.tsx
+++ b/web/app/components/plugins/base/deprecation-notice.tsx
@@ -1,6 +1,6 @@
import type { FC } from 'react'
import { RiAlertFill } from '@remixicon/react'
-import { camelCase } from 'es-toolkit/compat'
+import { camelCase } from 'es-toolkit/string'
import Link from 'next/link'
import * as React from 'react'
import { useMemo } from 'react'
diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx
index 97144630a6..31b6a7f592 100644
--- a/web/app/components/plugins/marketplace/context.tsx
+++ b/web/app/components/plugins/marketplace/context.tsx
@@ -11,7 +11,8 @@ import type {
SearchParams,
SearchParamsFromCollection,
} from './types'
-import { debounce, noop } from 'es-toolkit/compat'
+import { debounce } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import {
useCallback,
useEffect,
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx
index 18896b1f50..a4093ed00b 100644
--- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/apikey-edit-modal.tsx
@@ -2,7 +2,7 @@
import type { FormRefObject, FormSchema } from '@/app/components/base/form/types'
import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types'
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import { useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { EncryptedBottom } from '@/app/components/base/encrypted-bottom'
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx
index 75ffff781f..262235e6ed 100644
--- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/manual-edit-modal.tsx
@@ -2,7 +2,7 @@
import type { FormRefObject, FormSchema } from '@/app/components/base/form/types'
import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types'
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import { useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseForm } from '@/app/components/base/form/components/base'
diff --git a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx
index 3332cd6b03..e57b9c0151 100644
--- a/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx
+++ b/web/app/components/plugins/plugin-detail-panel/subscription-list/edit/oauth-edit-modal.tsx
@@ -2,7 +2,7 @@
import type { FormRefObject, FormSchema } from '@/app/components/base/form/types'
import type { ParametersSchema, PluginDetail } from '@/app/components/plugins/types'
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import { useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { BaseForm } from '@/app/components/base/form/components/base'
diff --git a/web/app/components/plugins/plugin-page/context.tsx b/web/app/components/plugins/plugin-page/context.tsx
index 3d420ca1ab..fea78ae181 100644
--- a/web/app/components/plugins/plugin-page/context.tsx
+++ b/web/app/components/plugins/plugin-page/context.tsx
@@ -2,7 +2,7 @@
import type { ReactNode, RefObject } from 'react'
import type { FilterState } from './filter-management'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useQueryState } from 'nuqs'
import {
useMemo,
diff --git a/web/app/components/plugins/plugin-page/empty/index.tsx b/web/app/components/plugins/plugin-page/empty/index.tsx
index 019dc9ec24..7149423d5f 100644
--- a/web/app/components/plugins/plugin-page/empty/index.tsx
+++ b/web/app/components/plugins/plugin-page/empty/index.tsx
@@ -1,5 +1,5 @@
'use client'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx
index 6d8542f5c9..b8fc891254 100644
--- a/web/app/components/plugins/plugin-page/index.tsx
+++ b/web/app/components/plugins/plugin-page/index.tsx
@@ -7,7 +7,7 @@ import {
RiEqualizer2Line,
} from '@remixicon/react'
import { useBoolean } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx
index 7dbd3e3026..322591a363 100644
--- a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx
+++ b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx
@@ -1,7 +1,7 @@
'use client'
import { RiAddLine, RiArrowDownSLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
diff --git a/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container.tsx b/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container.tsx
index 108f3a642f..a14af1f704 100644
--- a/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container.tsx
+++ b/web/app/components/rag-pipeline/components/panel/input-field/field-list/field-list-container.tsx
@@ -1,6 +1,6 @@
import type { SortableItem } from './types'
import type { InputVar } from '@/models/pipeline'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import {
memo,
useCallback,
diff --git a/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx b/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx
index 4c607810bb..8d8c7f1088 100644
--- a/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx
+++ b/web/app/components/rag-pipeline/components/publish-as-knowledge-pipeline-modal.tsx
@@ -2,7 +2,7 @@
import type { AppIconSelection } from '@/app/components/base/app-icon-picker'
import type { IconInfo } from '@/models/datasets'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import AppIcon from '@/app/components/base/app-icon'
diff --git a/web/app/components/tools/labels/selector.tsx b/web/app/components/tools/labels/selector.tsx
index f95ebed72e..08c1216700 100644
--- a/web/app/components/tools/labels/selector.tsx
+++ b/web/app/components/tools/labels/selector.tsx
@@ -2,7 +2,7 @@ import type { FC } from 'react'
import type { Label } from '@/app/components/tools/labels/constant'
import { RiArrowDownSLine } from '@remixicon/react'
import { useDebounceFn } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Checkbox from '@/app/components/base/checkbox'
diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx
index 9bf4b351b4..413a2d3948 100644
--- a/web/app/components/tools/mcp/modal.tsx
+++ b/web/app/components/tools/mcp/modal.tsx
@@ -5,7 +5,7 @@ import type { ToolWithProvider } from '@/app/components/workflow/types'
import type { AppIconType } from '@/types/app'
import { RiCloseLine, RiEditLine } from '@remixicon/react'
import { useHover } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useCallback, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/tools/setting/build-in/config-credentials.tsx b/web/app/components/tools/setting/build-in/config-credentials.tsx
index cb11c5cf16..863b3ba352 100644
--- a/web/app/components/tools/setting/build-in/config-credentials.tsx
+++ b/web/app/components/tools/setting/build-in/config-credentials.tsx
@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import type { Collection } from '../../types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/tools/workflow-tool/confirm-modal/index.tsx b/web/app/components/tools/workflow-tool/confirm-modal/index.tsx
index 2abee055bb..0c7e083a56 100644
--- a/web/app/components/tools/workflow-tool/confirm-modal/index.tsx
+++ b/web/app/components/tools/workflow-tool/confirm-modal/index.tsx
@@ -1,7 +1,7 @@
'use client'
import { RiCloseLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useTranslation } from 'react-i18next'
import Button from '@/app/components/base/button'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts
index 031e2949cb..49c5d20dde 100644
--- a/web/app/components/workflow-app/hooks/use-workflow-run.ts
+++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts
@@ -2,7 +2,7 @@ import type AudioPlayer from '@/app/components/base/audio-btn/audio'
import type { Node } from '@/app/components/workflow/types'
import type { IOtherOptions } from '@/service/base'
import type { VersionHistory } from '@/types/workflow'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import { usePathname } from 'next/navigation'
import { useCallback, useRef } from 'react'
diff --git a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx
index 9927df500d..29f1e77e14 100644
--- a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx
+++ b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx
@@ -2,7 +2,7 @@
import type { RefObject } from 'react'
import type { Plugin, PluginCategoryEnum } from '@/app/components/plugins/types'
import { RiArrowRightUpLine, RiSearchLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useEffect, useImperativeHandle, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx
index 6427520d81..0440ca0c3e 100644
--- a/web/app/components/workflow/custom-edge.tsx
+++ b/web/app/components/workflow/custom-edge.tsx
@@ -3,7 +3,7 @@ import type {
Edge,
OnSelectBlock,
} from './types'
-import { intersection } from 'es-toolkit/compat'
+import { intersection } from 'es-toolkit/array'
import {
memo,
useCallback,
diff --git a/web/app/components/workflow/dsl-export-confirm-modal.tsx b/web/app/components/workflow/dsl-export-confirm-modal.tsx
index 72f8c5857d..e698de722e 100644
--- a/web/app/components/workflow/dsl-export-confirm-modal.tsx
+++ b/web/app/components/workflow/dsl-export-confirm-modal.tsx
@@ -1,7 +1,7 @@
'use client'
import type { EnvironmentVariable } from '@/app/components/workflow/types'
import { RiCloseLine, RiLock2Line } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/hooks-store/store.ts b/web/app/components/workflow/hooks-store/store.ts
index 3aa4ba3d91..44014fc0d7 100644
--- a/web/app/components/workflow/hooks-store/store.ts
+++ b/web/app/components/workflow/hooks-store/store.ts
@@ -10,9 +10,7 @@ import type { IOtherOptions } from '@/service/base'
import type { SchemaTypeDefinition } from '@/service/use-common'
import type { FlowType } from '@/types/common'
import type { VarInInspect } from '@/types/workflow'
-import {
- noop,
-} from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useContext } from 'react'
import {
useStore as useZustandStore,
diff --git a/web/app/components/workflow/hooks/use-nodes-layout.ts b/web/app/components/workflow/hooks/use-nodes-layout.ts
index 2ad89ff100..0738703791 100644
--- a/web/app/components/workflow/hooks/use-nodes-layout.ts
+++ b/web/app/components/workflow/hooks/use-nodes-layout.ts
@@ -3,7 +3,7 @@ import type {
Node,
} from '../types'
import ELK from 'elkjs/lib/elk.bundled.js'
-import { cloneDeep } from 'es-toolkit/compat'
+import { cloneDeep } from 'es-toolkit/object'
import { useCallback } from 'react'
import {
useReactFlow,
diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx
index 8eeab43d7e..1543bce714 100644
--- a/web/app/components/workflow/index.tsx
+++ b/web/app/components/workflow/index.tsx
@@ -13,7 +13,7 @@ import type { VarInInspect } from '@/types/workflow'
import {
useEventListener,
} from 'ahooks'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import { setAutoFreeze } from 'immer'
import dynamic from 'next/dynamic'
import {
diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx
index 8ea786ffb4..cc58176fb6 100644
--- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx
+++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx
@@ -4,7 +4,7 @@ import type { NodeOutPutVar } from '../../../types'
import type { ToolVarInputs } from '../../tool/types'
import type { CredentialFormSchema, CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { PluginMeta } from '@/app/components/plugins/types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx
index ad5410986c..4714139541 100644
--- a/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx
+++ b/web/app/components/workflow/nodes/_base/components/editor/code-editor/index.tsx
@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import Editor, { loader } from '@monaco-editor/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import {
diff --git a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx
index cef2f76da6..aa73260af6 100644
--- a/web/app/components/workflow/nodes/_base/components/file-type-item.tsx
+++ b/web/app/components/workflow/nodes/_base/components/file-type-item.tsx
@@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx
index 5a1c467be5..8880aedf80 100644
--- a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx
+++ b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx
@@ -5,7 +5,7 @@ import type {
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { useBoolean } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx
index 686c2a17c6..0b22d44aac 100644
--- a/web/app/components/workflow/nodes/_base/components/next-step/index.tsx
+++ b/web/app/components/workflow/nodes/_base/components/next-step/index.tsx
@@ -1,7 +1,7 @@
import type {
Node,
} from '../../../../types'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import { memo, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import {
diff --git a/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx b/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx
index 9b4de96f9f..8484028868 100644
--- a/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx
+++ b/web/app/components/workflow/nodes/_base/components/next-step/operator.tsx
@@ -3,7 +3,7 @@ import type {
OnSelectBlock,
} from '@/app/components/workflow/types'
import { RiMoreFill } from '@remixicon/react'
-import { intersection } from 'es-toolkit/compat'
+import { intersection } from 'es-toolkit/array'
import {
useCallback,
} from 'react'
diff --git a/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx b/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx
index 6652ce58ec..32f9e9a174 100644
--- a/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx
+++ b/web/app/components/workflow/nodes/_base/components/panel-operator/change-block.tsx
@@ -2,7 +2,7 @@ import type {
Node,
OnSelectBlock,
} from '@/app/components/workflow/types'
-import { intersection } from 'es-toolkit/compat'
+import { intersection } from 'es-toolkit/array'
import {
memo,
useCallback,
diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts
index 9f033f9b27..9f77be0ce2 100644
--- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts
+++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts
@@ -33,7 +33,8 @@ import type {
import type { PromptItem } from '@/models/debug'
import type { RAGPipelineVariable } from '@/models/pipeline'
import type { SchemaTypeDefinition } from '@/service/use-common'
-import { isArray, uniq } from 'es-toolkit/compat'
+import { uniq } from 'es-toolkit/array'
+import { isArray } from 'es-toolkit/compat'
import { produce } from 'immer'
import {
AGENT_OUTPUT_STRUCT,
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
index 86ec5c3c09..6dfcbaf4d8 100644
--- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
+++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx
@@ -11,7 +11,7 @@ import {
RiLoader4Line,
RiMoreLine,
} from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx
index a8c1073d93..d44f560e08 100644
--- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx
+++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx
@@ -4,7 +4,7 @@ import type { StructuredOutput } from '../../../llm/types'
import type { Field } from '@/app/components/workflow/nodes/llm/types'
import type { NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
import { useHover } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx
index 828f7e5ebe..55d6a14d37 100644
--- a/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx
+++ b/web/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-label.tsx
@@ -3,7 +3,7 @@ import {
RiErrorWarningFill,
RiMoreLine,
} from '@remixicon/react'
-import { capitalize } from 'es-toolkit/compat'
+import { capitalize } from 'es-toolkit/string'
import { memo } from 'react'
import Tooltip from '@/app/components/base/tooltip'
import { cn } from '@/utils/classnames'
diff --git a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts
index d8fd4d2c57..d2d7b6b6d9 100644
--- a/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts
+++ b/web/app/components/workflow/nodes/_base/hooks/use-one-step-run.ts
@@ -1,7 +1,8 @@
import type { CommonNodeType, InputVar, TriggerNodeType, ValueSelector, Var, Variable } from '@/app/components/workflow/types'
import type { FlowType } from '@/types/common'
import type { NodeRunResult, NodeTracing } from '@/types/workflow'
-import { noop, unionBy } from 'es-toolkit/compat'
+import { unionBy } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import { useCallback, useEffect, useRef, useState } from 'react'
diff --git a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx
index 0fd735abbb..202d7469aa 100644
--- a/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx
+++ b/web/app/components/workflow/nodes/assigner/components/var-list/index.tsx
@@ -3,7 +3,7 @@ import type { FC } from 'react'
import type { AssignerNodeOperation } from '../../types'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { RiDeleteBinLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback } from 'react'
diff --git a/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx b/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx
index ee81d70106..ed538618e3 100644
--- a/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx
+++ b/web/app/components/workflow/nodes/if-else/components/condition-number-input.tsx
@@ -4,7 +4,7 @@ import type {
} from '@/app/components/workflow/types'
import { RiArrowDownSLine } from '@remixicon/react'
import { useBoolean } from 'ahooks'
-import { capitalize } from 'es-toolkit/compat'
+import { capitalize } from 'es-toolkit/string'
import {
memo,
useCallback,
diff --git a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx
index 5554696480..7ac76dd936 100644
--- a/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx
+++ b/web/app/components/workflow/nodes/if-else/components/condition-wrap.tsx
@@ -7,7 +7,7 @@ import {
RiDeleteBinLine,
RiDraggable,
} from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/nodes/iteration/use-config.ts b/web/app/components/workflow/nodes/iteration/use-config.ts
index 3106577085..79df409474 100644
--- a/web/app/components/workflow/nodes/iteration/use-config.ts
+++ b/web/app/components/workflow/nodes/iteration/use-config.ts
@@ -2,7 +2,7 @@ import type { ErrorHandleMode, ValueSelector, Var } from '../../types'
import type { IterationNodeType } from './types'
import type { Item } from '@/app/components/base/select'
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import { produce } from 'immer'
import { useCallback } from 'react'
import {
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx
index 574501a27d..08a316c82a 100644
--- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx
+++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/condition-list/condition-value-method.tsx
@@ -1,5 +1,5 @@
import { RiArrowDownSLine } from '@remixicon/react'
-import { capitalize } from 'es-toolkit/compat'
+import { capitalize } from 'es-toolkit/string'
import { useState } from 'react'
import Button from '@/app/components/base/button'
import {
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx
index 3394c1f7a7..88b7ff303c 100644
--- a/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx
+++ b/web/app/components/workflow/nodes/knowledge-retrieval/components/metadata/metadata-filter/index.tsx
@@ -1,5 +1,5 @@
import type { MetadataShape } from '@/app/components/workflow/nodes/knowledge-retrieval/types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import {
useCallback,
useState,
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts
index 5208200b25..a1662a1e1c 100644
--- a/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts
+++ b/web/app/components/workflow/nodes/knowledge-retrieval/use-config.ts
@@ -9,7 +9,7 @@ import type {
MultipleRetrievalConfig,
} from './types'
import type { DataSet } from '@/models/datasets'
-import { isEqual } from 'es-toolkit/compat'
+import { isEqual } from 'es-toolkit/predicate'
import { produce } from 'immer'
import {
useCallback,
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts b/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts
index d6cd69b39a..a30ec8e735 100644
--- a/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts
+++ b/web/app/components/workflow/nodes/knowledge-retrieval/utils.ts
@@ -3,10 +3,8 @@ import type {
DataSet,
SelectedDatasetsMode,
} from '@/models/datasets'
-import {
- uniq,
- xorBy,
-} from 'es-toolkit/compat'
+import { uniq } from 'es-toolkit/array'
+import { xorBy } from 'es-toolkit/compat'
import { DATASET_DEFAULT } from '@/config'
import {
DEFAULT_WEIGHTED_SCORE,
diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx
index 557cddfb61..82a3c27a98 100644
--- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx
+++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx
@@ -1,4 +1,4 @@
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import {
createContext,
useContext,
diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts
index 1673c80f4f..6159028c21 100644
--- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts
+++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/hooks.ts
@@ -1,7 +1,7 @@
import type { VisualEditorProps } from '.'
import type { Field } from '../../../types'
import type { EditData } from './edit-card'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import Toast from '@/app/components/base/toast'
import { ArrayType, Type } from '../../../types'
diff --git a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts
index 580a5d44ad..e0c4c97fad 100644
--- a/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts
+++ b/web/app/components/workflow/nodes/llm/use-single-run-form-params.ts
@@ -2,7 +2,7 @@ import type { RefObject } from 'react'
import type { LLMNodeType } from './types'
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
import type { InputVar, PromptItem, Var, Variable } from '@/app/components/workflow/types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { InputVarType, VarType } from '@/app/components/workflow/types'
diff --git a/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx b/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx
index ee81d70106..ed538618e3 100644
--- a/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx
+++ b/web/app/components/workflow/nodes/loop/components/condition-number-input.tsx
@@ -4,7 +4,7 @@ import type {
} from '@/app/components/workflow/types'
import { RiArrowDownSLine } from '@remixicon/react'
import { useBoolean } from 'ahooks'
-import { capitalize } from 'es-toolkit/compat'
+import { capitalize } from 'es-toolkit/string'
import {
memo,
useCallback,
diff --git a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts
index e736ee6e79..52a4462153 100644
--- a/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts
+++ b/web/app/components/workflow/nodes/parameter-extractor/use-single-run-form-params.ts
@@ -2,7 +2,7 @@ import type { RefObject } from 'react'
import type { ParameterExtractorNodeType } from './types'
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
import type { InputVar, Var, Variable } from '@/app/components/workflow/types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { InputVarType, VarType } from '@/app/components/workflow/types'
diff --git a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx
index 724de571b6..5e41b5ee1c 100644
--- a/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx
+++ b/web/app/components/workflow/nodes/question-classifier/components/class-list.tsx
@@ -3,7 +3,7 @@ import type { FC } from 'react'
import type { Topic } from '@/app/components/workflow/nodes/question-classifier/types'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
import { RiDraggable } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
diff --git a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts
index 2bea1f8318..9b4b05367e 100644
--- a/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts
+++ b/web/app/components/workflow/nodes/question-classifier/use-single-run-form-params.ts
@@ -2,7 +2,7 @@ import type { RefObject } from 'react'
import type { QuestionClassifierNodeType } from './types'
import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/components/before-run-form/form'
import type { InputVar, Var, Variable } from '@/app/components/workflow/types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { InputVarType, VarType } from '@/app/components/workflow/types'
diff --git a/web/app/components/workflow/nodes/start/components/var-item.tsx b/web/app/components/workflow/nodes/start/components/var-item.tsx
index fdb07d0be9..64fd1804b0 100644
--- a/web/app/components/workflow/nodes/start/components/var-item.tsx
+++ b/web/app/components/workflow/nodes/start/components/var-item.tsx
@@ -5,7 +5,7 @@ import {
RiDeleteBinLine,
} from '@remixicon/react'
import { useBoolean, useHover } from 'ahooks'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import * as React from 'react'
import { useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx
index 9bd01ac74e..96a878b506 100644
--- a/web/app/components/workflow/nodes/tool/components/input-var-list.tsx
+++ b/web/app/components/workflow/nodes/tool/components/input-var-list.tsx
@@ -4,7 +4,7 @@ import type { ToolVarInputs } from '../types'
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Tool } from '@/app/components/tools/types'
import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback, useState } from 'react'
diff --git a/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx b/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx
index c2f289b7d1..1f45a8a0e1 100644
--- a/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx
+++ b/web/app/components/workflow/nodes/variable-assigner/components/var-list/index.tsx
@@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import type { ValueSelector, Var } from '@/app/components/workflow/types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback } from 'react'
diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx
index 20027f37a1..b66827da45 100644
--- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx
+++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/component.tsx
@@ -11,7 +11,7 @@ import {
RiLinkUnlinkM,
} from '@remixicon/react'
import { useClickAway } from 'ahooks'
-import { escape } from 'es-toolkit/compat'
+import { escape } from 'es-toolkit/string'
import {
memo,
useEffect,
diff --git a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts
index 5248680d89..3a084ef0af 100644
--- a/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts
+++ b/web/app/components/workflow/note-node/note-editor/plugins/link-editor-plugin/hooks.ts
@@ -5,7 +5,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
import {
mergeRegister,
} from '@lexical/utils'
-import { escape } from 'es-toolkit/compat'
+import { escape } from 'es-toolkit/string'
import {
CLICK_COMMAND,
COMMAND_PRIORITY_LOW,
diff --git a/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx b/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx
index 9fec4f7d01..58548e3084 100644
--- a/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx
+++ b/web/app/components/workflow/panel/chat-variable-panel/components/variable-item.tsx
@@ -1,6 +1,6 @@
import type { ConversationVariable } from '@/app/components/workflow/types'
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
-import { capitalize } from 'es-toolkit/compat'
+import { capitalize } from 'es-toolkit/string'
import { memo, useState } from 'react'
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
import { cn } from '@/utils/classnames'
diff --git a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx
index 4e9b72f6e5..0f6a4c081d 100644
--- a/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx
+++ b/web/app/components/workflow/panel/debug-and-preview/conversation-variable-modal.tsx
@@ -5,7 +5,8 @@ import type {
import { RiCloseLine } from '@remixicon/react'
import { useMount } from 'ahooks'
import copy from 'copy-to-clipboard'
-import { capitalize, noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
+import { capitalize } from 'es-toolkit/string'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/app/components/workflow/panel/debug-and-preview/index.tsx b/web/app/components/workflow/panel/debug-and-preview/index.tsx
index b42d4205fa..bc5116bc65 100644
--- a/web/app/components/workflow/panel/debug-and-preview/index.tsx
+++ b/web/app/components/workflow/panel/debug-and-preview/index.tsx
@@ -1,7 +1,8 @@
import type { StartNodeType } from '../../nodes/start/types'
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
-import { debounce, noop } from 'es-toolkit/compat'
+import { debounce } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import {
memo,
useCallback,
diff --git a/web/app/components/workflow/panel/env-panel/env-item.tsx b/web/app/components/workflow/panel/env-panel/env-item.tsx
index 582539b85b..26696daad0 100644
--- a/web/app/components/workflow/panel/env-panel/env-item.tsx
+++ b/web/app/components/workflow/panel/env-panel/env-item.tsx
@@ -1,6 +1,6 @@
import type { EnvironmentVariable } from '@/app/components/workflow/types'
import { RiDeleteBinLine, RiEditLine, RiLock2Line } from '@remixicon/react'
-import { capitalize } from 'es-toolkit/compat'
+import { capitalize } from 'es-toolkit/string'
import { memo, useState } from 'react'
import { Env } from '@/app/components/base/icons/src/vender/line/others'
import { useStore } from '@/app/components/workflow/store'
diff --git a/web/app/components/workflow/panel/global-variable-panel/item.tsx b/web/app/components/workflow/panel/global-variable-panel/item.tsx
index 458dd27692..83c0df7758 100644
--- a/web/app/components/workflow/panel/global-variable-panel/item.tsx
+++ b/web/app/components/workflow/panel/global-variable-panel/item.tsx
@@ -1,5 +1,5 @@
import type { GlobalVariable } from '@/app/components/workflow/types'
-import { capitalize } from 'es-toolkit/compat'
+import { capitalize } from 'es-toolkit/string'
import { memo } from 'react'
import { GlobalVariable as GlobalVariableIcon } from '@/app/components/base/icons/src/vender/line/others'
diff --git a/web/app/components/workflow/run/utils/format-log/agent/index.ts b/web/app/components/workflow/run/utils/format-log/agent/index.ts
index f86e4b33bb..19acd8c120 100644
--- a/web/app/components/workflow/run/utils/format-log/agent/index.ts
+++ b/web/app/components/workflow/run/utils/format-log/agent/index.ts
@@ -1,5 +1,5 @@
import type { AgentLogItem, AgentLogItemWithChildren, NodeTracing } from '@/types/workflow'
-import { cloneDeep } from 'es-toolkit/compat'
+import { cloneDeep } from 'es-toolkit/object'
import { BlockEnum } from '@/app/components/workflow/types'
const supportedAgentLogNodes = [BlockEnum.Agent, BlockEnum.Tool]
diff --git a/web/app/components/workflow/run/utils/format-log/index.ts b/web/app/components/workflow/run/utils/format-log/index.ts
index 1dbe8f1682..c152a5156a 100644
--- a/web/app/components/workflow/run/utils/format-log/index.ts
+++ b/web/app/components/workflow/run/utils/format-log/index.ts
@@ -1,5 +1,5 @@
import type { NodeTracing } from '@/types/workflow'
-import { cloneDeep } from 'es-toolkit/compat'
+import { cloneDeep } from 'es-toolkit/object'
import { BlockEnum } from '../../../types'
import formatAgentNode from './agent'
import { addChildrenToIterationNode } from './iteration'
diff --git a/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts b/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts
index 8b4416f529..f984dbea76 100644
--- a/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts
+++ b/web/app/components/workflow/run/utils/format-log/iteration/index.spec.ts
@@ -1,4 +1,4 @@
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import format from '.'
import graphToLogStruct from '../graph-to-log-struct'
diff --git a/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts b/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts
index 3d31e43ba3..d2a2fd24bb 100644
--- a/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts
+++ b/web/app/components/workflow/run/utils/format-log/loop/index.spec.ts
@@ -1,4 +1,4 @@
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import format from '.'
import graphToLogStruct from '../graph-to-log-struct'
diff --git a/web/app/components/workflow/utils/elk-layout.ts b/web/app/components/workflow/utils/elk-layout.ts
index 1a4bbf2d50..c3b37c8f16 100644
--- a/web/app/components/workflow/utils/elk-layout.ts
+++ b/web/app/components/workflow/utils/elk-layout.ts
@@ -5,7 +5,7 @@ import type {
Node,
} from '@/app/components/workflow/types'
import ELK from 'elkjs/lib/elk.bundled.js'
-import { cloneDeep } from 'es-toolkit/compat'
+import { cloneDeep } from 'es-toolkit/object'
import {
CUSTOM_NODE,
NODE_LAYOUT_HORIZONTAL_PADDING,
diff --git a/web/app/components/workflow/utils/workflow-init.ts b/web/app/components/workflow/utils/workflow-init.ts
index fa211934e4..77a2ccefac 100644
--- a/web/app/components/workflow/utils/workflow-init.ts
+++ b/web/app/components/workflow/utils/workflow-init.ts
@@ -7,9 +7,7 @@ import type {
Edge,
Node,
} from '../types'
-import {
- cloneDeep,
-} from 'es-toolkit/compat'
+import { cloneDeep } from 'es-toolkit/object'
import {
getConnectedEdges,
} from 'reactflow'
diff --git a/web/app/components/workflow/workflow-history-store.tsx b/web/app/components/workflow/workflow-history-store.tsx
index 6729fe50e3..5d10f81b27 100644
--- a/web/app/components/workflow/workflow-history-store.tsx
+++ b/web/app/components/workflow/workflow-history-store.tsx
@@ -3,7 +3,7 @@ import type { TemporalState } from 'zundo'
import type { StoreApi } from 'zustand'
import type { WorkflowHistoryEventT } from './hooks'
import type { Edge, Node } from './types'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import isDeepEqual from 'fast-deep-equal'
import { createContext, useContext, useMemo, useState } from 'react'
import { temporal } from 'zundo'
diff --git a/web/app/education-apply/education-apply-page.tsx b/web/app/education-apply/education-apply-page.tsx
index f9ab2b4646..0be88091dc 100644
--- a/web/app/education-apply/education-apply-page.tsx
+++ b/web/app/education-apply/education-apply-page.tsx
@@ -1,7 +1,7 @@
'use client'
import { RiExternalLinkLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import {
useRouter,
useSearchParams,
diff --git a/web/app/reset-password/page.tsx b/web/app/reset-password/page.tsx
index 6be429960c..9fdccdfd87 100644
--- a/web/app/reset-password/page.tsx
+++ b/web/app/reset-password/page.tsx
@@ -1,6 +1,6 @@
'use client'
import { RiArrowLeftLine, RiLockPasswordLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { useState } from 'react'
diff --git a/web/app/signin/components/mail-and-password-auth.tsx b/web/app/signin/components/mail-and-password-auth.tsx
index 4a18e884ad..101ddf559a 100644
--- a/web/app/signin/components/mail-and-password-auth.tsx
+++ b/web/app/signin/components/mail-and-password-auth.tsx
@@ -1,5 +1,5 @@
import type { ResponseError } from '@/service/fetch'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { useState } from 'react'
diff --git a/web/app/signin/invite-settings/page.tsx b/web/app/signin/invite-settings/page.tsx
index 360f305cbd..1e38638360 100644
--- a/web/app/signin/invite-settings/page.tsx
+++ b/web/app/signin/invite-settings/page.tsx
@@ -1,7 +1,7 @@
'use client'
import type { Locale } from '@/i18n-config'
import { RiAccountCircleLine } from '@remixicon/react'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { useCallback, useState } from 'react'
diff --git a/web/app/signup/components/input-mail.tsx b/web/app/signup/components/input-mail.tsx
index a1730b90c9..6342e7909c 100644
--- a/web/app/signup/components/input-mail.tsx
+++ b/web/app/signup/components/input-mail.tsx
@@ -1,6 +1,6 @@
'use client'
import type { MailSendResponse } from '@/service/use-common'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import Link from 'next/link'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
diff --git a/web/context/app-context.tsx b/web/context/app-context.tsx
index cb4cab65b7..335f96fcce 100644
--- a/web/context/app-context.tsx
+++ b/web/context/app-context.tsx
@@ -3,7 +3,7 @@
import type { FC, ReactNode } from 'react'
import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import { useQueryClient } from '@tanstack/react-query'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useCallback, useEffect, useMemo } from 'react'
import { createContext, useContext, useContextSelector } from 'use-context-selector'
import { setUserId, setUserProperties } from '@/app/components/base/amplitude'
diff --git a/web/context/datasets-context.tsx b/web/context/datasets-context.tsx
index f35767bc21..d309a8ef3f 100644
--- a/web/context/datasets-context.tsx
+++ b/web/context/datasets-context.tsx
@@ -1,7 +1,7 @@
'use client'
import type { DataSet } from '@/models/datasets'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { createContext, useContext } from 'use-context-selector'
export type DatasetsContextValue = {
diff --git a/web/context/debug-configuration.ts b/web/context/debug-configuration.ts
index 2518af6260..ba157e1bf7 100644
--- a/web/context/debug-configuration.ts
+++ b/web/context/debug-configuration.ts
@@ -22,7 +22,7 @@ import type {
TextToSpeechConfig,
} from '@/models/debug'
import type { VisionSettings } from '@/types/app'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { createContext, useContext } from 'use-context-selector'
import { ANNOTATION_DEFAULT, DEFAULT_AGENT_SETTING, DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
import { PromptMode } from '@/models/debug'
diff --git a/web/context/explore-context.ts b/web/context/explore-context.ts
index 1a7b35a09b..fc446c0453 100644
--- a/web/context/explore-context.ts
+++ b/web/context/explore-context.ts
@@ -1,5 +1,5 @@
import type { InstalledApp } from '@/models/explore'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { createContext } from 'use-context-selector'
type IExplore = {
diff --git a/web/context/mitt-context.tsx b/web/context/mitt-context.tsx
index 0fc160613a..4317fc5660 100644
--- a/web/context/mitt-context.tsx
+++ b/web/context/mitt-context.tsx
@@ -1,4 +1,4 @@
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { createContext, useContext, useContextSelector } from 'use-context-selector'
import { useMitt } from '@/hooks/use-mitt'
diff --git a/web/context/modal-context.tsx b/web/context/modal-context.tsx
index dce7b9f6e1..293970259a 100644
--- a/web/context/modal-context.tsx
+++ b/web/context/modal-context.tsx
@@ -22,7 +22,7 @@ import type {
ExternalDataTool,
} from '@/models/common'
import type { ModerationConfig, PromptVariable } from '@/models/debug'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import dynamic from 'next/dynamic'
import { useCallback, useEffect, useRef, useState } from 'react'
import { createContext, useContext, useContextSelector } from 'use-context-selector'
diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx
index e6a2a1c5af..7eca72e322 100644
--- a/web/context/provider-context.tsx
+++ b/web/context/provider-context.tsx
@@ -5,7 +5,7 @@ import type { Model, ModelProvider } from '@/app/components/header/account-setti
import type { RETRIEVE_METHOD } from '@/types/app'
import { useQueryClient } from '@tanstack/react-query'
import dayjs from 'dayjs'
-import { noop } from 'es-toolkit/compat'
+import { noop } from 'es-toolkit/function'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { createContext, useContext, useContextSelector } from 'use-context-selector'
diff --git a/web/i18n-config/i18next-config.ts b/web/i18n-config/i18next-config.ts
index 107954a384..0997485967 100644
--- a/web/i18n-config/i18next-config.ts
+++ b/web/i18n-config/i18next-config.ts
@@ -1,6 +1,6 @@
'use client'
import type { Locale } from '.'
-import { camelCase, kebabCase } from 'es-toolkit/compat'
+import { camelCase, kebabCase } from 'es-toolkit/string'
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
import appAnnotation from '../i18n/en-US/app-annotation.json'
diff --git a/web/package.json b/web/package.json
index 300b9b450a..1d6812b820 100644
--- a/web/package.json
+++ b/web/package.json
@@ -206,7 +206,6 @@
"jsdom-testing-mocks": "^1.16.0",
"knip": "^5.66.1",
"lint-staged": "^15.5.2",
- "lodash": "^4.17.21",
"nock": "^14.0.10",
"postcss": "^8.5.6",
"react-scan": "^0.4.3",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 3c4f881fdf..d5cb16cd4c 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -529,9 +529,6 @@ importers:
lint-staged:
specifier: ^15.5.2
version: 15.5.2
- lodash:
- specifier: ^4.17.21
- version: 4.17.21
nock:
specifier: ^14.0.10
version: 14.0.10
diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts
index 32ea4f35fd..5c10bac5d2 100644
--- a/web/service/use-plugins.ts
+++ b/web/service/use-plugins.ts
@@ -33,7 +33,7 @@ import {
useQuery,
useQueryClient,
} from '@tanstack/react-query'
-import { cloneDeep } from 'es-toolkit/compat'
+import { cloneDeep } from 'es-toolkit/object'
import { useCallback, useEffect, useState } from 'react'
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
diff --git a/web/utils/index.ts b/web/utils/index.ts
index 5704e82d87..fe6463c18e 100644
--- a/web/utils/index.ts
+++ b/web/utils/index.ts
@@ -1,4 +1,4 @@
-import { escape } from 'es-toolkit/compat'
+import { escape } from 'es-toolkit/string'
export const sleep = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms))
From 0421387672d4fded149d1219d08540903d535710 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 31 Dec 2025 11:59:39 +0800
Subject: [PATCH 10/14] chore(deps): bump qs from 6.14.0 to 6.14.1 in /web
(#30409)
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
web/package.json | 2 +-
web/pnpm-lock.yaml | 31 ++++++++++++++++++++-----------
2 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/web/package.json b/web/package.json
index 1d6812b820..a78575304c 100644
--- a/web/package.json
+++ b/web/package.json
@@ -112,7 +112,7 @@
"nuqs": "^2.8.6",
"pinyin-pro": "^3.27.0",
"qrcode.react": "^4.2.0",
- "qs": "^6.14.0",
+ "qs": "^6.14.1",
"react": "19.2.3",
"react-18-input-autosize": "^3.0.0",
"react-dom": "19.2.3",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index d5cb16cd4c..fe9032d248 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -253,8 +253,8 @@ importers:
specifier: ^4.2.0
version: 4.2.0(react@19.2.3)
qs:
- specifier: ^6.14.0
- version: 6.14.0
+ specifier: ^6.14.1
+ version: 6.14.1
react:
specifier: 19.2.3
version: 19.2.3
@@ -3698,6 +3698,9 @@ packages:
'@types/node@20.19.26':
resolution: {integrity: sha512-0l6cjgF0XnihUpndDhk+nyD3exio3iKaYROSgvh/qSevPXax3L8p5DBRFjbvalnwatGgHEQn2R88y2fA3g4irg==}
+ '@types/node@20.19.27':
+ resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==}
+
'@types/papaparse@5.5.1':
resolution: {integrity: sha512-esEO+VISsLIyE+JZBmb89NzsYYbpwV8lmv2rPo6oX5y9KhBaIP7hhHgjuTut54qjdKVMufTEcrh5fUl9+58huw==}
@@ -6372,8 +6375,8 @@ packages:
lexical@0.38.2:
resolution: {integrity: sha512-JJmfsG3c4gwBHzUGffbV7ifMNkKAWMCnYE3xJl87gty7hjyV5f3xq7eqTjP5HFYvO4XpjJvvWO2/djHp5S10tw==}
- lib0@0.2.115:
- resolution: {integrity: sha512-noaW4yNp6hCjOgDnWWxW0vGXE3kZQI5Kqiwz+jIWXavI9J9WyfJ9zjsbQlQlgjIbHBrvlA/x3TSIXBUJj+0L6g==}
+ lib0@0.2.117:
+ resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==}
engines: {node: '>=16'}
hasBin: true
@@ -7348,8 +7351,8 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- qs@6.14.0:
- resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
+ qs@6.14.1:
+ resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
engines: {node: '>=0.6'}
quansync@0.2.11:
@@ -8776,6 +8779,7 @@ packages:
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
@@ -12408,6 +12412,11 @@ snapshots:
dependencies:
undici-types: 6.21.0
+ '@types/node@20.19.27':
+ dependencies:
+ undici-types: 6.21.0
+ optional: true
+
'@types/papaparse@5.5.1':
dependencies:
'@types/node': 18.15.0
@@ -14884,7 +14893,7 @@ snapshots:
happy-dom@20.0.11:
dependencies:
- '@types/node': 20.19.26
+ '@types/node': 20.19.27
'@types/whatwg-mimetype': 3.0.2
whatwg-mimetype: 3.0.0
optional: true
@@ -15508,7 +15517,7 @@ snapshots:
lexical@0.38.2: {}
- lib0@0.2.115:
+ lib0@0.2.117:
dependencies:
isomorphic.js: 0.2.5
@@ -16814,7 +16823,7 @@ snapshots:
dependencies:
react: 19.2.3
- qs@6.14.0:
+ qs@6.14.1:
dependencies:
side-channel: '@nolyfill/side-channel@1.0.44'
@@ -18106,7 +18115,7 @@ snapshots:
url@0.11.4:
dependencies:
punycode: 1.4.1
- qs: 6.14.0
+ qs: 6.14.1
use-callback-ref@1.3.3(@types/react@19.2.7)(react@19.2.3):
dependencies:
@@ -18585,7 +18594,7 @@ snapshots:
yjs@13.6.27:
dependencies:
- lib0: 0.2.115
+ lib0: 0.2.117
yocto-queue@0.1.0: {}
From 1b8e80a722d731e1142f0e33c42e35a70584637f Mon Sep 17 00:00:00 2001
From: DevByteAI <161969603+devbyteai@users.noreply.github.com>
Date: Wed, 31 Dec 2025 07:28:25 +0200
Subject: [PATCH 11/14] fix: Ensure chat history refreshes when switching back
to conversations (#30389)
---
web/service/use-share.spec.tsx | 45 ++++++++++++++++++++++++++++++++++
web/service/use-share.ts | 4 +++
2 files changed, 49 insertions(+)
diff --git a/web/service/use-share.spec.tsx b/web/service/use-share.spec.tsx
index d0202ed140..db20329767 100644
--- a/web/service/use-share.spec.tsx
+++ b/web/service/use-share.spec.tsx
@@ -160,6 +160,51 @@ describe('useShareChatList', () => {
})
expect(mockFetchChatList).not.toHaveBeenCalled()
})
+
+ it('should always consider data stale to ensure fresh data on conversation switch (GitHub #30378)', async () => {
+ // This test verifies that chat list data is always considered stale (staleTime: 0)
+ // which ensures fresh data is fetched when switching back to a conversation.
+ // Without this, users would see outdated messages until double-switching.
+ const queryClient = createQueryClient()
+ const wrapper = createWrapper(queryClient)
+ const params = {
+ conversationId: 'conversation-1',
+ isInstalledApp: false,
+ appId: undefined,
+ }
+ const initialResponse = { data: [{ id: '1', content: 'initial' }] }
+ const updatedResponse = { data: [{ id: '1', content: 'initial' }, { id: '2', content: 'new message' }] }
+
+ // First fetch
+ mockFetchChatList.mockResolvedValueOnce(initialResponse)
+ const { result, unmount } = renderHook(() => useShareChatList(params), { wrapper })
+
+ await waitFor(() => {
+ expect(result.current.data).toEqual(initialResponse)
+ })
+ expect(mockFetchChatList).toHaveBeenCalledTimes(1)
+
+ // Unmount (simulates switching away from conversation)
+ unmount()
+
+ // Remount with same params (simulates switching back)
+ // With staleTime: 0, this should trigger a background refetch
+ mockFetchChatList.mockResolvedValueOnce(updatedResponse)
+ const { result: result2 } = renderHook(() => useShareChatList(params), { wrapper })
+
+ // Should immediately return cached data
+ expect(result2.current.data).toEqual(initialResponse)
+
+ // Should trigger background refetch due to staleTime: 0
+ await waitFor(() => {
+ expect(mockFetchChatList).toHaveBeenCalledTimes(2)
+ })
+
+ // Should update with fresh data
+ await waitFor(() => {
+ expect(result2.current.data).toEqual(updatedResponse)
+ })
+ })
})
// Scenario: conversation name queries follow enabled flags and installation constraints.
diff --git a/web/service/use-share.ts b/web/service/use-share.ts
index 4dd43e06aa..eef61ccc29 100644
--- a/web/service/use-share.ts
+++ b/web/service/use-share.ts
@@ -122,6 +122,10 @@ export const useShareChatList = (params: ShareChatListParams, options: ShareQuer
enabled: isEnabled,
refetchOnReconnect,
refetchOnWindowFocus,
+ // Always consider chat list data stale to ensure fresh data when switching
+ // back to a conversation. This fixes issue where recent messages don't appear
+ // until switching away and back again (GitHub issue #30378).
+ staleTime: 0,
})
}
From 8129b04143ef283afc2d4f54617ec5c418813319 Mon Sep 17 00:00:00 2001
From: QuantumGhost
Date: Wed, 31 Dec 2025 13:38:16 +0800
Subject: [PATCH 12/14] fix(web): enable JSON_OBJECT type support in console UI
(#30412)
Co-authored-by: zhsama
---
.../config-var/config-modal/config.ts | 27 ++++----
.../config-var/config-modal/index.tsx | 63 +++++++++++++++----
web/i18n/ar-TN/app-debug.json | 2 +
web/i18n/de-DE/app-debug.json | 2 +
web/i18n/en-US/app-debug.json | 2 +
web/i18n/es-ES/app-debug.json | 2 +
web/i18n/fa-IR/app-debug.json | 2 +
web/i18n/fr-FR/app-debug.json | 2 +
web/i18n/hi-IN/app-debug.json | 2 +
web/i18n/id-ID/app-debug.json | 2 +
web/i18n/it-IT/app-debug.json | 2 +
web/i18n/ja-JP/app-debug.json | 2 +
web/i18n/ko-KR/app-debug.json | 2 +
web/i18n/pl-PL/app-debug.json | 2 +
web/i18n/pt-BR/app-debug.json | 2 +
web/i18n/ro-RO/app-debug.json | 2 +
web/i18n/ru-RU/app-debug.json | 2 +
web/i18n/sl-SI/app-debug.json | 2 +
web/i18n/th-TH/app-debug.json | 2 +
web/i18n/tr-TR/app-debug.json | 2 +
web/i18n/uk-UA/app-debug.json | 2 +
web/i18n/vi-VN/app-debug.json | 2 +
web/i18n/zh-Hans/app-debug.json | 2 +
web/i18n/zh-Hant/app-debug.json | 2 +
24 files changed, 110 insertions(+), 24 deletions(-)
diff --git a/web/app/components/app/configuration/config-var/config-modal/config.ts b/web/app/components/app/configuration/config-var/config-modal/config.ts
index 94a3cfae58..6586c2fd54 100644
--- a/web/app/components/app/configuration/config-var/config-modal/config.ts
+++ b/web/app/components/app/configuration/config-var/config-modal/config.ts
@@ -7,19 +7,24 @@ export const jsonObjectWrap = {
export const jsonConfigPlaceHolder = JSON.stringify(
{
- foo: {
- type: 'string',
- },
- bar: {
- type: 'object',
- properties: {
- sub: {
- type: 'number',
- },
+ type: 'object',
+ properties: {
+ foo: {
+ type: 'string',
+ },
+ bar: {
+ type: 'object',
+ properties: {
+ sub: {
+ type: 'number',
+ },
+ },
+ required: [],
+ additionalProperties: true,
},
- required: [],
- additionalProperties: true,
},
+ required: [],
+ additionalProperties: true,
},
null,
2,
diff --git a/web/app/components/app/configuration/config-var/config-modal/index.tsx b/web/app/components/app/configuration/config-var/config-modal/index.tsx
index df07231465..782744882e 100644
--- a/web/app/components/app/configuration/config-var/config-modal/index.tsx
+++ b/web/app/components/app/configuration/config-var/config-modal/index.tsx
@@ -28,7 +28,7 @@ import { checkKeys, getNewVarInWorkflow, replaceSpaceWithUnderscoreInVarNameInpu
import ConfigSelect from '../config-select'
import ConfigString from '../config-string'
import ModalFoot from '../modal-foot'
-import { jsonConfigPlaceHolder, jsonObjectWrap } from './config'
+import { jsonConfigPlaceHolder } from './config'
import Field from './field'
import TypeSelector from './type-select'
@@ -78,13 +78,12 @@ const ConfigModal: FC = ({
const modalRef = useRef(null)
const appDetail = useAppStore(state => state.appDetail)
const isBasicApp = appDetail?.mode !== AppModeEnum.ADVANCED_CHAT && appDetail?.mode !== AppModeEnum.WORKFLOW
- const isSupportJSON = false
const jsonSchemaStr = useMemo(() => {
const isJsonObject = type === InputVarType.jsonObject
if (!isJsonObject || !tempPayload.json_schema)
return ''
try {
- return JSON.stringify(JSON.parse(tempPayload.json_schema).properties, null, 2)
+ return JSON.stringify(JSON.parse(tempPayload.json_schema), null, 2)
}
catch {
return ''
@@ -129,13 +128,14 @@ const ConfigModal: FC = ({
}, [])
const handleJSONSchemaChange = useCallback((value: string) => {
+ const isEmpty = value == null || value.trim() === ''
+ if (isEmpty) {
+ handlePayloadChange('json_schema')(undefined)
+ return null
+ }
try {
const v = JSON.parse(value)
- const res = {
- ...jsonObjectWrap,
- properties: v,
- }
- handlePayloadChange('json_schema')(JSON.stringify(res, null, 2))
+ handlePayloadChange('json_schema')(JSON.stringify(v, null, 2))
}
catch {
return null
@@ -175,7 +175,7 @@ const ConfigModal: FC = ({
},
]
: []),
- ...((!isBasicApp && isSupportJSON)
+ ...((!isBasicApp)
? [{
name: t('variableConfig.json', { ns: 'appDebug' }),
value: InputVarType.jsonObject,
@@ -233,7 +233,28 @@ const ConfigModal: FC = ({
const checkboxDefaultSelectValue = useMemo(() => getCheckboxDefaultSelectValue(tempPayload.default), [tempPayload.default])
+ const isJsonSchemaEmpty = (value: InputVar['json_schema']) => {
+ if (value === null || value === undefined) {
+ return true
+ }
+ if (typeof value !== 'string') {
+ return false
+ }
+ const trimmed = value.trim()
+ return trimmed === ''
+ }
+
const handleConfirm = () => {
+ const jsonSchemaValue = tempPayload.json_schema
+ const isSchemaEmpty = isJsonSchemaEmpty(jsonSchemaValue)
+ const normalizedJsonSchema = isSchemaEmpty ? undefined : jsonSchemaValue
+
+ // if the input type is jsonObject and the schema is empty as determined by `isJsonSchemaEmpty`,
+ // remove the `json_schema` field from the payload by setting its value to `undefined`.
+ const payloadToSave = tempPayload.type === InputVarType.jsonObject && isSchemaEmpty
+ ? { ...tempPayload, json_schema: undefined }
+ : tempPayload
+
const moreInfo = tempPayload.variable === payload?.variable
? undefined
: {
@@ -250,7 +271,7 @@ const ConfigModal: FC = ({
return
}
if (isStringInput || type === InputVarType.number) {
- onConfirm(tempPayload, moreInfo)
+ onConfirm(payloadToSave, moreInfo)
}
else if (type === InputVarType.select) {
if (options?.length === 0) {
@@ -270,7 +291,7 @@ const ConfigModal: FC = ({
Toast.notify({ type: 'error', message: t('variableConfig.errorMsg.optionRepeat', { ns: 'appDebug' }) })
return
}
- onConfirm(tempPayload, moreInfo)
+ onConfirm(payloadToSave, moreInfo)
}
else if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) {
if (tempPayload.allowed_file_types?.length === 0) {
@@ -283,10 +304,26 @@ const ConfigModal: FC = ({
Toast.notify({ type: 'error', message: errorMessages })
return
}
- onConfirm(tempPayload, moreInfo)
+ onConfirm(payloadToSave, moreInfo)
+ }
+ else if (type === InputVarType.jsonObject) {
+ if (!isSchemaEmpty && typeof normalizedJsonSchema === 'string') {
+ try {
+ const schema = JSON.parse(normalizedJsonSchema)
+ if (schema?.type !== 'object') {
+ Toast.notify({ type: 'error', message: t('variableConfig.errorMsg.jsonSchemaMustBeObject', { ns: 'appDebug' }) })
+ return
+ }
+ }
+ catch {
+ Toast.notify({ type: 'error', message: t('variableConfig.errorMsg.jsonSchemaInvalid', { ns: 'appDebug' }) })
+ return
+ }
+ }
+ onConfirm(payloadToSave, moreInfo)
}
else {
- onConfirm(tempPayload, moreInfo)
+ onConfirm(payloadToSave, moreInfo)
}
}
diff --git a/web/i18n/ar-TN/app-debug.json b/web/i18n/ar-TN/app-debug.json
index 192f1b49cf..c35983397a 100644
--- a/web/i18n/ar-TN/app-debug.json
+++ b/web/i18n/ar-TN/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "اسم العرض",
"variableConfig.editModalTitle": "تعديل حقل إدخال",
"variableConfig.errorMsg.atLeastOneOption": "خيار واحد على الأقل مطلوب",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "مخطط JSON ليس JSON صالحًا",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "يجب أن يكون نوع مخطط JSON \"object\"",
"variableConfig.errorMsg.labelNameRequired": "اسم التسمية مطلوب",
"variableConfig.errorMsg.optionRepeat": "يوجد خيارات مكررة",
"variableConfig.errorMsg.varNameCanBeRepeat": "اسم المتغير لا يمكن تكراره",
diff --git a/web/i18n/de-DE/app-debug.json b/web/i18n/de-DE/app-debug.json
index 2ab907ef2d..d038320cf8 100644
--- a/web/i18n/de-DE/app-debug.json
+++ b/web/i18n/de-DE/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Anzeigename",
"variableConfig.editModalTitle": "Eingabefeld bearbeiten",
"variableConfig.errorMsg.atLeastOneOption": "Mindestens eine Option ist erforderlich",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON-Schema ist kein gültiges JSON",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON-Schema muss den Typ \"object\" haben",
"variableConfig.errorMsg.labelNameRequired": "Labelname ist erforderlich",
"variableConfig.errorMsg.optionRepeat": "Hat Wiederholungsoptionen",
"variableConfig.errorMsg.varNameCanBeRepeat": "Variablenname kann nicht wiederholt werden",
diff --git a/web/i18n/en-US/app-debug.json b/web/i18n/en-US/app-debug.json
index 266f0e5483..cc53891912 100644
--- a/web/i18n/en-US/app-debug.json
+++ b/web/i18n/en-US/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Display Name",
"variableConfig.editModalTitle": "Edit Input Field",
"variableConfig.errorMsg.atLeastOneOption": "At least one option is required",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema is not valid JSON",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Schema must have type \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Label name is required",
"variableConfig.errorMsg.optionRepeat": "Has repeat options",
"variableConfig.errorMsg.varNameCanBeRepeat": "Variable name can not be repeated",
diff --git a/web/i18n/es-ES/app-debug.json b/web/i18n/es-ES/app-debug.json
index a1dfd8ad53..8245f2b325 100644
--- a/web/i18n/es-ES/app-debug.json
+++ b/web/i18n/es-ES/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Nombre para mostrar",
"variableConfig.editModalTitle": "Editar Campo de Entrada",
"variableConfig.errorMsg.atLeastOneOption": "Se requiere al menos una opción",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "El esquema JSON no es un JSON válido",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "El esquema JSON debe tener el tipo \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Nombre de la etiqueta es requerido",
"variableConfig.errorMsg.optionRepeat": "Hay opciones repetidas",
"variableConfig.errorMsg.varNameCanBeRepeat": "El nombre de la variable no puede repetirse",
diff --git a/web/i18n/fa-IR/app-debug.json b/web/i18n/fa-IR/app-debug.json
index c6e1b588e5..5427ebb72a 100644
--- a/web/i18n/fa-IR/app-debug.json
+++ b/web/i18n/fa-IR/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "نام نمایشی",
"variableConfig.editModalTitle": "ویرایش فیلد ورودی",
"variableConfig.errorMsg.atLeastOneOption": "حداقل یک گزینه مورد نیاز است",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema یک JSON معتبر نیست",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "نوع JSON Schema باید \"object\" باشد",
"variableConfig.errorMsg.labelNameRequired": "نام برچسب الزامی است",
"variableConfig.errorMsg.optionRepeat": "دارای گزینه های تکرار",
"variableConfig.errorMsg.varNameCanBeRepeat": "نام متغیر را نمی توان تکرار کرد",
diff --git a/web/i18n/fr-FR/app-debug.json b/web/i18n/fr-FR/app-debug.json
index 2add116cd3..88f75f5136 100644
--- a/web/i18n/fr-FR/app-debug.json
+++ b/web/i18n/fr-FR/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Nom d’affichage",
"variableConfig.editModalTitle": "Edit Input Field",
"variableConfig.errorMsg.atLeastOneOption": "At least one option is required",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "Le schéma JSON n’est pas un JSON valide",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "Le schéma JSON doit avoir le type \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Label name is required",
"variableConfig.errorMsg.optionRepeat": "Has repeat options",
"variableConfig.errorMsg.varNameCanBeRepeat": "Variable name can not be repeated",
diff --git a/web/i18n/hi-IN/app-debug.json b/web/i18n/hi-IN/app-debug.json
index df891f60ec..97e733e167 100644
--- a/web/i18n/hi-IN/app-debug.json
+++ b/web/i18n/hi-IN/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "प्रदर्शन नाम",
"variableConfig.editModalTitle": "इनपुट फ़ील्ड संपादित करें",
"variableConfig.errorMsg.atLeastOneOption": "कम से कम एक विकल्प आवश्यक है",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON स्कीमा मान्य JSON नहीं है",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON स्कीमा का प्रकार \"object\" होना चाहिए",
"variableConfig.errorMsg.labelNameRequired": "लेबल नाम आवश्यक है",
"variableConfig.errorMsg.optionRepeat": "विकल्प दोहराए गए हैं",
"variableConfig.errorMsg.varNameCanBeRepeat": "वेरिएबल नाम दोहराया नहीं जा सकता",
diff --git a/web/i18n/id-ID/app-debug.json b/web/i18n/id-ID/app-debug.json
index dfce0159a5..21651349ba 100644
--- a/web/i18n/id-ID/app-debug.json
+++ b/web/i18n/id-ID/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Nama Tampilan",
"variableConfig.editModalTitle": "Edit Bidang Input",
"variableConfig.errorMsg.atLeastOneOption": "Setidaknya satu opsi diperlukan",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema bukan JSON yang valid",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Schema harus bertipe \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Nama label diperlukan",
"variableConfig.errorMsg.optionRepeat": "Memiliki opsi pengulangan",
"variableConfig.errorMsg.varNameCanBeRepeat": "Nama variabel tidak dapat diulang",
diff --git a/web/i18n/it-IT/app-debug.json b/web/i18n/it-IT/app-debug.json
index fce5620eef..94e4c00894 100644
--- a/web/i18n/it-IT/app-debug.json
+++ b/web/i18n/it-IT/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Nome visualizzato",
"variableConfig.editModalTitle": "Modifica Campo Input",
"variableConfig.errorMsg.atLeastOneOption": "È richiesta almeno un'opzione",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "Lo schema JSON non è un JSON valido",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "Lo schema JSON deve avere tipo \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Il nome dell'etichetta è richiesto",
"variableConfig.errorMsg.optionRepeat": "Ci sono opzioni ripetute",
"variableConfig.errorMsg.varNameCanBeRepeat": "Il nome della variabile non può essere ripetuto",
diff --git a/web/i18n/ja-JP/app-debug.json b/web/i18n/ja-JP/app-debug.json
index cf44ccff94..4b18745e5f 100644
--- a/web/i18n/ja-JP/app-debug.json
+++ b/web/i18n/ja-JP/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "表示名",
"variableConfig.editModalTitle": "入力フィールドを編集",
"variableConfig.errorMsg.atLeastOneOption": "少なくとも 1 つのオプションが必要です",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSONスキーマが有効なJSONではありません",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSONスキーマのtypeは\"object\"である必要があります",
"variableConfig.errorMsg.labelNameRequired": "ラベル名は必須です",
"variableConfig.errorMsg.optionRepeat": "繰り返しオプションがあります",
"variableConfig.errorMsg.varNameCanBeRepeat": "変数名は繰り返すことができません",
diff --git a/web/i18n/ko-KR/app-debug.json b/web/i18n/ko-KR/app-debug.json
index 600062464d..3cb295a3f7 100644
--- a/web/i18n/ko-KR/app-debug.json
+++ b/web/i18n/ko-KR/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "표시 이름",
"variableConfig.editModalTitle": "입력 필드 편집",
"variableConfig.errorMsg.atLeastOneOption": "적어도 하나의 옵션이 필요합니다",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON 스키마가 올바른 JSON이 아닙니다",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON 스키마의 type은 \"object\"이어야 합니다",
"variableConfig.errorMsg.labelNameRequired": "레이블명은 필수입니다",
"variableConfig.errorMsg.optionRepeat": "옵션이 중복되어 있습니다",
"variableConfig.errorMsg.varNameCanBeRepeat": "변수명은 중복될 수 없습니다",
diff --git a/web/i18n/pl-PL/app-debug.json b/web/i18n/pl-PL/app-debug.json
index ece00eb9ad..0a9da0fcce 100644
--- a/web/i18n/pl-PL/app-debug.json
+++ b/web/i18n/pl-PL/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Nazwa wyświetlana",
"variableConfig.editModalTitle": "Edytuj Pole Wejściowe",
"variableConfig.errorMsg.atLeastOneOption": "Wymagana jest co najmniej jedna opcja",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "Schemat JSON nie jest prawidłowym JSON-em",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "Schemat JSON musi mieć typ \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Wymagana nazwa etykiety",
"variableConfig.errorMsg.optionRepeat": "Powtarzają się opcje",
"variableConfig.errorMsg.varNameCanBeRepeat": "Nazwa zmiennej nie może się powtarzać",
diff --git a/web/i18n/pt-BR/app-debug.json b/web/i18n/pt-BR/app-debug.json
index 98ad27f69a..6059787894 100644
--- a/web/i18n/pt-BR/app-debug.json
+++ b/web/i18n/pt-BR/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Nome de exibição",
"variableConfig.editModalTitle": "Editar Campo de Entrada",
"variableConfig.errorMsg.atLeastOneOption": "Pelo menos uma opção é obrigatória",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "O JSON Schema não é um JSON válido",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "O JSON Schema deve ter o tipo \"object\"",
"variableConfig.errorMsg.labelNameRequired": "O nome do rótulo é obrigatório",
"variableConfig.errorMsg.optionRepeat": "Tem opções repetidas",
"variableConfig.errorMsg.varNameCanBeRepeat": "O nome da variável não pode ser repetido",
diff --git a/web/i18n/ro-RO/app-debug.json b/web/i18n/ro-RO/app-debug.json
index 83990992cc..6245ca680d 100644
--- a/web/i18n/ro-RO/app-debug.json
+++ b/web/i18n/ro-RO/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Nume afișat",
"variableConfig.editModalTitle": "Editați câmpul de intrare",
"variableConfig.errorMsg.atLeastOneOption": "Este necesară cel puțin o opțiune",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "Schema JSON nu este un JSON valid",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "Schema JSON trebuie să aibă tipul \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Numele etichetei este obligatoriu",
"variableConfig.errorMsg.optionRepeat": "Există opțiuni repetate",
"variableConfig.errorMsg.varNameCanBeRepeat": "Numele variabilei nu poate fi repetat",
diff --git a/web/i18n/ru-RU/app-debug.json b/web/i18n/ru-RU/app-debug.json
index c3075a5b01..fdc797da2f 100644
--- a/web/i18n/ru-RU/app-debug.json
+++ b/web/i18n/ru-RU/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Отображаемое имя",
"variableConfig.editModalTitle": "Редактировать поле ввода",
"variableConfig.errorMsg.atLeastOneOption": "Требуется хотя бы один вариант",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema не является корректным JSON",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Schema должна иметь тип \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Имя метки обязательно",
"variableConfig.errorMsg.optionRepeat": "Есть повторяющиеся варианты",
"variableConfig.errorMsg.varNameCanBeRepeat": "Имя переменной не может повторяться",
diff --git a/web/i18n/sl-SI/app-debug.json b/web/i18n/sl-SI/app-debug.json
index 28094186bf..948e3dbc67 100644
--- a/web/i18n/sl-SI/app-debug.json
+++ b/web/i18n/sl-SI/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Prikazno ime",
"variableConfig.editModalTitle": "Uredi vnosno polje",
"variableConfig.errorMsg.atLeastOneOption": "Potrebna je vsaj ena možnost",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "Shema JSON ni veljaven JSON",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "Shema JSON mora imeti tip \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Ime nalepke je obvezno",
"variableConfig.errorMsg.optionRepeat": "Ima možnosti ponavljanja",
"variableConfig.errorMsg.varNameCanBeRepeat": "Imena spremenljivke ni mogoče ponoviti",
diff --git a/web/i18n/th-TH/app-debug.json b/web/i18n/th-TH/app-debug.json
index c0dd099f08..53dc629a7f 100644
--- a/web/i18n/th-TH/app-debug.json
+++ b/web/i18n/th-TH/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "ชื่อที่แสดง",
"variableConfig.editModalTitle": "แก้ไขฟิลด์อินพุต",
"variableConfig.errorMsg.atLeastOneOption": "จําเป็นต้องมีอย่างน้อยหนึ่งตัวเลือก",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema ไม่ใช่ JSON ที่ถูกต้อง",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Schema ต้องมีชนิดเป็น \"object\"",
"variableConfig.errorMsg.labelNameRequired": "ต้องมีชื่อฉลาก",
"variableConfig.errorMsg.optionRepeat": "มีตัวเลือกการทําซ้ํา",
"variableConfig.errorMsg.varNameCanBeRepeat": "ไม่สามารถทําซ้ําชื่อตัวแปรได้",
diff --git a/web/i18n/tr-TR/app-debug.json b/web/i18n/tr-TR/app-debug.json
index f04a8fd64e..1ae01dca6c 100644
--- a/web/i18n/tr-TR/app-debug.json
+++ b/web/i18n/tr-TR/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Görünen Ad",
"variableConfig.editModalTitle": "Giriş Alanı Düzenle",
"variableConfig.errorMsg.atLeastOneOption": "En az bir seçenek gereklidir",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Şeması geçerli bir JSON değil",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Şeması’nın türü \"object\" olmalı",
"variableConfig.errorMsg.labelNameRequired": "Etiket adı gereklidir",
"variableConfig.errorMsg.optionRepeat": "Yinelenen seçenekler var",
"variableConfig.errorMsg.varNameCanBeRepeat": "Değişken adı tekrar edemez",
diff --git a/web/i18n/uk-UA/app-debug.json b/web/i18n/uk-UA/app-debug.json
index 6d7ecd92a7..74dcc72efd 100644
--- a/web/i18n/uk-UA/app-debug.json
+++ b/web/i18n/uk-UA/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Відображуване ім'я",
"variableConfig.editModalTitle": "Редагувати Поле Введення",
"variableConfig.errorMsg.atLeastOneOption": "Потрібно щонайменше одну опцію",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema не є коректним JSON",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Schema має мати тип \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Потрібно вказати назву мітки",
"variableConfig.errorMsg.optionRepeat": "Є повторні опції",
"variableConfig.errorMsg.varNameCanBeRepeat": "Назва змінної не може повторюватися",
diff --git a/web/i18n/vi-VN/app-debug.json b/web/i18n/vi-VN/app-debug.json
index e94e442626..f13df6c43d 100644
--- a/web/i18n/vi-VN/app-debug.json
+++ b/web/i18n/vi-VN/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "Tên hiển thị",
"variableConfig.editModalTitle": "Chỉnh sửa trường nhập",
"variableConfig.errorMsg.atLeastOneOption": "Cần ít nhất một tùy chọn",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema không phải là JSON hợp lệ",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Schema phải có kiểu \"object\"",
"variableConfig.errorMsg.labelNameRequired": "Tên nhãn là bắt buộc",
"variableConfig.errorMsg.optionRepeat": "Có các tùy chọn trùng lặp",
"variableConfig.errorMsg.varNameCanBeRepeat": "Tên biến không được trùng lặp",
diff --git a/web/i18n/zh-Hans/app-debug.json b/web/i18n/zh-Hans/app-debug.json
index 7965400dd7..7559bf2b56 100644
--- a/web/i18n/zh-Hans/app-debug.json
+++ b/web/i18n/zh-Hans/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "显示名称",
"variableConfig.editModalTitle": "编辑变量",
"variableConfig.errorMsg.atLeastOneOption": "至少需要一个选项",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema 不是合法的 JSON",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Schema 的 type 必须为 \"object\"",
"variableConfig.errorMsg.labelNameRequired": "显示名称必填",
"variableConfig.errorMsg.optionRepeat": "选项不能重复",
"variableConfig.errorMsg.varNameCanBeRepeat": "变量名称不能重复",
diff --git a/web/i18n/zh-Hant/app-debug.json b/web/i18n/zh-Hant/app-debug.json
index eabcebd8e0..ab7286691f 100644
--- a/web/i18n/zh-Hant/app-debug.json
+++ b/web/i18n/zh-Hant/app-debug.json
@@ -306,6 +306,8 @@
"variableConfig.displayName": "顯示名稱",
"variableConfig.editModalTitle": "編輯變數",
"variableConfig.errorMsg.atLeastOneOption": "至少需要一個選項",
+ "variableConfig.errorMsg.jsonSchemaInvalid": "JSON Schema 不是合法的 JSON",
+ "variableConfig.errorMsg.jsonSchemaMustBeObject": "JSON Schema 的 type 必須為「object」",
"variableConfig.errorMsg.labelNameRequired": "顯示名稱必填",
"variableConfig.errorMsg.optionRepeat": "選項不能重複",
"variableConfig.errorMsg.varNameCanBeRepeat": "變數名稱不能重複",
From f28a08a69642e43dc556d3c33b3ba75dc9eb528a Mon Sep 17 00:00:00 2001
From: yyh <92089059+lyzno1@users.noreply.github.com>
Date: Wed, 31 Dec 2025 13:51:05 +0800
Subject: [PATCH 13/14] fix: correct useEducationStatus query cache
configuration (#30416)
---
web/context/provider-context.tsx | 8 ++++----
web/service/use-education.ts | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx
index 7eca72e322..d350d08b4a 100644
--- a/web/context/provider-context.tsx
+++ b/web/context/provider-context.tsx
@@ -134,7 +134,7 @@ export const ProviderContextProvider = ({
const [enableEducationPlan, setEnableEducationPlan] = useState(false)
const [isEducationWorkspace, setIsEducationWorkspace] = useState(false)
- const { data: educationAccountInfo, isLoading: isLoadingEducationAccountInfo, isFetching: isFetchingEducationAccountInfo } = useEducationStatus(!enableEducationPlan)
+ const { data: educationAccountInfo, isLoading: isLoadingEducationAccountInfo, isFetching: isFetchingEducationAccountInfo, isFetchedAfterMount: isEducationDataFetchedAfterMount } = useEducationStatus(!enableEducationPlan)
const [isAllowTransferWorkspace, setIsAllowTransferWorkspace] = useState(false)
const [isAllowPublishAsCustomKnowledgePipelineTemplate, setIsAllowPublishAsCustomKnowledgePipelineTemplate] = useState(false)
@@ -240,9 +240,9 @@ export const ProviderContextProvider = ({
datasetOperatorEnabled,
enableEducationPlan,
isEducationWorkspace,
- isEducationAccount: educationAccountInfo?.is_student || false,
- allowRefreshEducationVerify: educationAccountInfo?.allow_refresh || false,
- educationAccountExpireAt: educationAccountInfo?.expire_at || null,
+ isEducationAccount: isEducationDataFetchedAfterMount ? (educationAccountInfo?.is_student ?? false) : false,
+ allowRefreshEducationVerify: isEducationDataFetchedAfterMount ? (educationAccountInfo?.allow_refresh ?? false) : false,
+ educationAccountExpireAt: isEducationDataFetchedAfterMount ? (educationAccountInfo?.expire_at ?? null) : null,
isLoadingEducationAccountInfo,
isFetchingEducationAccountInfo,
webappCopyrightEnabled,
diff --git a/web/service/use-education.ts b/web/service/use-education.ts
index 34b08e59c6..a75ec6f3c7 100644
--- a/web/service/use-education.ts
+++ b/web/service/use-education.ts
@@ -59,7 +59,7 @@ export const useEducationStatus = (disable?: boolean) => {
return get<{ is_student: boolean, allow_refresh: boolean, expire_at: number | null }>('/account/education')
},
retry: false,
- gcTime: 0, // No cache. Prevent switch account caused stale data
+ staleTime: 0, // Data expires immediately, ensuring fresh data on refetch
})
}
From fa69cce1e79f9703c6c16b3b318295d6cbe6efe8 Mon Sep 17 00:00:00 2001
From: wangxiaolei
Date: Wed, 31 Dec 2025 14:57:39 +0800
Subject: [PATCH 14/14] fix: fix create app xss issue (#30305)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
api/controllers/console/app/app.py | 58 ++++
.../console/app/test_xss_prevention.py | 254 ++++++++++++++++++
2 files changed, 312 insertions(+)
create mode 100644 api/tests/unit_tests/controllers/console/app/test_xss_prevention.py
diff --git a/api/controllers/console/app/app.py b/api/controllers/console/app/app.py
index 62e997dae2..44cf89d6a9 100644
--- a/api/controllers/console/app/app.py
+++ b/api/controllers/console/app/app.py
@@ -1,3 +1,4 @@
+import re
import uuid
from typing import Literal
@@ -73,6 +74,48 @@ class AppListQuery(BaseModel):
raise ValueError("Invalid UUID format in tag_ids.") from exc
+# XSS prevention: patterns that could lead to XSS attacks
+# Includes: script tags, iframe tags, javascript: protocol, SVG with onload, etc.
+_XSS_PATTERNS = [
+ r"", # Script tags
+ r")", # Iframe tags (including self-closing)
+ r"javascript:", # JavaScript protocol
+ r"