From de611ab344e9fc6eb28699d7011906cfbef0adf9 Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Thu, 24 Jul 2025 14:16:39 +0800 Subject: [PATCH 01/72] Feat: add notification for change email completed (#22812) Co-authored-by: Yansong Zhang <916125788@qq.com> --- api/controllers/console/workspace/account.py | 4 + api/libs/email_i18n.py | 13 ++ api/services/account_service.py | 21 ++- api/tasks/mail_change_mail_task.py | 40 +++++- .../change_mail_completed_template_en-US.html | 135 ++++++++++++++++++ .../change_mail_completed_template_zh-CN.html | 135 ++++++++++++++++++ .../change_mail_completed_template_en-US.html | 132 +++++++++++++++++ .../change_mail_completed_template_zh-CN.html | 132 +++++++++++++++++ 8 files changed, 610 insertions(+), 2 deletions(-) create mode 100644 api/templates/change_mail_completed_template_en-US.html create mode 100644 api/templates/change_mail_completed_template_zh-CN.html create mode 100644 api/templates/without-brand/change_mail_completed_template_en-US.html create mode 100644 api/templates/without-brand/change_mail_completed_template_zh-CN.html diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index 5cd2e0cd2d..657016e0a8 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -494,6 +494,10 @@ class ChangeEmailResetApi(Resource): updated_account = AccountService.update_account(current_user, email=args["new_email"]) + AccountService.send_change_email_completed_notify_email( + email=args["new_email"], + ) + return updated_account diff --git a/api/libs/email_i18n.py b/api/libs/email_i18n.py index bfbf41a073..b7c9f3ec6c 100644 --- a/api/libs/email_i18n.py +++ b/api/libs/email_i18n.py @@ -25,6 +25,7 @@ class EmailType(Enum): EMAIL_CODE_LOGIN = "email_code_login" CHANGE_EMAIL_OLD = "change_email_old" CHANGE_EMAIL_NEW = "change_email_new" + CHANGE_EMAIL_COMPLETED = "change_email_completed" OWNER_TRANSFER_CONFIRM = "owner_transfer_confirm" OWNER_TRANSFER_OLD_NOTIFY = "owner_transfer_old_notify" OWNER_TRANSFER_NEW_NOTIFY = "owner_transfer_new_notify" @@ -344,6 +345,18 @@ def create_default_email_config() -> EmailI18nConfig: branded_template_path="without-brand/change_mail_confirm_new_template_zh-CN.html", ), }, + EmailType.CHANGE_EMAIL_COMPLETED: { + EmailLanguage.EN_US: EmailTemplate( + subject="Your login email has been changed", + template_path="change_mail_completed_template_en-US.html", + branded_template_path="without-brand/change_mail_completed_template_en-US.html", + ), + EmailLanguage.ZH_HANS: EmailTemplate( + subject="您的登录邮箱已更改", + template_path="change_mail_completed_template_zh-CN.html", + branded_template_path="without-brand/change_mail_completed_template_zh-CN.html", + ), + }, EmailType.OWNER_TRANSFER_CONFIRM: { EmailLanguage.EN_US: EmailTemplate( subject="Verify Your Request to Transfer Workspace Ownership", diff --git a/api/services/account_service.py b/api/services/account_service.py index 59bffa873c..eb57b675c4 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -54,7 +54,10 @@ from services.errors.workspace import WorkSpaceNotAllowedCreateError, Workspaces from services.feature_service import FeatureService from tasks.delete_account_task import delete_account_task from tasks.mail_account_deletion_task import send_account_deletion_verification_code -from tasks.mail_change_mail_task import send_change_mail_task +from tasks.mail_change_mail_task import ( + send_change_mail_completed_notification_task, + send_change_mail_task, +) from tasks.mail_email_code_login import send_email_code_login_mail_task from tasks.mail_invite_member_task import send_invite_member_mail_task from tasks.mail_owner_transfer_task import ( @@ -461,6 +464,22 @@ class AccountService: cls.change_email_rate_limiter.increment_rate_limit(account_email) return token + @classmethod + def send_change_email_completed_notify_email( + cls, + account: Optional[Account] = None, + email: Optional[str] = None, + language: Optional[str] = "en-US", + ): + account_email = account.email if account else email + if account_email is None: + raise ValueError("Email must be provided.") + + send_change_mail_completed_notification_task.delay( + language=language, + to=account_email, + ) + @classmethod def send_owner_transfer_email( cls, diff --git a/api/tasks/mail_change_mail_task.py b/api/tasks/mail_change_mail_task.py index ea1875901c..6334fb22de 100644 --- a/api/tasks/mail_change_mail_task.py +++ b/api/tasks/mail_change_mail_task.py @@ -5,7 +5,7 @@ import click from celery import shared_task # type: ignore from extensions.ext_mail import mail -from libs.email_i18n import get_email_i18n_service +from libs.email_i18n import EmailType, get_email_i18n_service @shared_task(queue="mail") @@ -40,3 +40,41 @@ def send_change_mail_task(language: str, to: str, code: str, phase: str) -> None ) except Exception: logging.exception("Send change email mail to {} failed".format(to)) + + +@shared_task(queue="mail") +def send_change_mail_completed_notification_task(language: str, to: str) -> None: + """ + Send change email completed notification with internationalization support. + + Args: + language: Language code for email localization + to: Recipient email address + """ + if not mail.is_inited(): + return + + logging.info(click.style("Start change email completed notify mail to {}".format(to), fg="green")) + start_at = time.perf_counter() + + try: + email_service = get_email_i18n_service() + email_service.send_email( + email_type=EmailType.CHANGE_EMAIL_COMPLETED, + language_code=language, + to=to, + template_context={ + "to": to, + "email": to, + }, + ) + + end_at = time.perf_counter() + logging.info( + click.style( + "Send change email completed mail to {} succeeded: latency: {}".format(to, end_at - start_at), + fg="green", + ) + ) + except Exception: + logging.exception("Send change email completed mail to {} failed".format(to)) diff --git a/api/templates/change_mail_completed_template_en-US.html b/api/templates/change_mail_completed_template_en-US.html new file mode 100644 index 0000000000..ecaf35868d --- /dev/null +++ b/api/templates/change_mail_completed_template_en-US.html @@ -0,0 +1,135 @@ + + + + + + + + +
+
+ + Dify Logo +
+

Your login email has been changed

+
+

You can now log into Dify with your new email address:

+
+
+ {{email}} +
+

If you did not make this change, email support@dify.ai.

+
+ + + + diff --git a/api/templates/change_mail_completed_template_zh-CN.html b/api/templates/change_mail_completed_template_zh-CN.html new file mode 100644 index 0000000000..b4fdb4b9ab --- /dev/null +++ b/api/templates/change_mail_completed_template_zh-CN.html @@ -0,0 +1,135 @@ + + + + + + + + +
+
+ + Dify Logo +
+

您的登录邮箱已更改

+
+

您现在可以使用新的电子邮件地址登录 Dify:

+
+
+ {{email}} +
+

如果您没有进行此更改,请发送电子邮件至 support@dify.ai

+
+ + + + diff --git a/api/templates/without-brand/change_mail_completed_template_en-US.html b/api/templates/without-brand/change_mail_completed_template_en-US.html new file mode 100644 index 0000000000..f211cc74d9 --- /dev/null +++ b/api/templates/without-brand/change_mail_completed_template_en-US.html @@ -0,0 +1,132 @@ + + + + + + + + +
+
+

Your login email has been changed

+
+

You can now log into {{application_title}} with your new email address:

+
+
+ {{email}} +
+

If you did not make this change, please ignore this email or contact support immediately.

+
+ + + + diff --git a/api/templates/without-brand/change_mail_completed_template_zh-CN.html b/api/templates/without-brand/change_mail_completed_template_zh-CN.html new file mode 100644 index 0000000000..c96604f0e5 --- /dev/null +++ b/api/templates/without-brand/change_mail_completed_template_zh-CN.html @@ -0,0 +1,132 @@ + + + + + + + + +
+
+

您的登录邮箱已更改

+
+

您现在可以使用新的电子邮件地址登录 {{application_title}}:

+
+
+ {{email}} +
+

如果您没有进行此更改,请忽略此电子邮件或立即联系支持。

+
+ + + + From a8f09ad43fbd6de52234fa8378ba7348024c72f8 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:40:37 +0800 Subject: [PATCH 02/72] =?UTF-8?q?refactor(i18next):=20streamline=20fallbac?= =?UTF-8?q?k=20translation=20handling=20and=20initi=E2=80=A6=20(#22894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/i18n/i18next-config.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/web/i18n/i18next-config.ts b/web/i18n/i18next-config.ts index 8c5bd375a7..7af727af7e 100644 --- a/web/i18n/i18next-config.ts +++ b/web/i18n/i18next-config.ts @@ -50,24 +50,35 @@ export const loadLangResources = async (lang: string) => { acc[camelCase(NAMESPACES[index])] = mod return acc }, {} as Record) + return resources +} + +const getFallbackTranslation = () => { + const resources = NAMESPACES.reduce((acc, ns, index) => { + acc[camelCase(NAMESPACES[index])] = require(`./en-US/${ns}`).default + return acc + }, {} as Record) return { translation: resources, } } -i18n.use(initReactI18next) - .init({ - lng: undefined, - fallbackLng: 'en-US', - }) +if (!i18n.isInitialized) { + i18n.use(initReactI18next) + .init({ + lng: undefined, + fallbackLng: 'en-US', + resources: { + 'en-US': getFallbackTranslation(), + }, + }) +} export const changeLanguage = async (lng?: string) => { const resolvedLng = lng ?? 'en-US' - const resources = { - [resolvedLng]: await loadLangResources(resolvedLng), - } + const resource = await loadLangResources(resolvedLng) if (!i18n.hasResourceBundle(resolvedLng, 'translation')) - i18n.addResourceBundle(resolvedLng, 'translation', resources[resolvedLng].translation, true, true) + i18n.addResourceBundle(resolvedLng, 'translation', resource, true, true) await i18n.changeLanguage(resolvedLng) } From aca8b836696167fc47b71c5507876897f8f6dd0b Mon Sep 17 00:00:00 2001 From: NFish Date: Thu, 24 Jul 2025 15:10:15 +0800 Subject: [PATCH 03/72] fix: support authorization using session and user_id in URL. (#22898) --- web/context/web-app-context.tsx | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/web/context/web-app-context.tsx b/web/context/web-app-context.tsx index 55f95e4811..db1c5158dd 100644 --- a/web/context/web-app-context.tsx +++ b/web/context/web-app-context.tsx @@ -2,6 +2,7 @@ import type { ChatConfig } from '@/app/components/base/chat/types' import Loading from '@/app/components/base/loading' +import { checkOrSetAccessToken } from '@/app/components/share/utils' import { AccessMode } from '@/models/access-control' import type { AppData, AppMeta } from '@/models/share' import { useGetWebAppAccessModeByCode } from '@/service/use-share' @@ -60,6 +61,8 @@ const WebAppStoreProvider: FC = ({ children }) => { const pathname = usePathname() const searchParams = useSearchParams() const redirectUrlParam = searchParams.get('redirect_url') + const session = searchParams.get('session') + const sysUserId = searchParams.get('sys.user_id') const [shareCode, setShareCode] = useState(null) useEffect(() => { const shareCodeFromRedirect = getShareCodeFromRedirectUrl(redirectUrlParam) @@ -69,11 +72,22 @@ const WebAppStoreProvider: FC = ({ children }) => { updateShareCode(newShareCode) }, [pathname, redirectUrlParam, updateShareCode]) const { isFetching, data: accessModeResult } = useGetWebAppAccessModeByCode(shareCode) + const [isFetchingAccessToken, setIsFetchingAccessToken] = useState(true) useEffect(() => { - if (accessModeResult?.accessMode) + if (accessModeResult?.accessMode) { updateWebAppAccessMode(accessModeResult.accessMode) - }, [accessModeResult, updateWebAppAccessMode]) - if (isFetching) { + if (accessModeResult?.accessMode === AccessMode.PUBLIC && session && sysUserId) { + setIsFetchingAccessToken(true) + checkOrSetAccessToken(shareCode).finally(() => { + setIsFetchingAccessToken(false) + }) + } + else { + setIsFetchingAccessToken(false) + } + } + }, [accessModeResult, updateWebAppAccessMode, setIsFetchingAccessToken, shareCode, session, sysUserId]) + if (isFetching || isFetchingAccessToken) { return
From 061d4c8ea0dc082d7dce467bcea3af36c5d5dd94 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:14:30 +0800 Subject: [PATCH 04/72] fix(plugins_select): Adjust z-index, fix issue where options cannot be displayed (#22873) (#22893) --- .../[appId]/overview/tracing/config-popup.tsx | 2 +- web/app/components/app-sidebar/app-operations.tsx | 6 +++--- web/app/components/base/select/pure.tsx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index 2afe451fe1..907c270017 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -1,5 +1,5 @@ 'use client' -import type { FC } from 'react' +import type { FC, JSX } from 'react' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' diff --git a/web/app/components/app-sidebar/app-operations.tsx b/web/app/components/app-sidebar/app-operations.tsx index 49cad71573..79c460419d 100644 --- a/web/app/components/app-sidebar/app-operations.tsx +++ b/web/app/components/app-sidebar/app-operations.tsx @@ -1,4 +1,4 @@ -import type { ReactElement } from 'react' +import type { JSX } from 'react' import { cloneElement, useCallback } from 'react' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -7,7 +7,7 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigge import { RiMoreLine } from '@remixicon/react' export type Operation = { - id: string; title: string; icon: ReactElement; onClick: () => void + id: string; title: string; icon: JSX.Element; onClick: () => void } const AppOperations = ({ operations, gap }: { @@ -47,7 +47,7 @@ const AppOperations = ({ operations, gap }: { updatedEntries[id] = true width += gap + childWidth } - else { + else { if (i === childrens.length - 1 && width + childWidth <= containerWidth) updatedEntries[id] = true else diff --git a/web/app/components/base/select/pure.tsx b/web/app/components/base/select/pure.tsx index be88c936fd..046f32676a 100644 --- a/web/app/components/base/select/pure.tsx +++ b/web/app/components/base/select/pure.tsx @@ -91,7 +91,7 @@ const PureSelect = ({ triggerPopupSameWidth={triggerPopupSameWidth} > handleOpenChange(!mergedOpen)} + onClick={() => !disabled && handleOpenChange(!mergedOpen)} asChild >
Date: Thu, 24 Jul 2025 15:36:53 +0800 Subject: [PATCH 05/72] Feat/change user email freezes limit (#22900) --- api/controllers/console/auth/error.py | 6 ++++++ api/controllers/console/workspace/account.py | 10 +++++++--- api/services/account_service.py | 6 ++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/api/controllers/console/auth/error.py b/api/controllers/console/auth/error.py index 8c5e23de58..1984339add 100644 --- a/api/controllers/console/auth/error.py +++ b/api/controllers/console/auth/error.py @@ -113,3 +113,9 @@ class MemberNotInTenantError(BaseHTTPException): error_code = "member_not_in_tenant" description = "The member is not in the workspace." code = 400 + + +class AccountInFreezeError(BaseHTTPException): + error_code = "account_in_freeze" + description = "This email is temporarily unavailable." + code = 400 diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index 657016e0a8..9218ddf91d 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -9,6 +9,7 @@ from configs import dify_config from constants.languages import supported_language from controllers.console import api from controllers.console.auth.error import ( + AccountInFreezeError, EmailAlreadyInUseError, EmailChangeLimitError, EmailCodeError, @@ -479,15 +480,18 @@ class ChangeEmailResetApi(Resource): parser.add_argument("token", type=str, required=True, nullable=False, location="json") args = parser.parse_args() + if AccountService.is_account_in_freeze(args["new_email"]): + raise AccountInFreezeError() + + if not AccountService.check_email_unique(args["new_email"]): + raise EmailAlreadyInUseError() + reset_data = AccountService.get_change_email_data(args["token"]) if not reset_data: raise InvalidTokenError() AccountService.revoke_change_email_token(args["token"]) - if not AccountService.check_email_unique(args["new_email"]): - raise EmailAlreadyInUseError() - old_email = reset_data.get("old_email", "") if current_user.email != old_email: raise AccountNotFound() diff --git a/api/services/account_service.py b/api/services/account_service.py index eb57b675c4..e11f1580e5 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -671,6 +671,12 @@ class AccountService: return account + @classmethod + def is_account_in_freeze(cls, email: str) -> bool: + if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(email): + return True + return False + @staticmethod @redis_fallback(default_return=None) def add_login_error_rate_limit(email: str) -> None: From 9237976988c29301cfcc7f8e2ec30238e652382d Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Thu, 24 Jul 2025 16:14:08 +0800 Subject: [PATCH 06/72] fix: refine handling of constant and mixed input types in ToolManager and ToolNodeData (#22903) --- api/core/tools/tool_manager.py | 4 +++- api/core/workflow/nodes/tool/entities.py | 2 +- .../workflow/nodes/_base/components/form-input-item.tsx | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index f286466de0..71c237c7f7 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -1011,7 +1011,9 @@ class ToolManager: if variable is None: raise ToolParameterError(f"Variable {tool_input.value} does not exist") parameter_value = variable.value - elif tool_input.type in {"mixed", "constant"}: + elif tool_input.type == "constant": + parameter_value = tool_input.value + elif tool_input.type == "mixed": segment_group = variable_pool.convert_template(str(tool_input.value)) parameter_value = segment_group.text else: diff --git a/api/core/workflow/nodes/tool/entities.py b/api/core/workflow/nodes/tool/entities.py index f0a44d919b..4f47fb1efc 100644 --- a/api/core/workflow/nodes/tool/entities.py +++ b/api/core/workflow/nodes/tool/entities.py @@ -54,7 +54,7 @@ class ToolNodeData(BaseNodeData, ToolEntity): for val in value: if not isinstance(val, str): raise ValueError("value must be a list of strings") - elif typ == "constant" and not isinstance(value, str | int | float | bool): + elif typ == "constant" and not isinstance(value, str | int | float | bool | dict): raise ValueError("value must be a string, int, float, or bool") return typ diff --git a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx index 316a5c9819..1efa8aab02 100644 --- a/web/app/components/workflow/nodes/_base/components/form-input-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/form-input-item.tsx @@ -164,7 +164,7 @@ const FormInputItem: FC = ({ ...value, [variable]: { ...varInput, - ...newValue, + value: newValue, }, }) } @@ -242,7 +242,7 @@ const FormInputItem: FC = ({ )} @@ -251,7 +251,7 @@ const FormInputItem: FC = ({ popupClassName='!w-[387px]' isAdvancedMode isInWorkflow - value={varInput} + value={varInput?.value} setModel={handleAppOrModelSelect} readonly={readOnly} scope={scope} From bd43ca6275f8553ce623556936ad84e7f865a656 Mon Sep 17 00:00:00 2001 From: GuanMu Date: Thu, 24 Jul 2025 16:40:37 +0800 Subject: [PATCH 07/72] fix: rounded (#22909) --- web/app/components/datasets/hit-testing/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/components/datasets/hit-testing/index.tsx b/web/app/components/datasets/hit-testing/index.tsx index fef69a5e61..8da7b9e349 100644 --- a/web/app/components/datasets/hit-testing/index.tsx +++ b/web/app/components/datasets/hit-testing/index.tsx @@ -70,7 +70,7 @@ const HitTestingPage: FC = ({ datasetId }: Props) => { const [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false) const [isShowRightPanel, { setTrue: showRightPanel, setFalse: hideRightPanel, set: setShowRightPanel }] = useBoolean(!isMobile) const renderHitResults = (results: HitTesting[] | ExternalKnowledgeBaseHitTesting[]) => ( -
+
{t('datasetHitTesting.hit.title', { num: results.length })}
@@ -93,7 +93,7 @@ const HitTestingPage: FC = ({ datasetId }: Props) => { ) const renderEmptyState = () => ( -
+
{t('datasetHitTesting.hit.emptyTip')} @@ -180,7 +180,7 @@ const HitTestingPage: FC = ({ datasetId }: Props) => {
{/* {renderHitResults(generalResultData)} */} {submitLoading - ?
+ ?
: ( From 45cebf09b04223a71f93603386684bca0a305ee6 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Thu, 24 Jul 2025 21:57:55 +0800 Subject: [PATCH 08/72] fix: Optimize input variable retrieval logic (#22888) (#22914) --- web/app/components/base/prompt-editor/constants.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/base/prompt-editor/constants.tsx b/web/app/components/base/prompt-editor/constants.tsx index 31fbc0abb4..48e67a4ee7 100644 --- a/web/app/components/base/prompt-editor/constants.tsx +++ b/web/app/components/base/prompt-editor/constants.tsx @@ -30,7 +30,7 @@ export const checkHasQueryBlock = (text: string) => { * {{#1711617514996.sys.query#}} => [sys, query] */ export const getInputVars = (text: string): ValueSelector[] => { - if (!text) + if (!text || typeof text !== 'string') return [] const allVars = text.match(/{{#([^#]*)#}}/g) From 206bc4b36de97140eeb39a33e805bb68ec01f80b Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Thu, 24 Jul 2025 21:58:39 +0800 Subject: [PATCH 09/72] chore: enhance error message when handling PluginInvokeError (#22908) --- api/core/plugin/impl/exc.py | 20 +++++++++++++ api/core/workflow/nodes/tool/tool_node.py | 29 +++++++++++++++++-- .../workflow/nodes/tool/test_tool_node.py | 2 +- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/api/core/plugin/impl/exc.py b/api/core/plugin/impl/exc.py index 54a0b90a8d..8b660c807d 100644 --- a/api/core/plugin/impl/exc.py +++ b/api/core/plugin/impl/exc.py @@ -1,3 +1,8 @@ +from collections.abc import Mapping + +from pydantic import TypeAdapter + + class PluginDaemonError(Exception): """Base class for all plugin daemon errors.""" @@ -36,6 +41,21 @@ class PluginDaemonBadRequestError(PluginDaemonClientSideError): class PluginInvokeError(PluginDaemonClientSideError): description: str = "Invoke Error" + def _get_error_object(self) -> Mapping: + try: + return TypeAdapter(Mapping).validate_json(self.description) + except Exception: + return {} + + def get_error_type(self) -> str: + return self._get_error_object().get("error_type", "unknown") + + def get_error_message(self) -> str: + try: + return self._get_error_object().get("message", "unknown") + except Exception: + return self.description + class PluginUniqueIdentifierError(PluginDaemonClientSideError): description: str = "Unique Identifier Error" diff --git a/api/core/workflow/nodes/tool/tool_node.py b/api/core/workflow/nodes/tool/tool_node.py index f437ac841d..4c8e13de70 100644 --- a/api/core/workflow/nodes/tool/tool_node.py +++ b/api/core/workflow/nodes/tool/tool_node.py @@ -6,7 +6,7 @@ from sqlalchemy.orm import Session from core.callback_handler.workflow_tool_callback_handler import DifyWorkflowCallbackHandler from core.file import File, FileTransferMethod -from core.plugin.impl.exc import PluginDaemonClientSideError +from core.plugin.impl.exc import PluginDaemonClientSideError, PluginInvokeError from core.plugin.impl.plugin import PluginInstaller from core.tools.entities.tool_entities import ToolInvokeMessage, ToolParameter from core.tools.errors import ToolInvokeError @@ -141,13 +141,36 @@ class ToolNode(BaseNode): tenant_id=self.tenant_id, node_id=self.node_id, ) - except (PluginDaemonClientSideError, ToolInvokeError) as e: + except ToolInvokeError as e: yield RunCompletedEvent( run_result=NodeRunResult( status=WorkflowNodeExecutionStatus.FAILED, inputs=parameters_for_log, metadata={WorkflowNodeExecutionMetadataKey.TOOL_INFO: tool_info}, - error=f"Failed to transform tool message: {str(e)}", + error=f"Failed to invoke tool {node_data.provider_name}: {str(e)}", + error_type=type(e).__name__, + ) + ) + except PluginInvokeError as e: + yield RunCompletedEvent( + run_result=NodeRunResult( + status=WorkflowNodeExecutionStatus.FAILED, + inputs=parameters_for_log, + metadata={WorkflowNodeExecutionMetadataKey.TOOL_INFO: tool_info}, + error="An error occurred in the plugin, " + f"please contact the author of {node_data.provider_name} for help, " + f"error type: {e.get_error_type()}, " + f"error details: {e.get_error_message()}", + error_type=type(e).__name__, + ) + ) + except PluginDaemonClientSideError as e: + yield RunCompletedEvent( + run_result=NodeRunResult( + status=WorkflowNodeExecutionStatus.FAILED, + inputs=parameters_for_log, + metadata={WorkflowNodeExecutionMetadataKey.TOOL_INFO: tool_info}, + error=f"Failed to invoke tool, error: {e.description}", error_type=type(e).__name__, ) ) diff --git a/api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py b/api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py index 0eaabd0c40..1d37b4803c 100644 --- a/api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py +++ b/api/tests/unit_tests/core/workflow/nodes/tool/test_tool_node.py @@ -111,5 +111,5 @@ def test_tool_node_on_tool_invoke_error(monkeypatch: pytest.MonkeyPatch): assert isinstance(result, NodeRunResult) assert result.status == WorkflowNodeExecutionStatus.FAILED assert "oops" in result.error - assert "Failed to transform tool message:" in result.error + assert "Failed to invoke tool" in result.error assert result.error_type == "ToolInvokeError" From d6b980a2ddc006fe29ad499a02556db081d6b479 Mon Sep 17 00:00:00 2001 From: KVOJJJin Date: Fri, 25 Jul 2025 08:48:23 +0800 Subject: [PATCH 10/72] Feat: change user email freezes limit (#22912) Co-authored-by: Yansong Zhang <916125788@qq.com> --- api/controllers/console/workspace/account.py | 2 ++ .../account-page/email-change-modal.tsx | 22 +++++++++++++++---- web/i18n/en-US/common.ts | 1 + web/i18n/ja-JP/common.ts | 1 + web/i18n/zh-Hans/common.ts | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/api/controllers/console/workspace/account.py b/api/controllers/console/workspace/account.py index 9218ddf91d..45513c368d 100644 --- a/api/controllers/console/workspace/account.py +++ b/api/controllers/console/workspace/account.py @@ -511,6 +511,8 @@ class CheckEmailUnique(Resource): parser = reqparse.RequestParser() parser.add_argument("email", type=email, required=True, location="json") args = parser.parse_args() + if AccountService.is_account_in_freeze(args["email"]): + raise AccountInFreezeError() if not AccountService.check_email_unique(args["email"]): raise EmailAlreadyInUseError() return {"result": "success"} diff --git a/web/app/account/account-page/email-change-modal.tsx b/web/app/account/account-page/email-change-modal.tsx index c3efad104a..bd00f27ac5 100644 --- a/web/app/account/account-page/email-change-modal.tsx +++ b/web/app/account/account-page/email-change-modal.tsx @@ -15,6 +15,8 @@ import { verifyEmail, } from '@/service/common' import { noop } from 'lodash-es' +import { asyncRunSafe } from '@/utils' +import type { ResponseError } from '@/service/fetch' type Props = { show: boolean @@ -39,6 +41,7 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => { const [time, setTime] = useState(0) const [stepToken, setStepToken] = useState('') const [newEmailExited, setNewEmailExited] = useState(false) + const [unAvailableEmail, setUnAvailableEmail] = useState(false) const [isCheckingEmail, setIsCheckingEmail] = useState(false) const startCount = () => { @@ -124,9 +127,17 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => { email, }) setNewEmailExited(false) + setUnAvailableEmail(false) } - catch { - setNewEmailExited(true) + catch (e: any) { + if (e.status === 400) { + const [, errRespData] = await asyncRunSafe(e.json()) + const { code } = errRespData || {} + if (code === 'email_already_in_use') + setNewEmailExited(true) + if (code === 'account_in_freeze') + setUnAvailableEmail(true) + } } finally { setIsCheckingEmail(false) @@ -291,15 +302,18 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => { placeholder={t('common.account.changeEmail.emailPlaceholder')} value={mail} onChange={e => handleNewEmailValueChange(e.target.value)} - destructive={newEmailExited} + destructive={newEmailExited || unAvailableEmail} /> {newEmailExited && (
{t('common.account.changeEmail.existingEmail')}
)} + {unAvailableEmail && ( +
{t('common.account.changeEmail.unAvailableEmail')}
+ )}
) } @@ -169,6 +195,15 @@ const HeaderOptions: FC = ({ /> ) } + { + showClearConfirm && ( + setShowClearConfirm(false)} + onConfirm={handleConfirmed} + /> + ) + }
) } diff --git a/web/i18n/en-US/app-annotation.ts b/web/i18n/en-US/app-annotation.ts index 43f24a7619..c0a8008d9a 100644 --- a/web/i18n/en-US/app-annotation.ts +++ b/web/i18n/en-US/app-annotation.ts @@ -16,7 +16,8 @@ const translation = { addAnnotation: 'Add Annotation', bulkImport: 'Bulk Import', bulkExport: 'Bulk Export', - clearAll: 'Clear All Annotation', + clearAll: 'Delete All', + clearAllConfirm: 'Delete all annotations?', }, }, editModal: { diff --git a/web/i18n/ja-JP/app-annotation.ts b/web/i18n/ja-JP/app-annotation.ts index 38b891d9d8..7dbdfe018f 100644 --- a/web/i18n/ja-JP/app-annotation.ts +++ b/web/i18n/ja-JP/app-annotation.ts @@ -18,7 +18,8 @@ const translation = { addAnnotation: '注釈を追加', bulkImport: '一括インポート', bulkExport: '一括エクスポート', - clearAll: 'すべての注釈をクリア', + clearAll: 'すべて削除', + clearAllConfirm: 'すべての寸法を削除?', }, }, editModal: { diff --git a/web/i18n/zh-Hans/app-annotation.ts b/web/i18n/zh-Hans/app-annotation.ts index 3a6cacf5b5..44d075715f 100644 --- a/web/i18n/zh-Hans/app-annotation.ts +++ b/web/i18n/zh-Hans/app-annotation.ts @@ -18,7 +18,8 @@ const translation = { addAnnotation: '添加标注', bulkImport: '批量导入', bulkExport: '批量导出', - clearAll: '删除所有标注', + clearAll: '删除所有', + clearAllConfirm: '删除所有标注?', }, }, editModal: { diff --git a/web/service/annotation.ts b/web/service/annotation.ts index 5096a4f58a..9f025f8eb9 100644 --- a/web/service/annotation.ts +++ b/web/service/annotation.ts @@ -63,3 +63,7 @@ export const delAnnotation = (appId: string, annotationId: string) => { export const fetchHitHistoryList = (appId: string, annotationId: string, params: Record) => { return get(`apps/${appId}/annotations/${annotationId}/hit-histories`, { params }) } + +export const clearAllAnnotations = (appId: string): Promise => { + return del(`apps/${appId}/annotations`) +} From faaf828dff4cb0b6c2055d7872b0abb95d8fbd6b Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Fri, 25 Jul 2025 23:38:16 +0800 Subject: [PATCH 23/72] Remove redundant condition check (#22983) Signed-off-by: Yongtao Huang --- .../knowledge_retrieval_node.py | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py index 34b0afc75d..e041e217ca 100644 --- a/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py +++ b/api/core/workflow/nodes/knowledge_retrieval/knowledge_retrieval_node.py @@ -453,35 +453,34 @@ class KnowledgeRetrievalNode(BaseNode): elif node_data.metadata_filtering_mode == "manual": if node_data.metadata_filtering_conditions: conditions = [] - if node_data.metadata_filtering_conditions: - for sequence, condition in enumerate(node_data.metadata_filtering_conditions.conditions): # type: ignore - metadata_name = condition.name - expected_value = condition.value - if expected_value is not None and condition.comparison_operator not in ("empty", "not empty"): - if isinstance(expected_value, str): - expected_value = self.graph_runtime_state.variable_pool.convert_template( - expected_value - ).value[0] - if expected_value.value_type in {"number", "integer", "float"}: # type: ignore - expected_value = expected_value.value # type: ignore - elif expected_value.value_type == "string": # type: ignore - expected_value = re.sub(r"[\r\n\t]+", " ", expected_value.text).strip() # type: ignore - else: - raise ValueError("Invalid expected metadata value type") - conditions.append( - Condition( - name=metadata_name, - comparison_operator=condition.comparison_operator, - value=expected_value, - ) - ) - filters = self._process_metadata_filter_func( - sequence, - condition.comparison_operator, - metadata_name, - expected_value, - filters, + for sequence, condition in enumerate(node_data.metadata_filtering_conditions.conditions): # type: ignore + metadata_name = condition.name + expected_value = condition.value + if expected_value is not None and condition.comparison_operator not in ("empty", "not empty"): + if isinstance(expected_value, str): + expected_value = self.graph_runtime_state.variable_pool.convert_template( + expected_value + ).value[0] + if expected_value.value_type in {"number", "integer", "float"}: # type: ignore + expected_value = expected_value.value # type: ignore + elif expected_value.value_type == "string": # type: ignore + expected_value = re.sub(r"[\r\n\t]+", " ", expected_value.text).strip() # type: ignore + else: + raise ValueError("Invalid expected metadata value type") + conditions.append( + Condition( + name=metadata_name, + comparison_operator=condition.comparison_operator, + value=expected_value, ) + ) + filters = self._process_metadata_filter_func( + sequence, + condition.comparison_operator, + metadata_name, + expected_value, + filters, + ) metadata_condition = MetadataCondition( logical_operator=node_data.metadata_filtering_conditions.logical_operator, conditions=conditions, From 773932b1e7374e7997021271158cd3bc450955bc Mon Sep 17 00:00:00 2001 From: znn Date: Sat, 26 Jul 2025 08:07:52 +0530 Subject: [PATCH 24/72] adding mcp error in toast (#22987) --- web/app/components/tools/mcp/modal.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/app/components/tools/mcp/modal.tsx b/web/app/components/tools/mcp/modal.tsx index 88e831bc3a..b7202f5242 100644 --- a/web/app/components/tools/mcp/modal.tsx +++ b/web/app/components/tools/mcp/modal.tsx @@ -95,8 +95,12 @@ const MCPModal = ({ setAppIcon({ type: 'image', url: res.url, fileId: extractFileId(res.url) || '' }) } catch (e) { + let errorMessage = 'Failed to fetch remote icon' + const errorData = await (e as Response).json() + if (errorData?.code) + errorMessage = `Upload failed: ${errorData.code}` console.error('Failed to fetch remote icon:', e) - Toast.notify({ type: 'warning', message: 'Failed to fetch remote icon' }) + Toast.notify({ type: 'warning', message: errorMessage }) } finally { setIsFetchingIcon(false) From 1446f19709329ba3701a5e261fc30ba0f9ef8040 Mon Sep 17 00:00:00 2001 From: Dylan Jiang <74290639+weijunjiang123@users.noreply.github.com> Date: Sat, 26 Jul 2025 10:53:59 +0800 Subject: [PATCH 25/72] fix: Update trigger styles for disabled state in PureSelect component (#22986) --- web/app/components/base/select/pure.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/app/components/base/select/pure.tsx b/web/app/components/base/select/pure.tsx index 046f32676a..0905cd56ff 100644 --- a/web/app/components/base/select/pure.tsx +++ b/web/app/components/base/select/pure.tsx @@ -92,12 +92,13 @@ const PureSelect = ({ > !disabled && handleOpenChange(!mergedOpen)} - asChild - > + asChild >
From 3328addb58f522f4d72e7a97104557499130b567 Mon Sep 17 00:00:00 2001 From: lyzno1 <92089059+lyzno1@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:28:28 +0800 Subject: [PATCH 26/72] fix: eliminate dark mode flicker by moving ThemeProvider to root level (#22996) --- web/__tests__/real-browser-flicker.test.tsx | 445 ++++++++++++++++++++ web/app/layout.tsx | 27 +- 2 files changed, 459 insertions(+), 13 deletions(-) create mode 100644 web/__tests__/real-browser-flicker.test.tsx diff --git a/web/__tests__/real-browser-flicker.test.tsx b/web/__tests__/real-browser-flicker.test.tsx new file mode 100644 index 0000000000..cf3abd5f80 --- /dev/null +++ b/web/__tests__/real-browser-flicker.test.tsx @@ -0,0 +1,445 @@ +/** + * Real Browser Environment Dark Mode Flicker Test + * + * This test attempts to simulate real browser refresh scenarios including: + * 1. SSR HTML generation phase + * 2. Client-side JavaScript loading + * 3. Theme system initialization + * 4. CSS styles application timing + */ + +import { render, screen, waitFor } from '@testing-library/react' +import { ThemeProvider } from 'next-themes' +import useTheme from '@/hooks/use-theme' +import { useEffect, useState } from 'react' + +// Setup browser environment for testing +const setupMockEnvironment = (storedTheme: string | null, systemPrefersDark = false) => { + // Mock localStorage + const mockStorage = { + getItem: jest.fn((key: string) => { + if (key === 'theme') return storedTheme + return null + }), + setItem: jest.fn(), + removeItem: jest.fn(), + } + + // Mock system theme preference + const mockMatchMedia = jest.fn((query: string) => ({ + matches: query.includes('dark') && systemPrefersDark, + media: query, + addListener: jest.fn(), + removeListener: jest.fn(), + })) + + if (typeof window !== 'undefined') { + Object.defineProperty(window, 'localStorage', { + value: mockStorage, + configurable: true, + }) + + Object.defineProperty(window, 'matchMedia', { + value: mockMatchMedia, + configurable: true, + }) + } + + return { mockStorage, mockMatchMedia } +} + +// Simulate real page component based on Dify's actual theme usage +const PageComponent = () => { + const [mounted, setMounted] = useState(false) + const { theme } = useTheme() + + useEffect(() => { + setMounted(true) + }, []) + + // Simulate common theme usage pattern in Dify + const isDark = mounted ? theme === 'dark' : false + + return ( +
+
+

+ Dify Application +

+
+ Current Theme: {mounted ? theme : 'unknown'} +
+
+ Appearance: {isDark ? 'dark' : 'light'} +
+
+
+ ) +} + +const TestThemeProvider = ({ children }: { children: React.ReactNode }) => ( + + {children} + +) + +describe('Real Browser Environment Dark Mode Flicker Test', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('Page Refresh Scenario Simulation', () => { + test('simulates complete page loading process with dark theme', async () => { + // Setup: User previously selected dark mode + setupMockEnvironment('dark') + + render( + + + , + ) + + // Check initial client-side rendering state + const initialState = { + theme: screen.getByTestId('theme-indicator').textContent, + appearance: screen.getByTestId('visual-appearance').textContent, + } + console.log('Initial client state:', initialState) + + // Wait for theme system to fully initialize + await waitFor(() => { + expect(screen.getByTestId('theme-indicator')).toHaveTextContent('Current Theme: dark') + }) + + const finalState = { + theme: screen.getByTestId('theme-indicator').textContent, + appearance: screen.getByTestId('visual-appearance').textContent, + } + console.log('Final state:', finalState) + + // Document the state change - this is the source of flicker + console.log('State change detection: Initial -> Final') + }) + + test('handles light theme correctly', async () => { + setupMockEnvironment('light') + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('theme-indicator')).toHaveTextContent('Current Theme: light') + }) + + expect(screen.getByTestId('visual-appearance')).toHaveTextContent('Appearance: light') + }) + + test('handles system theme with dark preference', async () => { + setupMockEnvironment('system', true) // system theme, dark preference + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('theme-indicator')).toHaveTextContent('Current Theme: dark') + }) + + expect(screen.getByTestId('visual-appearance')).toHaveTextContent('Appearance: dark') + }) + + test('handles system theme with light preference', async () => { + setupMockEnvironment('system', false) // system theme, light preference + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('theme-indicator')).toHaveTextContent('Current Theme: light') + }) + + expect(screen.getByTestId('visual-appearance')).toHaveTextContent('Appearance: light') + }) + + test('handles no stored theme (defaults to system)', async () => { + setupMockEnvironment(null, false) // no stored theme, system prefers light + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('theme-indicator')).toHaveTextContent('Current Theme: light') + }) + }) + + test('measures timing window of style changes', async () => { + setupMockEnvironment('dark') + + const timingData: Array<{ phase: string; timestamp: number; styles: any }> = [] + + const TimingPageComponent = () => { + const [mounted, setMounted] = useState(false) + const { theme } = useTheme() + const isDark = mounted ? theme === 'dark' : false + + // Record timing and styles for each render phase + const currentStyles = { + backgroundColor: isDark ? '#1f2937' : '#ffffff', + color: isDark ? '#ffffff' : '#000000', + } + + timingData.push({ + phase: mounted ? 'CSR' : 'Initial', + timestamp: performance.now(), + styles: currentStyles, + }) + + useEffect(() => { + setMounted(true) + }, []) + + return ( +
+
+ Phase: {mounted ? 'CSR' : 'Initial'} | Theme: {theme} | Visual: {isDark ? 'dark' : 'light'} +
+
+ ) + } + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('timing-status')).toHaveTextContent('Phase: CSR') + }) + + // Analyze timing and style changes + console.log('\n=== Style Change Timeline ===') + timingData.forEach((data, index) => { + console.log(`${index + 1}. ${data.phase}: bg=${data.styles.backgroundColor}, color=${data.styles.color}`) + }) + + // Check if there are style changes (this is visible flicker) + const hasStyleChange = timingData.length > 1 + && timingData[0].styles.backgroundColor !== timingData[timingData.length - 1].styles.backgroundColor + + if (hasStyleChange) + console.log('⚠️ Style changes detected - this causes visible flicker') + else + console.log('✅ No style changes detected') + + expect(timingData.length).toBeGreaterThan(1) + }) + }) + + describe('CSS Application Timing Tests', () => { + test('checks CSS class changes causing flicker', async () => { + setupMockEnvironment('dark') + + const cssStates: Array<{ className: string; timestamp: number }> = [] + + const CSSTestComponent = () => { + const [mounted, setMounted] = useState(false) + const { theme } = useTheme() + const isDark = mounted ? theme === 'dark' : false + + // Simulate Tailwind CSS class application + const className = `min-h-screen ${isDark ? 'bg-gray-900 text-white' : 'bg-white text-black'}` + + cssStates.push({ + className, + timestamp: performance.now(), + }) + + useEffect(() => { + setMounted(true) + }, []) + + return ( +
+
Classes: {className}
+
+ ) + } + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('css-classes')).toHaveTextContent('bg-gray-900 text-white') + }) + + console.log('\n=== CSS Class Change Detection ===') + cssStates.forEach((state, index) => { + console.log(`${index + 1}. ${state.className}`) + }) + + // Check if CSS classes have changed + const hasCSSChange = cssStates.length > 1 + && cssStates[0].className !== cssStates[cssStates.length - 1].className + + if (hasCSSChange) { + console.log('⚠️ CSS class changes detected - may cause style flicker') + console.log(`From: "${cssStates[0].className}"`) + console.log(`To: "${cssStates[cssStates.length - 1].className}"`) + } + + expect(hasCSSChange).toBe(true) // We expect to see this change + }) + }) + + describe('Edge Cases and Error Handling', () => { + test('handles localStorage access errors gracefully', async () => { + // Mock localStorage to throw an error + const mockStorage = { + getItem: jest.fn(() => { + throw new Error('LocalStorage access denied') + }), + setItem: jest.fn(), + removeItem: jest.fn(), + } + + if (typeof window !== 'undefined') { + Object.defineProperty(window, 'localStorage', { + value: mockStorage, + configurable: true, + }) + } + + render( + + + , + ) + + // Should fallback gracefully without crashing + await waitFor(() => { + expect(screen.getByTestId('theme-indicator')).toBeInTheDocument() + }) + + // Should default to light theme when localStorage fails + expect(screen.getByTestId('visual-appearance')).toHaveTextContent('Appearance: light') + }) + + test('handles invalid theme values in localStorage', async () => { + setupMockEnvironment('invalid-theme-value') + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('theme-indicator')).toBeInTheDocument() + }) + + // Should handle invalid values gracefully + const themeIndicator = screen.getByTestId('theme-indicator') + expect(themeIndicator).toBeInTheDocument() + }) + }) + + describe('Performance and Regression Tests', () => { + test('verifies ThemeProvider position fix reduces initialization delay', async () => { + const performanceMarks: Array<{ event: string; timestamp: number }> = [] + + const PerformanceTestComponent = () => { + const [mounted, setMounted] = useState(false) + const { theme } = useTheme() + + performanceMarks.push({ event: 'component-render', timestamp: performance.now() }) + + useEffect(() => { + performanceMarks.push({ event: 'mount-start', timestamp: performance.now() }) + setMounted(true) + performanceMarks.push({ event: 'mount-complete', timestamp: performance.now() }) + }, []) + + useEffect(() => { + if (theme) + performanceMarks.push({ event: 'theme-available', timestamp: performance.now() }) + }, [theme]) + + return ( +
+ Mounted: {mounted.toString()} | Theme: {theme || 'loading'} +
+ ) + } + + setupMockEnvironment('dark') + + render( + + + , + ) + + await waitFor(() => { + expect(screen.getByTestId('performance-test')).toHaveTextContent('Theme: dark') + }) + + // Analyze performance timeline + console.log('\n=== Performance Timeline ===') + performanceMarks.forEach((mark) => { + console.log(`${mark.event}: ${mark.timestamp.toFixed(2)}ms`) + }) + + expect(performanceMarks.length).toBeGreaterThan(3) + }) + }) + + describe('Solution Requirements Definition', () => { + test('defines technical requirements to eliminate flicker', () => { + const technicalRequirements = { + ssrConsistency: 'SSR and CSR must render identical initial styles', + synchronousDetection: 'Theme detection must complete synchronously before first render', + noStyleChanges: 'No visible style changes should occur after hydration', + performanceImpact: 'Solution should not significantly impact page load performance', + browserCompatibility: 'Must work consistently across all major browsers', + } + + console.log('\n=== Technical Requirements ===') + Object.entries(technicalRequirements).forEach(([key, requirement]) => { + console.log(`${key}: ${requirement}`) + expect(requirement).toBeDefined() + }) + + // A successful solution should pass all these requirements + }) + }) +}) diff --git a/web/app/layout.tsx b/web/app/layout.tsx index 0f0ea0f705..46afd95b97 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -62,24 +62,25 @@ const LocaleLayout = async ({ className="color-scheme h-full select-auto" {...datasetMap} > - - - - + + + + {children} - - - - + + + + From 5411fd3757e39a6ab61769b1322512b48702492c Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Sat, 26 Jul 2025 18:29:03 +0800 Subject: [PATCH 27/72] Fix: correct misplaced `ensure_ascii=False` (#22997) Signed-off-by: Yongtao Huang --- .../nodes/question_classifier/question_classifier_node.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/core/workflow/nodes/question_classifier/question_classifier_node.py b/api/core/workflow/nodes/question_classifier/question_classifier_node.py index 15012fa48d..3e4984ecd5 100644 --- a/api/core/workflow/nodes/question_classifier/question_classifier_node.py +++ b/api/core/workflow/nodes/question_classifier/question_classifier_node.py @@ -385,9 +385,8 @@ class QuestionClassifierNode(BaseNode): text=QUESTION_CLASSIFIER_COMPLETION_PROMPT.format( histories=memory_str, input_text=input_text, - categories=json.dumps(categories), + categories=json.dumps(categories, ensure_ascii=False), classification_instructions=instruction, - ensure_ascii=False, ) ) From e0fe158f0bd3c4599a4a5874b72f5f1a7879d388 Mon Sep 17 00:00:00 2001 From: znn Date: Sun, 27 Jul 2025 06:40:04 +0530 Subject: [PATCH 28/72] node title number on copied iteration node (#23004) --- .../workflow/nodes/iteration/use-interactions.ts | 10 +++++++++- web/app/components/workflow/types.ts | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/web/app/components/workflow/nodes/iteration/use-interactions.ts b/web/app/components/workflow/nodes/iteration/use-interactions.ts index c294cfd6aa..e0c0b222aa 100644 --- a/web/app/components/workflow/nodes/iteration/use-interactions.ts +++ b/web/app/components/workflow/nodes/iteration/use-interactions.ts @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next' import { useStoreApi } from 'reactflow' import type { BlockEnum, + ChildNodeTypeCount, Node, } from '../../types' import { @@ -113,10 +114,17 @@ export const useNodeIterationInteractions = () => { const nodes = getNodes() const childrenNodes = nodes.filter(n => n.parentId === nodeId && n.type !== CUSTOM_ITERATION_START_NODE) const newIdMapping = { ...idMapping } + const childNodeTypeCount: ChildNodeTypeCount = {} const copyChildren = childrenNodes.map((child, index) => { const childNodeType = child.data.type as BlockEnum const nodesWithSameType = nodes.filter(node => node.data.type === childNodeType) + + if(!childNodeTypeCount[childNodeType]) + childNodeTypeCount[childNodeType] = nodesWithSameType.length + 1 + else + childNodeTypeCount[childNodeType] = childNodeTypeCount[childNodeType] + 1 + const { newNode } = generateNewNode({ type: getNodeCustomTypeByNodeDataType(childNodeType), data: { @@ -126,7 +134,7 @@ export const useNodeIterationInteractions = () => { _isBundled: false, _connectedSourceHandleIds: [], _connectedTargetHandleIds: [], - title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${childNodeType}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${childNodeType}`), + title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${childNodeType}`)} ${childNodeTypeCount[childNodeType]}` : t(`workflow.blocks.${childNodeType}`), iteration_id: newNodeId, }, position: child.position, diff --git a/web/app/components/workflow/types.ts b/web/app/components/workflow/types.ts index 5f36956798..d8153cf08f 100644 --- a/web/app/components/workflow/types.ts +++ b/web/app/components/workflow/types.ts @@ -446,3 +446,7 @@ export enum VersionHistoryContextMenuOptions { edit = 'edit', delete = 'delete', } + +export interface ChildNodeTypeCount { + [key: string]: number; +} From d776a7cde79750fd5fea8134257abb2a4286feed Mon Sep 17 00:00:00 2001 From: znn Date: Sun, 27 Jul 2025 06:49:13 +0530 Subject: [PATCH 29/72] adding LANG LC_ALL PYTHONIOENCODING UTF-8 (#22928) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- api/.env.example | 5 +++++ api/Dockerfile | 5 +++++ api/docker/entrypoint.sh | 5 +++++ docker/.env.example | 5 +++++ docker/docker-compose.yaml | 3 +++ 5 files changed, 23 insertions(+) diff --git a/api/.env.example b/api/.env.example index 80b1c12cd8..18f2dbf647 100644 --- a/api/.env.example +++ b/api/.env.example @@ -4,6 +4,11 @@ # Alternatively you can set it with `SECRET_KEY` environment variable. SECRET_KEY= +# Ensure UTF-8 encoding +LANG=en_US.UTF-8 +LC_ALL=en_US.UTF-8 +PYTHONIOENCODING=utf-8 + # Console API base URL CONSOLE_API_URL=http://localhost:5001 CONSOLE_WEB_URL=http://localhost:3000 diff --git a/api/Dockerfile b/api/Dockerfile index 8c7a1717b9..e097b5811e 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -37,6 +37,11 @@ EXPOSE 5001 # set timezone ENV TZ=UTC +# Set UTF-8 locale +ENV LANG=en_US.UTF-8 +ENV LC_ALL=en_US.UTF-8 +ENV PYTHONIOENCODING=utf-8 + WORKDIR /app/api RUN \ diff --git a/api/docker/entrypoint.sh b/api/docker/entrypoint.sh index 4de9a25c2f..a850ea9a50 100755 --- a/api/docker/entrypoint.sh +++ b/api/docker/entrypoint.sh @@ -2,6 +2,11 @@ set -e +# Set UTF-8 encoding to address potential encoding issues in containerized environments +export LANG=${LANG:-en_US.UTF-8} +export LC_ALL=${LC_ALL:-en_US.UTF-8} +export PYTHONIOENCODING=${PYTHONIOENCODING:-utf-8} + if [[ "${MIGRATION_ENABLED}" == "true" ]]; then echo "Running migrations" flask upgrade-db diff --git a/docker/.env.example b/docker/.env.example index 88cc544730..9d15ba53d3 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -52,6 +52,11 @@ FILES_URL= # Example: INTERNAL_FILES_URL=http://api:5001 INTERNAL_FILES_URL= +# Ensure UTF-8 encoding +LANG=en_US.UTF-8 +LC_ALL=en_US.UTF-8 +PYTHONIOENCODING=utf-8 + # ------------------------------ # Server Configuration # ------------------------------ diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index c2ef2ff723..2c1429b5da 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -12,6 +12,9 @@ x-shared-env: &shared-api-worker-env APP_WEB_URL: ${APP_WEB_URL:-} FILES_URL: ${FILES_URL:-} INTERNAL_FILES_URL: ${INTERNAL_FILES_URL:-} + LANG: ${LANG:-en_US.UTF-8} + LC_ALL: ${LC_ALL:-en_US.UTF-8} + PYTHONIOENCODING: ${PYTHONIOENCODING:-utf-8} LOG_LEVEL: ${LOG_LEVEL:-INFO} LOG_FILE: ${LOG_FILE:-/app/logs/server.log} LOG_FILE_MAX_SIZE: ${LOG_FILE_MAX_SIZE:-20} From 665fcad6551dfa0f95709692b05db927b98071bf Mon Sep 17 00:00:00 2001 From: Guangdong Liu <804167098@qq.com> Date: Sun, 27 Jul 2025 09:22:36 +0800 Subject: [PATCH 30/72] fix: resolve cross-page document selection issue in metadata batch edit (#23000) Co-authored-by: crazywoola <427733928@qq.com> --- web/app/components/datasets/documents/index.tsx | 9 +++++++-- web/app/components/datasets/documents/list.tsx | 3 ++- .../hooks/use-batch-edit-document-metadata.ts | 13 +++++++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/web/app/components/datasets/documents/index.tsx b/web/app/components/datasets/documents/index.tsx index 676581a50f..1f9f36e7b1 100644 --- a/web/app/components/datasets/documents/index.tsx +++ b/web/app/components/datasets/documents/index.tsx @@ -164,7 +164,6 @@ const Documents: FC = ({ datasetId }) => { if (totalPages < currPage + 1) setCurrPage(totalPages === 0 ? 0 : totalPages - 1) } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [documentsRes]) const invalidDocumentDetail = useInvalidDocumentDetailKey() @@ -178,7 +177,6 @@ const Documents: FC = ({ datasetId }) => { invalidChunkList() invalidChildChunkList() }, 5000) - // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const documentsWithProgress = useMemo(() => { @@ -273,6 +271,13 @@ const Documents: FC = ({ datasetId }) => { const documentsList = isDataSourceNotion ? documentsWithProgress?.data : documentsRes?.data const [selectedIds, setSelectedIds] = useState([]) + + // Clear selection when search changes to avoid confusion + useEffect(() => { + if (searchValue !== query.keyword) + setSelectedIds([]) + }, [searchValue, query.keyword]) + const { run: handleSearch } = useDebounceFn(() => { setSearchValue(inputValue) }, { wait: 500 }) diff --git a/web/app/components/datasets/documents/list.tsx b/web/app/components/datasets/documents/list.tsx index 2eb6a3ac1e..2697580f4e 100644 --- a/web/app/components/datasets/documents/list.tsx +++ b/web/app/components/datasets/documents/list.tsx @@ -458,7 +458,8 @@ const DocumentList: FC = ({ handleSave, } = useBatchEditDocumentMetadata({ datasetId, - docList: documents.filter(item => selectedIds.includes(item.id)), + docList: documents.filter(doc => selectedIds.includes(doc.id)), + selectedDocumentIds: selectedIds, // Pass all selected IDs separately onUpdate, }) diff --git a/web/app/components/datasets/metadata/hooks/use-batch-edit-document-metadata.ts b/web/app/components/datasets/metadata/hooks/use-batch-edit-document-metadata.ts index 3bb6e1d6ed..f350fd7b8b 100644 --- a/web/app/components/datasets/metadata/hooks/use-batch-edit-document-metadata.ts +++ b/web/app/components/datasets/metadata/hooks/use-batch-edit-document-metadata.ts @@ -9,12 +9,14 @@ import { t } from 'i18next' type Props = { datasetId: string docList: SimpleDocumentDetail[] + selectedDocumentIds?: string[] onUpdate: () => void } const useBatchEditDocumentMetadata = ({ datasetId, docList, + selectedDocumentIds, onUpdate, }: Props) => { const [isShowEditModal, { @@ -79,9 +81,12 @@ const useBatchEditDocumentMetadata = ({ return false }) - const res: MetadataBatchEditToServer = docList.map((item, i) => { - // the new metadata will override the old one - const oldMetadataList = metaDataList[i] + // Use selectedDocumentIds if available, otherwise fall back to docList + const documentIds = selectedDocumentIds || docList.map(doc => doc.id) + const res: MetadataBatchEditToServer = documentIds.map((documentId) => { + // Find the document in docList to get its metadata + const docIndex = docList.findIndex(doc => doc.id === documentId) + const oldMetadataList = docIndex >= 0 ? metaDataList[docIndex] : [] let newMetadataList: MetadataItemWithValue[] = [...oldMetadataList, ...addedList] .filter((item) => { return !removedList.find(removedItem => removedItem.id === item.id) @@ -108,7 +113,7 @@ const useBatchEditDocumentMetadata = ({ }) return { - document_id: item.id, + document_id: documentId, metadata_list: newMetadataList, } }) From 67a0751cf36adcabc9a4478e4d2e20a4bca80c67 Mon Sep 17 00:00:00 2001 From: Will Date: Sun, 27 Jul 2025 11:06:37 +0800 Subject: [PATCH 31/72] fix: Improve create_agent_thought and save_agent_thought Logic (#21263) --- api/core/agent/base_agent_runner.py | 48 +++++++++++++---------------- api/core/agent/cot_agent_runner.py | 16 +++++----- api/core/agent/fc_agent_runner.py | 14 ++++----- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/api/core/agent/base_agent_runner.py b/api/core/agent/base_agent_runner.py index 1f3c218d59..ad9b625350 100644 --- a/api/core/agent/base_agent_runner.py +++ b/api/core/agent/base_agent_runner.py @@ -280,7 +280,7 @@ class BaseAgentRunner(AppRunner): def create_agent_thought( self, message_id: str, message: str, tool_name: str, tool_input: str, messages_ids: list[str] - ) -> MessageAgentThought: + ) -> str: """ Create agent thought """ @@ -313,16 +313,15 @@ class BaseAgentRunner(AppRunner): db.session.add(thought) db.session.commit() - db.session.refresh(thought) + agent_thought_id = str(thought.id) + self.agent_thought_count += 1 db.session.close() - self.agent_thought_count += 1 - - return thought + return agent_thought_id def save_agent_thought( self, - agent_thought: MessageAgentThought, + agent_thought_id: str, tool_name: str | None, tool_input: Union[str, dict, None], thought: str | None, @@ -335,12 +334,9 @@ class BaseAgentRunner(AppRunner): """ Save agent thought """ - updated_agent_thought = ( - db.session.query(MessageAgentThought).where(MessageAgentThought.id == agent_thought.id).first() - ) - if not updated_agent_thought: + agent_thought = db.session.query(MessageAgentThought).where(MessageAgentThought.id == agent_thought_id).first() + if not agent_thought: raise ValueError("agent thought not found") - agent_thought = updated_agent_thought if thought: agent_thought.thought += thought @@ -355,7 +351,7 @@ class BaseAgentRunner(AppRunner): except Exception: tool_input = json.dumps(tool_input) - updated_agent_thought.tool_input = tool_input + agent_thought.tool_input = tool_input if observation: if isinstance(observation, dict): @@ -364,27 +360,27 @@ class BaseAgentRunner(AppRunner): except Exception: observation = json.dumps(observation) - updated_agent_thought.observation = observation + agent_thought.observation = observation if answer: agent_thought.answer = answer if messages_ids is not None and len(messages_ids) > 0: - updated_agent_thought.message_files = json.dumps(messages_ids) + agent_thought.message_files = json.dumps(messages_ids) if llm_usage: - updated_agent_thought.message_token = llm_usage.prompt_tokens - updated_agent_thought.message_price_unit = llm_usage.prompt_price_unit - updated_agent_thought.message_unit_price = llm_usage.prompt_unit_price - updated_agent_thought.answer_token = llm_usage.completion_tokens - updated_agent_thought.answer_price_unit = llm_usage.completion_price_unit - updated_agent_thought.answer_unit_price = llm_usage.completion_unit_price - updated_agent_thought.tokens = llm_usage.total_tokens - updated_agent_thought.total_price = llm_usage.total_price + agent_thought.message_token = llm_usage.prompt_tokens + agent_thought.message_price_unit = llm_usage.prompt_price_unit + agent_thought.message_unit_price = llm_usage.prompt_unit_price + agent_thought.answer_token = llm_usage.completion_tokens + agent_thought.answer_price_unit = llm_usage.completion_price_unit + agent_thought.answer_unit_price = llm_usage.completion_unit_price + agent_thought.tokens = llm_usage.total_tokens + agent_thought.total_price = llm_usage.total_price # check if tool labels is not empty - labels = updated_agent_thought.tool_labels or {} - tools = updated_agent_thought.tool.split(";") if updated_agent_thought.tool else [] + labels = agent_thought.tool_labels or {} + tools = agent_thought.tool.split(";") if agent_thought.tool else [] for tool in tools: if not tool: continue @@ -395,7 +391,7 @@ class BaseAgentRunner(AppRunner): else: labels[tool] = {"en_US": tool, "zh_Hans": tool} - updated_agent_thought.tool_labels_str = json.dumps(labels) + agent_thought.tool_labels_str = json.dumps(labels) if tool_invoke_meta is not None: if isinstance(tool_invoke_meta, dict): @@ -404,7 +400,7 @@ class BaseAgentRunner(AppRunner): except Exception: tool_invoke_meta = json.dumps(tool_invoke_meta) - updated_agent_thought.tool_meta_str = tool_invoke_meta + agent_thought.tool_meta_str = tool_invoke_meta db.session.commit() db.session.close() diff --git a/api/core/agent/cot_agent_runner.py b/api/core/agent/cot_agent_runner.py index 4979f63432..565fb42478 100644 --- a/api/core/agent/cot_agent_runner.py +++ b/api/core/agent/cot_agent_runner.py @@ -97,13 +97,13 @@ class CotAgentRunner(BaseAgentRunner, ABC): message_file_ids: list[str] = [] - agent_thought = self.create_agent_thought( + agent_thought_id = self.create_agent_thought( message_id=message.id, message="", tool_name="", tool_input="", messages_ids=message_file_ids ) if iteration_step > 1: self.queue_manager.publish( - QueueAgentThoughtEvent(agent_thought_id=agent_thought.id), PublishFrom.APPLICATION_MANAGER + QueueAgentThoughtEvent(agent_thought_id=agent_thought_id), PublishFrom.APPLICATION_MANAGER ) # recalc llm max tokens @@ -133,7 +133,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): # publish agent thought if it's first iteration if iteration_step == 1: self.queue_manager.publish( - QueueAgentThoughtEvent(agent_thought_id=agent_thought.id), PublishFrom.APPLICATION_MANAGER + QueueAgentThoughtEvent(agent_thought_id=agent_thought_id), PublishFrom.APPLICATION_MANAGER ) for chunk in react_chunks: @@ -168,7 +168,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): usage_dict["usage"] = LLMUsage.empty_usage() self.save_agent_thought( - agent_thought=agent_thought, + agent_thought_id=agent_thought_id, tool_name=(scratchpad.action.action_name if scratchpad.action and not scratchpad.is_final() else ""), tool_input={scratchpad.action.action_name: scratchpad.action.action_input} if scratchpad.action else {}, tool_invoke_meta={}, @@ -181,7 +181,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): if not scratchpad.is_final(): self.queue_manager.publish( - QueueAgentThoughtEvent(agent_thought_id=agent_thought.id), PublishFrom.APPLICATION_MANAGER + QueueAgentThoughtEvent(agent_thought_id=agent_thought_id), PublishFrom.APPLICATION_MANAGER ) if not scratchpad.action: @@ -212,7 +212,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): scratchpad.agent_response = tool_invoke_response self.save_agent_thought( - agent_thought=agent_thought, + agent_thought_id=agent_thought_id, tool_name=scratchpad.action.action_name, tool_input={scratchpad.action.action_name: scratchpad.action.action_input}, thought=scratchpad.thought or "", @@ -224,7 +224,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): ) self.queue_manager.publish( - QueueAgentThoughtEvent(agent_thought_id=agent_thought.id), PublishFrom.APPLICATION_MANAGER + QueueAgentThoughtEvent(agent_thought_id=agent_thought_id), PublishFrom.APPLICATION_MANAGER ) # update prompt tool message @@ -244,7 +244,7 @@ class CotAgentRunner(BaseAgentRunner, ABC): # save agent thought self.save_agent_thought( - agent_thought=agent_thought, + agent_thought_id=agent_thought_id, tool_name="", tool_input={}, tool_invoke_meta={}, diff --git a/api/core/agent/fc_agent_runner.py b/api/core/agent/fc_agent_runner.py index 5491689ece..4df71ce9de 100644 --- a/api/core/agent/fc_agent_runner.py +++ b/api/core/agent/fc_agent_runner.py @@ -80,7 +80,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): prompt_messages_tools = [] message_file_ids: list[str] = [] - agent_thought = self.create_agent_thought( + agent_thought_id = self.create_agent_thought( message_id=message.id, message="", tool_name="", tool_input="", messages_ids=message_file_ids ) @@ -114,7 +114,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): for chunk in chunks: if is_first_chunk: self.queue_manager.publish( - QueueAgentThoughtEvent(agent_thought_id=agent_thought.id), PublishFrom.APPLICATION_MANAGER + QueueAgentThoughtEvent(agent_thought_id=agent_thought_id), PublishFrom.APPLICATION_MANAGER ) is_first_chunk = False # check if there is any tool call @@ -172,7 +172,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): result.message.content = "" self.queue_manager.publish( - QueueAgentThoughtEvent(agent_thought_id=agent_thought.id), PublishFrom.APPLICATION_MANAGER + QueueAgentThoughtEvent(agent_thought_id=agent_thought_id), PublishFrom.APPLICATION_MANAGER ) yield LLMResultChunk( @@ -205,7 +205,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): # save thought self.save_agent_thought( - agent_thought=agent_thought, + agent_thought_id=agent_thought_id, tool_name=tool_call_names, tool_input=tool_call_inputs, thought=response, @@ -216,7 +216,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): llm_usage=current_llm_usage, ) self.queue_manager.publish( - QueueAgentThoughtEvent(agent_thought_id=agent_thought.id), PublishFrom.APPLICATION_MANAGER + QueueAgentThoughtEvent(agent_thought_id=agent_thought_id), PublishFrom.APPLICATION_MANAGER ) final_answer += response + "\n" @@ -276,7 +276,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): if len(tool_responses) > 0: # save agent thought self.save_agent_thought( - agent_thought=agent_thought, + agent_thought_id=agent_thought_id, tool_name="", tool_input="", thought="", @@ -291,7 +291,7 @@ class FunctionCallAgentRunner(BaseAgentRunner): messages_ids=message_file_ids, ) self.queue_manager.publish( - QueueAgentThoughtEvent(agent_thought_id=agent_thought.id), PublishFrom.APPLICATION_MANAGER + QueueAgentThoughtEvent(agent_thought_id=agent_thought_id), PublishFrom.APPLICATION_MANAGER ) # update prompt tool From 177b0fb5e805b7fce223b711eaafcddb5a9a8846 Mon Sep 17 00:00:00 2001 From: znn Date: Mon, 28 Jul 2025 07:34:31 +0530 Subject: [PATCH 32/72] =?UTF-8?q?ability=20to=20select=20same=20type=20sub?= =?UTF-8?q?=20item=20by=20preserving=20children=20of=20both=20f=E2=80=A6?= =?UTF-8?q?=20(#23002)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/_base/components/variable/utils.ts | 65 ++++++++++++++++--- 1 file changed, 55 insertions(+), 10 deletions(-) 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 3737db8abf..8c3ffb8810 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -49,6 +49,13 @@ export const isConversationVar = (valueSelector: ValueSelector) => { return valueSelector[0] === 'conversation' } +export const hasValidChildren = (children: any): boolean => { + return children && ( + (Array.isArray(children) && children.length > 0) + || (!Array.isArray(children) && Object.keys((children as StructuredOutput)?.schema?.properties || {}).length > 0) + ) +} + const inputVarTypeToVarType = (type: InputVarType): VarType => { return ({ [InputVarType.number]: VarType.number, @@ -139,19 +146,57 @@ const findExceptVarInObject = (obj: any, filterVar: (payload: Var, selector: Val if (isStructuredOutput) { childrenResult = findExceptVarInStructuredOutput(children, filterVar) } - else if (Array.isArray(children)) { - childrenResult = children.filter((item: Var) => { - const { children: itemChildren } = item - const currSelector = [...value_selector, item.variable] + else if (Array.isArray(children)) { + childrenResult = children + .map((item: Var) => { + const { children: itemChildren } = item + const currSelector = [...value_selector, item.variable] - if (!itemChildren) - return filterVar(item, currSelector) + if (!itemChildren) { + return { + item, + filteredObj: null, + passesFilter: filterVar(item, currSelector), + } + } - const filteredObj = findExceptVarInObject(item, filterVar, currSelector, false) // File doesn't contain file children - return filteredObj.children && (filteredObj.children as Var[])?.length > 0 - }) + const filteredObj = findExceptVarInObject(item, filterVar, currSelector, false) + const itemHasValidChildren = hasValidChildren(filteredObj.children) + + let passesFilter + if ((item.type === VarType.object || item.type === VarType.file) && itemChildren) + passesFilter = itemHasValidChildren || filterVar(item, currSelector) + else + passesFilter = itemHasValidChildren + + return { + item, + filteredObj, + passesFilter, + } + }) + .filter(({ passesFilter }) => passesFilter) + .map(({ item, filteredObj }) => { + const { children: itemChildren } = item + if (!itemChildren || !filteredObj) + return item + + return { + ...item, + children: filteredObj.children, + } + }) + + if (isFile && Array.isArray(childrenResult)) { + if (childrenResult.length === 0) { + childrenResult = OUTPUT_FILE_SUB_VARIABLES.map(key => ({ + variable: key, + type: key === 'size' ? VarType.number : VarType.string, + })) + } + } } - else { + else { childrenResult = [] } From 1c05491f1cfbf47575949695417a6e90d9d5cebd Mon Sep 17 00:00:00 2001 From: Yongtao Huang Date: Mon, 28 Jul 2025 10:04:45 +0800 Subject: [PATCH 33/72] Chore: remove duplicate TYPE_CHECKING import (#23013) Signed-off-by: Yongtao Huang --- api/core/rag/docstore/dataset_docstore.py | 6 +++--- api/models/model.py | 3 --- api/models/workflow.py | 3 --- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/api/core/rag/docstore/dataset_docstore.py b/api/core/rag/docstore/dataset_docstore.py index f844770a20..f8da3657fc 100644 --- a/api/core/rag/docstore/dataset_docstore.py +++ b/api/core/rag/docstore/dataset_docstore.py @@ -32,7 +32,7 @@ class DatasetDocumentStore: } @property - def dateset_id(self) -> Any: + def dataset_id(self) -> Any: return self._dataset.id @property @@ -123,13 +123,13 @@ class DatasetDocumentStore: db.session.flush() if save_child: if doc.children: - for postion, child in enumerate(doc.children, start=1): + for position, child in enumerate(doc.children, start=1): child_segment = ChildChunk( tenant_id=self._dataset.tenant_id, dataset_id=self._dataset.id, document_id=self._document_id, segment_id=segment_document.id, - position=postion, + position=position, index_node_id=child.metadata.get("doc_id"), index_node_hash=child.metadata.get("doc_hash"), content=child.page_content, diff --git a/api/models/model.py b/api/models/model.py index a78a91ebd5..9f6d51b315 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -32,9 +32,6 @@ from .engine import db from .enums import CreatorUserRole from .types import StringUUID -if TYPE_CHECKING: - from .workflow import Workflow - class DifySetup(Base): __tablename__ = "dify_setups" diff --git a/api/models/workflow.py b/api/models/workflow.py index 79d96e42dd..d89db6c7da 100644 --- a/api/models/workflow.py +++ b/api/models/workflow.py @@ -42,9 +42,6 @@ from .types import EnumText, StringUUID _logger = logging.getLogger(__name__) -if TYPE_CHECKING: - from models.model import AppMode - class WorkflowType(Enum): """ From 0546351d3e27cf7eca6021783af1826b4b59d1f5 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:34:11 +0800 Subject: [PATCH 34/72] refactor(web): Optimize the interaction effect of ToolTip component in menu items (#23020) (#23023) --- web/app/components/explore/create-app-modal/index.tsx | 2 +- web/app/components/workflow/block-selector/blocks.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/app/components/explore/create-app-modal/index.tsx b/web/app/components/explore/create-app-modal/index.tsx index 7e1e59b51b..e94999db04 100644 --- a/web/app/components/explore/create-app-modal/index.tsx +++ b/web/app/components/explore/create-app-modal/index.tsx @@ -27,7 +27,7 @@ export type CreateAppModalProps = { appIconUrl?: string | null appMode?: string appUseIconAsAnswerIcon?: boolean - max_active_requests: number | null + max_active_requests?: number | null onConfirm: (info: { name: string icon_type: AppIconType diff --git a/web/app/components/workflow/block-selector/blocks.tsx b/web/app/components/workflow/block-selector/blocks.tsx index 4182530a91..27f8847655 100644 --- a/web/app/components/workflow/block-selector/blocks.tsx +++ b/web/app/components/workflow/block-selector/blocks.tsx @@ -70,6 +70,7 @@ const Blocks = ({ key={block.type} position='right' popupClassName='w-[200px]' + needsDelay={false} popupContent={(
Date: Mon, 28 Jul 2025 11:01:38 +0800 Subject: [PATCH 35/72] Rollback Aliyun Trace Icon File (#23027) --- api/core/ops/aliyun_trace/aliyun_trace.py | 20 +- .../icons/src/public/tracing/AliyunIcon.json | 245 +++++++++--------- .../src/public/tracing/AliyunIconBig.json | 184 +++++-------- 3 files changed, 195 insertions(+), 254 deletions(-) diff --git a/api/core/ops/aliyun_trace/aliyun_trace.py b/api/core/ops/aliyun_trace/aliyun_trace.py index 9dd830a023..af0e38f7ef 100644 --- a/api/core/ops/aliyun_trace/aliyun_trace.py +++ b/api/core/ops/aliyun_trace/aliyun_trace.py @@ -139,7 +139,7 @@ class AliyunDataTrace(BaseTraceInstance): start_time=convert_datetime_to_nanoseconds(trace_info.start_time), end_time=convert_datetime_to_nanoseconds(trace_info.end_time), attributes={ - GEN_AI_SESSION_ID: trace_info.metadata.get("conversation_id", ""), + GEN_AI_SESSION_ID: trace_info.metadata.get("conversation_id") or "", GEN_AI_USER_ID: str(user_id), GEN_AI_SPAN_KIND: GenAISpanKind.CHAIN.value, GEN_AI_FRAMEWORK: "dify", @@ -161,12 +161,12 @@ class AliyunDataTrace(BaseTraceInstance): start_time=convert_datetime_to_nanoseconds(trace_info.start_time), end_time=convert_datetime_to_nanoseconds(trace_info.end_time), attributes={ - GEN_AI_SESSION_ID: trace_info.metadata.get("conversation_id", ""), + GEN_AI_SESSION_ID: trace_info.metadata.get("conversation_id") or "", GEN_AI_USER_ID: str(user_id), GEN_AI_SPAN_KIND: GenAISpanKind.LLM.value, GEN_AI_FRAMEWORK: "dify", - GEN_AI_MODEL_NAME: trace_info.metadata.get("ls_model_name", ""), - GEN_AI_SYSTEM: trace_info.metadata.get("ls_provider", ""), + GEN_AI_MODEL_NAME: trace_info.metadata.get("ls_model_name") or "", + GEN_AI_SYSTEM: trace_info.metadata.get("ls_provider") or "", GEN_AI_USAGE_INPUT_TOKENS: str(trace_info.message_tokens), GEN_AI_USAGE_OUTPUT_TOKENS: str(trace_info.answer_tokens), GEN_AI_USAGE_TOTAL_TOKENS: str(trace_info.total_tokens), @@ -386,14 +386,14 @@ class AliyunDataTrace(BaseTraceInstance): GEN_AI_SESSION_ID: trace_info.metadata.get("conversation_id") or "", GEN_AI_SPAN_KIND: GenAISpanKind.LLM.value, GEN_AI_FRAMEWORK: "dify", - GEN_AI_MODEL_NAME: process_data.get("model_name", ""), - GEN_AI_SYSTEM: process_data.get("model_provider", ""), + GEN_AI_MODEL_NAME: process_data.get("model_name") or "", + GEN_AI_SYSTEM: process_data.get("model_provider") or "", GEN_AI_USAGE_INPUT_TOKENS: str(usage_data.get("prompt_tokens", 0)), GEN_AI_USAGE_OUTPUT_TOKENS: str(usage_data.get("completion_tokens", 0)), GEN_AI_USAGE_TOTAL_TOKENS: str(usage_data.get("total_tokens", 0)), GEN_AI_PROMPT: json.dumps(process_data.get("prompts", []), ensure_ascii=False), GEN_AI_COMPLETION: str(outputs.get("text", "")), - GEN_AI_RESPONSE_FINISH_REASON: outputs.get("finish_reason", ""), + GEN_AI_RESPONSE_FINISH_REASON: outputs.get("finish_reason") or "", INPUT_VALUE: json.dumps(process_data.get("prompts", []), ensure_ascii=False), OUTPUT_VALUE: str(outputs.get("text", "")), }, @@ -421,7 +421,7 @@ class AliyunDataTrace(BaseTraceInstance): GEN_AI_USER_ID: str(user_id), GEN_AI_SPAN_KIND: GenAISpanKind.CHAIN.value, GEN_AI_FRAMEWORK: "dify", - INPUT_VALUE: trace_info.workflow_run_inputs.get("sys.query", ""), + INPUT_VALUE: trace_info.workflow_run_inputs.get("sys.query") or "", OUTPUT_VALUE: json.dumps(trace_info.workflow_run_outputs, ensure_ascii=False), }, status=status, @@ -461,8 +461,8 @@ class AliyunDataTrace(BaseTraceInstance): attributes={ GEN_AI_SPAN_KIND: GenAISpanKind.LLM.value, GEN_AI_FRAMEWORK: "dify", - GEN_AI_MODEL_NAME: trace_info.metadata.get("ls_model_name", ""), - GEN_AI_SYSTEM: trace_info.metadata.get("ls_provider", ""), + GEN_AI_MODEL_NAME: trace_info.metadata.get("ls_model_name") or "", + GEN_AI_SYSTEM: trace_info.metadata.get("ls_provider") or "", GEN_AI_PROMPT: json.dumps(trace_info.inputs, ensure_ascii=False), GEN_AI_COMPLETION: json.dumps(trace_info.suggested_question, ensure_ascii=False), INPUT_VALUE: json.dumps(trace_info.inputs, ensure_ascii=False), diff --git a/web/app/components/base/icons/src/public/tracing/AliyunIcon.json b/web/app/components/base/icons/src/public/tracing/AliyunIcon.json index 9a0b89f20a..5cbb52c237 100644 --- a/web/app/components/base/icons/src/public/tracing/AliyunIcon.json +++ b/web/app/components/base/icons/src/public/tracing/AliyunIcon.json @@ -1,131 +1,118 @@ { - "icon": { - "type": "element", - "isRootNode": true, - "name": "svg", - "attributes": { - "xmlns": "http://www.w3.org/2000/svg", - "xmlns:xlink": "http://www.w3.org/1999/xlink", - "fill": "none", - "version": "1.1", - "width": "106", - "height": "16", - "viewBox": "0 0 106 16" - }, - "children": [ - { - "type": "element", - "name": "defs", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "clipPath", - "attributes": { - "id": "master_svg0_36_00924" - }, - "children": [ - { - "type": "element", - "name": "rect", - "attributes": { - "x": "0", - "y": "0", - "width": "19", - "height": "16", - "rx": "0" - }, - "children": [] - } - ] - } - ] - }, - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "g", - "attributes": { - "clip-path": "url(#master_svg0_36_00924)" - }, - "children": [ - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M4.06862,14.6667C3.79213,14.6667,3.45463,14.5688,3.05614,14.373C2.97908,14.3351,2.92692,14.3105,2.89968,14.2992C2.33193,14.0628,1.82911,13.7294,1.39123,13.2989C0.463742,12.3871,0,11.2874,0,10C0,8.71258,0.463742,7.61293,1.39123,6.70107C2.16172,5.94358,3.06404,5.50073,4.09819,5.37252C4.23172,3.98276,4.81755,2.77756,5.85569,1.75693C7.04708,0.585642,8.4857,0,10.1716,0C11.5256,0,12.743,0.396982,13.8239,1.19095C14.8847,1.97019,15.61,2.97855,16,4.21604L14.7045,4.61063C14.4016,3.64918,13.8374,2.86532,13.0121,2.25905C12.1719,1.64191,11.2251,1.33333,10.1716,1.33333C8.8602,1.33333,7.74124,1.7888,6.81467,2.69974C5.88811,3.61067,5.42483,4.71076,5.42483,6L5.42483,6.66667L4.74673,6.66667C3.81172,6.66667,3.01288,6.99242,2.35021,7.64393C1.68754,8.2954,1.35621,9.08076,1.35621,10C1.35621,10.9192,1.68754,11.7046,2.35021,12.3561C2.66354,12.6641,3.02298,12.9026,3.42852,13.0714C3.48193,13.0937,3.55988,13.13,3.66237,13.1803C3.87004,13.2823,4.00545,13.3333,4.06862,13.3333L4.06862,14.6667Z", - "fill-rule": "evenodd", - "fill": "#000000", - "fill-opacity": "1", - "style": "mix-blend-mode:passthrough" - }, - "children": [] - } - ] - }, - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M13.458613505859375,7.779393492279053C12.975613505859375,7.717463492279053,12.484813505859375,7.686503492279053,11.993983505859376,7.686503492279053C11.152583505859376,7.686503492279053,10.303403505859375,7.779393492279053,9.493183505859374,7.941943492279053C8.682953505859375,8.104503492279052,7.903893505859375,8.359943492279053,7.155983505859375,8.654083492279053C6.657383505859375,8.870823492279053,6.158783505859375,9.128843492279053,5.660181505859375,9.428153492279053C5.332974751859375,9.621673492279053,5.239486705859375,10.070633492279054,5.434253505859375,10.395743492279053L7.413073505859375,13.298533492279052C7.639003505859375,13.623603492279052,8.090863505859375,13.716463492279052,8.418073505859375,13.523003492279052C8.547913505859375,13.435263492279052,8.763453505859374,13.326893492279053,9.064693505859374,13.197863492279053C9.516553505859374,13.004333492279052,9.976203505859374,12.872733492279053,10.459223505859375,12.779863492279052C10.942243505859375,12.679263492279052,11.433053505859375,12.617333492279052,11.955023505859375,12.617333492279052L13.380683505859375,7.810353492279052L13.458613505859375,7.779393492279053ZM15.273813505859374,8.135463492279053L15.016753505859375,5.333333492279053L13.458613505859375,7.787133492279053C13.817013505859375,7.818093492279052,14.144213505859375,7.880023492279053,14.494743505859375,7.949683492279053C14.494743505859375,7.944523492279053,14.754433505859375,8.006453492279054,15.273813505859374,8.135463492279053ZM12.064083505859376,12.648273492279053L11.378523505859375,14.970463492279054L12.515943505859376,16.00003349227905L14.074083505859376,15.643933492279054L14.525943505859376,13.027603492279052C14.198743505859374,12.934663492279054,13.879283505859375,12.834063492279054,13.552083505859375,12.772133492279053C13.069083505859375,12.717933492279052,12.578283505859375,12.648273492279053,12.064083505859376,12.648273492279053ZM18.327743505859374,9.428153492279053C17.829143505859374,9.128843492279053,17.330543505859374,8.870823492279053,16.831943505859375,8.654083492279053C16.348943505859374,8.460573492279053,15.826943505859376,8.267053492279054,15.305013505859375,8.135463492279053L15.305013505859375,8.267053492279054L14.463613505859374,13.043063492279053C14.596083505859376,13.105003492279053,14.759683505859375,13.135933492279053,14.884283505859376,13.205603492279053C15.185523505859376,13.334623492279052,15.401043505859375,13.443003492279052,15.530943505859375,13.530733492279053C15.858143505859376,13.724263492279054,16.341143505859375,13.623603492279052,16.535943505859375,13.306263492279053L18.514743505859375,10.403483492279053C18.779643505859376,10.039673492279054,18.686143505859377,9.621673492279053,18.327743505859374,9.428153492279053Z", - "fill": "#000000", - "fill-opacity": "1", - "style": "mix-blend-mode:passthrough" - }, - "children": [] - } - ] - } - ] - } - ] - }, - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M36.174,12.958L35.278,14.358Q31.344,11.964,29.958,8.884Q29.258,10.48,27.9,11.81Q26.542,13.14,24.624,14.372L23.644,12.986Q28.642,10.186,29.034,6.378L24.12,6.378L24.12,4.95L29.076,4.95L29.076,1.743999L30.616,1.7999990000000001L30.616,4.95L35.614000000000004,4.95L35.614000000000004,6.378L30.588,6.378L30.573999999999998,6.56Q31.078,8.646,32.408,10.144Q33.738,11.642,36.174,12.958ZM44.658,4.922000000000001L43.454,4.922000000000001L43.454,4.152L41.745999999999995,4.152L41.745999999999995,2.948L43.454,2.948L43.454,1.716L44.658,1.771999L44.658,2.948L46.492000000000004,2.948L46.492000000000004,1.716L47.682,1.771999L47.682,2.948L49.586,2.948L49.586,4.152L47.682,4.152L47.682,4.922000000000001L46.492000000000004,4.922000000000001L46.492000000000004,4.152L44.658,4.152L44.658,4.922000000000001ZM46.519999999999996,11.474Q47.010000000000005,12.146,47.870999999999995,12.615Q48.732,13.084,50.104,13.364L49.726,14.624Q46.884,13.924,45.61,12.286Q45.106,13.042,44.111999999999995,13.616Q43.117999999999995,14.19,41.507999999999996,14.638L41.004000000000005,13.42Q42.488,13.098,43.349000000000004,12.615Q44.21,12.132,44.574,11.474L41.522,11.474L41.522,10.368L44.896,10.368Q44.91,10.312,44.91,10.214Q44.924,10.032,44.924,9.542L42.152,9.542L42.152,8.492L41.284,9.108Q40.989999999999995,8.464,40.5,7.708L40.5,14.358L39.282,14.358L39.282,8.268Q38.61,9.99,37.952,11.082L36.944,10.074Q37.532,9.122,38.106,7.939Q38.68,6.756,39.058,5.664L37.658,5.664L37.658,4.390000000000001L39.282,4.390000000000001L39.282,1.7579989999999999L40.5,1.814L40.5,4.390000000000001L41.816,4.390000000000001L41.816,5.664L40.5,5.664L40.5,7.134L41.116,6.658Q41.704,7.386,42.152,8.24L42.152,5.244L48.97,5.244L48.97,9.542L46.226,9.542Q46.198,10.018,46.198,10.214L46.198,10.368L49.641999999999996,10.368L49.641999999999996,11.474L46.519999999999996,11.474ZM47.85,6.952L47.85,6.28L43.314,6.28L43.314,6.952L47.85,6.952ZM47.85,7.862L43.314,7.862L43.314,8.506L47.85,8.506L47.85,7.862ZM59.904,9.388L59.512,8.114L60.548,8.030000000000001Q61.066,7.988,61.234,7.855Q61.402,7.722,61.402,7.274L61.402,2.01L62.704,2.066L62.704,7.624Q62.704,8.268,62.55,8.604Q62.396,8.940000000000001,62.025,9.094Q61.654,9.248,60.94,9.304L59.904,9.388ZM51.546,9.276Q52.274,8.52,52.596000000000004,7.988Q52.918,7.456,53.016,6.784L51.518,6.784L51.518,5.566L53.1,5.566L53.1,5.188L53.1,3.718L51.867999999999995,3.718L51.867999999999995,2.458L58.448,2.458L58.448,3.718L57.244,3.718L57.244,5.566L58.728,5.566L58.728,6.784L57.244,6.784L57.244,9.206L55.928,9.206L55.928,6.784L54.332,6.784Q54.22,7.792,53.842,8.52Q53.464,9.248,52.61,10.102L51.546,9.276ZM59.092,2.724L60.366,2.7800000000000002L60.366,7.61L59.092,7.61L59.092,2.724ZM54.402,3.718L54.402,5.202L54.402,5.566L55.928,5.566L55.928,3.718L54.402,3.718ZM58.126,11.348L58.126,12.86L63.53,12.86L63.53,14.106L51.322,14.106L51.322,12.86L56.74,12.86L56.74,11.348L52.75,11.348L52.75,10.13L56.74,10.13L56.74,9.332L58.126,9.388L58.126,10.13L62.13,10.13L62.13,11.348L58.126,11.348ZM77.39,2.528L77.39,3.9L75.64,3.9L75.64,12.272Q75.64,13.098,75.465,13.49Q75.28999999999999,13.882,74.84899999999999,14.05Q74.408,14.218,73.47,14.302L72.56,14.386L72.126,13L73.19,12.916Q73.68,12.874,73.89699999999999,12.79Q74.114,12.706,74.184,12.51Q74.25399999999999,12.314,74.25399999999999,11.88L74.25399999999999,3.9L65.042,3.9L65.042,2.528L77.39,2.528ZM66.512,5.524L72.26599999999999,5.524L72.26599999999999,11.712L66.512,11.712L66.512,5.524ZM67.842,10.354L70.95,10.354L70.95,6.896L67.842,6.896L67.842,10.354ZM88.772,3.648L85.118,3.648L85.118,10.298L83.80199999999999,10.298L83.80199999999999,2.332L90.088,2.332L90.088,10.27L88.772,10.27L88.772,3.648ZM82.668,12.65Q82.23400000000001,11.712,81.632,10.522Q80.862,12.146,79.518,14.092L78.45400000000001,13.182Q80.036,11.068,80.89,9.024Q79.7,6.728,79,5.552L80.02199999999999,4.894Q80.48400000000001,5.622,81.47800000000001,7.386Q81.87,6.042,82.122,4.2780000000000005L79.02799999999999,4.2780000000000005L79.02799999999999,2.934L83.47999999999999,2.934L83.47999999999999,4.2780000000000005Q83.144,6.784,82.318,8.940000000000001Q83.158,10.508,83.774,11.782L82.668,12.65ZM91.166,11.264Q91.124,12.104,91.04,12.636Q90.956,13.28,90.802,13.602Q90.648,13.924,90.326,14.064Q90.004,14.204,89.374,14.204L88.142,14.204Q87.344,14.204,87.029,13.868Q86.714,13.532,86.714,12.636L86.714,11.11Q86.21000000000001,12.104,85.356,12.972Q84.50200000000001,13.84,83.2,14.708L82.332,13.56Q83.886,12.608,84.691,11.705Q85.49600000000001,10.802,85.804,9.745Q86.112,8.687999999999999,86.168,7.05L86.21000000000001,4.306L87.61,4.362L87.568,7.218Q87.526,8.366,87.344,9.276L88.016,9.304L88.016,12.16Q88.016,12.608,88.128,12.734Q88.24,12.86,88.632,12.86L89.108,12.86Q89.486,12.86,89.619,12.741Q89.752,12.622,89.808,12.174Q89.892,11.362,89.892,10.788L91.166,11.264ZM93.56,1.884Q94.036,2.206,94.68,2.759Q95.324,3.312,95.702,3.704L94.904,4.795999999999999Q94.596,4.418,93.938,3.809Q93.28,3.2,92.832,2.85L93.56,1.884ZM102.1,12.93Q102.478,12.888,102.653,12.832Q102.828,12.776,102.898,12.636Q102.968,12.496,102.968,12.188L102.968,1.981999L104.06,2.0380000000000003L104.06,12.608Q104.06,13.238,103.948,13.546Q103.836,13.854,103.521,13.994Q103.206,14.134,102.534,14.19L101.75,14.246L101.372,12.986L102.1,12.93ZM95.702,10.774L95.702,2.5140000000000002L100.168,2.5140000000000002L100.168,10.732L99.006,10.732L99.006,3.732L96.836,3.732L96.836,10.774L95.702,10.774ZM101.008,11.152L101.008,3.2L102.1,3.256L102.1,11.152L101.008,11.152ZM94.652,13.364Q95.856,12.482,96.43,11.789Q97.004,11.096,97.2,10.277Q97.396,9.458,97.396,8.058L97.396,4.362L98.488,4.418L98.488,8.058Q98.488,9.738,98.201,10.809Q97.914,11.88,97.277,12.664Q96.64,13.448,95.45,14.344L94.652,13.364ZM93.07,5.034Q93.546,5.37,94.197,5.937Q94.848,6.504,95.282,6.952L94.484,8.072Q94.078,7.61,93.427,7.015Q92.776,6.42,92.258,6.028L93.07,5.034ZM92.524,13.742Q92.748,13.126,93.266,11.278Q93.784,9.43,93.896,8.814L94.498,9.01L95.072,9.206Q94.89,10.032,94.421,11.733Q93.952,13.434,93.714,14.162L92.524,13.742ZM98.74,10.858Q99.888,11.908,100.714,12.958L99.888,13.868Q99.356,13.154,98.943,12.671Q98.53,12.188,97.984,11.684L98.74,10.858Z", - "fill": "#000000", - "fill-opacity": "1" - }, - "children": [] - } - ] - } - ] - } - ] - } - ] - }, - "name": "AliyunIcon" + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "fill": "none", + "version": "1.1", + "width": "65", + "height": "16", + "viewBox": "0 0 65 16" + }, + "children": [ + { + "type": "element", + "name": "defs", + "children": [ + { + "type": "element", + "name": "clipPath", + "attributes": { + "id": "master_svg0_42_34281" + }, + "children": [ + { + "type": "element", + "name": "rect", + "attributes": { + "x": "0", + "y": "0", + "width": "19", + "height": "16", + "rx": "0" + } + } + ] + } + ] + }, + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "g", + "attributes": { + "clip-path": "url(#master_svg0_42_34281)" + }, + "children": [ + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M4.06862,14.6667C3.79213,14.6667,3.45463,14.5688,3.05614,14.373C2.97908,14.3351,2.92692,14.3105,2.89968,14.2992C2.33193,14.0628,1.82911,13.7294,1.39123,13.2989C0.463742,12.3871,0,11.2874,0,10C0,8.71258,0.463742,7.61293,1.39123,6.70107C2.16172,5.94358,3.06404,5.50073,4.09819,5.37252C4.23172,3.98276,4.81755,2.77756,5.85569,1.75693C7.04708,0.585642,8.4857,0,10.1716,0C11.5256,0,12.743,0.396982,13.8239,1.19095C14.8847,1.97019,15.61,2.97855,16,4.21604L14.7045,4.61063C14.4016,3.64918,13.8374,2.86532,13.0121,2.25905C12.1719,1.64191,11.2251,1.33333,10.1716,1.33333C8.8602,1.33333,7.74124,1.7888,6.81467,2.69974C5.88811,3.61067,5.42483,4.71076,5.42483,6L5.42483,6.66667L4.74673,6.66667C3.81172,6.66667,3.01288,6.99242,2.35021,7.64393C1.68754,8.2954,1.35621,9.08076,1.35621,10C1.35621,10.9192,1.68754,11.7046,2.35021,12.3561C2.66354,12.6641,3.02298,12.9026,3.42852,13.0714C3.48193,13.0937,3.55988,13.13,3.66237,13.1803C3.87004,13.2823,4.00545,13.3333,4.06862,13.3333L4.06862,14.6667Z", + "fill-rule": "evenodd", + "fill": "#FF6A00", + "fill-opacity": "1" + } + } + ] + }, + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M13.458613505859375,7.779393492279053C12.975613505859375,7.717463492279053,12.484813505859375,7.686503492279053,11.993983505859376,7.686503492279053C11.152583505859376,7.686503492279053,10.303403505859375,7.779393492279053,9.493183505859374,7.941943492279053C8.682953505859375,8.104503492279052,7.903893505859375,8.359943492279053,7.155983505859375,8.654083492279053C6.657383505859375,8.870823492279053,6.158783505859375,9.128843492279053,5.660181505859375,9.428153492279053C5.332974751859375,9.621673492279053,5.239486705859375,10.070633492279054,5.434253505859375,10.395743492279053L7.413073505859375,13.298533492279052C7.639003505859375,13.623603492279052,8.090863505859375,13.716463492279052,8.418073505859375,13.523003492279052C8.547913505859375,13.435263492279052,8.763453505859374,13.326893492279053,9.064693505859374,13.197863492279053C9.516553505859374,13.004333492279052,9.976203505859374,12.872733492279053,10.459223505859375,12.779863492279052C10.942243505859375,12.679263492279052,11.433053505859375,12.617333492279052,11.955023505859375,12.617333492279052L13.380683505859375,7.810353492279052L13.458613505859375,7.779393492279053ZM15.273813505859374,8.135463492279053L15.016753505859375,5.333333492279053L13.458613505859375,7.787133492279053C13.817013505859375,7.818093492279052,14.144213505859375,7.880023492279053,14.494743505859375,7.949683492279053C14.494743505859375,7.944523492279053,14.754433505859375,8.006453492279054,15.273813505859374,8.135463492279053ZM12.064083505859376,12.648273492279053L11.378523505859375,14.970463492279054L12.515943505859376,16.00003349227905L14.074083505859376,15.643933492279054L14.525943505859376,13.027603492279052C14.198743505859374,12.934663492279054,13.879283505859375,12.834063492279054,13.552083505859375,12.772133492279053C13.069083505859375,12.717933492279052,12.578283505859375,12.648273492279053,12.064083505859376,12.648273492279053ZM18.327743505859374,9.428153492279053C17.829143505859374,9.128843492279053,17.330543505859374,8.870823492279053,16.831943505859375,8.654083492279053C16.348943505859374,8.460573492279053,15.826943505859376,8.267053492279054,15.305013505859375,8.135463492279053L15.305013505859375,8.267053492279054L14.463613505859374,13.043063492279053C14.596083505859376,13.105003492279053,14.759683505859375,13.135933492279053,14.884283505859376,13.205603492279053C15.185523505859376,13.334623492279052,15.401043505859375,13.443003492279052,15.530943505859375,13.530733492279053C15.858143505859376,13.724263492279054,16.341143505859375,13.623603492279052,16.535943505859375,13.306263492279053L18.514743505859375,10.403483492279053C18.779643505859376,10.039673492279054,18.686143505859377,9.621673492279053,18.327743505859374,9.428153492279053Z", + "fill": "#FF6A00", + "fill-opacity": "1" + } + } + ] + } + ] + } + ] + }, + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M25.044,2.668L34.676,2.668L34.676,4.04L25.044,4.04L25.044,2.668ZM29.958,7.82Q29.258,9.066,28.355,10.41Q27.451999999999998,11.754,26.92,12.3L32.506,11.782Q31.442,10.158,30.84,9.346L32.058,8.562000000000001Q32.786,9.5,33.843,11.012Q34.9,12.524,35.516,13.546L34.214,14.526Q33.891999999999996,13.966,33.346000000000004,13.098Q32.016,13.182,29.734,13.378Q27.451999999999998,13.574,25.87,13.742L25.31,13.812L24.834,13.882L24.414,12.468Q24.708,12.37,24.862000000000002,12.265Q25.016,12.16,25.121,12.069Q25.226,11.978,25.268,11.936Q25.912,11.32,26.724,10.165Q27.536,9.01,28.208,7.82L23.854,7.82L23.854,6.434L35.866,6.434L35.866,7.82L29.958,7.82ZM42.656,7.414L42.656,8.576L41.354,8.576L41.354,1.814L42.656,1.87L42.656,7.036Q43.314,5.846,43.888000000000005,4.369Q44.462,2.892,44.714,1.6600000000000001L46.086,1.981999Q45.96,2.612,45.722,3.41L49.6,3.41L49.6,4.74L45.274,4.74Q44.616,6.56,43.706,8.128L42.656,7.414ZM38.596000000000004,2.346L39.884,2.402L39.884,8.212L38.596000000000004,8.212L38.596000000000004,2.346ZM46.184,4.964Q46.688,5.356,47.5,6.175Q48.312,6.994,48.788,7.582L47.751999999999995,8.59Q47.346000000000004,8.072,46.576,7.274Q45.806,6.476,45.204,5.902L46.184,4.964ZM48.41,9.01L48.41,12.706L49.894,12.706L49.894,13.966L37.391999999999996,13.966L37.391999999999996,12.706L38.848,12.706L38.848,9.01L48.41,9.01ZM41.676,10.256L40.164,10.256L40.164,12.706L41.676,12.706L41.676,10.256ZM42.908,12.706L44.364000000000004,12.706L44.364000000000004,10.256L42.908,10.256L42.908,12.706ZM45.582,12.706L47.108000000000004,12.706L47.108000000000004,10.256L45.582,10.256L45.582,12.706ZM54.906,7.456L55.116,8.394L54.178,8.814L54.178,12.818Q54.178,13.434,54.031,13.735Q53.884,14.036,53.534,14.162Q53.184,14.288,52.456,14.358L51.867999999999995,14.414L51.476,13.084L52.162,13.028Q52.512,13,52.652,12.958Q52.792,12.916,52.841,12.797Q52.89,12.678,52.89,12.384L52.89,9.36Q51.980000000000004,9.724,51.322,9.948L51.013999999999996,8.576Q51.798,8.324,52.89,7.876L52.89,5.524L51.42,5.524L51.42,4.166L52.89,4.166L52.89,1.7579989999999999L54.178,1.814L54.178,4.166L55.214,4.166L55.214,5.524L54.178,5.524L54.178,7.316L54.808,7.022L54.906,7.456ZM56.894,4.5440000000000005L56.894,6.098L55.564,6.098L55.564,3.256L58.686,3.256Q58.42,2.346,58.266,1.9260000000000002L59.624,1.7579989999999999Q59.848,2.276,60.142,3.256L63.25,3.256L63.25,6.098L61.962,6.098L61.962,4.5440000000000005L56.894,4.5440000000000005ZM59.008,6.322Q58.392,6.938,57.685,7.512Q56.978,8.086,55.956,8.841999999999999L55.242,7.764Q56.824,6.728,58.126,5.37L59.008,6.322ZM60.422,5.37Q61.024,5.776,62.095,6.581Q63.166,7.386,63.656,7.806L62.942,8.982Q62.368,8.45,61.332,7.652Q60.296,6.854,59.666,6.434L60.422,5.37ZM62.592,10.256L60.044,10.256L60.044,12.566L63.572,12.566L63.572,13.826L55.144,13.826L55.144,12.566L58.63,12.566L58.63,10.256L56.054,10.256L56.054,8.982L62.592,8.982L62.592,10.256Z", + "fill": "#FF6A00", + "fill-opacity": "1" + } + } + ] + } + ] + } + ] + } + ] + }, + "name": "AliyunIcon" } diff --git a/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json b/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json index c8093ba660..ea60744daf 100644 --- a/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json +++ b/web/app/components/base/icons/src/public/tracing/AliyunIconBig.json @@ -1,117 +1,71 @@ { - "icon": { - "type": "element", - "isRootNode": true, - "name": "svg", - "attributes": { - "xmlns": "http://www.w3.org/2000/svg", - "xmlns:xlink": "http://www.w3.org/1999/xlink", - "fill": "none", - "version": "1.1", - "width": "159", - "height": "24", - "viewBox": "0 0 159 24" - }, - "children": [ - { - "type": "element", - "name": "defs", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "clipPath", - "attributes": { - "id": "master_svg0_42_18775" - }, - "children": [ - { - "type": "element", - "name": "rect", - "attributes": { - "x": "0", - "y": "0", - "width": "28.5", - "height": "24", - "rx": "0" - }, - "children": [] - } - ] - } - ] - }, - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "g", - "attributes": { - "clip-path": "url(#master_svg0_42_18775)" - }, - "children": [ - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M6.10294,22C5.68819,22,5.18195,21.8532,4.58421,21.5595C4.46861,21.5027,4.39038,21.4658,4.34951,21.4488C3.49789,21.0943,2.74367,20.5941,2.08684,19.9484C0.695613,18.5806,0,16.9311,0,15C0,13.0689,0.695612,11.4194,2.08684,10.0516C3.24259,8.91537,4.59607,8.2511,6.14728,8.05878C6.34758,5.97414,7.22633,4.16634,8.78354,2.63539C10.5706,0.878463,12.7286,0,15.2573,0C17.2884,0,19.1146,0.595472,20.7358,1.78642C22.327,2.95528,23.4151,4.46783,24,6.32406L22.0568,6.91594C21.6024,5.47377,20.7561,4.29798,19.5181,3.38858C18.2579,2.46286,16.8377,2,15.2573,2C13.2903,2,11.6119,2.6832,10.222,4.04961C8.83217,5.41601,8.13725,7.06614,8.13725,9L8.13725,10L7.12009,10C5.71758,10,4.51932,10.4886,3.52532,11.4659C2.53132,12.4431,2.03431,13.6211,2.03431,15C2.03431,16.3789,2.53132,17.5569,3.52532,18.5341C3.99531,18.9962,4.53447,19.3538,5.14278,19.6071C5.2229,19.6405,5.33983,19.695,5.49356,19.7705C5.80505,19.9235,6.00818,20,6.10294,20L6.10294,22Z", - "fill-rule": "evenodd", - "fill": "#000000", - "fill-opacity": "1", - "style": "mix-blend-mode:passthrough" - }, - "children": [] - } - ] - }, - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M20.18796103515625,11.66909C19.46346103515625,11.5762,18.72726103515625,11.52975,17.991011035156248,11.52975C16.728921035156247,11.52975,15.45515103515625,11.66909,14.23981103515625,11.91292C13.02447103515625,12.156749999999999,11.85588103515625,12.539909999999999,10.73402103515625,12.98113C9.98612103515625,13.306239999999999,9.23822103515625,13.69327,8.49031803515625,14.14223C7.99950790415625,14.43251,7.85927603515625,15.10595,8.15142503515625,15.59361L11.11966103515625,19.9478C11.45855103515625,20.4354,12.13634103515625,20.5747,12.627151035156249,20.2845C12.821921035156251,20.152900000000002,13.14523103515625,19.990299999999998,13.59708103515625,19.796799999999998C14.27487103515625,19.506500000000003,14.964341035156249,19.3091,15.68887103515625,19.169800000000002C16.413401035156248,19.018900000000002,17.14962103515625,18.926000000000002,17.93258103515625,18.926000000000002L20.071061035156248,11.715530000000001L20.18796103515625,11.66909ZM22.91076103515625,12.20319L22.525161035156252,8L20.18796103515625,11.6807C20.72556103515625,11.72714,21.21636103515625,11.82003,21.74216103515625,11.92453C21.74216103515625,11.91679,22.13166103515625,12.00968,22.91076103515625,12.20319ZM18.09616103515625,18.9724L17.06782103515625,22.4557L18.773961035156248,24L21.11116103515625,23.465899999999998L21.788961035156248,19.5414C21.298161035156248,19.402,20.81896103515625,19.2511,20.32816103515625,19.1582C19.60366103515625,19.076900000000002,18.86746103515625,18.9724,18.09616103515625,18.9724ZM27.49166103515625,14.14223C26.74376103515625,13.69327,25.99586103515625,13.306239999999999,25.24796103515625,12.98113C24.52346103515625,12.69086,23.74046103515625,12.40058,22.95756103515625,12.20319L22.95756103515625,12.40058L21.69546103515625,19.5646C21.89416103515625,19.6575,22.139561035156248,19.7039,22.32646103515625,19.8084C22.77836103515625,20.0019,23.101661035156248,20.1645,23.29646103515625,20.2961C23.78726103515625,20.586399999999998,24.51176103515625,20.4354,24.80396103515625,19.959400000000002L27.77216103515625,15.605229999999999C28.16946103515625,15.05951,28.02926103515625,14.43251,27.49166103515625,14.14223Z", - "fill": "#000000", - "fill-opacity": "1", - "style": "mix-blend-mode:passthrough" - }, - "children": [] - } - ] - } - ] - }, - { - "type": "element", - "name": "g", - "attributes": {}, - "children": [ - { - "type": "element", - "name": "path", - "attributes": { - "d": "M53.295,19.1189814453125L51.951,21.2189814453125Q46.05,17.6279814453125,43.971000000000004,13.0079814453125Q42.921,15.4019814453125,40.884,17.3969814453125Q38.847,19.3919814453125,35.97,21.2399814453125L34.5,19.1609814453125Q41.997,14.9609814453125,42.585,9.2489814453125L35.214,9.2489814453125L35.214,7.1069814453125L42.647999999999996,7.1069814453125L42.647999999999996,2.2979812453125L44.958,2.3819804453125L44.958,7.1069814453125L52.455,7.1069814453125L52.455,9.2489814453125L44.916,9.2489814453125L44.894999999999996,9.5219814453125Q45.650999999999996,12.6509814453125,47.646,14.8979814453125Q49.641,17.1449814453125,53.295,19.1189814453125ZM66.021,7.0649814453125L64.215,7.0649814453125L64.215,5.9099814453125L61.653,5.9099814453125L61.653,4.1039814453125L64.215,4.1039814453125L64.215,2.2559814453125L66.021,2.3399810453125L66.021,4.1039814453125L68.77199999999999,4.1039814453125L68.77199999999999,2.2559814453125L70.557,2.3399810453125L70.557,4.1039814453125L73.413,4.1039814453125L73.413,5.9099814453125L70.557,5.9099814453125L70.557,7.0649814453125L68.77199999999999,7.0649814453125L68.77199999999999,5.9099814453125L66.021,5.9099814453125L66.021,7.0649814453125ZM68.814,16.8929814453125Q69.549,17.9009814453125,70.84049999999999,18.6044814453125Q72.132,19.3079814453125,74.19,19.7279814453125L73.62299999999999,21.6179814453125Q69.36,20.5679814453125,67.449,18.1109814453125Q66.693,19.2449814453125,65.202,20.1059814453125Q63.711,20.9669814453125,61.296,21.6389814453125L60.54,19.8119814453125Q62.766,19.3289814453125,64.0575,18.6044814453125Q65.349,17.879981445312502,65.895,16.8929814453125L61.317,16.8929814453125L61.317,15.2339814453125L66.378,15.2339814453125Q66.399,15.1499814453125,66.399,15.0029814453125Q66.42,14.7299814453125,66.42,13.9949814453125L62.262,13.9949814453125L62.262,12.4199814453125L60.96,13.3439814453125Q60.519,12.3779814453125,59.784,11.2439814453125L59.784,21.2189814453125L57.957,21.2189814453125L57.957,12.0839814453125Q56.949,14.6669814453125,55.962,16.3049814453125L54.45,14.7929814453125Q55.332,13.3649814453125,56.193,11.5904814453125Q57.054,9.815981445312499,57.620999999999995,8.1779814453125L55.521,8.1779814453125L55.521,6.2669814453125L57.957,6.2669814453125L57.957,2.3189811453125L59.784,2.4029824453125L59.784,6.2669814453125L61.757999999999996,6.2669814453125L61.757999999999996,8.1779814453125L59.784,8.1779814453125L59.784,10.3829814453125L60.708,9.6689814453125Q61.59,10.7609814453125,62.262,12.0419814453125L62.262,7.5479814453125L72.489,7.5479814453125L72.489,13.9949814453125L68.37299999999999,13.9949814453125Q68.331,14.7089814453125,68.331,15.0029814453125L68.331,15.2339814453125L73.497,15.2339814453125L73.497,16.8929814453125L68.814,16.8929814453125ZM70.809,10.1099814453125L70.809,9.1019814453125L64.005,9.1019814453125L64.005,10.1099814453125L70.809,10.1099814453125ZM70.809,11.4749814453125L64.005,11.4749814453125L64.005,12.4409814453125L70.809,12.4409814453125L70.809,11.4749814453125ZM88.89,13.7639814453125L88.30199999999999,11.8529814453125L89.856,11.7269814453125Q90.63300000000001,11.6639814453125,90.88499999999999,11.4644814453125Q91.137,11.2649814453125,91.137,10.5929814453125L91.137,2.6969814453125L93.09,2.7809824453125L93.09,11.1179814453125Q93.09,12.0839814453125,92.85900000000001,12.5879814453125Q92.628,13.0919814453125,92.0715,13.3229814453125Q91.515,13.5539814453125,90.444,13.6379814453125L88.89,13.7639814453125ZM76.35300000000001,13.5959814453125Q77.445,12.4619814453125,77.928,11.6639814453125Q78.411,10.8659814453125,78.55799999999999,9.8579814453125L76.311,9.8579814453125L76.311,8.0309814453125L78.684,8.0309814453125L78.684,7.4639814453125L78.684,5.2589814453125L76.836,5.2589814453125L76.836,3.3689814453125L86.706,3.3689814453125L86.706,5.2589814453125L84.9,5.2589814453125L84.9,8.0309814453125L87.126,8.0309814453125L87.126,9.8579814453125L84.9,9.8579814453125L84.9,13.4909814453125L82.926,13.4909814453125L82.926,9.8579814453125L80.532,9.8579814453125Q80.364,11.3699814453125,79.797,12.4619814453125Q79.22999999999999,13.5539814453125,77.949,14.8349814453125L76.35300000000001,13.5959814453125ZM87.672,3.7679814453125L89.583,3.8519814453125L89.583,11.0969814453125L87.672,11.0969814453125L87.672,3.7679814453125ZM80.637,5.2589814453125L80.637,7.4849814453125L80.637,8.0309814453125L82.926,8.0309814453125L82.926,5.2589814453125L80.637,5.2589814453125ZM86.223,16.7039814453125L86.223,18.9719814453125L94.32900000000001,18.9719814453125L94.32900000000001,20.8409814453125L76.017,20.8409814453125L76.017,18.9719814453125L84.144,18.9719814453125L84.144,16.7039814453125L78.15899999999999,16.7039814453125L78.15899999999999,14.8769814453125L84.144,14.8769814453125L84.144,13.6799814453125L86.223,13.7639814453125L86.223,14.8769814453125L92.229,14.8769814453125L92.229,16.7039814453125L86.223,16.7039814453125ZM115.119,3.4739814453125L115.119,5.5319814453125L112.494,5.5319814453125L112.494,18.0899814453125Q112.494,19.3289814453125,112.2315,19.9169814453125Q111.969,20.5049814453125,111.3075,20.7569814453125Q110.646,21.0089814453125,109.239,21.1349814453125L107.874,21.2609814453125L107.223,19.1819814453125L108.819,19.0559814453125Q109.554,18.9929814453125,109.8795,18.8669814453125Q110.205,18.7409814453125,110.31,18.4469814453125Q110.415,18.1529814453125,110.415,17.501981445312502L110.415,5.5319814453125L96.59700000000001,5.5319814453125L96.59700000000001,3.4739814453125L115.119,3.4739814453125ZM98.802,7.9679814453125L107.433,7.9679814453125L107.433,17.2499814453125L98.802,17.2499814453125L98.802,7.9679814453125ZM100.797,15.2129814453125L105.459,15.2129814453125L105.459,10.0259814453125L100.797,10.0259814453125L100.797,15.2129814453125ZM132.192,5.1539814453125L126.711,5.1539814453125L126.711,15.1289814453125L124.737,15.1289814453125L124.737,3.1799814453125L134.166,3.1799814453125L134.166,15.0869814453125L132.192,15.0869814453125L132.192,5.1539814453125ZM123.036,18.6569814453125Q122.385,17.2499814453125,121.482,15.4649814453125Q120.327,17.9009814453125,118.311,20.8199814453125L116.715,19.4549814453125Q119.088,16.2839814453125,120.369,13.2179814453125Q118.584,9.7739814453125,117.534,8.0099814453125L119.067,7.0229814453125Q119.76,8.1149814453125,121.251,10.7609814453125Q121.839,8.7449814453125,122.217,6.0989814453125L117.576,6.0989814453125L117.576,4.0829814453125L124.254,4.0829814453125L124.254,6.0989814453125Q123.75,9.8579814453125,122.511,13.0919814453125Q123.771,15.4439814453125,124.695,17.3549814453125L123.036,18.6569814453125ZM135.78300000000002,16.5779814453125Q135.72,17.8379814453125,135.594,18.6359814453125Q135.46800000000002,19.6019814453125,135.237,20.0849814453125Q135.006,20.5679814453125,134.523,20.7779814453125Q134.04000000000002,20.9879814453125,133.095,20.9879814453125L131.247,20.9879814453125Q130.05,20.9879814453125,129.5775,20.4839814453125Q129.10500000000002,19.9799814453125,129.10500000000002,18.6359814453125L129.10500000000002,16.3469814453125Q128.349,17.8379814453125,127.068,19.1399814453125Q125.787,20.4419814453125,123.834,21.7439814453125L122.532,20.0219814453125Q124.863,18.5939814453125,126.0705,17.2394814453125Q127.278,15.8849814453125,127.74,14.2994814453125Q128.202,12.7139814453125,128.286,10.2569814453125L128.349,6.1409814453125L130.449,6.224981445312499L130.386,10.5089814453125Q130.32299999999998,12.2309814453125,130.05,13.5959814453125L131.058,13.6379814453125L131.058,17.9219814453125Q131.058,18.5939814453125,131.226,18.7829814453125Q131.394,18.9719814453125,131.982,18.9719814453125L132.696,18.9719814453125Q133.263,18.9719814453125,133.4625,18.7934814453125Q133.662,18.6149814453125,133.74599999999998,17.942981445312498Q133.872,16.7249814453125,133.872,15.8639814453125L135.78300000000002,16.5779814453125ZM139.374,2.5079814453125Q140.088,2.9909814453125,141.054,3.8204814453125Q142.01999999999998,4.6499814453125,142.587,5.2379814453125L141.39,6.8759814453125Q140.928,6.3089814453125,139.941,5.3954814453125Q138.954,4.4819814453125,138.28199999999998,3.9569814453125L139.374,2.5079814453125ZM152.184,19.0769814453125Q152.751,19.0139814453125,153.014,18.9299814453125Q153.276,18.8459814453125,153.381,18.6359814453125Q153.486,18.4259814453125,153.486,17.9639814453125L153.486,2.6549814453125L155.124,2.7389824453125L155.124,18.5939814453125Q155.124,19.5389814453125,154.95600000000002,20.0009814453125Q154.788,20.4629814453125,154.315,20.6729814453125Q153.84300000000002,20.8829814453125,152.83499999999998,20.9669814453125L151.659,21.0509814453125L151.09199999999998,19.1609814453125L152.184,19.0769814453125ZM142.587,15.8429814453125L142.587,3.4529814453125L149.286,3.4529814453125L149.286,15.7799814453125L147.543,15.7799814453125L147.543,5.2799814453125L144.288,5.2799814453125L144.288,15.8429814453125L142.587,15.8429814453125ZM150.546,16.4099814453125L150.546,4.4819814453125L152.184,4.5659814453125005L152.184,16.4099814453125L150.546,16.4099814453125ZM141.012,19.7279814453125Q142.81799999999998,18.4049814453125,143.679,17.3654814453125Q144.54000000000002,16.3259814453125,144.834,15.0974814453125Q145.128,13.8689814453125,145.128,11.7689814453125L145.128,6.224981445312499L146.76600000000002,6.3089814453125L146.76600000000002,11.7689814453125Q146.76600000000002,14.2889814453125,146.33499999999998,15.8954814453125Q145.905,17.501981445312502,144.95,18.6779814453125Q143.994,19.8539814453125,142.209,21.1979814453125L141.012,19.7279814453125ZM138.639,7.2329814453125Q139.353,7.7369814453125,140.329,8.5874814453125Q141.30599999999998,9.4379814453125,141.957,10.1099814453125L140.76,11.7899814453125Q140.151,11.0969814453125,139.174,10.2044814453125Q138.19799999999998,9.311981445312501,137.421,8.7239814453125L138.639,7.2329814453125ZM137.82,20.2949814453125Q138.156,19.3709814453125,138.933,16.5989814453125Q139.70999999999998,13.8269814453125,139.878,12.9029814453125L140.781,13.1969814453125L141.642,13.4909814453125Q141.369,14.7299814453125,140.66500000000002,17.2814814453125Q139.962,19.8329814453125,139.60500000000002,20.9249814453125L137.82,20.2949814453125ZM147.144,15.9689814453125Q148.86599999999999,17.5439814453125,150.10500000000002,19.1189814453125L148.86599999999999,20.4839814453125Q148.06799999999998,19.4129814453125,147.449,18.6884814453125Q146.829,17.9639814453125,146.01,17.207981445312498L147.144,15.9689814453125Z", - "fill": "#000000", - "fill-opacity": "1" - }, - "children": [] - } - ] - } - ] - } - ] - }, - "name": "AliyunIconBig" + "icon": { + "type": "element", + "isRootNode": true, + "name": "svg", + "attributes": { + "xmlns": "http://www.w3.org/2000/svg", + "xmlns:xlink": "http://www.w3.org/1999/xlink", + "fill": "none", + "version": "1.1", + "width": "96", + "height": "24", + "viewBox": "0 0 96 24" + }, + "children": [ + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M6.10294,22C5.68819,22,5.18195,21.8532,4.58421,21.5595C4.46861,21.5027,4.39038,21.4658,4.34951,21.4488C3.49789,21.0943,2.74367,20.5941,2.08684,19.9484C0.695613,18.5806,0,16.9311,0,15C0,13.0689,0.695612,11.4194,2.08684,10.0516C3.24259,8.91537,4.59607,8.2511,6.14728,8.05878C6.34758,5.97414,7.22633,4.16634,8.78354,2.63539C10.5706,0.878463,12.7286,0,15.2573,0C17.2884,0,19.1146,0.595472,20.7358,1.78642C22.327,2.95528,23.4151,4.46783,24,6.32406L22.0568,6.91594C21.6024,5.47377,20.7561,4.29798,19.5181,3.38858C18.2579,2.46286,16.8377,2,15.2573,2C13.2903,2,11.6119,2.6832,10.222,4.04961C8.83217,5.41601,8.13725,7.06614,8.13725,9L8.13725,10L7.12009,10C5.71758,10,4.51932,10.4886,3.52532,11.4659C2.53132,12.4431,2.03431,13.6211,2.03431,15C2.03431,16.3789,2.53132,17.5569,3.52532,18.5341C3.99531,18.9962,4.53447,19.3538,5.14278,19.6071C5.2229,19.6405,5.33983,19.695,5.49356,19.7705C5.80505,19.9235,6.00818,20,6.10294,20L6.10294,22Z", + "fill-rule": "evenodd", + "fill": "#FF6A00", + "fill-opacity": "1" + } + } + ] + }, + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M20.18796103515625,11.66909C19.46346103515625,11.5762,18.72726103515625,11.52975,17.991011035156248,11.52975C16.728921035156247,11.52975,15.45515103515625,11.66909,14.23981103515625,11.91292C13.02447103515625,12.156749999999999,11.85588103515625,12.539909999999999,10.73402103515625,12.98113C9.98612103515625,13.306239999999999,9.23822103515625,13.69327,8.49031803515625,14.14223C7.99950790415625,14.43251,7.85927603515625,15.10595,8.15142503515625,15.59361L11.11966103515625,19.9478C11.45855103515625,20.4354,12.13634103515625,20.5747,12.627151035156249,20.2845C12.821921035156251,20.152900000000002,13.14523103515625,19.990299999999998,13.59708103515625,19.796799999999998C14.27487103515625,19.506500000000003,14.964341035156249,19.3091,15.68887103515625,19.169800000000002C16.413401035156248,19.018900000000002,17.14962103515625,18.926000000000002,17.93258103515625,18.926000000000002L20.071061035156248,11.715530000000001L20.18796103515625,11.66909ZM22.91076103515625,12.20319L22.525161035156252,8L20.18796103515625,11.6807C20.72556103515625,11.72714,21.21636103515625,11.82003,21.74216103515625,11.92453C21.74216103515625,11.91679,22.13166103515625,12.00968,22.91076103515625,12.20319ZM18.09616103515625,18.9724L17.06782103515625,22.4557L18.773961035156248,24L21.11116103515625,23.465899999999998L21.788961035156248,19.5414C21.298161035156248,19.402,20.81896103515625,19.2511,20.32816103515625,19.1582C19.60366103515625,19.076900000000002,18.86746103515625,18.9724,18.09616103515625,18.9724ZM27.49166103515625,14.14223C26.74376103515625,13.69327,25.99586103515625,13.306239999999999,25.24796103515625,12.98113C24.52346103515625,12.69086,23.74046103515625,12.40058,22.95756103515625,12.20319L22.95756103515625,12.40058L21.69546103515625,19.5646C21.89416103515625,19.6575,22.139561035156248,19.7039,22.32646103515625,19.8084C22.77836103515625,20.0019,23.101661035156248,20.1645,23.29646103515625,20.2961C23.78726103515625,20.586399999999998,24.51176103515625,20.4354,24.80396103515625,19.959400000000002L27.77216103515625,15.605229999999999C28.16946103515625,15.05951,28.02926103515625,14.43251,27.49166103515625,14.14223Z", + "fill": "#FF6A00", + "fill-opacity": "1" + } + } + ] + }, + { + "type": "element", + "name": "g", + "children": [ + { + "type": "element", + "name": "path", + "attributes": { + "d": "M35.785,3.8624638671875L50.233000000000004,3.8624638671875L50.233000000000004,5.9204638671875L35.785,5.9204638671875L35.785,3.8624638671875ZM43.156,11.5904638671875Q42.106,13.4594638671875,40.7515,15.4754638671875Q39.397,17.4914638671875,38.599000000000004,18.3104638671875L46.978,17.5334638671875Q45.382,15.0974638671875,44.479,13.8794638671875L46.306,12.7034638671875Q47.397999999999996,14.1104638671875,48.9835,16.3784638671875Q50.569,18.6464638671875,51.492999999999995,20.1794638671875L49.54,21.6494638671875Q49.057,20.8094638671875,48.238,19.5074638671875Q46.243,19.6334638671875,42.82,19.9274638671875Q39.397,20.2214638671875,37.024,20.4734638671875L36.184,20.5784638671875L35.47,20.6834638671875L34.84,18.5624638671875Q35.281,18.4154638671875,35.512,18.2579638671875Q35.743,18.1004638671875,35.9005,17.963963867187502Q36.058,17.8274638671875,36.121,17.7644638671875Q37.087,16.840463867187502,38.305,15.1079638671875Q39.522999999999996,13.3754638671875,40.531,11.5904638671875L34,11.5904638671875L34,9.5114638671875L52.018,9.5114638671875L52.018,11.5904638671875L43.156,11.5904638671875ZM62.203,10.9814638671875L62.203,12.7244638671875L60.25,12.7244638671875L60.25,2.5814638671875L62.203,2.6654638671875L62.203,10.4144638671875Q63.19,8.6294638671875,64.051,6.4139638671875Q64.912,4.1984638671875,65.28999999999999,2.3504638671875L67.348,2.8334628671875Q67.15899999999999,3.7784638671875,66.80199999999999,4.9754638671875L72.619,4.9754638671875L72.619,6.9704638671875L66.13,6.9704638671875Q65.143,9.7004638671875,63.778,12.0524638671875L62.203,10.9814638671875ZM56.113,3.3794638671875L58.045,3.4634638671875L58.045,12.1784638671875L56.113,12.1784638671875L56.113,3.3794638671875ZM67.495,7.3064638671875Q68.251,7.8944638671875,69.469,9.1229638671875Q70.687,10.3514638671875,71.40100000000001,11.2334638671875L69.84700000000001,12.7454638671875Q69.238,11.9684638671875,68.083,10.7714638671875Q66.928,9.5744638671875,66.025,8.7134638671875L67.495,7.3064638671875ZM70.834,13.3754638671875L70.834,18.9194638671875L73.06,18.9194638671875L73.06,20.8094638671875L54.307,20.8094638671875L54.307,18.9194638671875L56.491,18.9194638671875L56.491,13.3754638671875L70.834,13.3754638671875ZM60.733000000000004,15.2444638671875L58.465,15.2444638671875L58.465,18.9194638671875L60.733000000000004,18.9194638671875L60.733000000000004,15.2444638671875ZM62.581,18.9194638671875L64.765,18.9194638671875L64.765,15.2444638671875L62.581,15.2444638671875L62.581,18.9194638671875ZM66.592,18.9194638671875L68.881,18.9194638671875L68.881,15.2444638671875L66.592,15.2444638671875L66.592,18.9194638671875ZM80.578,11.0444638671875L80.893,12.4514638671875L79.48599999999999,13.0814638671875L79.48599999999999,19.0874638671875Q79.48599999999999,20.0114638671875,79.2655,20.4629638671875Q79.045,20.9144638671875,78.52000000000001,21.1034638671875Q77.995,21.2924638671875,76.90299999999999,21.3974638671875L76.021,21.4814638671875L75.43299999999999,19.4864638671875L76.462,19.4024638671875Q76.987,19.3604638671875,77.197,19.2974638671875Q77.407,19.2344638671875,77.4805,19.0559638671875Q77.554,18.8774638671875,77.554,18.4364638671875L77.554,13.9004638671875Q76.189,14.4464638671875,75.202,14.7824638671875L74.74000000000001,12.7244638671875Q75.916,12.3464638671875,77.554,11.6744638671875L77.554,8.1464638671875L75.34899999999999,8.1464638671875L75.34899999999999,6.1094638671875L77.554,6.1094638671875L77.554,2.4974628671875L79.48599999999999,2.5814638671875L79.48599999999999,6.1094638671875L81.03999999999999,6.1094638671875L81.03999999999999,8.1464638671875L79.48599999999999,8.1464638671875L79.48599999999999,10.8344638671875L80.431,10.3934638671875L80.578,11.0444638671875ZM83.56,6.6764638671875L83.56,9.0074638671875L81.565,9.0074638671875L81.565,4.7444638671875L86.24799999999999,4.7444638671875Q85.84899999999999,3.3794638671875,85.618,2.7494638671875L87.655,2.4974628671875Q87.991,3.2744638671875,88.432,4.7444638671875L93.094,4.7444638671875L93.094,9.0074638671875L91.162,9.0074638671875L91.162,6.6764638671875L83.56,6.6764638671875ZM86.731,9.3434638671875Q85.807,10.2674638671875,84.7465,11.1284638671875Q83.686,11.9894638671875,82.15299999999999,13.1234638671875L81.082,11.5064638671875Q83.455,9.9524638671875,85.408,7.9154638671875L86.731,9.3434638671875ZM88.852,7.9154638671875Q89.755,8.5244638671875,91.3615,9.731963867187499Q92.968,10.9394638671875,93.703,11.5694638671875L92.632,13.3334638671875Q91.771,12.5354638671875,90.217,11.3384638671875Q88.663,10.1414638671875,87.718,9.5114638671875L88.852,7.9154638671875ZM92.107,15.2444638671875L88.285,15.2444638671875L88.285,18.7094638671875L93.577,18.7094638671875L93.577,20.5994638671875L80.935,20.5994638671875L80.935,18.7094638671875L86.164,18.7094638671875L86.164,15.2444638671875L82.3,15.2444638671875L82.3,13.3334638671875L92.107,13.3334638671875L92.107,15.2444638671875Z", + "fill": "#FF6A00", + "fill-opacity": "1" + } + } + ] + } + ] + } + ] + }, + "name": "AliyunBigIcon" } From bd5b9385719e642cad37c32e725ca8846b8d192d Mon Sep 17 00:00:00 2001 From: Mike Zixuan HE Date: Mon, 28 Jul 2025 11:03:19 +0800 Subject: [PATCH 36/72] feat: Support allOf in OpenAPI properties inside schema #22946 (#22975) --- api/core/tools/utils/parser.py | 23 ++++++++ .../core/tools/utils/test_parser.py | 55 +++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/api/core/tools/utils/parser.py b/api/core/tools/utils/parser.py index a3c84615ca..3857a2a16b 100644 --- a/api/core/tools/utils/parser.py +++ b/api/core/tools/utils/parser.py @@ -105,6 +105,29 @@ class ApiBasedToolSchemaParser: # overwrite the content interface["operation"]["requestBody"]["content"][content_type]["schema"] = root + # handle allOf reference in schema properties + for prop_dict in root.get("properties", {}).values(): + for item in prop_dict.get("allOf", []): + if "$ref" in item: + ref_schema = openapi + reference = item["$ref"].split("/")[1:] + for ref in reference: + ref_schema = ref_schema[ref] + else: + ref_schema = item + for key, value in ref_schema.items(): + if isinstance(value, list): + if key not in prop_dict: + prop_dict[key] = [] + # extends list field + if isinstance(prop_dict[key], list): + prop_dict[key].extend(value) + elif key not in prop_dict: + # add new field + prop_dict[key] = value + if "allOf" in prop_dict: + del prop_dict["allOf"] + # parse body parameters if "schema" in interface["operation"]["requestBody"]["content"][content_type]: body_schema = interface["operation"]["requestBody"]["content"][content_type]["schema"] diff --git a/api/tests/unit_tests/core/tools/utils/test_parser.py b/api/tests/unit_tests/core/tools/utils/test_parser.py index 8e07293ce0..e1eab21ca4 100644 --- a/api/tests/unit_tests/core/tools/utils/test_parser.py +++ b/api/tests/unit_tests/core/tools/utils/test_parser.py @@ -54,3 +54,58 @@ def test_parse_openapi_to_tool_bundle_operation_id(app): assert tool_bundles[0].operation_id == "_get" assert tool_bundles[1].operation_id == "apiresources_get" assert tool_bundles[2].operation_id == "createResource" + + +def test_parse_openapi_to_tool_bundle_properties_all_of(app): + openapi = { + "openapi": "3.0.0", + "info": {"title": "Simple API", "version": "1.0.0"}, + "servers": [{"url": "http://localhost:3000"}], + "paths": { + "/api/resource": { + "get": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Request", + }, + }, + }, + "required": True, + }, + }, + }, + }, + "components": { + "schemas": { + "Request": { + "type": "object", + "properties": { + "prop1": { + "enum": ["option1"], + "description": "desc prop1", + "allOf": [ + {"$ref": "#/components/schemas/AllOfItem"}, + { + "enum": ["option2"], + }, + ], + }, + }, + }, + "AllOfItem": { + "type": "string", + "enum": ["option3"], + "description": "desc allOf item", + }, + } + }, + } + with app.test_request_context(): + tool_bundles = ApiBasedToolSchemaParser.parse_openapi_to_tool_bundle(openapi) + + assert tool_bundles[0].parameters[0].type == "string" + assert tool_bundles[0].parameters[0].llm_description == "desc prop1" + # TODO: support enum in OpenAPI + # assert set(tool_bundles[0].parameters[0].options) == {"option1", "option2", "option3"} From ee731c7810be81cc6b935a78299755ae95a0b718 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Mon, 28 Jul 2025 13:58:21 +0800 Subject: [PATCH 37/72] chore: Updata eslint config dependencies (#23040) --- web/package.json | 10 +- web/pnpm-lock.yaml | 328 +++++++++++++++++++++++++++++---------------- 2 files changed, 220 insertions(+), 118 deletions(-) diff --git a/web/package.json b/web/package.json index 2470a70dec..6915620312 100644 --- a/web/package.json +++ b/web/package.json @@ -152,7 +152,7 @@ "zustand": "^4.5.2" }, "devDependencies": { - "@antfu/eslint-config": "^4.1.1", + "@antfu/eslint-config": "^5.0.0", "@chromatic-com/storybook": "^3.1.0", "@eslint-react/eslint-plugin": "^1.15.0", "@eslint/eslintrc": "^3.1.0", @@ -160,7 +160,7 @@ "@faker-js/faker": "^9.0.3", "@happy-dom/jest-environment": "^17.4.4", "@next/bundle-analyzer": "^15.4.1", - "@next/eslint-plugin-next": "~15.3.5", + "@next/eslint-plugin-next": "~15.4.4", "@rgrove/parse-xml": "^4.1.0", "@storybook/addon-essentials": "8.5.0", "@storybook/addon-interactions": "8.5.0", @@ -197,7 +197,7 @@ "code-inspector-plugin": "^0.18.1", "cross-env": "^7.0.3", "eslint": "^9.20.1", - "eslint-config-next": "~15.3.5", + "eslint-config-next": "~15.4.4", "eslint-plugin-oxlint": "^1.6.0", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-refresh": "^0.4.19", @@ -216,7 +216,7 @@ "tailwindcss": "^3.4.14", "ts-node": "^10.9.2", "typescript": "^5.8.3", - "typescript-eslint": "^8.36.0", + "typescript-eslint": "^8.38.0", "uglify-js": "^3.19.3" }, "resolutions": { @@ -270,4 +270,4 @@ "which-typed-array": "npm:@nolyfill/which-typed-array@^1" } } -} +} \ No newline at end of file diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index eaff8c8504..58153b9fc1 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -379,8 +379,8 @@ importers: version: 4.5.7(@types/react@19.1.8)(immer@9.0.21)(react@19.1.0) devDependencies: '@antfu/eslint-config': - specifier: ^4.1.1 - version: 4.17.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.31.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.20(eslint@9.31.0(jiti@1.21.7)))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + specifier: ^5.0.0 + version: 5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.4.4)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.31.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.20(eslint@9.31.0(jiti@1.21.7)))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) '@chromatic-com/storybook': specifier: ^3.1.0 version: 3.2.7(react@19.1.0)(storybook@8.5.0) @@ -403,8 +403,8 @@ importers: specifier: ^15.4.1 version: 15.4.1 '@next/eslint-plugin-next': - specifier: ~15.3.5 - version: 15.3.5 + specifier: ~15.4.4 + version: 15.4.4 '@rgrove/parse-xml': specifier: ^4.1.0 version: 4.2.0 @@ -514,8 +514,8 @@ importers: specifier: ^9.20.1 version: 9.31.0(jiti@1.21.7) eslint-config-next: - specifier: ~15.3.5 - version: 15.3.5(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + specifier: ~15.4.4 + version: 15.4.4(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) eslint-plugin-oxlint: specifier: ^1.6.0 version: 1.6.0 @@ -571,8 +571,8 @@ importers: specifier: ^5.8.3 version: 5.8.3 typescript-eslint: - specifier: ^8.36.0 - version: 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + specifier: ^8.38.0 + version: 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) uglify-js: specifier: ^3.19.3 version: 3.19.3 @@ -590,11 +590,12 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@antfu/eslint-config@4.17.0': - resolution: {integrity: sha512-S1y0A1+0DcpV6GmjwB9gQCQc7ni9zlKa3MQRqRCEZ0E1WW+nRL1BUwnbk3DpMJAMsb3UIAt1lsAiIBnvIw2NDw==} + '@antfu/eslint-config@5.0.0': + resolution: {integrity: sha512-uAMv8PiW9BOAGmIyTDtWXGnNfv6PFV4DmpqmlUpST5k4bue38VRdIfnM4jvgPuny1xnjYX3flN3kB9++6LknMw==} hasBin: true peerDependencies: '@eslint-react/eslint-plugin': ^1.38.4 + '@next/eslint-plugin-next': ^15.4.0-canary.115 '@prettier/plugin-xml': ^3.4.1 '@unocss/eslint-plugin': '>=0.50.0' astro-eslint-parser: ^1.0.2 @@ -612,6 +613,8 @@ packages: peerDependenciesMeta: '@eslint-react/eslint-plugin': optional: true + '@next/eslint-plugin-next': + optional: true '@prettier/plugin-xml': optional: true '@unocss/eslint-plugin': @@ -1563,14 +1566,6 @@ packages: resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.13.0': - resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.14.0': - resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.15.1': resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1583,22 +1578,22 @@ packages: resolution: {integrity: sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/markdown@7.0.0': - resolution: {integrity: sha512-0WNH6pSFHNlWSlNaIFQP0sLHpMUJw1FaJtyqapvGqOt0ISRgTUkTLVT0hT/zekDA1QlP2TT8pwjPkqYTu2s8yg==} + '@eslint/markdown@7.1.0': + resolution: {integrity: sha512-Y+X1B1j+/zupKDVJfkKc8uYMjQkGzfnd8lt7vK3y8x9Br6H5dBuhAfFrQ6ff7HAMm/1BwgecyEiRFkYCWPRxmA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.8': - resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.3': resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.3.4': + resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@faker-js/faker@9.9.0': resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} @@ -2115,8 +2110,8 @@ packages: '@next/env@15.3.5': resolution: {integrity: sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g==} - '@next/eslint-plugin-next@15.3.5': - resolution: {integrity: sha512-BZwWPGfp9po/rAnJcwUBaM+yT/+yTWIkWdyDwc74G9jcfTrNrmsHe+hXHljV066YNdVs8cxROxX5IgMQGX190w==} + '@next/eslint-plugin-next@15.4.4': + resolution: {integrity: sha512-1FDsyN//ai3Jd97SEd7scw5h1yLdzDACGOPRofr2GD3sEFsBylEEoL0MHSerd4n2dq9Zm/mFMqi4+NRMOreOKA==} '@next/mdx@15.3.5': resolution: {integrity: sha512-/2rRCgPKNp2ttQscU13auI+cYYACdPa80Okgi/1+NNJJeWn9yVxwGnqZc3SX30T889bZbLqcY4oUjqYGAygL4g==} @@ -2825,8 +2820,8 @@ packages: peerDependencies: storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@stylistic/eslint-plugin@5.2.0': - resolution: {integrity: sha512-RCEdbREv9EBiToUBQTlRhVYKG093I6ZnnQ990j08eJ6uRZh71DXkOnoxtTLfDQ6utVCVQzrhZFHZP0zfrfOIjA==} + '@stylistic/eslint-plugin@5.2.2': + resolution: {integrity: sha512-bE2DUjruqXlHYP3Q2Gpqiuj2bHq7/88FnuaS0FjeGGLCy+X6a07bGVuwtiOYnPSLHR6jmx5Bwdv+j7l8H+G97A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' @@ -3219,16 +3214,16 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.37.0': - resolution: {integrity: sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==} + '@typescript-eslint/eslint-plugin@8.38.0': + resolution: {integrity: sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.37.0 + '@typescript-eslint/parser': ^8.38.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/parser@8.37.0': - resolution: {integrity: sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==} + '@typescript-eslint/parser@8.38.0': + resolution: {integrity: sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -3240,16 +3235,32 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/project-service@8.38.0': + resolution: {integrity: sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/scope-manager@8.37.0': resolution: {integrity: sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.38.0': + resolution: {integrity: sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.37.0': resolution: {integrity: sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/tsconfig-utils@8.38.0': + resolution: {integrity: sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/type-utils@8.37.0': resolution: {integrity: sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3257,16 +3268,33 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/type-utils@8.38.0': + resolution: {integrity: sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/types@8.37.0': resolution: {integrity: sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.38.0': + resolution: {integrity: sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.37.0': resolution: {integrity: sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/typescript-estree@8.38.0': + resolution: {integrity: sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/utils@8.37.0': resolution: {integrity: sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3274,10 +3302,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/utils@8.38.0': + resolution: {integrity: sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/visitor-keys@8.37.0': resolution: {integrity: sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.38.0': + resolution: {integrity: sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -3876,6 +3915,9 @@ packages: resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -4649,8 +4691,8 @@ packages: peerDependencies: eslint: ^9.5.0 - eslint-config-next@15.3.5: - resolution: {integrity: sha512-oQdvnIgP68wh2RlR3MdQpvaJ94R6qEFl+lnu8ZKxPj5fsAHrSF/HlAOZcsimLw3DT6bnEQIUdbZC2Ab6sWyptg==} + eslint-config-next@15.4.4: + resolution: {integrity: sha512-sK/lWLUVF5om18O5w76Jt3F8uzu/LP5mVa6TprCMWkjWHUmByq80iHGHcdH7k1dLiJlj+DRIWf98d5piwRsSuA==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -4787,8 +4829,8 @@ packages: peerDependencies: eslint: '>=8.45.0' - eslint-plugin-pnpm@1.0.0: - resolution: {integrity: sha512-tyEA10k7psB9HFCx8R4/bU4JS2tSKfXaCnrCcis+1R4FucfMIc6HgcFl4msZbwY2I0D9Vec3xAEkXV0aPechhQ==} + eslint-plugin-pnpm@1.1.0: + resolution: {integrity: sha512-sL93w0muBtjnogzk/loDsxzMbmXQOLP5Blw3swLDBXZgfb+qQI73bPcUbjVR+ZL+K62vGJdErV+43i3r5DsZPg==} peerDependencies: eslint: ^9.0.0 @@ -4901,11 +4943,11 @@ packages: peerDependencies: eslint: '>=6.0.0' - eslint-plugin-unicorn@59.0.1: - resolution: {integrity: sha512-EtNXYuWPUmkgSU2E7Ttn57LbRREQesIP1BiLn7OZLKodopKfDXfBUkC/0j6mpw2JExwf43Uf3qLSvrSvppgy8Q==} - engines: {node: ^18.20.0 || ^20.10.0 || >=21.0.0} + eslint-plugin-unicorn@60.0.0: + resolution: {integrity: sha512-QUzTefvP8stfSXsqKQ+vBQSEsXIlAiCduS/V1Em+FKgL9c21U/IIm20/e3MFy1jyCf14tHAhqC1sX8OTy6VUCg==} + engines: {node: ^20.10.0 || >=21.0.0} peerDependencies: - eslint: '>=9.22.0' + eslint: '>=9.29.0' eslint-plugin-unused-imports@4.1.4: resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} @@ -6691,8 +6733,8 @@ packages: resolution: {integrity: sha512-2Rb3vm+EXble/sMXNSu6eoBx8e79gKqhNq9F5ZWW6ERNCTE/Q0wQNne5541tE5vKjfM8hpNCYL+LGc1YTfI0dg==} engines: {node: '>=6'} - pnpm-workspace-yaml@1.0.0: - resolution: {integrity: sha512-2RKg3khFgX/oeKIQnxxlj+OUoKbaZjBt7EsmQiLfl8AHZKMIpLmXLRPptZ5eq2Rlumh2gILs6OWNky5dzP+f8A==} + pnpm-workspace-yaml@1.1.0: + resolution: {integrity: sha512-OWUzBxtitpyUV0fBYYwLAfWxn3mSzVbVB7cwgNaHvTTU9P0V2QHjyaY5i7f1hEiT9VeKsNH1Skfhe2E3lx/zhA==} points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -7829,8 +7871,8 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - typescript-eslint@8.37.0: - resolution: {integrity: sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==} + typescript-eslint@8.38.0: + resolution: {integrity: sha512-FsZlrYK6bPDGoLeZRuvx2v6qrM03I0U0SnfCLPs/XCCPCFD80xU9Pg09H/K+XFa68uJuZo7l/Xhs+eDRg2l3hg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -8243,15 +8285,15 @@ snapshots: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 - '@antfu/eslint-config@4.17.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.31.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.20(eslint@9.31.0(jiti@1.21.7)))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@antfu/eslint-config@5.0.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@next/eslint-plugin-next@15.4.4)(@vue/compiler-sfc@3.5.17)(eslint-plugin-react-hooks@5.2.0(eslint@9.31.0(jiti@1.21.7)))(eslint-plugin-react-refresh@0.4.20(eslint@9.31.0(jiti@1.21.7)))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 0.11.0 '@eslint-community/eslint-plugin-eslint-comments': 4.5.0(eslint@9.31.0(jiti@1.21.7)) - '@eslint/markdown': 7.0.0 - '@stylistic/eslint-plugin': 5.2.0(eslint@9.31.0(jiti@1.21.7)) - '@typescript-eslint/eslint-plugin': 8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@eslint/markdown': 7.1.0 + '@stylistic/eslint-plugin': 5.2.2(eslint@9.31.0(jiti@1.21.7)) + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) '@vitest/eslint-plugin': 1.3.4(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) ansis: 4.1.0 cac: 6.7.14 @@ -8267,12 +8309,12 @@ snapshots: eslint-plugin-n: 17.21.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) eslint-plugin-no-only-tests: 3.3.0 eslint-plugin-perfectionist: 4.15.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-pnpm: 1.0.0(eslint@9.31.0(jiti@1.21.7)) + eslint-plugin-pnpm: 1.1.0(eslint@9.31.0(jiti@1.21.7)) eslint-plugin-regexp: 2.9.0(eslint@9.31.0(jiti@1.21.7)) eslint-plugin-toml: 0.12.0(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-unicorn: 59.0.1(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-vue: 10.3.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@1.21.7))) + eslint-plugin-unicorn: 60.0.0(eslint@9.31.0(jiti@1.21.7)) + eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7)) + eslint-plugin-vue: 10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@1.21.7))) eslint-plugin-yml: 1.18.0(eslint@9.31.0(jiti@1.21.7)) eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.17)(eslint@9.31.0(jiti@1.21.7)) globals: 16.3.0 @@ -8284,6 +8326,7 @@ snapshots: yaml-eslint-parser: 1.3.0 optionalDependencies: '@eslint-react/eslint-plugin': 1.52.3(eslint@9.31.0(jiti@1.21.7))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) + '@next/eslint-plugin-next': 15.4.4 eslint-plugin-react-hooks: 5.2.0(eslint@9.31.0(jiti@1.21.7)) eslint-plugin-react-refresh: 0.4.20(eslint@9.31.0(jiti@1.21.7)) transitivePeerDependencies: @@ -9192,7 +9235,7 @@ snapshots: '@es-joy/jsdoccomment@0.50.2': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/types': 8.38.0 comment-parser: 1.4.1 esquery: 1.6.0 jsdoc-type-pratt-parser: 4.1.0 @@ -9200,7 +9243,7 @@ snapshots: '@es-joy/jsdoccomment@0.52.0': dependencies: '@types/estree': 1.0.8 - '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/types': 8.38.0 comment-parser: 1.4.1 esquery: 1.6.0 jsdoc-type-pratt-parser: 4.1.0 @@ -9399,14 +9442,6 @@ snapshots: '@eslint/config-helpers@0.3.0': {} - '@eslint/core@0.13.0': - dependencies: - '@types/json-schema': 7.0.15 - - '@eslint/core@0.14.0': - dependencies: - '@types/json-schema': 7.0.15 - '@eslint/core@0.15.1': dependencies: '@types/json-schema': 7.0.15 @@ -9427,10 +9462,10 @@ snapshots: '@eslint/js@9.31.0': {} - '@eslint/markdown@7.0.0': + '@eslint/markdown@7.1.0': dependencies: - '@eslint/core': 0.14.0 - '@eslint/plugin-kit': 0.3.3 + '@eslint/core': 0.15.1 + '@eslint/plugin-kit': 0.3.4 github-slugger: 2.0.0 mdast-util-from-markdown: 2.0.2 mdast-util-frontmatter: 2.0.1 @@ -9442,12 +9477,12 @@ snapshots: '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.2.8': + '@eslint/plugin-kit@0.3.3': dependencies: - '@eslint/core': 0.13.0 + '@eslint/core': 0.15.1 levn: 0.4.1 - '@eslint/plugin-kit@0.3.3': + '@eslint/plugin-kit@0.3.4': dependencies: '@eslint/core': 0.15.1 levn: 0.4.1 @@ -10151,7 +10186,7 @@ snapshots: '@next/env@15.3.5': {} - '@next/eslint-plugin-next@15.3.5': + '@next/eslint-plugin-next@15.4.4': dependencies: fast-glob: 3.3.1 @@ -11003,10 +11038,10 @@ snapshots: dependencies: storybook: 8.5.0 - '@stylistic/eslint-plugin@5.2.0(eslint@9.31.0(jiti@1.21.7))': + '@stylistic/eslint-plugin@5.2.2(eslint@9.31.0(jiti@1.21.7))': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) - '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/types': 8.38.0 eslint: 9.31.0(jiti@1.21.7) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -11441,14 +11476,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.37.0 - '@typescript-eslint/type-utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.37.0 + '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.38.0 + '@typescript-eslint/type-utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.38.0 eslint: 9.31.0(jiti@1.21.7) graphemer: 1.4.0 ignore: 7.0.5 @@ -11458,12 +11493,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + '@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.37.0 - '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.37.0 + '@typescript-eslint/scope-manager': 8.38.0 + '@typescript-eslint/types': 8.38.0 + '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.38.0 debug: 4.4.1 eslint: 9.31.0(jiti@1.21.7) typescript: 5.8.3 @@ -11479,15 +11514,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.38.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3) + '@typescript-eslint/types': 8.38.0 + debug: 4.4.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.37.0': dependencies: '@typescript-eslint/types': 8.37.0 '@typescript-eslint/visitor-keys': 8.37.0 + '@typescript-eslint/scope-manager@8.38.0': + dependencies: + '@typescript-eslint/types': 8.38.0 + '@typescript-eslint/visitor-keys': 8.38.0 + '@typescript-eslint/tsconfig-utils@8.37.0(typescript@5.8.3)': dependencies: typescript: 5.8.3 + '@typescript-eslint/tsconfig-utils@8.38.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + '@typescript-eslint/type-utils@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@typescript-eslint/types': 8.37.0 @@ -11500,8 +11553,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.38.0 + '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.31.0(jiti@1.21.7) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@8.37.0': {} + '@typescript-eslint/types@8.38.0': {} + '@typescript-eslint/typescript-estree@8.37.0(typescript@5.8.3)': dependencies: '@typescript-eslint/project-service': 8.37.0(typescript@5.8.3) @@ -11518,6 +11585,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.38.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.38.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.8.3) + '@typescript-eslint/types': 8.38.0 + '@typescript-eslint/visitor-keys': 8.38.0 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) @@ -11529,11 +11612,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.38.0 + '@typescript-eslint/types': 8.38.0 + '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) + eslint: 9.31.0(jiti@1.21.7) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.37.0': dependencies: '@typescript-eslint/types': 8.37.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.38.0': + dependencies: + '@typescript-eslint/types': 8.38.0 + eslint-visitor-keys: 4.2.1 + '@ungap/structured-clone@1.3.0': {} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -11597,7 +11696,7 @@ snapshots: '@vitest/eslint-plugin@1.3.4(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) eslint: 9.31.0(jiti@1.21.7) optionalDependencies: typescript: 5.8.3 @@ -12187,6 +12286,8 @@ snapshots: chalk@5.4.1: {} + change-case@5.4.4: {} + char-regex@1.0.2: {} character-entities-html4@2.1.0: {} @@ -12990,16 +13091,16 @@ snapshots: '@eslint/compat': 1.3.1(eslint@9.31.0(jiti@1.21.7)) eslint: 9.31.0(jiti@1.21.7) - eslint-config-next@15.3.5(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + eslint-config-next@15.4.4(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@next/eslint-plugin-next': 15.3.5 + '@next/eslint-plugin-next': 15.4.4 '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) eslint: 9.31.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.31.0(jiti@1.21.7)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.31.0(jiti@1.21.7)) eslint-plugin-react: 7.37.5(eslint@9.31.0(jiti@1.21.7)) eslint-plugin-react-hooks: 5.2.0(eslint@9.31.0(jiti@1.21.7)) @@ -13033,7 +13134,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) transitivePeerDependencies: - supports-color @@ -13047,11 +13148,11 @@ snapshots: dependencies: eslint: 9.31.0(jiti@1.21.7) - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) eslint: 9.31.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.31.0(jiti@1.21.7)) @@ -13077,12 +13178,12 @@ snapshots: eslint-plugin-import-lite@0.3.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) - '@typescript-eslint/types': 8.37.0 + '@typescript-eslint/types': 8.38.0 eslint: 9.31.0(jiti@1.21.7) optionalDependencies: typescript: 5.8.3 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: '@nolyfill/array-includes@1.0.44' @@ -13093,7 +13194,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.31.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.31.0(jiti@1.21.7)) hasown: '@nolyfill/hasown@1.0.44' is-core-module: '@nolyfill/is-core-module@1.0.39' is-glob: 4.0.3 @@ -13105,7 +13206,7 @@ snapshots: string.prototype.trimend: '@nolyfill/string.prototype.trimend@1.0.44' tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -13183,21 +13284,21 @@ snapshots: eslint-plugin-perfectionist@4.15.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@typescript-eslint/types': 8.37.0 - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/types': 8.38.0 + '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) eslint: 9.31.0(jiti@1.21.7) natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-pnpm@1.0.0(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-pnpm@1.1.0(eslint@9.31.0(jiti@1.21.7)): dependencies: eslint: 9.31.0(jiti@1.21.7) find-up-simple: 1.0.1 jsonc-eslint-parser: 2.4.0 pathe: 2.0.3 - pnpm-workspace-yaml: 1.0.0 + pnpm-workspace-yaml: 1.1.0 tinyglobby: 0.2.14 yaml-eslint-parser: 1.3.0 @@ -13404,11 +13505,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@59.0.1(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-unicorn@60.0.0(eslint@9.31.0(jiti@1.21.7)): dependencies: '@babel/helper-validator-identifier': 7.27.1 '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) - '@eslint/plugin-kit': 0.2.8 + '@eslint/plugin-kit': 0.3.4 + change-case: 5.4.4 ci-info: 4.3.0 clean-regexp: 1.0.0 core-js-compat: 3.44.0 @@ -13425,13 +13527,13 @@ snapshots: semver: 7.7.2 strip-indent: 4.0.0 - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7)): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7)): dependencies: eslint: 9.31.0(jiti@1.21.7) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@1.21.7))): + eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@1.21.7))): dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@1.21.7)) eslint: 9.31.0(jiti@1.21.7) @@ -13442,7 +13544,7 @@ snapshots: vue-eslint-parser: 10.2.0(eslint@9.31.0(jiti@1.21.7)) xml-name-validator: 4.0.0 optionalDependencies: - '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) eslint-plugin-yml@1.18.0(eslint@9.31.0(jiti@1.21.7)): dependencies: @@ -15832,7 +15934,7 @@ snapshots: transitivePeerDependencies: - typescript - pnpm-workspace-yaml@1.0.0: + pnpm-workspace-yaml@1.1.0: dependencies: yaml: 2.8.0 @@ -17106,12 +17208,12 @@ snapshots: type-fest@2.19.0: {} - typescript-eslint@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): + typescript-eslint@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.37.0(@typescript-eslint/parser@8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/parser': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) - '@typescript-eslint/typescript-estree': 8.37.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.37.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3))(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.38.0(eslint@9.31.0(jiti@1.21.7))(typescript@5.8.3) eslint: 9.31.0(jiti@1.21.7) typescript: 5.8.3 transitivePeerDependencies: From f72c03a174ba7be4440f88e645f78f7418e85403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=86=E8=90=8C=E9=97=B7=E6=B2=B9=E7=93=B6?= <253605712@qq.com> Date: Mon, 28 Jul 2025 13:59:34 +0800 Subject: [PATCH 38/72] feat: Support selecting variables in conditional filtering in list operations. (#23029) Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: crazywoola <427733928@qq.com> --- api/core/workflow/nodes/list_operator/node.py | 2 +- .../components/filter-condition.tsx | 72 +++++++++++++++---- .../workflow/nodes/list-operator/panel.tsx | 1 + 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/api/core/workflow/nodes/list_operator/node.py b/api/core/workflow/nodes/list_operator/node.py index b91fc622f6..d2e022dc9d 100644 --- a/api/core/workflow/nodes/list_operator/node.py +++ b/api/core/workflow/nodes/list_operator/node.py @@ -299,7 +299,7 @@ def _endswith(value: str) -> Callable[[str], bool]: def _is(value: str) -> Callable[[str], bool]: - return lambda x: x is value + return lambda x: x == value def _in(value: str | Sequence[str]) -> Callable[[str], bool]: diff --git a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx index 0c261a70d6..a7ea6d78e7 100644 --- a/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx +++ b/web/app/components/workflow/nodes/list-operator/components/filter-condition.tsx @@ -1,36 +1,60 @@ 'use client' import type { FC } from 'react' -import React, { useCallback, useMemo } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import ConditionOperator from '../../if-else/components/condition-list/condition-operator' -import { VarType } from '../../../types' import type { Condition } from '../types' import { ComparisonOperator } from '../../if-else/types' import { comparisonOperatorNotRequireValue, getOperators } from '../../if-else/utils' import SubVariablePicker from './sub-variable-picker' -import Input from '@/app/components/base/input' import { FILE_TYPE_OPTIONS, TRANSFER_METHOD } from '@/app/components/workflow/nodes/constants' import { SimpleSelect as Select } from '@/app/components/base/select' +import Input from '@/app/components/workflow/nodes/_base/components/input-support-select-var' +import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list' +import cn from '@/utils/classnames' +import { VarType } from '../../../types' const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName' + +const VAR_INPUT_SUPPORTED_KEYS: Record = { + name: VarType.string, + url: VarType.string, + extension: VarType.string, + mime_type: VarType.string, + related_id: VarType.number, +} + type Props = { condition: Condition onChange: (condition: Condition) => void - varType: VarType hasSubVariable: boolean readOnly: boolean + nodeId: string } const FilterCondition: FC = ({ condition = { key: '', comparison_operator: ComparisonOperator.equal, value: '' }, - varType, onChange, hasSubVariable, readOnly, + nodeId, }) => { const { t } = useTranslation() + const [isFocus, setIsFocus] = useState(false) + + const expectedVarType = VAR_INPUT_SUPPORTED_KEYS[condition.key] + const supportVariableInput = !!expectedVarType + + const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, { + onlyLeafNodeVar: false, + filterVar: (varPayload) => { + return expectedVarType ? varPayload.type === expectedVarType : true + }, + }) + const isSelect = [ComparisonOperator.in, ComparisonOperator.notIn, ComparisonOperator.allOf].includes(condition.comparison_operator) const isArrayValue = condition.key === 'transfer_method' || condition.key === 'type' + const selectOptions = useMemo(() => { if (isSelect) { if (condition.key === 'type' || condition.comparison_operator === ComparisonOperator.allOf) { @@ -49,6 +73,7 @@ const FilterCondition: FC = ({ } return [] }, [condition.comparison_operator, condition.key, isSelect, t]) + const handleChange = useCallback((key: string) => { return (value: any) => { onChange({ @@ -59,12 +84,14 @@ const FilterCondition: FC = ({ }, [condition, onChange, isArrayValue]) const handleSubVariableChange = useCallback((value: string) => { + const operators = getOperators(expectedVarType ?? VarType.string, { key: value }) + const newOperator = operators.length > 0 ? operators[0] : ComparisonOperator.equal onChange({ key: value, - comparison_operator: getOperators(varType, { key: value })[0], + comparison_operator: newOperator, value: '', }) - }, [onChange, varType]) + }, [onChange, expectedVarType]) return (
@@ -78,7 +105,7 @@ const FilterCondition: FC = ({
= ({ /> {!comparisonOperatorNotRequireValue(condition.comparison_operator) && ( <> - {isSelect && ( + {isSelect ? ( + ) : ( + handleChange('value')(e.target.value)} + readOnly={readOnly} /> )} @@ -110,4 +155,5 @@ const FilterCondition: FC = ({
) } + export default React.memo(FilterCondition) diff --git a/web/app/components/workflow/nodes/list-operator/panel.tsx b/web/app/components/workflow/nodes/list-operator/panel.tsx index d93a79397d..9a89629f09 100644 --- a/web/app/components/workflow/nodes/list-operator/panel.tsx +++ b/web/app/components/workflow/nodes/list-operator/panel.tsx @@ -78,6 +78,7 @@ const Panel: FC> = ({ varType={itemVarType} hasSubVariable={hasSubVariable} readOnly={readOnly} + nodeId={id} /> ) : null} From 3f8fb18c898fbbbbadfff87d9a2c0aef54d86c15 Mon Sep 17 00:00:00 2001 From: Tianyi Jing Date: Mon, 28 Jul 2025 14:07:51 +0800 Subject: [PATCH 39/72] fix: delete the old provider_config_cache after refresh_credentials (#23033) Signed-off-by: jingfelix --- api/core/tools/tool_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index 6b06cc7f10..1bb4cfa4cd 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -237,7 +237,7 @@ class ToolManager: if builtin_provider is None: raise ToolProviderNotFoundError(f"builtin provider {provider_id} not found") - encrypter, _ = create_provider_encrypter( + encrypter, cache = create_provider_encrypter( tenant_id=tenant_id, config=[ x.to_basic_provider_config() @@ -281,6 +281,7 @@ class ToolManager: builtin_provider.expires_at = refreshed_credentials.expires_at db.session.commit() decrypted_credentials = refreshed_credentials.credentials + cache.delete() return cast( BuiltinTool, From 5c5f61b2aaeeb048deaa5730222a7591d000b600 Mon Sep 17 00:00:00 2001 From: zhaobingshuang <1475195565@qq.com> Date: Mon, 28 Jul 2025 14:24:13 +0800 Subject: [PATCH 40/72] fix(dataset): CELERY_BROKER uses amqp rabbitmq. When adding document segments in batches and uploading large files, the status will always remain stuck at "In batch processing" #22709 (#23038) --- .../console/datasets/datasets_segments.py | 35 ++---- .../batch_create_segment_to_index_task.py | 34 ++++- .../detail/batch-modal/csv-uploader.tsx | 119 +++++++++++++++++- .../documents/detail/batch-modal/index.tsx | 10 +- .../datasets/documents/detail/index.tsx | 8 +- web/service/knowledge/use-segment.ts | 4 +- 6 files changed, 166 insertions(+), 44 deletions(-) diff --git a/api/controllers/console/datasets/datasets_segments.py b/api/controllers/console/datasets/datasets_segments.py index acb2265309..8c429044d7 100644 --- a/api/controllers/console/datasets/datasets_segments.py +++ b/api/controllers/console/datasets/datasets_segments.py @@ -1,6 +1,5 @@ import uuid -import pandas as pd from flask import request from flask_login import current_user from flask_restful import Resource, marshal, reqparse @@ -14,8 +13,6 @@ from controllers.console.datasets.error import ( ChildChunkDeleteIndexError, ChildChunkIndexingError, InvalidActionError, - NoFileUploadedError, - TooManyFilesError, ) from controllers.console.wraps import ( account_initialization_required, @@ -32,6 +29,7 @@ from extensions.ext_redis import redis_client from fields.segment_fields import child_chunk_fields, segment_fields from libs.login import login_required from models.dataset import ChildChunk, DocumentSegment +from models.model import UploadFile from services.dataset_service import DatasetService, DocumentService, SegmentService from services.entities.knowledge_entities.knowledge_entities import ChildChunkUpdateArgs, SegmentUpdateArgs from services.errors.chunk import ChildChunkDeleteIndexError as ChildChunkDeleteIndexServiceError @@ -365,37 +363,28 @@ class DatasetDocumentSegmentBatchImportApi(Resource): document = DocumentService.get_document(dataset_id, document_id) if not document: raise NotFound("Document not found.") - # get file from request - file = request.files["file"] - # check file - if "file" not in request.files: - raise NoFileUploadedError() - if len(request.files) > 1: - raise TooManyFilesError() + parser = reqparse.RequestParser() + parser.add_argument("upload_file_id", type=str, required=True, nullable=False, location="json") + args = parser.parse_args() + upload_file_id = args["upload_file_id"] + + upload_file = db.session.query(UploadFile).where(UploadFile.id == upload_file_id).first() + if not upload_file: + raise NotFound("UploadFile not found.") + # check file type - if not file.filename or not file.filename.lower().endswith(".csv"): + if not upload_file.name or not upload_file.name.lower().endswith(".csv"): raise ValueError("Invalid file type. Only CSV files are allowed") try: - # Skip the first row - df = pd.read_csv(file) - result = [] - for index, row in df.iterrows(): - if document.doc_form == "qa_model": - data = {"content": row.iloc[0], "answer": row.iloc[1]} - else: - data = {"content": row.iloc[0]} - result.append(data) - if len(result) == 0: - raise ValueError("The CSV file is empty.") # async job job_id = str(uuid.uuid4()) indexing_cache_key = f"segment_batch_import_{str(job_id)}" # send batch add segments task redis_client.setnx(indexing_cache_key, "waiting") batch_create_segment_to_index_task.delay( - str(job_id), result, dataset_id, document_id, current_user.current_tenant_id, current_user.id + str(job_id), upload_file_id, dataset_id, document_id, current_user.current_tenant_id, current_user.id ) except Exception as e: return {"error": str(e)}, 500 diff --git a/api/tasks/batch_create_segment_to_index_task.py b/api/tasks/batch_create_segment_to_index_task.py index d72e350299..714e30acc3 100644 --- a/api/tasks/batch_create_segment_to_index_task.py +++ b/api/tasks/batch_create_segment_to_index_task.py @@ -1,9 +1,12 @@ import datetime import logging +import tempfile import time import uuid +from pathlib import Path import click +import pandas as pd from celery import shared_task # type: ignore from sqlalchemy import func from sqlalchemy.orm import Session @@ -12,15 +15,17 @@ from core.model_manager import ModelManager from core.model_runtime.entities.model_entities import ModelType from extensions.ext_database import db from extensions.ext_redis import redis_client +from extensions.ext_storage import storage from libs import helper from models.dataset import Dataset, Document, DocumentSegment +from models.model import UploadFile from services.vector_service import VectorService @shared_task(queue="dataset") def batch_create_segment_to_index_task( job_id: str, - content: list, + upload_file_id: str, dataset_id: str, document_id: str, tenant_id: str, @@ -29,13 +34,13 @@ def batch_create_segment_to_index_task( """ Async batch create segment to index :param job_id: - :param content: + :param upload_file_id: :param dataset_id: :param document_id: :param tenant_id: :param user_id: - Usage: batch_create_segment_to_index_task.delay(job_id, content, dataset_id, document_id, tenant_id, user_id) + Usage: batch_create_segment_to_index_task.delay(job_id, upload_file_id, dataset_id, document_id, tenant_id, user_id) """ logging.info(click.style(f"Start batch create segment jobId: {job_id}", fg="green")) start_at = time.perf_counter() @@ -58,6 +63,29 @@ def batch_create_segment_to_index_task( or dataset_document.indexing_status != "completed" ): raise ValueError("Document is not available.") + + upload_file = session.get(UploadFile, upload_file_id) + if not upload_file: + raise ValueError("UploadFile not found.") + + with tempfile.TemporaryDirectory() as temp_dir: + suffix = Path(upload_file.key).suffix + # FIXME mypy: Cannot determine type of 'tempfile._get_candidate_names' better not use it here + file_path = f"{temp_dir}/{next(tempfile._get_candidate_names())}{suffix}" # type: ignore + storage.download(upload_file.key, file_path) + + # Skip the first row + df = pd.read_csv(file_path) + content = [] + for index, row in df.iterrows(): + if dataset_document.doc_form == "qa_model": + data = {"content": row.iloc[0], "answer": row.iloc[1]} + else: + data = {"content": row.iloc[0]} + content.append(data) + if len(content) == 0: + raise ValueError("The CSV file is empty.") + document_segments = [] embedding_model = None if dataset.indexing_technique == "high_quality": diff --git a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx index c2224296d6..c352f11d7f 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/csv-uploader.tsx @@ -1,6 +1,6 @@ 'use client' import type { FC } from 'react' -import React, { useEffect, useRef, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { RiDeleteBinLine, } from '@remixicon/react' @@ -10,10 +10,17 @@ import cn from '@/utils/classnames' import { Csv as CSVIcon } from '@/app/components/base/icons/src/public/files' import { ToastContext } from '@/app/components/base/toast' import Button from '@/app/components/base/button' +import type { FileItem } from '@/models/datasets' +import { upload } from '@/service/base' +import useSWR from 'swr' +import { fetchFileUploadConfig } from '@/service/common' +import SimplePieChart from '@/app/components/base/simple-pie-chart' +import { Theme } from '@/types/app' +import useTheme from '@/hooks/use-theme' export type Props = { - file: File | undefined - updateFile: (file?: File) => void + file: FileItem | undefined + updateFile: (file?: FileItem) => void } const CSVUploader: FC = ({ @@ -26,6 +33,68 @@ const CSVUploader: FC = ({ const dropRef = useRef(null) const dragRef = useRef(null) const fileUploader = useRef(null) + const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig) + const fileUploadConfig = useMemo(() => fileUploadConfigResponse ?? { + file_size_limit: 15, + }, [fileUploadConfigResponse]) + + const fileUpload = useCallback(async (fileItem: FileItem): Promise => { + fileItem.progress = 0 + + const formData = new FormData() + formData.append('file', fileItem.file) + const onProgress = (e: ProgressEvent) => { + if (e.lengthComputable) { + const progress = Math.floor(e.loaded / e.total * 100) + updateFile({ + ...fileItem, + progress, + }) + } + } + + return upload({ + xhr: new XMLHttpRequest(), + data: formData, + onprogress: onProgress, + }, false, undefined, '?source=datasets') + .then((res: File) => { + const completeFile = { + fileID: fileItem.fileID, + file: res, + progress: 100, + } + updateFile(completeFile) + return Promise.resolve({ ...completeFile }) + }) + .catch((e) => { + notify({ type: 'error', message: e?.response?.code === 'forbidden' ? e?.response?.message : t('datasetCreation.stepOne.uploader.failed') }) + const errorFile = { + ...fileItem, + progress: -2, + } + updateFile(errorFile) + return Promise.resolve({ ...errorFile }) + }) + .finally() + }, [notify, t, updateFile]) + + const uploadFile = useCallback(async (fileItem: FileItem) => { + await fileUpload(fileItem) + }, [fileUpload]) + + const initialUpload = useCallback((file?: File) => { + if (!file) + return false + + const newFile: FileItem = { + fileID: `file0-${Date.now()}`, + file, + progress: -1, + } + updateFile(newFile) + uploadFile(newFile) + }, [updateFile, uploadFile]) const handleDragEnter = (e: DragEvent) => { e.preventDefault() @@ -52,7 +121,7 @@ const CSVUploader: FC = ({ notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.count') }) return } - updateFile(files[0]) + initialUpload(files[0]) } const selectHandle = () => { if (fileUploader.current) @@ -63,11 +132,43 @@ const CSVUploader: FC = ({ fileUploader.current.value = '' updateFile() } + + const getFileType = (currentFile: File) => { + if (!currentFile) + return '' + + const arr = currentFile.name.split('.') + return arr[arr.length - 1] + } + + const isValid = useCallback((file?: File) => { + if (!file) + return false + + const { size } = file + const ext = `.${getFileType(file)}` + const isValidType = ext.toLowerCase() === '.csv' + if (!isValidType) + notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.typeError') }) + + const isValidSize = size <= fileUploadConfig.file_size_limit * 1024 * 1024 + if (!isValidSize) + notify({ type: 'error', message: t('datasetCreation.stepOne.uploader.validation.size', { size: fileUploadConfig.file_size_limit }) }) + + return isValidType && isValidSize + }, [fileUploadConfig, notify, t]) + const fileChangeHandle = (e: React.ChangeEvent) => { const currentFile = e.target.files?.[0] - updateFile(currentFile) + if (!isValid(currentFile)) + return + + initialUpload(currentFile) } + const { theme } = useTheme() + const chartColor = useMemo(() => theme === Theme.dark ? '#5289ff' : '#296dff', [theme]) + useEffect(() => { dropRef.current?.addEventListener('dragenter', handleDragEnter) dropRef.current?.addEventListener('dragover', handleDragOver) @@ -108,10 +209,16 @@ const CSVUploader: FC = ({
- {file.name.replace(/.csv$/, '')} + {file.file.name.replace(/.csv$/, '')} .csv
+ {(file.progress < 100 && file.progress >= 0) && ( + <> + +
+ + )}
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 614471c565..0952a823b4 100644 --- a/web/app/components/datasets/documents/detail/batch-modal/index.tsx +++ b/web/app/components/datasets/documents/detail/batch-modal/index.tsx @@ -7,14 +7,14 @@ import CSVUploader from './csv-uploader' import CSVDownloader from './csv-downloader' import Button from '@/app/components/base/button' import Modal from '@/app/components/base/modal' -import type { ChunkingMode } from '@/models/datasets' +import type { ChunkingMode, FileItem } from '@/models/datasets' import { noop } from 'lodash-es' export type IBatchModalProps = { isShow: boolean docForm: ChunkingMode onCancel: () => void - onConfirm: (file: File) => void + onConfirm: (file: FileItem) => void } const BatchModal: FC = ({ @@ -24,8 +24,8 @@ const BatchModal: FC = ({ onConfirm, }) => { const { t } = useTranslation() - const [currentCSV, setCurrentCSV] = useState() - const handleFile = (file?: File) => setCurrentCSV(file) + const [currentCSV, setCurrentCSV] = useState() + const handleFile = (file?: FileItem) => setCurrentCSV(file) const handleSend = () => { if (!currentCSV) @@ -56,7 +56,7 @@ const BatchModal: FC = ({ -
diff --git a/web/app/components/datasets/documents/detail/index.tsx b/web/app/components/datasets/documents/detail/index.tsx index aff74038e3..79d12e47e3 100644 --- a/web/app/components/datasets/documents/detail/index.tsx +++ b/web/app/components/datasets/documents/detail/index.tsx @@ -17,7 +17,7 @@ import cn from '@/utils/classnames' import Divider from '@/app/components/base/divider' import Loading from '@/app/components/base/loading' import { ToastContext } from '@/app/components/base/toast' -import type { ChunkingMode, ParentMode, ProcessMode } from '@/models/datasets' +import type { ChunkingMode, FileItem, ParentMode, ProcessMode } from '@/models/datasets' import { useDatasetDetailContext } from '@/context/dataset-detail' import FloatRightContainer from '@/app/components/base/float-right-container' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' @@ -111,12 +111,10 @@ const DocumentDetail: FC = ({ datasetId, documentId }) => { } const { mutateAsync: segmentBatchImport } = useSegmentBatchImport() - const runBatch = async (csv: File) => { - const formData = new FormData() - formData.append('file', csv) + const runBatch = async (csv: FileItem) => { await segmentBatchImport({ url: `/datasets/${datasetId}/documents/${documentId}/segments/batch_import`, - body: formData, + body: { upload_file_id: csv.file.id! }, }, { onSuccess: (res) => { setImportStatus(res.job_status) diff --git a/web/service/knowledge/use-segment.ts b/web/service/knowledge/use-segment.ts index ca1778fb94..8b3e939e73 100644 --- a/web/service/knowledge/use-segment.ts +++ b/web/service/knowledge/use-segment.ts @@ -154,9 +154,9 @@ export const useUpdateChildSegment = () => { export const useSegmentBatchImport = () => { return useMutation({ mutationKey: [NAME_SPACE, 'batchImport'], - mutationFn: (payload: { url: string; body: FormData }) => { + mutationFn: (payload: { url: string; body: { upload_file_id: string } }) => { const { url, body } = payload - return post(url, { body }, { bodyStringify: false, deleteContentType: true }) + return post(url, { body }) }, }) } From fce126b206a7b08b6d47f1c06462e54f257c03f4 Mon Sep 17 00:00:00 2001 From: chenguowei <457219884@qq.com> Date: Mon, 28 Jul 2025 15:37:13 +0800 Subject: [PATCH 41/72] fix(api): fix incorrect path handling in Langfuse integration (#22766) Co-authored-by: QuantumGhost --- api/core/ops/entities/config_entity.py | 2 +- api/core/ops/utils.py | 8 +++++++- api/tests/unit_tests/core/ops/test_config_entity.py | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/api/core/ops/entities/config_entity.py b/api/core/ops/entities/config_entity.py index 89ff0cfded..626782cee5 100644 --- a/api/core/ops/entities/config_entity.py +++ b/api/core/ops/entities/config_entity.py @@ -102,7 +102,7 @@ class LangfuseConfig(BaseTracingConfig): @field_validator("host") @classmethod def host_validator(cls, v, info: ValidationInfo): - return cls.validate_endpoint_url(v, "https://api.langfuse.com") + return validate_url_with_path(v, "https://api.langfuse.com") class LangSmithConfig(BaseTracingConfig): diff --git a/api/core/ops/utils.py b/api/core/ops/utils.py index 573e8cac88..2c0afb1600 100644 --- a/api/core/ops/utils.py +++ b/api/core/ops/utils.py @@ -67,7 +67,13 @@ def generate_dotted_order( def validate_url(url: str, default_url: str, allowed_schemes: tuple = ("https", "http")) -> str: """ - Validate and normalize URL with proper error handling + Validate and normalize URL with proper error handling. + + NOTE: This function does not retain the `path` component of the provided URL. + In most cases, it is recommended to use `validate_url_with_path` instead. + + This function is deprecated and retained only for compatibility purposes. + New implementations should use `validate_url_with_path`. Args: url: The URL to validate diff --git a/api/tests/unit_tests/core/ops/test_config_entity.py b/api/tests/unit_tests/core/ops/test_config_entity.py index 81cb04548d..4bcc6cb605 100644 --- a/api/tests/unit_tests/core/ops/test_config_entity.py +++ b/api/tests/unit_tests/core/ops/test_config_entity.py @@ -117,6 +117,13 @@ class TestLangfuseConfig: assert config.secret_key == "secret_key" assert config.host == "https://custom.langfuse.com" + def test_valid_config_with_path(self): + host = host = "https://custom.langfuse.com/api/v1" + config = LangfuseConfig(public_key="public_key", secret_key="secret_key", host=host) + assert config.public_key == "public_key" + assert config.secret_key == "secret_key" + assert config.host == host + def test_default_values(self): """Test default values are set correctly""" config = LangfuseConfig(public_key="public", secret_key="secret") From 15757110cff780a72637b52037676f598e0a4072 Mon Sep 17 00:00:00 2001 From: Anton Kovalev Date: Mon, 28 Jul 2025 10:37:23 +0300 Subject: [PATCH 42/72] feat: default value option for select input fields (#21192) Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: GuanMu --- .../config-var/config-modal/index.tsx | 28 +++++++++++++-- .../base/chat/chat-with-history/hooks.tsx | 2 +- .../chat-with-history/inputs-form/content.tsx | 2 +- .../base/chat/embedded-chatbot/hooks.tsx | 2 +- .../embedded-chatbot/inputs-form/content.tsx | 2 +- .../components/before-run-form/form-item.tsx | 2 +- .../panel/debug-and-preview/chat-wrapper.tsx | 35 +++++++++++++++++-- web/i18n/de-DE/app-debug.ts | 3 ++ web/i18n/en-US/app-debug.ts | 3 ++ web/i18n/es-ES/app-debug.ts | 3 ++ web/i18n/fr-FR/app-debug.ts | 3 ++ web/i18n/hi-IN/app-debug.ts | 3 ++ web/i18n/it-IT/app-debug.ts | 3 ++ web/i18n/ja-JP/app-debug.ts | 3 ++ web/i18n/ko-KR/app-debug.ts | 3 ++ web/i18n/pl-PL/app-debug.ts | 3 ++ web/i18n/pt-BR/app-debug.ts | 3 ++ web/i18n/ro-RO/app-debug.ts | 3 ++ web/i18n/ru-RU/app-debug.ts | 3 ++ web/i18n/tr-TR/app-debug.ts | 3 ++ web/i18n/uk-UA/app-debug.ts | 3 ++ web/i18n/vi-VN/app-debug.ts | 3 ++ web/i18n/zh-Hans/app-debug.ts | 3 ++ web/i18n/zh-Hant/app-debug.ts | 3 ++ 24 files changed, 113 insertions(+), 11 deletions(-) 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 8fcc0f4c08..72c66cf76a 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 @@ -20,6 +20,7 @@ import FileUploadSetting from '@/app/components/workflow/nodes/_base/components/ import Checkbox from '@/app/components/base/checkbox' import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants' import { DEFAULT_VALUE_MAX_LEN } from '@/config' +import { SimpleSelect } from '@/app/components/base/select' const TEXT_MAX_LENGTH = 256 @@ -234,9 +235,30 @@ const ConfigModal: FC = ({ )} {type === InputVarType.select && ( - - - + <> + + + + {options && options.length > 0 && ( + + opt.trim() !== '').map(option => ({ + value: option, + name: option, + })), + ]} + defaultValue={tempPayload.default || ''} + onSelect={item => handlePayloadChange('default')(item.value === '' ? undefined : item.value)} + placeholder={t('appDebug.variableConfig.selectDefaultValue')} + allowSearch={false} + /> + + )} + )} {[InputVarType.singleFile, InputVarType.multiFiles].includes(type) && ( 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 76eb89164e..382ded3201 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -211,7 +211,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const isInputInOptions = item.select.options.includes(initInputs[item.select.variable]) return { ...item.select, - default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.default, + default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.select.default, type: 'select', } } diff --git a/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx b/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx index 73a1f07b69..3304d50a50 100644 --- a/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx +++ b/web/app/components/base/chat/chat-with-history/inputs-form/content.tsx @@ -73,7 +73,7 @@ const InputsFormContent = ({ showTip }: Props) => { {form.type === InputVarType.select && ( ({ value: option, name: option }))} onSelect={item => handleFormChange(form.variable, item.value as string)} placeholder={form.label} diff --git a/web/app/components/base/chat/embedded-chatbot/hooks.tsx b/web/app/components/base/chat/embedded-chatbot/hooks.tsx index 8ae86bda84..4e86ad50e4 100644 --- a/web/app/components/base/chat/embedded-chatbot/hooks.tsx +++ b/web/app/components/base/chat/embedded-chatbot/hooks.tsx @@ -199,7 +199,7 @@ export const useEmbeddedChatbot = () => { const isInputInOptions = item.select.options.includes(initInputs[item.select.variable]) return { ...item.select, - default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.default, + default: (isInputInOptions ? initInputs[item.select.variable] : undefined) || item.select.default, type: 'select', } } diff --git a/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx b/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx index c5f39718f1..29fa5394ef 100644 --- a/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx +++ b/web/app/components/base/chat/embedded-chatbot/inputs-form/content.tsx @@ -73,7 +73,7 @@ const InputsFormContent = ({ showTip }: Props) => { {form.type === InputVarType.select && ( ({ value: option, name: option }))} onSelect={item => handleFormChange(form.variable, item.value as string)} placeholder={form.label} diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx index 269f5e0a96..430359b845 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/form-item.tsx @@ -158,7 +158,7 @@ const FormItem: FC = ({ type === InputVarType.select && ( handleValueChange(e.target.value)} placeholder={placeholder?.[language] || placeholder?.en_US} /> diff --git a/web/app/components/workflow/nodes/tool/node.tsx b/web/app/components/workflow/nodes/tool/node.tsx index e15ddcaaaa..8cc3ec580d 100644 --- a/web/app/components/workflow/nodes/tool/node.tsx +++ b/web/app/components/workflow/nodes/tool/node.tsx @@ -22,13 +22,13 @@ const Node: FC> = ({ {key}
{typeof tool_configurations[key].value === 'string' && ( -
+
{paramSchemas?.find(i => i.name === key)?.type === FormTypeEnum.secretInput ? '********' : tool_configurations[key].value}
)} {typeof tool_configurations[key].value === 'number' && ( -
- {tool_configurations[key].value} +
+ {Number.isNaN(tool_configurations[key].value) ? '' : tool_configurations[key].value}
)} {typeof tool_configurations[key] !== 'string' && tool_configurations[key]?.type === FormTypeEnum.modelSelector && ( From 63b6026e6e414d3c1970944457dd77fb4b105336 Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:59:43 +0800 Subject: [PATCH 58/72] minor fix: fix error messages (#23081) --- api/core/workflow/nodes/tool/entities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/core/workflow/nodes/tool/entities.py b/api/core/workflow/nodes/tool/entities.py index 4f47fb1efc..c1cfbb1edc 100644 --- a/api/core/workflow/nodes/tool/entities.py +++ b/api/core/workflow/nodes/tool/entities.py @@ -55,7 +55,7 @@ class ToolNodeData(BaseNodeData, ToolEntity): if not isinstance(val, str): raise ValueError("value must be a list of strings") elif typ == "constant" and not isinstance(value, str | int | float | bool | dict): - raise ValueError("value must be a string, int, float, or bool") + raise ValueError("value must be a string, int, float, bool or dict") return typ tool_parameters: dict[str, ToolInput] From 47cc95184132b756a94e58c2e0c85fb01044eced Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Tue, 29 Jul 2025 11:17:50 +0800 Subject: [PATCH 59/72] Fix Empty Collection WHERE Filter Issue (#23086) --- api/services/app_service.py | 5 +++-- api/services/conversation_service.py | 6 ++++-- api/services/dataset_service.py | 26 +++++++++++++++++++++----- api/services/message_service.py | 3 ++- api/services/tag_service.py | 6 ++++++ 5 files changed, 36 insertions(+), 10 deletions(-) diff --git a/api/services/app_service.py b/api/services/app_service.py index 3557f13337..0f22666d5a 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -53,9 +53,10 @@ class AppService: if args.get("name"): name = args["name"][:30] filters.append(App.name.ilike(f"%{name}%")) - if args.get("tag_ids"): + # Check if tag_ids is not empty to avoid WHERE false condition + if args.get("tag_ids") and len(args["tag_ids"]) > 0: target_ids = TagService.get_target_ids_by_tag_ids("app", tenant_id, args["tag_ids"]) - if target_ids: + if target_ids and len(target_ids) > 0: filters.append(App.id.in_(target_ids)) else: return None diff --git a/api/services/conversation_service.py b/api/services/conversation_service.py index 525c87fe4a..206c832a20 100644 --- a/api/services/conversation_service.py +++ b/api/services/conversation_service.py @@ -46,9 +46,11 @@ class ConversationService: Conversation.from_account_id == (user.id if isinstance(user, Account) else None), or_(Conversation.invoke_from.is_(None), Conversation.invoke_from == invoke_from.value), ) - if include_ids is not None: + # Check if include_ids is not None and not empty to avoid WHERE false condition + if include_ids is not None and len(include_ids) > 0: stmt = stmt.where(Conversation.id.in_(include_ids)) - if exclude_ids is not None: + # Check if exclude_ids is not None and not empty to avoid WHERE false condition + if exclude_ids is not None and len(exclude_ids) > 0: stmt = stmt.where(~Conversation.id.in_(exclude_ids)) # define sort fields and directions diff --git a/api/services/dataset_service.py b/api/services/dataset_service.py index 209d153b0c..1280399990 100644 --- a/api/services/dataset_service.py +++ b/api/services/dataset_service.py @@ -91,14 +91,16 @@ class DatasetService: if user.current_role == TenantAccountRole.DATASET_OPERATOR: # only show datasets that the user has permission to access - if permitted_dataset_ids: + # Check if permitted_dataset_ids is not empty to avoid WHERE false condition + if permitted_dataset_ids and len(permitted_dataset_ids) > 0: query = query.where(Dataset.id.in_(permitted_dataset_ids)) else: return [], 0 else: if user.current_role != TenantAccountRole.OWNER or not include_all: # show all datasets that the user has permission to access - if permitted_dataset_ids: + # Check if permitted_dataset_ids is not empty to avoid WHERE false condition + if permitted_dataset_ids and len(permitted_dataset_ids) > 0: query = query.where( db.or_( Dataset.permission == DatasetPermissionEnum.ALL_TEAM, @@ -127,9 +129,10 @@ class DatasetService: if search: query = query.where(Dataset.name.ilike(f"%{search}%")) - if tag_ids: + # Check if tag_ids is not empty to avoid WHERE false condition + if tag_ids and len(tag_ids) > 0: target_ids = TagService.get_target_ids_by_tag_ids("knowledge", tenant_id, tag_ids) - if target_ids: + if target_ids and len(target_ids) > 0: query = query.where(Dataset.id.in_(target_ids)) else: return [], 0 @@ -158,6 +161,9 @@ class DatasetService: @staticmethod def get_datasets_by_ids(ids, tenant_id): + # Check if ids is not empty to avoid WHERE false condition + if not ids or len(ids) == 0: + return [], 0 stmt = select(Dataset).where(Dataset.id.in_(ids), Dataset.tenant_id == tenant_id) datasets = db.paginate(select=stmt, page=1, per_page=len(ids), max_per_page=len(ids), error_out=False) @@ -951,6 +957,9 @@ class DocumentService: @staticmethod def delete_documents(dataset: Dataset, document_ids: list[str]): + # Check if document_ids is not empty to avoid WHERE false condition + if not document_ids or len(document_ids) == 0: + return documents = db.session.query(Document).where(Document.id.in_(document_ids)).all() file_ids = [ document.data_source_info_dict["upload_file_id"] @@ -2320,6 +2329,9 @@ class SegmentService: @classmethod def delete_segments(cls, segment_ids: list, document: Document, dataset: Dataset): + # Check if segment_ids is not empty to avoid WHERE false condition + if not segment_ids or len(segment_ids) == 0: + return index_node_ids = ( db.session.query(DocumentSegment) .with_entities(DocumentSegment.index_node_id) @@ -2339,6 +2351,9 @@ class SegmentService: @classmethod def update_segments_status(cls, segment_ids: list, action: str, dataset: Dataset, document: Document): + # Check if segment_ids is not empty to avoid WHERE false condition + if not segment_ids or len(segment_ids) == 0: + return if action == "enable": segments = ( db.session.query(DocumentSegment) @@ -2600,7 +2615,8 @@ class SegmentService: DocumentSegment.document_id == document_id, DocumentSegment.tenant_id == tenant_id ) - if status_list: + # Check if status_list is not empty to avoid WHERE false condition + if status_list and len(status_list) > 0: query = query.where(DocumentSegment.status.in_(status_list)) if keyword: diff --git a/api/services/message_service.py b/api/services/message_service.py index 283b7b9b4b..a19d6ee157 100644 --- a/api/services/message_service.py +++ b/api/services/message_service.py @@ -111,7 +111,8 @@ class MessageService: base_query = base_query.where(Message.conversation_id == conversation.id) - if include_ids is not None: + # Check if include_ids is not None and not empty to avoid WHERE false condition + if include_ids is not None and len(include_ids) > 0: base_query = base_query.where(Message.id.in_(include_ids)) if last_id: diff --git a/api/services/tag_service.py b/api/services/tag_service.py index 75fa52a75c..2e5e96214b 100644 --- a/api/services/tag_service.py +++ b/api/services/tag_service.py @@ -26,6 +26,9 @@ class TagService: @staticmethod def get_target_ids_by_tag_ids(tag_type: str, current_tenant_id: str, tag_ids: list) -> list: + # Check if tag_ids is not empty to avoid WHERE false condition + if not tag_ids or len(tag_ids) == 0: + return [] tags = ( db.session.query(Tag) .where(Tag.id.in_(tag_ids), Tag.tenant_id == current_tenant_id, Tag.type == tag_type) @@ -34,6 +37,9 @@ class TagService: if not tags: return [] tag_ids = [tag.id for tag in tags] + # Check if tag_ids is not empty to avoid WHERE false condition + if not tag_ids or len(tag_ids) == 0: + return [] tag_bindings = ( db.session.query(TagBinding.target_id) .where(TagBinding.tag_id.in_(tag_ids), TagBinding.tenant_id == current_tenant_id) From 77216488675db30d21e43466cca95dfe82bd9c0c Mon Sep 17 00:00:00 2001 From: GuanMu Date: Tue, 29 Jul 2025 11:24:59 +0800 Subject: [PATCH 60/72] Fix variable config (#23070) --- .../config-var/config-modal/index.tsx | 1 + web/app/components/base/select/index.tsx | 4 +--- .../share/text-generation/run-once/index.tsx | 4 +++- .../workflow/panel/inputs-panel.tsx | 24 ++++++++++++++++++- web/utils/model-config.ts | 3 ++- 5 files changed, 30 insertions(+), 6 deletions(-) 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 72c66cf76a..27072f5208 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 @@ -244,6 +244,7 @@ const ConfigModal: FC = ({ opt.trim() !== '').map(option => ({ diff --git a/web/app/components/base/select/index.tsx b/web/app/components/base/select/index.tsx index 77d229672f..d9285c1061 100644 --- a/web/app/components/base/select/index.tsx +++ b/web/app/components/base/select/index.tsx @@ -77,7 +77,6 @@ const Select: FC = ({ defaultSelect = existed setSelectedItem(defaultSelect) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue]) const filteredItems: Item[] @@ -201,7 +200,6 @@ const SimpleSelect: FC = ({ defaultSelect = existed setSelectedItem(defaultSelect) - // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultValue]) const listboxRef = useRef(null) @@ -344,7 +342,7 @@ const PortalSelect: FC = ({ > diff --git a/web/app/components/share/text-generation/run-once/index.tsx b/web/app/components/share/text-generation/run-once/index.tsx index 546b21d2b0..cfafe73bf2 100644 --- a/web/app/components/share/text-generation/run-once/index.tsx +++ b/web/app/components/share/text-generation/run-once/index.tsx @@ -66,7 +66,9 @@ const RunOnce: FC = ({ useEffect(() => { const newInputs: Record = {} promptConfig.prompt_variables.forEach((item) => { - if (item.type === 'string' || item.type === 'paragraph') + if (item.type === 'select') + newInputs[item.key] = item.default + else if (item.type === 'string' || item.type === 'paragraph') newInputs[item.key] = '' else newInputs[item.key] = undefined diff --git a/web/app/components/workflow/panel/inputs-panel.tsx b/web/app/components/workflow/panel/inputs-panel.tsx index 8be8d810f0..64ac6d8686 100644 --- a/web/app/components/workflow/panel/inputs-panel.tsx +++ b/web/app/components/workflow/panel/inputs-panel.tsx @@ -1,6 +1,7 @@ import { memo, useCallback, + useEffect, useMemo, } from 'react' import { useTranslation } from 'react-i18next' @@ -32,9 +33,12 @@ type Props = { const InputsPanel = ({ onRun }: Props) => { const { t } = useTranslation() const workflowStore = useWorkflowStore() + const { inputs, setInputs } = useStore(s => ({ + inputs: s.inputs, + setInputs: s.setInputs, + })) const fileSettings = useFeatures(s => s.features.file) const nodes = useNodes() - const inputs = useStore(s => s.inputs) const files = useStore(s => s.files) const workflowRunningData = useStore(s => s.workflowRunningData) const { @@ -44,6 +48,24 @@ const InputsPanel = ({ onRun }: Props) => { const startVariables = startNode?.data.variables const { checkInputsForm } = useCheckInputsForms() + const initialInputs = useMemo(() => { + const initInputs: Record = {} + if (startVariables) { + startVariables.forEach((variable) => { + if (variable.default) + initInputs[variable.variable] = variable.default + }) + } + return initInputs + }, [startVariables]) + + useEffect(() => { + setInputs({ + ...initialInputs, + ...inputs, + }) + }, [initialInputs]) + const variables = useMemo(() => { const data = startVariables || [] if (fileSettings?.image?.enabled) { diff --git a/web/utils/model-config.ts b/web/utils/model-config.ts index 330d8f9b52..3a500f22bc 100644 --- a/web/utils/model-config.ts +++ b/web/utils/model-config.ts @@ -62,6 +62,7 @@ export const userInputsFormToPromptVariables = (useInputs: UserInputFormItem[] | options: content.options, is_context_var, hide: content.hide, + default: content.default, }) } else if (type === 'file') { @@ -148,7 +149,7 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[ variable: item.key, required: item.required !== false, // default true options: item.options, - default: '', + default: item.default ?? '', hide: item.hide, }, } as any) From 27f400e13f85b656fc57ac7d7bcb120b6ba8161b Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:05:59 +0800 Subject: [PATCH 61/72] feat: update banner (#23095) --- images/GitHub_README_if.png | Bin 191695 -> 72664 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/GitHub_README_if.png b/images/GitHub_README_if.png index 10c9d87b08fab53abb7cef67cd6ccc7cdd1b91e5..281d95cf9cfc4c9fd6aede63a1b9b4d00d8f2e85 100644 GIT binary patch literal 72664 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 literal 191695 zcmeFZ2UwHMwlEq+K}A7DKtMnNk&g5ZDgpumAE6V9Ql%vH-uy&Bx|9&<3Wgq#KoUYI zN{0YS3ndh#3Lzl9!;O2NefD;r|D1E5d;a}D_uj_|@4RcuyJluhTeH?0PDW0?0xoN* zYpMfIodN((QGS4v=~E+`s;bt10QJ;0AFKT}p#wm1XYK+3V2HOTP~*W(Q#13MG++Pv z#m}^-wqEW(-~WlD;N2$t#0~%q2>&N}{;TNocJ^Mjlng&85059Ma7tybQQ&Kie}(ye z!Zv?}6@S8h-tOL%Jb(OzJq>}X6xfae^Ev(lZ1WGWt-I&X{Be{#ieOitpJn}YKWlu^ z9%5ubd7q;^HvnD$AV34~;Ai_OVTyCh0{~>V0064je~+_C2LLb;0KoOJzsK>s1pw$@ z0s!dGe~2GZg={g98AtUI+kOG6Mh@z5oChKz~W2c>hV=Zc>tNQ0nDI z`8fhy0rr5K08Ib{U<(kTK;nSA05O2{2@#-5>CXR|`-$X#hJo_+$w$DIb5ti6;;2q> z0Zv~zMRn!WNeh6JLT;+R;r?rb&t0HBPeXn74AtqM87waYPEnmXb?Wqan)B3DXV09W z2T+|kd+Ibb&AIazu3lld#=>fvMi^$4GxPI*gJEL6B_nGBL)TpA;;u#wN&EQnh`oXi zh)dibxh-j6>{K-P{ekLJ8!35B{ixIq3VII895{XYXAS;ZIK@AAo|@*&*$aOy-sTEr zf}A;Z`t)h)E0?KhXwFapPE+c0mYU|uxvLDf&R@GOX8e?qiA7r9#KR{E3ads94iSDZ zb8$<_$lf>d_Vr6mtE?K3H1tY&j}e#f{4*Je=D98R0BF;%YHI)Pp@Hqm7~momr4Co9 zt^ia3HJ>vD3l{n|n&@jIBX`aOAZsBZoQgLOik`W-Yc>F0a=4af=4yOCr@X|~nj4e};x^A?X?8 z0uM<$oXX*Z)AVaJ(bvuA?o`tyhG%j3ho*9Ba-LJ($OxP4TZme`znSzSd{wUSh)k9L zb%ExW4+6gJ{}I9yeDk2!=>!mT@B6OYr(eGK1=$yQWjgm?ztH#v+yD0BRo+#YtfW>j z>mZdh{l#oz{BRigIptKF2!%8Yc(83DHs80{AB?WXv>_Zr_s6oc=R}dj@Vu|#ua9*1 zUF>8)b~Jx9(CJ!x#&CmQA0odb`&pD|>iYoE1hf6ElIVBlLhw{Lq*9KDvOorl>YlT-LPYarBNmyHRFv$Pk|yr!?!RaAl(fP*U~Q;@_8eeD%AUI>k`GzUV9 zkKlsl{i$#lb3!VdUv~@PvIOVX3M82iPrh3XTHQMH1iy!ERy^f1ws50*Mwe>~wWaC6 z!n8pUyDBZ{7F3tmxr+3*8b>L>cJ)!T(Brui!1(dpBz>etv4)=7P$ipObIXAnaD|Kl zg*k6`0vmTu03m)X;*V~-eLn$=>1zxBI8LOGtd@8T$=G6+Zt?uEOCyDE%?kd~**1+M zZr|n@tjLf-JkcG`AFHki)T$<%n>0Gt;xc<~7ZWBX+R7)oOi)j~o<^VVuRriDvk!#A zgZAsacLtttSUz867ev*mWRM$6(RwekmyYi(U(^%(0h%sp3~Wq6_A6iT&oFYMKbjPP z8k_)Zau^05gA}ZDOFMHo@H7&ZFgMKm*50=I=oGic`i06QT0h$Jd6SP!w@gqUYR9y9{o5w_L`B>+0M@Bhng3Wiu$qE;FpnRPn~&F+z-d@M9G^!kYZ@cKGr7*!VRA|pD5wVF71 zFjUk#OB#b#JZnQOAL`But5;!VK`%o*v*sSDOYYzZKm@;@bzkZ7pY*))k?vflKt%k8 zb@XypK}EDt_F=@}l9Hz0*<)6r&Wn{MkBC}@AsDMX=~3_|9A-%w9b?C$S(w7a3K~Y0 zgni;9wcoTabSdTN7h-|1t2u|kI8aEs%$Cjm?0Mu9s z;uFrc0zsQ;?R3z>A-Ef`xb~ddg7D%D=~JuTOapr7RRBKFfB%Pv!R{)eep)%VSi`U( zt?mSHz~0e#C)oQy^fR6=e0M`{`!ns~2_PZYUgrL}y19DA5P#X?LqEdg8oivG9;X$Z zKp6WLrcQN>Zwb~voO=SW#WK&XCgj@fJ^d2K;dDIuEi89!V7cJ?E#`l0&aGdn5(;Mr z?wfC%0G9o@4znrQbA{yTXL?kKilQ6G@*$}cXSg=fHrKYkP8Y6Cfr45Tf2qe{eu=fV zRoGF&taVC*d`QY%)cwPN!UM;M^9KWd1F z#w5F@bnNl%AS(26ZK4@Mu>}dQ^&eW?EOCa15UjyNM+~R4?l^m4Jg;Ir$KkYT3#?7< zS%Rj$3~|Mp>iSSY7Ij0ot+_E2E~w$A)o=Jd-`rRVE~wRCjS$otsHZ$b=Ejsx$tn2) z;Dl}dFGuA6pCeDbk}(#|mML~5o&eH02aVfqo>JMb3|_3)R-QWSILAqitj5Vr=NwJ_N6O8h#2FgQFHxB z>P`U=n6O(yShBX4e9mh53^F!Fy&*UvJ}b(+$C%HFL$R?x`gYY+eXYf2xB;#2lA~F) zVQlXrrVa(7YZ5+LD;lSPwA4q91Roxt+Inl*dF3pFgM@W@g$0IhrC=q^z7c^>R}#C~ zl^aSyrX$N?9(S8KdK-{Mg68lTx zABg*+uLfzY?C!cxXbdcYjlh@iE@njd)@DDn0S|$%@8XGN2*`@@r52oVft!UOJYMRPYVLtT z6G-7&?j9O1l-~Bjf{$zI*@qR5RSe0Ujn`u2HC{(o4W;k*3&5Sz+U_a8P6~SND9%iX z?c+OSkm}@F%CtAJ47WRg;!q#|<52qkc32%u9_;p*q2 zqhl4TO_;T}pR+ETi1YShDi)X{w!6Has69VfQetM0La01@hp|p5sTvO-+moS_3bzS7LQtFZLZKO=o z2_TBjXxgpVzmD^M(5!JLb*uDYd#Yun;BKAYeaIG00>@^U$_+#H;^b|dwi?pLk?SrB z!;X$r^eUok7Z$RJ)}JK(q_IicZ91a|AhU|NMcQ<%PrUsjf+ZZ}ziq2E-q|<@6>dt6 zb5a;~C1NG_D4V{G8x6long6BO*3U!Pz1$Y-+)p*+thjcJtx5!H3Hxh9@uEo(*q>7Q z>lPE*W>}N9(a?))@obGg#+Kx64*hkAPD}a0RFJCU`1FA&T9Bt}cCdAX&;?8zrH`yK zE4Y0oF%k-#|M8{la(&Q{N1&fbNr^Svr^S@K9LD`yUz8j~L{EQ{?^tmi!`x`FuWfH1 z_hN^BZ%f679&hmWxIJy#TwJ{UXHg`^2oj3BE?x5CFh6&_+&Xlwr-5+f0T&=lRxx?C z5k}%&mR^d1@p#Y5!V(zLC5)?jOKbW0saqqq`>Z(c)-HM$Irp9!dsnlnf>OQ-yiW>3 zM8}R3vz_+=JfN=y{# z#Up-H#b!W5AmCWu3~yk3Qk(dzngd3IHf%x=xIv&UpPH?w*6-tfN--{PVO%9)I!&s2hIjbwmJr-Vg%I5wFmteUkQx7ab>CtD*@UcRWNL(E9*mh{~|wam_$~TYtw=! ztD8W&^E&nd7*sN?u4-lnlZn%oc4)1otq)^!^@iv$wcer6Zko6@WM;l~*uKiri4>CH z3ddKnbXqv_x7nNO&S0-G?1*{&;iV5T2w)RrapoNA#&K zelMy%2^ZRrT7_JQrd)&6!U7a{qk%DsP*_+9>Ve4B+f|Fvjy{!Hw#<-J9k$}2L6hn* zly6lu+3eGMp6W>yu1y0s0qh}byU%Hkz`SUYQu|(Q8gRL)ZXQ=V`Efc!PbL|N3VNz7194pDhqz-esh6}t zx;|uyTDDh3o6R;~m{&Xj#Q%shw;Z?ML#{L-TZg9i++4+Re(>A0QmM8Mp977d9Ua-F z?2D#tp4c%))GDRN+)n^6lhCrW$_wotW;@`z5^n2ObCSR)gQlhZ@XgX2+#Ko;z*RIR zL_=Wq8#m?zQ`ED}^hnB{S6!BPN5O!lE-(3V@WYv9WqwX;j#$fxx~bwMz4Bz3nX_(& zOSjr#Fb`YXwmG7cpeotTDje8pQAI1{nx|Xh@p{&b8YIJ?V((QYC#@;F=EPDKek&zN zN=EeN^vXe0e{#PzHtgnF&*o{z#Ym1hE2P?v% zms^*T9E@7A>UdOZNm@v_RuQ2#q2Lu*enSWzrn6K6-1ufxuJ7bzQzWp`n(5+Nxd&9d<ss-};JQMdO&?iUm|f$R4{oV~Y0PqC+XlF{ZRe&z^4n>1nw3O)#b#@0i1bNIVhn+D+N8d*{PsfgqxbA!&N#`)f zsV%EkFXbSMxUA_&s?nExmKVc6T~xx5UlK_Cey= z_QgFf99xwQULgBfPpNm6nF8>Bl^th6%(+4^zq8YA`!q8!zcv`o|CQ?5T@RJ2+&i}= z`fUGP_qr+yx6A%2ceL`JC%YziuV)+5lL3Xv`Yx)On3I6IEo7{k2%>n@J{3Xf-9p#D zw491Fv@f!C0SFnM(6mXP@avyEcru91Z09-#V=Wu1}zAc_Zp zk|blb`di3oypy(*)*nKqe~8}Kvc1R$*xC;{Z^z$Sv5AMM5J zBr{r7Ui{IWC&A{r<}8=eR*;08clNk;3Ss@YeBcD2cl?%ajqFrucZ0G^Bvo-d>TJLg zQ(SM5mTR55USUuPsPSl-nCI#4IG8XI`LcWZ$HfB8{t}s&FEZ3hR<(KGP^GCaijG;% z!*m(flnknJA9(B`!ok{&h=1)5jX@i+>|_dL60M;KBgh->zW%Kv)nN1 zolc{{p4EbFnDhs@=y?B{Yj=)l$;Yz#J=2!aC+ipSN=M$<>c-W!O7gkGdn@J3a@9V{ zfd?SfiqFWc4oyC7X`6w+;dgk4SIgMJ45Ivf#i$(V$-fFYAv0D zi!;s=?4Og(+5%OdNv(#BWZAMYsl^#ul#(Doyn)8m?&5g+Sb5zccAh5o13Mw(;aC}K zsN<%j_grkjXS4EB$GCB>#nM!_5*HcI32o#1-l^#7+E$=3-cXmqSrbF5YoR~BG}lHo zq1YdN&1!tUTGq6k4_lkSaQ3|a#W5UPATt+F21|TWu89Rn!U7AEkcfyDUO9OjtlyMV>O=W7#3UGXYTKcIbI4n zHN)mu(K+afW-S{DRP1r^wQ3ZZs@PV{65J81Xdc!w*}{Nzm1}_w6_$KevCN3sk*Xx; z1{V`Dh$4DapBMqUR~)TD1psukOMf4Kmj|-4zW3Ep_4g};dq1}H?1gx-Jb!XU@RsjW zB=S9RclNDVoAZPg@5BYGn2muIGWrht6L^BPpchQ6+xm!8LN27(U+HZX`yS?fnXVuw z*!T@WWRz$p&K{nwsA>J$Jq|5kA-&Bv#oKj_(id&QKbV*mEYo{qHWko z)bN8O_jsSEOhV?c5n}3-C_|p;_}X|`)Gi|IF>hd6w#O@M)hu`p~wE{t?bFH za1V)3RD$Gdyf&M>Lj`MAc!nU1mpbYPP@Sh1VJr4Yv&a*87w9(uM%9iH#*A~_0q??` z6sk~d*?w)V8@X9m36=YfJOd@-9hjWtrYqQ->dvkFMe4N_vj+O;Li^!8=9jv zuh{a|m}^vB|6CVyEx1KA6Z@k#L9wR8UvseP6_LPYwCPF}QXlhGL8(e@MTb9Iv&x?J z>%mne5~v(i4Mm787D}vvDW}QNfsSvtc!cyz(ZW{J1W*~;C(@-nWkPLTXbaEug>KiU z@oo!07vdEc31(RgNY_kuwCtv41g(s21ZSLL9#caGp5g%W{;t>653Pl#an@wp|H+;V2hOS05uu}i{ z$e2gw-2%_aXik{4b~ndXj^#YC_E^3Y6>r~|{aDLI=vgX6Fu`^`sge5~eVPqM!Xv2I zgu}Qrf}_uHS-oXh9X;jwr~MZLg{qX{!Ym~tx$qiqH{R(@+lzu{X6%lKUpAUNk)!69 zEHGfNma@7z*1kHok@b#odXeFcHeowj0)kjMgu3y=Us)u>!~my&y8V8j1iQ`bF-1_b zERGRw-LfJ?V{5nPoM~__7tb-u9Xo3}YiOTDt2$qUe4Mc-nOU0k9!WQ7WMX{Y)70FP4pjs!#J1m`dxJ|?Vh$)ww2O^^GXPn40ntr zNU<9m-^>(z-LN3TAGm3ZH$cnYaGLYGCjt!~ox-l(3E*zmj9c|y{@m$?S45OAPx{AP zZA^M&C09{7#hmz*$V77^as7tDwHNWY+3y7nCP50hifgOmwVu_LUn_L!ZhDV3Nm%+c z*V`SKOtaAoxMj>Gxm8X4nQMJJS?U$|Ozh^=vQQ>pk}bG0jwn+qEs`l-TkOAtDcLE4 zZ^@Dx$E26e4uluwgtX=;HLZP{ucoltWu3NJt~y?+cIgk2&C+-XzSdoETVGzq9`$WS zkC3>2U18R|Num&ND;3@~L4@@B0Vmab{`kh*Kg-FR?&8Zb&)yY0rSmYqbS?d{@#$p^ zj=>C7=_mOed%-Dup4F`b3Ur~v^scYf&a#+sM3fEa*OtoC9bC`%LF|po$A?txL~T^R zLv_Z;m``Ol7Y&b{0Q|!gv`!UHIqb}9ebpE`{&eU*M&p)nbK5P{uKY}h^)hGsrMJGj z0J*+FSc%5feKV(0>0#a~=5t+koP5ZkeHg?2^C9`@!hC%-6A%BFS9{G+Zk%i`1+dR^ z=~TPH2Aq{d9-iCe)YOQ$l((EBI9tm{+uNE##F&xE!fBEE$|k~033qhU3IgG!H3?+^ z@pMVMTIAGwPm}aZqNO8;^v@&nF_&O|VxBrV1+3BGH^)^`Y=jfQwM-Ww(i`#y?=yb; zUXSrcRga-To@8Ik$x=U)X6GWgw$7;&fX~uH9d!i+>W6+$WBw^8V$@5m8wFcJ2VooB`!ORe07}FDNyqYEV!6r?dssTldC`waqG1}=A5M>p4L`Pydt;a5}IJ^4oY{0L*&y)2U@d__6=NAy8zwF)JY zcFOVNMYj8*Sy$E|%zn>#aFJsNUQ)5zZH*n(Em>6<2qIMtfqA4uVu7t7{R^CM$uC0E z?yKu)HqX|Cwkf^-JZd%VsD?Dj_}zUL@8-u5|4MQM3)aPeK-eyNg;kayp?}`mFS59QCt4}9ULKe>ru{>b3Z?8zj8%jH9~3M5Wt{T_Sd$t!o0EGxZbSs+h(npF?g2K zAOf71stz-vmW^}a#Q(5VnTyMtA}GpoIlkz=K^F-9 z{^;IX=A$a8nwB;AJAR_vWBBT#@sJ>BdCk*B9+_tO6_E-Tf}??BAWdKYmZbt+WjBNU z#Pn!BJcDS?#>Ff%)uHpYQ?m+eX!{F(70(FcGp75bB{Hd`S9S|K_T?Gb0`BBFnQI1_ z6I|_gZ>nVJuRjVo9IOb716*gQ`CBj1_rhSY?bG&m3Z|-KB3>p0iHhkiJ86ZE z_F;4g&HMa`$V5ud#^j=!3SLg)&qjBFmZe|#1Yjv&+`Rv^ zJ(z&R^7b-Sp_LObU?*??$~3DDpHQV477)QjXm+-#yAXmkyHXhe@vcvSK-&Wxxtphh z!|WS+$b$aTf`Z1ulV86I-ev{R%%Vot{`?AJ<_IkIm#!xir>|k@#uFS|LgGcc64?m#0(R+5MKbzqd>K-&T1{tuV7Sewk)LPx zcutW@3xla*H>+N*_2&NQ@u|0Fo8BkNPe6o%jAv)u>eeO1SNg@ub(^0;6-J3Sumk<} z^Rw0+*!=^j2`|Ybw{gs2MH)s*%FYcnSskw^kwgTkGa=gRgVS7zaYCaQt!<3+~ z?{=4aDm2$U`a2%+#)X62YIB6UoJO5fk0jBNp>@ck;yuyr=i`m9uq@-cqP}LeZ`NlL z&7)FSkzR2+K7{2a8?R-lix_A7Bo+a81I_V#fyZrP1R(i(10rn)3VE4{XZX>dBfqrk zbPwAIpXsAWfEsWofL#WKsyp*`o$=k16j?7`xzi&}`s?A1N-<-0&bzz|psG!#FkOAhCkLst|{_Q9% z?NDVtyI|tCOWrgLPmm6BLZKxIZyN9P_%dlTxS)Te9H@%wq=Ev@*d8KGKKLtM)RsLL zEmao4vmu-o8=jFs#@G|z8c((P&a(_wrSD!tSHJq$=fmIHBazi4%${zy$J4Fa(c-83 zG@;8pcHD%$ZD!Wkt8Avgb|9xioH4@@@g*X%ea%Uzp(rydR!J*Rel<=|V$2-l?BU`( zV{q*?;Xq2-eUF-1RG^m#`|<7I0@Gos$vlLWH#?ENMFIx%V*Bb`m*u5~cz~Y3yyJ9s z(10FrpZ|u@ryj|X!bzl4j}UTZt;9G6t~PJ`>MlxufB2Os%H$UE`c2x$PCLbS4N^%q z(GQ=dOXhcU>nrN9z)ES%Zd$(0=Y-I1QGIkPztndKi)7B?YbU-qy7z;P6z&~%``dFb zs>=HtN$V!d_tTCQzcc7miR-q~^lEfkS5kv^H{R+}0 z5b|SAwIu|!7H!w%zg;`{h69B3DjsqzLAOksK4v&fyczZoj1YCte^#@gl{YGSq1OoA zas@qSh#XsIt_s???asY)0(f#P3qH;3i`hGS`+cqQ{F`8jq3a#%AYbQ@%OyRq5>b8{ zHOC$5huPUJslc+?JQUZLtD$VfyiyjpSy}y{pNq-;%tnLIw?UxdC^6{@zHP6?Gp2S1 zbl-f`>E>Fm*_KuxDlGUuF}>k=t~iKiTtiLJWjXsm0a(S=ZRdF-$#B`qOj{U}`>?l@ zfEme1JU=I6NX_CM9v{KlE-+m8$BRy$A8CCxt{IG8mn3lF7WTd@(3DYHM)U6Wue*_I zv9)d7Lk?+{0xKS~<#!T{wcjRQ_2_llvYF=1?0wpoJ$waQokJYy6J>k9A^>6>xIWm{ zRiGr_=WW5WLUT8_{pN?x$e26YmUlZ~V52|s8kCf>KF*NJpNFtc%s^YAi>q&!f~h9nR#g3ed+IuIDyVUtpK#2;tw z%rgX%tTa096~f{JB=eV>xm!M@G4XAiNDawpPjSAsbN7#>H3~|e4gf{FCZUykmJe>^ z*c-dFOyu7`Tp5!L9m3O)GwW@(YI`^&d}AQyxMy<;N$#U~A4EMPu{?g~JyKva(7EuZ zQ50dq-0-eauh;T`gPqnKk>*ZDw`at(#~T?I>IZvHa(?=zz7kg=XD+d;c=@cbgXrV# zsWwX)HN}V;dyv1El<;e(;!JxxPXOJ}cF{7rhoQ0#^G@u>{sZTmSMuxdGYJB%vUL*X zt8sk|R9B%USlM{LWQ8j6;XbCxdD5`SY@I0Ai^P(yA~=40vSfG2DdZYXJMwiUY`q>(&&{{^Wo*wpjO(CAh7O46} zT;F{!E|RWGX&^XM*QvCy^6`6-Ag(SFQ}9pv6kO)2joB&eqhc(jQRWTFICx)E><}O>W zwtc?&{~!nFJmy{l@*Gde@aEPmJAo^EY0CMo`wAnXmAd z!!#Q<Zc%{XVUPu)OjPTlZ=oZARD=ly1x*bFfuZD#mS2^dJvv`w@!fTu;^r2%RiHQ}`S zYcj@Zr89zVu@qy?;|qky#kUvF%yPfyvZ{%P+N_JKWkA)4=c(~kfkPB4+Y((?Y^Br} zU@Afo#HdRwrkL~6pM4g_QeV(CD4z*YC$PCJ9r;z|31ojK2tIvkN^qi>-Q=ZBLQQ9r zl69Q3w@VP1(PAK93Dh^KR2OwS4u`D`*jn>2Ohsi4@t}%-bGryG{FCXPL&2_zk6y3WOs6 zGo$M=P-Q!__vvGbS~Y=^t@^32*2Dpvl=sM;Fke0QHyr1macX=o!Q_lyKt#1 zzD@VTtM$D*Nk++@LtUEXFQO@jI0IlqPp(y3y((&kgKIs74+4|%fk@+Cxh6k|uBu87 zT;RMfXoxqT0tNzl6 zr~5*Xd#Gz0K7Uz6F-Mp7W~7&G0VEkgm<&%_GA3p|(oj-s@h$kUNc5dUzN>#O z$I$0i($KJkZ{8K|WV5r9!F`b8{s4L8!Yg3h^_0lkcFn4@L*1p;_XMyJmWx(vIljEt z-z#Y2^w?I!3YXF9P*GL>@5J~kQQX<8|9rJcgG^nPKgLZZMcHltS zsCy{rOML;jR53grypDx2x7B;$y=>bsVy#u!%BTzbg~Gfm6B6Q-spE<1aP7UECANMC z!sQ=5y|WDV8ArAjYtt*RPF5+;+D=M)Rl0bZR$Eh?!+ZCH4!asoS1p0Wm z$bKhTJ$c=`Y$VAkEcgZ%nZv7Tay*Rj1Q`SeC#6_fB4Ol_<&_(~j1;dTaG!8r z3rMku_%|=RSj%9D0&f9R3+wLzLaizPitjIuX04+`>{VijAnziRSY_PAqen^}AqI9@`I+JxH&GeXW2FebSo4~jE90&`k49<8ly zhNzQ#+nL0BM;68w7p^yYifzo0P}?7Nf^)Vq2v8_^S;Dj0v>L}Lnid#bYFJh**Dt2f z;>Vq}UY%v!)73u|D?Yx^Bvs)wSua>qa~bAllF?Hd;3vqsv-&vGbIZ5T9Z>u53B~NQ zKO!onqkzj}S6y54aj&LW)T;!rmc_Tjtjzc6ckLoD#zg0;LYMb13~NbFpg}Yg!N0H9 zqZFG!G5)2F+n**__kM`7#B}H245~-Wy;SZ1a}m=*T!D7v~}zOn4Nums%Z117I` zLCNOZ2tiUvB?5S?tCVhB9Z%XuP;O96reZzsBAlk`75bs8x|;}vf;;IO#TCUl(HFQFdgKkunXts)w=m7ym9++1T;uE}O$`ICdpUCi3*%>((>mWGN&J*y`Tj=cp9 z1wpYV0O9BpfFH-NLCJlp`T8x)qGNyKPB3O8W$8F|tG?$Dw7c^I1 zfwh5B>j@yOg;G}HufdFvQyW(M#LcQj>w^Z+UguVrR^a6PvG8F~hjK4&O|*31VEB#b zFO~o2raa%P2m$Y#ZB`sj2_MexH^PMHXn9sczOtFwY652O8UQIjp46*Ld!C)I>!#LLItI7|xe>Q2Jc0PE?Wy!IoaeX9r z$KxLZUV3kpU8X>xX(56X^5DbdlqqBbjZd{UCI--q4u{_U*`7nMgbxzeD>%K4#a_49 zM0bew$dx^D`8J$?Y7!1O^(o}uU#IoI%>N!eitM9kJSG`t+| zyDCe2a$kt=l}c-i@M5{yIpZyo{AcQ#KtJ2vmRTcBqsl`(Q*fT=$oPTCOwH&ShK8}C z2+~_Lx5e8&Gq=lF&H5UWlBnli82gTKNEuUS9>R;oHJ|JQ>%1?9zf~tQMo}BSpJAQ7 zyfdV2kvBABq(CUuyCh?imKO6oCM7#NB{AY#uv2BbPIX1M!4fdhMqU1#P zsJ&qL8?P;Wvr?(oX2;@jpw`UtQJHfB_1`N%1mu?JrRVYw)*Bo%CnWtnI_uu8b_EnP z4XjlXFMtlJ_Z%HuqRqf&6kVCoD;XUOUT&TtNh^R=bBNc=0(%LkfL0&i1O~s{5pR5ZldzU3WP!+jTS0M$93XIuzTRd8x>Qk_@O8F?m_)=8a42MGrW(jKC z_PJ9=Iqb|({tSx@?|O4YLcS+|83KJfbdWqQ<3B+IW>y<8dG!8URoc=B&hOGNa!B9u zY;x@^LCtOj^$@B{Dg)vX4fs;9lX=tuAXHiP_5JBT7}AaPggH;e+umIdpjK57qA(AnGr+u!2qG~XVyW;u#MXucoJ0-X95d0of#OaFEwAN zgmvtCgqyOAUs0OKn0=$f(~qr7mj6~3|HXW$EIeo#Ilr3mL|=Bs1iXTV-ft4PZGFl0 z$FR=i|JURSRBFA=t4lPwDi*BPd!iDevigZ14)*_C_&iJvJJ#;T_x|JNaf$_dzga|J zL+`9txtzf}sq!DKOwNn>l3u|+RoL#pL}#c4uGxz^N8`w3Gc7Y-@$^g_1FlP~hN`SI++AKydF)ARI?o#WY+6?_$M0fK-i?Ra(&&Q< z6mEXd7$yac3Ul^DFw0&2wkcWVtUqYG1Fon9)ddq5iew(=aBfKs(JrkRxSTtTCQ`c% zeDr;JyXXNq#Ef^Fav{l;K|=E^N2P8#&~-`UMlw?Ga);ZqQK5v>MrVM!-IcX%-ompd z08OFoWbA1d@QTnpgB}ZsPSsY7J|$iYBN}4+p{I)7<^pDz$T}Y~lqsga(H$gs|Bjv* ze=%eJ!t@iWsIUJ)t9(|Z>vXIeNTdsf^Ct2977u)U5$jkaK68K?{kq%?Zv{r@S zPyT;v&0iZumBFr4>*x&09}9fbrsn0U>hTTy-g0rGI##P}gC*6^K1TG^>%2stfHLNr zmN@VEY21)|C+JFg6qcU}RXMEv3Hl?{qpQ}fYcp%=o zR1tF=#2cDbrdY~=i_H#CfvoWQvXgexecfy)u@KA9VWWx((mD!c0Wy2Tf-ZI8<{&Gy zc_5rjyTsv6f_~uMB&;(Tt3Fp+8G-kVn_)5FYp`%HP0G48N{1@E`GaP+nUof=Cj*tJ z?2e?#X{)v>I>fxRmvWwXKX6vKL|b6!;b^bg*tauxY`CnZjMG{rtk^S4P&wb5-*0V? ze-yg}3-2a^c`6^zaKv^#$>z%z&7*T+7aAS#o{~iD@U`ws^KQL3rnxwNvmRaSc6N_y zH1BIdzyH3Az8+Xvu3LUR;%ZEr!OH=gK`oWwHuR06fT+)mk)Ho1nt0g!}ZT z&q-<#4G^&0K0W))LR>tAsiHjC5!}3YsnnNj?+Fo%@(OB|SUA@YlYlJE1lEOXD?ls4 zo=wlJvw0(K%R7MHzq7fblDg|ZK4XnbYT2mVHU&cM-Kd`W%4X6^B0`MwlS~Kb1t=Es zN0>WNB13ScYo%Jf)zoLJJSV#xMxNDHf0B!+5T~Bl_m4$Rm?S?X4ax5)Doe=*SIOxT z$#6rsLX0K&2roi0aci4!RsrCV*e*Cz?Wl<6+E#v`K(>%k3VZxbR0rXps04PiL5#&d zX4GC7D_JysVAMb=!`c;tMH{Sab`O7q!xA5tTDtT#P1tG%h*t+U(^3Q*kil|ZNA^mn zra@!5a#X!i$fE8Lxm?6MyqIZW7RH1xt6$k(-@-gSUMIMcPXJT%Wb3a+EzGwFN~s;G zS#L>RUUX^)S`?#K=RXPZ@M`T$1RQe2XP<6tDG7yHx_Qz&o@UsK5& z)|D7r!6Vx9q>)E$R^O~_(z8EEQL;A&K7g`W@S%Hv{cLhHav1zku&Da|6)Thgp1g_ zY#ju&LElp@q!$JnaQAOAWG|N!_ij7Vs>Yl5V?^fdNAQi0{0aldT8GCwHpa>31nE_) zHn@vczsaqoRYT=8_6stccX>S~>N`f_5_LQxsvm&si?Z220Z=`UZ*?ydI4#KBDpusMjf#2E z{&kd_ossENeFG=cXbSK5x7i}Kq!l>j-!Rl`mp7~yo#otpvt~4y%W#;>>4sk!8T6=^ zL~^(K>lVz7Emlh-6)SN`p5!t0?O`t{WTIxCy#DU|`1=q#4(Y}VubW?%e63gMcUL?= z<6^6rwVd$)cd7iL?c*%0YQ62bHl>-wSO^LMwgn-aw1#_p{mOT+e*ogj{(fWs)E{(1 zkwhcD9Io$?E+#DvNh1etx7;S-!g-ZCZ$B){IfEt;5QOWS-m==I39t~Haz&)E&A52L zzY^v|6g3Vil4gt_t2L4QR5SFax_+(Ek0F-xWdnAz2}2&{quK~}9RH)zU`x?@0qOgc z?X1rFWIo(vU!VXq>#|ek=oeMJPF?~93Gt<=g z$)UAq2f;mwS{qv4bCr+nEH^eP^H^X03M%~ZKvwq6E;3#@LeO`pbNB>M>sj&R`ROO9 z4*)!iJod%X6O=HMEOb^h`@YQUb#7~K3A+IQd~?2k z+hqRFy#7gDj&s!QCBSHRJw7#vc(}>Tgxy)s!l^kOnpn=7(Fsf$9_!${K1pGj^`5$j zR@%NNaUga~I#+@}mV=0% z)w=j{-CI4bx(7pW*m!nPX;0V#w$#Zwd8%E%%CN(?N9TH5=y{+%_9-{W{TEqGa5Fd5 zowL@>h8h?d;74tc!l(L;wedy`lN+-KU8WgEE9P)K&yC3??AXhB8Q?{#TVGgL=_01} z*gOd(Z41{S?5Qf<+q(D&D`LN2{6f(EZgr8|S=X45XM4X(5$r>gJhn@N`IT%4Yl zTwvCug9hPC7Wh2s<;tfEZb|Vf@Y6_igbcYbx+E@TdzS78=LZ4>r97_h*x0$UX^Ihc zU!Y3kUO1h##G!ITX;XAnCsgB(Vw7)vH4sY)hR+#)oz)Vu;93h-v7WwLOTz$Nb9sxh zDVQ+1HD;DlgaTGJ%edH%s&flWw<0`yHnlqYyNIy~ice;yE^h^^znPJK_h}Q2i~#Nk zdpu@kx&D8#_uf%WcJ03>KB%aOJctyjD!oe$9UHw#3nlapNeG1A6;L`z4PB-89w1bu zhbk?UP=rvV_t5d=dEc||t-$~ZJcXnHQU)T5gT-`&3@D z8XB#QC92lbEJtZ|)tc;RRyO!uNq|E2Lk%aDz8R*_vSU13k^B6u;!4`YpU&zwAC_h= z?gh*1C9BXp?07PTOIcP?6>rg-(2!&jO_!oFk*C%kckroS@ONr#mfGgt1MsiatNN!# zZCv`}bsB2f^M%Xss^)nKKCXwWts2KhbTa#9b)c~YOY`S?E5PPC1`jN!;r2>Z{kw*eYWh%ap2VE>YDw0^-fz&@riYP?B(E;q1=-Om&&iF zpBoOj?(&98eamy{89Nt{?PIO**k3*r6C{6|KA}S?uq3Hoz#G@>|D0C_)@_P6wCeGWUt$a`j;FsGk2Gpdg6SjGzw=>l% z=z!rBV|vN760j66xNpeX@`+p4Hpn@8#Q<$JQg2W$dlgAK>`YPxWsX{L_TJT+e_@3> z5_z3##`nc*#Y1C^2`wORjgZeWL&3wEH90k?thZWn$ElfvU#d1_PnuCyS`~jNMy*$X zoQ6tIv?niBB5?fZ(F`MuF=*nN{n3(a%)rJ{p5+a^KmWYLL~uoLKKM%MUD8)IK(rb|S9Cw%tx^OQ7u z+?{T^Z;c)`zic2@J_5FJM0-#2AyZkEEiEutwT>~ zm#2W9QNtFf015WYzV2n(R?SB{lutrFsbEV{3PM!^nUx-y_0#U#vs?tI5;D)>UZaWg z3F}YhKfKmE8QKJcryjE>bRQ^5sCCerUkg2i8V(SQ*8|!n!H-qT1Lj2Nt?h-iCuxA2 z2uN`<0hQn%azRYZUnRpYhEQ=bx24nMj7>=#OH~WOfVu~B_qmHZEH|1Ku)sSK@$xl5 zPrH;?Faw+~-BS@d?ml=YJ6%n_XdF&+*>`0Gg!JU-S=&bcPGnFTbxa*P0A55t0fN&B0mJK2HPKGdaxf` z$R3x5JK;V~G(}GyI8i1=w&-Pa*@y$*PEd_R4OH@$i@srZG0qn1A}}vn-VSu@Q$tRa?>&J722B zbaP$=@FGJ+{QblhOd)eQrxb|Q(&c@3h_o5GRW6i!z>wdcYXebOfwrga_kY4$_boVj znklR|eZoMTXZZXvBRo#}eCjQaT4&~4!nKcii|jzMnC# z<%$4n$VDqJ_;lehl)ASX+7Do3b~M>+I>{csH;FRtEB55u%Z-mK+ipFktdftb@19R8 zl2pbUh&PSxN_I<-^-5#rjo^wDei+xAdU(+@!Te+3i^*OHJgO4R$@+d7XjjwlIk{r8BRDF=~1KUuEOi&}1dzC;_t&%Z4z;yyaTPl^R> zSUwqFu2noc^ACe}Xn-$a9@Z(d%cjin7KXJMv(Mgo-DV5r4nElj8+~t&MD@^kWe#rV z)qPoE-T6)Qu)m+_$n__`*_iNqqmD3%p}=T@q2mv@|y%W8&e@Hk@JlU!ebYqcY8c!tEQX-o$@)sI+%)+#j3Q&fZivx`*OCUZ6l?9gM%X(9$1**w|>WSr~D;rDquix z0$UMn=R&rn(*5|p&#*yGfD9ke)hT@MpAC~J&AsB$`E!^1$c_hTbOK6QS3Bq6Rgf&# zwV&j8pQjf+;G8NyT6e7TVaSE#FePdb4bm%%N^|$2s`AxoooXhUkCb;ZW{xw`*MSW> zJax-&ojqS8n?ALg;7lsl>k;<}f%3*m>oPSQ?8irHAU&AY#NaW$4NcR;P~?=~qqplXWdVepNaY*k19S2>@QV}_4GlQxFsDDaukrTG2v4!K?!xDjQhu>=|DsX6&S=HvT|2>~~H=Y>~B+LJw zIfDJ$#QA^0?f<-0fshqYW^;SAk+N9xHhe(!@rT%^1RwS0)?u8+n{43B736CI>jYZV zr-E;XM@a^Gm)S|e*FCvfBFlK;K6=3bnKDsWH2dWCjhz`O^KF8VF+<1?-Tn_DV{~S% z$F%A{g^Yyo_9sZhUqVL9a4s8ykg*HP~u)<*|&$Jo~_} z+=9?+q$<;@&~$X?zEI>D`=^eiE-vonITxmDXpJlU=mzM@Bi^ZflhF0?aa2Yp!>|AZ4>piQwPCj z-gY0t%fP^EZ-VY0=tnofm}zL3=K9ZQb_mQwjb$9&Wrb-?v@Kpie$(Le6kl%I7I55a zRAW0>2wcHVmd}=2I88Jt=%C>6sHb=S!YHatYL} zq=6NQ4$PqHN!Sr6e73%D&d~$G+l~+bO+qRU3mhL-BnzYNgC}k7sF)V3=;ra5>G8}R ze@r{V+i6)Cr)}DSih`KTTQ`3%=@fP<>`Q?cohDCa*C%{jxfF=#WV{1WP_5hB? z4LpC*8|k*bgnd)0Pa22<&?Wu9j-zxltshP(IGOE(-Pa>oB|Ar0HLzjYU~5i+Uz<(o z)Z%<>+7R$T08Ec2p)VA(@!E zt_y&bDFGZdIGZwC4;MXFp^UOoIWJL>oPVI?jrY;=!FB|a0xH_~*QVFyH(_L#yz_0O zLp`$#ODyZ8m{=4{{4(U!uW7IC!=>5wb;*7k0so2FgTMaQYRCTTjFK6=ekS{=#r`7s zFt%5*zm4?Ntm}GVqmBM_9d807Vg1{T``e8ATbch>q)yBz^AI@kN@GLw2(flcqv^PL zIX`|SP|6O!%UqLuL1`k5Nn5>Dr!+&u_D9+{~6}b{jt%b<|7Th2BfT}^ug3A zudB_^gfYz>W%q3>N{YlTC8cK6?IQklFi6;`dP2FHls}Jm=UF_#2a~jpqAEc&T;m7R zU7LJ%(j6QpO5P^*Bb z`d?CH0JT7TyKTylR0>X@EV28bmoo~%oPZ=zhsK3u#=^KBo@0hTL5^F>rzD-0!lWQ` zqar$84KLZCEmDDQW1gjRJx!j>&nEh5wv02=F|b#_^64f=y$}8R1m9<7Q}Wv5SI!v@ zoGM9C1W|6S^+=)}?a;%hMb1&JMDSmTTB&ZrYm@*7{tHU$V5Hw7^Fo?3AtP&~88C|S zeCExP5db|Jx}7hZWtP4WZF2@Ppj(k(&|vu^XDWl98C^}z5g4!0`DSEPkh{)l@xnK= zI0&Ez@|IW^ag#XS>G@3*WSn}+>$gzg`M#Z5Ip^zeg@@eb!{y=c7JvT*$^U@wr*=k% z-#k{nEf_7a5IHg%**!q7H=nf(aWxY6Xo(&_`#=isu~JbE(nvy(OrX@a)y^jwO&idng zZ=ZOVFJIRp>nkRI90rrowL{Su4o0{41*UijV%k5cg$h>RMt}|biD442i0?5;xl$(kN%XaGd5!eSbbVdJ%Bnlu ze13l>kUlCA-nYV=V_ZQcKPNbwS*&kb7AbGNcM!#22CMQw1f1Y?r0yQHoD|{jvtFH$%cK?8 zg~rINY-dbO?8cu7M`9}+Rm^k~^V+ORJZ;nF1s^`Xorv}6aenbe8Au0o1+F!ee+!F3 zs^+bwb2V6rY4nbS+|C#gOz?0We%y5d$18A z6HWE6DrN)jJO1SN`FmyWF&@9#1|8A|Gn_B?P>L=voQ@x@tTo#dQkuhwDVmuRfi_~t^EMXjnWFvHrZSZpy&d>1plXVw-)sE8 zav@VyaCFz`&V7UBN=9Tlcxp3eJ+6O9bwaUfs!4y?aaA7VhwP7dYN@0pvi;!eJEE{5 z$#;9A=^tjKRwpUa8Xc?4X6szgjAFqbcm8Z$PJZd8H?XZm6{n3&&|~APJKC+=6sSXJ z*XAa53iG};$336UCr+~RZe@%Uu2W?07rfg$fOwo4+j=opbtH1cQ&0tu+HDc#MfP*3 zes#y2K?Mj9{KW4SkZYuv8RoXlWe+RF{Rhh`bbT8i82X#YIE%RA+uz&jzi~&hw4BN= z6>4>u>SQ8A8nM(cpcaBxPOy%TGT*-pxW`spk!M@LZaoR6r=+Cqx;3w))CQkRXD=*X z>_cZ~o{ZsRoZUmu3MJ#N!XzY4<&xiT$IYJpENu>^P04cD7t#i5;p~6)_U9YKRiMVn_Y#getJ|Z4u zOYpmkYQeKz(~uZLAHlFQbyoPDW{9a2uJg!Z-h zMm?E19zUB_fZ-NIOtB+wgE&0`q`DDf8sA&w;|Ov`0Fyevg#cO;=eG3O{H0NR^8q7Q zlW-}3*7d1lvP{yqxKxwt#w-`ddegi|z0QECPGcpNveKaKW6-~FThx6@i8 zV^k(B_gMU0^0i-0>KKC;%o3lMZscX2Zo9TTX$5_RzTs}!IB}X1cN+DFAIaZ70lw-i z-H&8#IUXpiAGTr+EGj=wfcif=TZjl*HlsgKH4x5&<_#TgE5^ODeS1&8#a#-G{64pR zZ_XxpHc!`tXC0)iZZt0Pn@|CXr;~Q8*(+_A>A(QVjl;4vR>`f)abrTLeS+g~kBO;M zEEyr{p5;s1n4)Unad&u>D=cHkeBwmiGdz8kal zMNPOC1P=Zt`W{=g@Hp_6b9`1yirnl%PTR2m$xnI*j>Q`MVf>Y2=fzG@7!jwSzzd?s z`yNTx@DHtDMa|BdYbzGbWk=hF-qw$^m;5FIBqv?UxOz!nZTjE7jymm`f36SEZVg0v zPR{-&+Ak)$%i8|WQSo2D(~Y@+@MgHcasJPjGCi6;(lr0hVpu9bT;dO6*Qj^*vb293 zw;`vPd#eJ!)x|J;$HfTt&+{CRM?H>>)FAQMkj8;{ug|sX+m);<1s=b&9t6W5K&xL< zXSE8mL<3jLOI2R+RoB@b_p)tfmMI8DhIziBqj@5zAqUL z@;4M40%{FCMlKa1@gVmD4fvK5YAeVMW&K0dyb>u2t=naX#-NG^-~FQ{=(XJBS9~%? z)@?fc7r4YS@D3gYQ5Y=WaUa$#0?VR_axaBX-_3O}JyceUyXI4CJStazH0kiECSZ`q zYF?zw#YBX6OZfoJ#Xz58%$z~+FxKNFZi}J^VGnB}^$fj?lX5+3uz*^H^AX#Tz4#w# zsi1g;ViDA}@w!j`MBhf?r9jHRbjcqnnYB(vV<6{}S1u19sEpg40qQbrr@c2Y3Ah4 z(npqXmjwpoElLz}Y@aWA(8DTyOr;(KNig}4GHeOc)!sz3+*b}#(UN>>$pswvXJsA? zdjy=C7l@yix&(sIog94S$Z{>%Q;!3uPsni?!CIy!Zj%6_;l~u`!1ulLp=XcF=Vme| z^(1Yn%dA@?j&usDOIo zwb6klZ4d1mn|sS!BPR9@e8o#oo9yVxiIZLZ4OB$4&J>JpH6d53^j!{=7p$7!t$h_9 zH7gEzfNuWWM8e^OWM&bnT|@&nhS?IlHS4qc}@L!>){3hI1dW~M9R0`S0}r2_A> z_1MrIFA@tfUrOaJr3blVz6>2{=lM}*67y_ML&6YYzg#;&HX~YXeF74s0<=LG!bzx8 z32x`ijdq;&pY)>i{Rw=D#2aNs)OrMAu%1!H#Xz}Qbxd&4B-wfK&NfMRjG@g;iI(N5 zQ&Rl<_G}kMGv00qyk@bWT9A7?tlEzbZlRiM%gQ9HJ^7G_JuPxJjhZ#Oj>F#m=$_h9 z`}W;I`_@Qbo(vPEx^BJR%)Ykg9!^Q4;;um2k8&B1$saQb8Y|DI)NC4-Z}Q&1%|o=| z1NY<~V$C)wh*eaYgBA`C_gc|5cR8McX7V<+duOw>Ft-|?o>o*Od##>A{j*(o-AnR$ zoyyQv)%sQ|Bi`)oNd)ecA9-fakk#I<>3j)hY8jenJMjtZcFzR=616s93VAwCQP1m5 zUJaP>DY@XhhB-~euMbp21{g1(-Gzn|GQEZPOAc11@+A}lv}|#fF)H4kV zt0@{=O2`I&UfvT2l6`p5w;~Z>me^Mv)V&|&bT@ufN4Ta&NEHwp86Fwy)YP*(&mU=9 z7N=#CSl!QKL^4J!HDB0L<)6ueu%1EW;ROil@u~;87P~9Ahr7pm4|%`qIyR}YA1Bvl zsvfvfyx&hgbJI=ki0aGFPb-|zpGtmKty=90EQ`i47nfr)UE*wIUZaBbhk$y%sY~g* zmf!*RTfo-M@sj(BrbA0pxi+ozCD8DmS4ef%B1bMmOEH7YuXPQAUO(o%e46($4k4|S z!vQZ=_@*W0-38~yQY9C>`d2YLHk;i76ezyI1pBTfkJ&lU*ThJ_IZ<6B(L#J#*5cAV&BU&_LxR_?{< z3rg@ZWJFzG{i{(ku+!2dMb%i;1z{VXSVzwYSxL zy5=h8Sf!6UL#!D$NEwQL3;t=szGGJ}g?EIYCHg@XaHkBM_LPQXBj(wD9sBpuJDe$C zxBGJ0=f%fT9}y}(a)kxrV@b+gL(N42Kp0?N20XqWF|k2jQ^5B*zvY@f_m zWPToh8IQ$K%2frE*)P8yrf-eGbDrEsJZtJx%3DgafsF5#`I~Kw*dyR^<%wa!?JuL>TWN+cop_G>R1Ia@N8YflMYnJL88dv7y*o6D(SNkkvM z$djphnL~Sk!_y(7o7voP@M}km?5en!#m$y}!ZJ6&liuJ+5T9FWq)NZqC>+vee|fvQ zax^;YO}~&0I~w~2GV!kue24mVTO;Fe`w#aI6VfZI9_5p>vqORzBp`TOoWTR~=-Tfh zV;>u?pN2v&I~cpa(i24ApeDa2uM~q-H44Mc0{#j0mU@(P^`vRu_fi81?5L^>r2OU4 z=%U8ftT$+2%>lAa@?qs{-s}lRG+{QBB?*!}ygVxsYQso9t$5FiN0;`w{RDx7mL24w zojjKPf=KXWswL+^?H-myDl}>>z;9HHM+l9xnwfs*AhiYQio&jz(PMoY(eh5nM-SDd zoU|{-8K{-n7}iM;J}$YF?4y$c|5#ixln zvzKks>3b89laGz}3Ok`?J5i%sVYEqKFHaBAnC2#8pxS&oxf|KSy7cxtlR{CegjxLS z9KlEe%>8kO!ej9}cJo3Q38$I<%a0bk^vv#W#xbU1W<+(*BvB6Vrp8g9qG9U(+g)!j49oz0pS5tckHkCzt45 zYr+_&`v`yJHZgf`OtFcE`O4e0s13HEN37{(xGfV6y}q`nr`$d#5K9T^H5*ybhV7NBtayk9q>_#9=9n!kE3U% z3w2;lXnZL;=}{Zd*K?pa#tpEe#tL+$VNQ~7=YNSb7F~rgdGCL5VN%(Tl~9z#NVpF_ zHmWWIM)V41#Bd*tW_Hx@`Y*i4OC`N5Ef(>o+d!~4De|&gW-7nv>+F*q#u3sZf%pqA z6*d<8Wu^Cr5fUFO$BZrx$>k$+!9Jaap0SgNinR54163uSS0tk) z_EnWQBLg3fh|uRUXl*~ZJo0}b+wkl+(F5n;*F$|h1FoI0^Wb>7am=hx;C#~<1a1Xg zqsMx$YSvNDObcN&Y|k@#JY4L@loqS~vcY&Mb znX}+cy$O=ga~KuwgdPP(I1b8$lzkHK+6&8&0;&jxgSdKE;M2#Hf$`=EIvV_Oy#;Kn zi3ezoN6LzxPv*mr$~Pa9mEOn-v<)m});5K?^SxF+_?$RxJ3oQ^;R_Nde<~QUX#mvg z{mHOo->E#iOWFQ~jbAGsu4$h}BUb|2XmK*UC7{kz`s3RJciDB)(Q5TgG}nWSW_GXZ zmt&#BS>iacFM1sXF_;!letqDUNkTI6c;3*{OyMQG1&AweGfmQma!}&Oq$*eq_0XRGc~r zOg#~0rI)*6Ch3vmg;$&U3P&oJ=>AzHQg*lfM(2DLBV4>^l;vi$LkiNR9>taQx+!5I z!@DbQTA@$cGHFv(QFxkiBmcVb!UQ9KD+jB#Kp`v(qC_-&GruRe zWL0CiwlQz%{c%Ye_wzM&^jh$n#vs=KOS=dIvB(9`YdemVrSlo;yTQh!W=exS%ABo? z?>m=a%k*u^j~30dY1~=lgqrSnO`#QYA->}a1fJv5K*bs}&TUUim0{43F zke9!4C0j_%!?5NukUiMlNIK+mR9vJdw49Dh#rL0wL3nIA`!)6ww5~V6c=qMcZf4DC zGXp>t-(F*Yd48bz#Y`FMN5bs4*u)SdY0B6&^mJv}U=I9iO?f80WL%@4x=NJy3yIoJ zE2GIts1_61dP~uuF-`!Ju9|dCd1JUr-%I0*LITGljwBrFjY4 z>XF?vsVdR(;s+0PzWWpFRcZ;8F@qHXm9~wskuoCLm8i>LlOa`0DV{`$S}>%CwuU>@ z#g)t6ge}ql|H+Wpi;I;e*0&PA$?k&oj9#ek-d7g)wGuj`%hANYZyZ>z>06GMdFh$% za4XRSg#9KEZs{z7>e4MvRHf>w@`9M$+}=lcg=mGUvyW%HK`nKa`F}!YZgd4y%(;ly zj0)V2fn`l?LJp+F!(BK^PB+8)M66^u7}6qxp)KW4niKSH8qTcL< z*6)-;s_l7eGVoF3T8&QWib6(6iG7L*)uI80VoI&dH>mcI1ovkD$fCZM<%2GPvbl?G zx9J`DV#%@|0mK+Ew?ta+qJo)-|0~Xz?A!O`9?gJBO}U2kL|nt9;AHHoLUeW%Zgozj z!|F8utmoMl@SKY+((WzUm#+uwHz|#dXM45^QHUCZs6CpugRX9ke0LNa-ds7tF#Dr{ zhV^tYLvYyLwj=tW7sY;b;H~7%ew-~Vp07vle8`6#Y5KU)5O1Bgsi)?LNkS z` zoo3l1Vx|@3<*O5Vg&vFhc{~H^rKo+fF&qxS3~N8ouzpyXVUZZQD3)Fvlhz||&bN4Q z)h59V|NK+3udY?|cH_R#qr7C^2%jO!TPQ@7USyHP`1S$PE1hdXuBdq+zWTuA4XS%d z-i8mjMt#uaQ@`(NXnNr0lmOX&M3`p zSD8oOt7n$nLdr?n#*zfF;=Py0x!X1h`wJ5N4Q*p4G2}IMVx0Q}x7(LjHIjtN>>@O4 zQ5}ZHo;p_18Gx86%1`CnRUF=({gYnwc7qv}(LIL>eV2tp2&`+F0FVzWz4>g&Cj?$r zQviG9?`GSIJu}TF5F+w)3D_cWkAlDtOQn=5U5Uy!(_ZITa+&Ck<<((o3!y%)!KHG8 zFUMoIUZRJ;U)J`;#xnX!HtiQYjm_xro@jCX2*KnH3Cipvzk?%yiAbS$#eu ziIPaZVs|q}-@BZni1|{tNDdmW4bXzIN0hsq?sZ~|5+MUdqu^!Wn?qTa_YB$bcHxSB zy!ph<+1a)E_6KlRof1C(1DENW^z41;WuF_W6&m+>hLZw12JkaBdg zKj^^wGzhg1lB_8wMP~ zeD-Zt&avgEo;ja5wqRYgHthK_!`>qA;+J&YDJFvz}5_q>X`(vi`Fi0Brr zuZ{`KTfc)z#lDr(+1E>U3?(a3W(MUu{$%x|!B*j1e#f5!5`bzqEl9x`-V-E!@d76_ z?b`I1J-%A)u-RKLv98<0pr8b=d*(lWSg2ryhMo*O(6+_u$DYSilL9Tg{JJ?Rem>(k zp*CJD*W?k;$YKey+LcwU%1Xe!Sq0KB?N|MSZJI0-W1vFPUfA2Nv12D|?~JrE@! z;S|kK`gjfD=sXa+!DIF3e||0*jTCuXA(P*gvqRu=^FZ^Wg6!Y90RKDX_ea(#_Pjyb z>68k1b@j~5up~4GbgZqIPQ`~$UC2#T81(`FY07AI|X>M5wc8ZIwcjO$q5 znFlDhV2sbediUpF{G&b(8jq-EMMqjJB?D|HJY89BP{gqt2=EAqLL(Yb3#p5DQRL<# z#6lo7%`xTNc@EGb?ag&}1PF>h;5_0}{@gl24>H*1?Bg@!16NiyU&}`CsspI}L#^Xb zlqyes>gJOo^}LoZ^)?KBJQn84@N-Zem5=yu*R*IcO0B9f3XPFK8s4tl4DjlSC$`1> zs?jO6>D6zdkp=ka}%OM%YB@r)h;j{KnCL};WPuDn8W9{##~cu>AS z@Jy`q(tTp}*WSX#k|gPsh#SG*U~He#BX%~hvM?C^o9Jh0ox?ZcsUYOLzx4sJ-cTmN z3Gkcfwrr1|%_UX4TH&C+r03{K4MS*F4yLC@YUeC)aA)Sz>T{20D3u?gaD>g zvvnio-5is54?^n5|Cf??`Tp{&;<=A%U?O@h12?!pZum1u2 zH5}!SyXET6vvz^-_aFXEME8X#UhT=>>hoW2Zj=H3Eq~m1ud)k){k9Dn&1)uHK?@VN zci=FL^V-GfH__b{K6227$@|MlnoCj(ZT_iPGTj9k$>-PVxVU>twizPRVH)W256Oj% zlAg|H6d`u_{OGXxMHuS|;|E zAW)i{9~~0xQv~srQ~$GvSr)t^bWc35cOsE8WK);FwTIK?(e*F11g#G6ZfFV8mnXh_R!&8 z>2*~|M{RQ15I$cSYVvGQQIM8Nm@U59e7gMzC1s{mKo6wA1G`{`hsnQkBl?Og^)K!M zH6{ajOMjT~m^mngPKph9zpt%gGzfwsvdKSvdtP4%<$KSbWM39x+GQi?2fCM}(NBf6 z_3j$P)P?i8kKeBs``F!A|6pJ$9VY1u$%56^l-66>3IPq_9!fTb&6gk5KeZsG#(@`n zo&xOqzK|9@r@9DjY+`F|*~IBTdVq)-6=kI}ouwRWaayW_p3ULS2=A_H5MOR~3mG`t z#P2)rt7+|xbvMU-a+^yhWDeMmj?6#O3nw*AUXQ*GKF2~_+dihXSuXjMt4MKw^WfkViJ?lf61r!H z3ht*}rma)WP|$cS-I9|(R0@6ez4}DVIq0amh&yxAX0cyj<}+byW>40C8X1?>UwUrk zPCI+b4yYBSxpih$LHMeO!xYyH@PcVU=6;xvKb$lU`g!r_~C4sM?=>uc7DR7^9N7?tY22V`I3bU(7`6 zRwf2yx($Vq1}#rB^dp<)P5_SI?Pzo^FnORr<@mr_{7sl`) zn{sr&59!O!I|mb5FB{!BaWFThPxvHrd+nqsp;l)w>?t3j1GZ0EBZ%Yf32rzRJ4qT zx#z{xgFm67j08cXac5h)V72O3{4@0?-5l?}*Pw9f;EGwLIt}$9DP|5qVUc|?z{<3( z7KBL(^1@9&jKQVrG`Oo=&Lxmci$?r9e7r#c`AVI-Ug+{mVDw92E2+ftQ9_=uIlQ5i zb+YQSOk=*eK<>3m)uMB7G6GDzhFpeC{3aq042buFK{>3Z7?NRd??TD7)HTT>5gh!% zScd@8(Q274Y|#0l?H=FPmWDo{HJC0EH87{^@m(;!9TL&ITIu~tdF%jqJ% z5)u%8(U>W>hjcm0;OloSG|k{gv^CI0j!?S+N=!o&GtMihgAaH?3lBc zCy`RK-HU1Uce?6sCfH+}&7vG9S$f5Cr1H5OCk?LrWa(RzlA<+^VK;C7u}GpiPj<8v zSoe9aLm8CUCVOY|s7{9+OKi8g#R0>}B`0>w0$|myYTcD!!5TTVjyr^Gzihd=h|26s z(?@iv_F)~@grtwB1&)#54X=Fw`1AWT?gEhH*rDVV+gIA{66`Q&QoK@Oxzm*t+_^tl z=E45*7HfRf!QnI$$x~Oi&x_FHrZ>d*ANcR42E29kOg0Wm4cL%}*7Wj~Ai4{;!8BUQ zLIf~;at(Qi{F_}pxu-oYA{OQ>5OqBr6-m)zAU7c!$3<|>Ri^1jzViyGT0L|()RiDV zB&!uUQd6+cl3lbf+8(T&6H$SZsS;!x!1 zM&890kcsDX`n_Vgjnij3mQC%kFw94nrD)JoFbZvp>|`goN+r(E`4zAyniy%>B}lLi zls2vWChGAzb8l;X=@cXl4qBR2n*Y;$UrVV+%}!FEcEPZH0&L|V!8_((t?GzgknpEe zw-b5`fqON}@XUE)z!cH)-JrmP%2!xlA`%hQ;3!MM$=haXU#ewh#!9Mc6z$~T7k~oQ z4XO`LrQGTrHVIb3#FbwRYg2q7R$UJh@`V$MYLb@isk_L1(kX8 zRX>Yq*mf^2P1G)ZgE8%ULRiE0=VCk4-lvhPe2;m2>bq2*>~L!`_AQpW9dq)w--b(s zWOQ0m9BNBJ6Z5vq_gl%i78htx+CyCQHGHpIGaMgFyrIX$?0y5>rvEg&013`r*QD#_ zd5-yXN0D`{1+3`(dZV^jU(Q?chV%vhl@46?tKXBr5>E}4I%Tx!Gufa?RPsxnC+P#h zmK$fu8c7rM)RxCQ;K_t>2MR#YiV#2N@jz)76P6p1=)#8(mnY>|mbl5pc0(e``e0;_ z79&i~@yS}adMM6{ij?F&+MCP40Z`6gb=zgXlwREHc=Ti{(xbPq8UeCF08@eZSDv-4 zWg8cf8>_p)=7!_kT)U{&Qe!0xuLcGHS zFDg;XNF=S7b8!`x2J8~3@*CsGLX~MnSIZ=*!tg##LTNC*F#{U$^BD8+EpNuJG*oH7 zvP7)`D#9IKoRuGel&yS8oVv1e;VA1>^USHw1s?r}#Jt|M5pG!hr zFjC3Qmy2r*97e_$)Yyy<6Q^}fm@X=1dF1xlYm$q5;haH*WtNyl-hv8+%#ic6H~dMn9yQ zAhu2X;&qee&(q9ET$shwFs=A*%o1PvL=FTjvgGm%|VmX`lRJ~=Suj0)krQ?%oShD zy|Ry~rZ3lFh=P%mx9!Q;^wJyDDvZ;c0FvHFbqXs>L8|-tTDo6mnj4DAv(}&Nm;wzC z`ponMS`%vtvA7fUF0N29&{Dwo_N`;f+Q|kioic;5x6T@lv00G`~9pJdpM>VIXcy z1mj8-UYV6zMFov%ow+*EgiX-|FIYbixxQ~9|5^&>yQ;wQb`KOavP)eDkeULSdD^Ir zq27$5pcb)K1`1R@*Y{@9@MR_?>2sEP*;#A~VkQ2(jmtdFT9PWN@r*A}221kiD6zfe z6Rx@mWNT55BBgh7`><`Pe3Na|3~K^ySaQFFa>*UM((@h{X_tum(}2i#;qufoVMp0p zKlWfjMq?Cc)ZLQf1zriu1DT9`aHUv)I97Dh!1w@N@0KrBB)X#=JYW(PAac;h!uVPB z8(-Fdl)QIh77k)s*Zs^fb!rU9$~3bfRpWgt{yFj@fXBYbLG4w2eQ~lPq^yO_sVzJa z1#Utc!X?ks<Aam^0e}j?zc;^Uan6|yKTr&bh8#ESR1`g5fl>7w}_Ij4t(D6a4Mx)XMN5a zDg7Z0Z1NbamoEFQTG@=$YvaYL7$D8jx-k?0c7;eGHn^Mtf*_ofeb~t@* zmi&49mb!OmCRy+XErJZoA}8^%OIi9vSgVXncWzDUfr|t0n1G9A<}nB>CAOdIuiHXf zH()f{jaPFP1aIb)igu@Lj13N&6;`S#8iM_#92B}Oy_iU1<(C==2^j-!g-N$WG0EJS zNHn$r%}c^JjvgcBrDhvUe9MX>7ThOuwy;MF>leNJvC`%<7h@l!jc28#STh#Fmgz}s ze$Bb2tqLo@JRrCG*1^UVXDxnsreD$`UGx<#2Je=GT)dSC+&+e?ffD$?1BAq3J?%ccBYX zNf1!?489z5-Fn$A_x)0-hbFzPE|mXz?BZk}#49HGYya@A+w8@kF0p%o4~el7ll?8e z-{x6%V~n;o$8;W#o-!mW%+Rd?g~@YRMva3rj3(mF7@60})TE|_M$KWLrp)t2X> zab=EqnHNjThsfWhouCau7?g1*#lMaBRoLpL@GV0r{3q_-GOF!%UH9d!QH8b?x3;*u z+gkyO7l(vEaR~_yK>}?l(BkgW;t&Ewk^n(kC>o?lf&?c(ad%30=GtrQGv_>GtTV=5 zV|_WF2!lcX^8Dp_?)$oamuG#g^Nksy5lItmVVz;hTKjhBwRIK_B@%aS+bK_)6dEv+zo>_D1eE+ooc5?up5A>P~s^ z2kkdQEsWZES3hWqP{KcSw5E4)%z56Fd2osXoiY2U_csE)LavOD4Qhlq#^eaysXUeY z09e4h=wEM;!AQQ4DxeiIX0ivA9fVD!%KmOflG0oVUNi+qKPOx#q0&aFpQ3XklgPbo zN1=NXY)9Od{Hl$*i=;(PAaDOg;Bpi(Kscj`th>*~0C2Yg1a0a$H>QM}((=$b?@P zTB6BmMIAeloc&Fs&sb|BV)O`hMU?Qj;vfbBs-lUz~!2sA#T6Tf6MQc-o<@&l@T64&E=^S zWzZ1JlNmRcRta5TXp;8<7;^WDXN12VUshq8ZMJ7oPRsKBp2R8Km|D7N#5a#_xl>je zqAA?ldb_?1`SpTah)lmBfrA=ozwFRn5^Y#x7j}vP@*jpFUd^3UHZ}~8w{)OcaVZ>} zwL0O0PoBenc#5A86vAcPYiU=%PdE@@oxSVE9cN1({Y&QSzm&RapQdBckgu6&zGjQPYWmWKu|*ye-P%PsaXvEA52Vo#G%oiY&T#V~uKvAVh>=Je zJ*lf(HvTM~D|6cSlM$*K=tpzdk(}s5BNw_Pk|Ft~kVRaibi*{`P+-hAQNyv~eYiIZDc{VO%Qvo>nTOiN zdYoGsaslbut9-n3m`_3d>(^#t1VTRM4+OTz&&4Sfiew1n{-H2EJKt?LP2XQIhw8ku z`{|#{D907Ol-`%*obC5C+_QiWCg-cE?;u{Smw-f6C)zZlG~yitZ>{;A6`ij@GqPQE*?H=ZRBkyN;<7au&t8ByIBhlB~6U2yGIW!{ltlZ``*I*LVCe`Riq;;8y+j&0|PG%so{d8c3^r_SNOcr zQeh(3VKJ=@km`|!8L@)Hja<`L-uOMAz33F5tY>fPf$CB^T1?9Q`x!xA_up)S6*CHY z>R99(J68qZ^=%tL8M)VD-zE3aj-GW39SIb89LYeA(Co2FoV>HaSkg-+OZs)F9nk7cby2Lf6zr2e# zX54K{W*R$c?$uW8DC_T`NEYCWMOTQTgNj5)wMFa^n)irCd){9{#@(!fMFM3}XPqWQ zV{z%vU%ICWP)bUzC#idj|Gu7&rV>U|=dOaZvgUP}@KMn(M8+6=wW2C}*Ap9;!M6LN zgEq!sV1n8jrn#ucgp`PM-6?Tksi$yM+bmO4PI7%50mHe(_H? zH$wQo|1{S2rC#cG79p=1eB+HHpITZ90FwXn2ORK!lQlQ)E z;}Y#Rak^JCNJATRAY1=HT{=P!MRQGMGEO3UaV5B++qd@|AotJrU%s(g_Os=~Yz+H) z=1s2*%CyDbqgDQs!w^=_5ZVvvq*89OGTd!!n8Eb9UkLC$yJlvnG$~7(8s+qsHa+de zO(=TZiSKOmjcQ`!xoKjGic>8^1{&r?^l&Rum7+zzpM_;&A{=ZqFIH!;g!KFJEFi)7_p-*-Zsah?W{0 zGLuA4E0rFfQ|Wj+Yaw-BK?-h#>J}e;IvHga4I)Gq;?H}h$rufI1+)BH4?{oqMSy$n zrt{k^v-FRALMb^=CcBJK-`D=RGU*bYUANzUx3RXF=0{5>sq@L=_8yfd;e|ENh20>y zosj&rf%%cN&5#1JPc;AABne^a(pA>-?EApIj$cYIlK5iXDjn;)ceo8bL>@sB<^fjh z5N6+r9vf5FBE-vHyPE^g*T*vyE`Z4Za;qczN_YR?oR`wW#fI>z<|Urm#%4RF4##5w z>dsA?5})>)#2~uQz3LM0O?`W@jW{=K%h@yZV1!0{)Mm9RV^`-Fpi7ofRg>(}2z3dR zxb_a$9MXX_s_NER4l3ZBz|!Jw_q<%|2i+(LSYoV8;+MDGi~68gmaV3A3rz4Ew14<> zND9`0EqQqNFnDg|1vJy*R@>(}DL*EkOaGj&4xJOz^H{NZ)*|$te|XukY36AlSF{g% z_$eUwy2$%6ec5p799PYA8>km#%XrSpCL;Q)S?j}74V+`e1FQs*h{{d-7>sl^Z&##z zZR76GkFKtY+7?p_6rS+Z=q+)XUcIx+YfrC6WT@2#*1o%iSD_ZZzD~vuc6}rk5L_Z=?`P1t8 zDKr7dLo>w^(k*efAR@5wx!a?TWk<`CjaGvdy?lH6l#$aQk{9ft4F-_FC!V z%Ru(n-s-*_MB3NediDW>Eb#T!u3x(92k%yV*@YNpRJR`F@4+{tg#x5Y&m_2h@)9u< zjUP=?EwKKSeLJT&uPdgaE6&6n)SjNrOlq|8hd)!)`CWE0bo6$_>SO&p_WQqkOKGL~d@$`cV9^7ILFOeKuK~Sq$hYdg$gG68iX@i$F7sD;~QXl1hFPMM@t`miGrD z!}0Mk%oz0OBZEH_nHy$aT6^pXERl1$`T`4|G308nuM45lbSpmXZ=u@u0|dU zJxK#+NbgtCn@?&;TbDz0B#n*nQ1tS&qpXJI=NxH&l`e*l2zy__@Y{>ZBR7d!ylb01 z72Cy*Z#yMGs>H&9jYeJmjR*U9w`T5YMx#VbKH2VXYIpU!@M}^K$Fdc3gMOU9PgR}6 z%#VG>^kv@?a8I97T}(BR`oUI}r|$&R>U~R(n7T48b9v^&bz`1;spyW8dp`&JGt#5F z^0^#W*in%mLSx`!V!Wro*Y?cx40zKT>KPHZvyG5>-7`lj+Lk($WKzpHLKA4Xx6{U?Fj3?HR6Yz|SiyDF~Sf+Z_Qlpu}$}N>LI7lRm<2Z|B20%e#`F*_$+6 z7yIpXC;BDXaSwzHWC~-VbeEttnFMc%x#?-;0K45<)qXV~bFIT}-XPt2&A}w#sJ|Mu z;g&?YKP*tA`-83VyDeQPp+~7`OED;zp%dNcnNo)EwPVeLQt=JymSuaB%UegdwM}Cw z-IrU*Qxe7-kjZ`hSE{%#w*(_yp3KJ#)OMMfVEuds9_|Y5;Vp>Z$S)+E>*xQwKJ)*i zi~c>@JrR` z6ih5Nn4E^2RIAWsmirktiM{&IbMaS0oNnK55b_%pwds=QO zig?n7tZgb<1L@I1RbHMaH-0La+I2R|G%?G`c~9}LTA&r+z$>4L)6xdFKNQ^fyx7L3 zX*T9R6hZ3GLW*O_cGLA38iwUrxoqrdb?XIfYQHQ+;C+*?|2mE?4dezgd4AY6z8&5A zzFs4Rv3y0Byte*B5z21cB7S*$o=YiEG^C+bJtX&h`FOaT+^FMXc2j8@3~#o@P}g2 zh~lBfhkxCn|K~TU;swk`Yv&(|Qjp3?U~KLIOyu9>LLGV{r<+Uc146U&ibY@6L%?5E z638I;cdir+>zTa>)L6UxF0KrJzIH{JAWJXtm0>#l{YNuYMbf0K_=9@xkvDPh8mFNV z1kk#nDd(a0)_I{s(Ubs*P_KI1*c~5-!nI@l8HXod_&rnecL;2UeEJIw5DcT$KRX){ z0<|fhwjyA#6kOs>LIB^3nVF25@^ner^aU@5by*ZqGV$UTX3`iq*26?ZGYU2&+n zx;Z7?@%08}0k(NE;l=m*Sj#K|9lnWl`MgXDj z59GC|)r$d<9hj7IB6|)GaDwc@i==)QD?rzXP*9S^ifL7VB=(?JWkB@T1~*qnSy_P0 z@<2Sc1MRzNcbNNs&Zzwokz*;wk4JxsGJ5wRy=*7!U5TwNDPyQV>NC-K``Uspx8?k! zk;}!vv~NJ?BVEH5jL4Fh<`94gDJyZvIn~ws>%$@=n`25Z>U~Ew17lDgR~FodARh7d zZJdXI1pnT-P|G=>t4q>zvx8s`CltmUqKODigL;scK!QJ|aD!EMr4FfAzR}PhDtK&b zHnDUxQ}I$+J-)?1W)W@DYcMI*=|Rk|Ugjgt=W7=*m&{-rW=hpQ|HlDb)xYd2x%23! zl6+099;rHJa(U|Kc&xZ=gp?YQ^WjK9hJQ5kRfjEa?RPwN9OOJnYMQpQ{zYFDJ|+6VkwJIm9&Uzx-~NNEBNp4)#DeQQtj(m zg^^ksiV#-Y1{ot=V(u&9rQsG@la{pzd%2(Jp+sq5zL-TQ#15Ne zGK>S0sHC7vNq%2FIBn?~jH}$oSTrF7x_SFY<)bN`oC%i6=3Klt8grD27sM^E?nFH4 z+HwnGS$QB!6nJ2gGss;C{w)(OZ}0MmoOOdzU@p%|ov=?e=mkz~nNM#GTcsp!6z#XO zFGXl;bEs=-6<03K8%8?2c5?aM!tXaj=SQ&E0tO7bS+n+`y{iS2Q!BY`%!Z?fEMtes~@_NE|oEb(xKs0_0L!zyw++za1(Nsh5^}w!{!l;!=u*UI z=Shrv6soztYL5W|!9o)B_Pi)py;h%;BD~2bbGWSnJ&SJgxZ>EXMRQ8*_I$SYhzocM zet4)OKL&>ONdJ=5FLE>Nl+_ghF8H6D8znrigBI)EEFXx^FOvxj6h>SC5|&GJg>5`? zujkarIrI)Uu)6b}Z)l#gZ&XOB1zrV+W4ky}Ws}=V9UedU7e?1=UHDAzZRxaW@V&9O zpP0^ydAqLjjdB=MVrzUv8hvVP`hB`0RjXpZ!jq{N25HO{)9^Qu<~UPSmR?uD0R7&t zxmLUx`Fu=e)rmYWKAvGpDRY$PfV@!vvbbF0AE-45qjC8)rOV#(>>g?HC+v7|Is}q>xLbe_V$CsBK?BG3@vog)|X=D{F;N9 z96~_Xu#3cA2;~YTh1}T+L}vbNHx*DAc%i!y>2F6bbUN8!MFo;EiMEO+MS5;hcT= zr-!rjg1X0J4jU&8Fcv-MGLMicjWcUU!x+C+AM|lSwaEFEz1Gd(#T{GIM^)snkot*^g>^mID8)HKiVtj zu60?@8fkw}XS$^-*#a`iYKohBV&P6TX_*@fk%r71^N?qHdJ?XSC6)mk@t z^y7!(!lBljlPyUE*-E{l+8a0PH5^|vvZ4EV766?ZoSEe_LnD0}+CUE1=@2^PF3nIJ zZ^}n`HPqJUtJmPQXQLA#W{he3ED7~0^8U8(cHBM^w^M+0&!TwmDO5Rla_i(Z=pY;D zESpn*%)z|0ws#1pf;g%8cby!Gx*bN%q;k;c_Krz7L!vmfeIdJMkE}i^I;+codKw8Z zHr2Bf2~5+aC9`3TRmb#I-N+wI))8|>d}kC>m=es!o$uQ{k)W zmTng33x>AHxuf3t6p@Crz_m)PMX>?SqLWYc!d^+^q4x9$R(-5_r}e~3jQ_P1z~Ixt z&nTrzg-gCnk4jE9viz`G4SzK~CqiyK{9K_FEnd?@R1$f&TjML$?35Q{!ok`tBN#ME zFu~|s(S`OEuvY`jA%@5S{atch>S56t3*)sTXL2QEhrawqOU|TW-)@Xlx3(@gR)L2r z#XIzsg=XKd6?^XYnpHpHbzA8N9tt$H9peN2^6o=7SpbetQrs6}nB`O<@V69R1qf<+ z>qbv^`uM3b?=`lJ@Fx-V??!%(m`u$V>oo4OQz=z^Nr}-g0^!uZBs96j+f|sCCmz|{ z_oq3}mV}c1VD0&HC64Ns_$~SY){=5qMz5|i2{4bD6{=HI?(_rRIyE~I{dc{P@EWM`^^g_vi_y`eb z=V-Qz+yF(E>FprR)y6lPyUjyRBF(a*5--7m;pgiXJ*u;9*^^{tqN7 zax1OQ**>zl5}g#6J{EXS!LV6NfF#zl6wjDl^Kml&mpWOcJ6+&CHTp&68I{*}@3XbA zu9&f$ZXj)$c**dYp69DFa$e@gv2$}|yh;l7l-11c16g5f2aY@)yd)k(_TrtZ9U1%G zns#y>px#%t%jQh{#h>|0I49~U8GVtj#aUF>EM;KOJ8ko+%zpGA)ll>-#MuW$hDGN6 zqUac@V!_^OsaATCt5yY0{k5kq`cYpiWD9k1MQq2Q)b#QX#kN#(42((EN z`f)O8E*pO#u^7C#bYm%1mj|>O&1NN@g)~*APogp}dil8e6+i^Qy~x?;N%;U)DcOtK zY0gQH1M5-7?2J%BvT_|2<7NWyI1WB8B;aNhYy}6of$ChIkvK;MbSfgTEEy>A5S2B7$l^SQH#w1cIWocUx6*lsv>k?eU-{T`kN6Ih z{j!%cu(7_I-Ans|wH&>vaq7C(l@DzyUx@C3)SN|v-{h=w6OPlK*kDTRdx7>MjbuI{ zq@?GJy7A7~ihIcQoz@_7G$?P^FXdk%Ryp4<=-Ynp3(*XZl~l?MpMR7sc~GsCB8Rs2 z(5{v1jqlqNgojJ;ZdxqBf1HPdRn@vR>U5-U*Eci@n5|(1bI`N!RM31;05`<~wey{2 z&6QUCWy^&-e%T_2P1)x%D-Uuhb}4G3RsjE(R$hjuH|%um3<}4bbe()Bui?5WZc`mC zSr?ji1vrv!nW1cKxf^`$9%{K7AU6|oY&$X4zWhZhZ76$Y+nVDi_T=IZpAA%4>%6So zWaZ1Y8pBpi!}8@f+NFC10B?=J=v_%m|Cl8TUE^r6jHlf_Eg1qIxM z`RJ^P=MQ|LeSZSteBh|QK1Hs3CME7DnEmFOt-yjBq4PL!onxpV;(#E$P$H_7GN(q) zoaL~dB<*DM5kFMut9h6iruhvMJ~p-_2W`yyTY6_?egTzxu9ctunsNiw;SX*mLkeZRg=Y?zcTiSQ@U`@`vDacE%_VZmh#7 zwmq;+L*S>bIFnQjxFt0W)+w%3TQWezm{eL&ZU*mQll@~UOXaQ?p;s8u1K)bYDg>Cl zuDr(*&2;U}C6?Vi9&~Hjyxvt{k|K?s6Dk(Rrz?ugrBTDd!1&=$yr}Px^i+1P4mCB{ zEc>2!AUB&z!$Ol^0$QgvW*2m*XcfH+zg&rKZJ6IW-rDg)JX8KT=8S-X`&(=3=|>Pk zdRI&Vn#$jg2YH{APS4n(eb{I1Q^6qV)c|24jazstqRCBU{E?#t`l3x)xzEG`8~i66Wh%-Ci*oUfd&AJNmU905SQh@>^^|1f8pt!U z3=Ty?ua+oEu6m5NBA}pz6@Y@-3J{(@LA!}&e=0p-ZKwQj5+0D>6y}Kb{gzzN*Z$Nc ztFy{}Zr82%q6ce=%&QMWINn0~WW!F!!zK*q)L-otWT)#2008e3(^KCQyfX0XCTm^*(Tdx}><9y~TFC@Gi8NfXj)P^=6YX=1c%rRs92?95`rb02?(co5Z5 zZRX_ABkktp@bRQ8iE2v|V{r+7nrKYAxM)wYP|nOs3=w#|iW`fnEW!Nse5RB&V<79d z;+c`>Z2UO&3eII4V*g=59OfH>Ix^1HyDB1q5@qpxws`ji=`di! zQt)1-cJR60u7McH)4z2u3X<7zJ1AzmR^dpneDLOp& z=u{P|ss-GzZ(c|WPpZ?dh$=&VK>_M7k=o4hOvWwE8R;7QmNWeY#mk(6z7u;r#5vcx z*w)^!<*H_D=$XszTbXs`Xf^r#zpS^qvNsf4)emeJh1IG&ps%;a1tUM1Yq`}}x8J4V zem+K9lxnPsh$Fd^l{`>`IyX-`zf~>XOy4|-iVX{|LB?-KdXLY792@Sy!eq3<6-Hi4 z1uYyfYr6^>TqOx1qN>s7TdEfC{r>vYA#QSWJW~ciQ|ZZ6TGPwfbK9wy;{EjN8C1;b zmrz_&>YkY7>wSsWVI;3J%Kz*`& zg`2NKHr=2WjW&}%B~+~BTkO6va`F@>N z4oB1QZBF(V&mAY%u1BdsN$eyAswW7rB7GU;!ZJMsD<8ifbMaotr2BYKvf*iY3`Y{k zlsDJ{+H{Goj)r0p8>m!;8*4svLN2IH+b?3{BqY*Yo%}AjCV6ec!5!L|7D4pOmh)CB zU1~z~wLV4FcJUDdHtW#YiOCT`XUd0+`AaDc!J}4!V9~JH9H|6Im4^&kUtkz1(l9a4 z0$Nbhr zGWHzRh%-VGhFkN=fkjq7Z`}U;p9e2>veBqbu>DeUYZWe>KP2P%lo5=uPc?@#dgl3B z5X$`AS};KH^-1oc;~|AD4;%cJUm#}o%@s5#NoK}$NbjZ0uB8a6`AfI75_t|D(sk00 zN$i2mYyGE)>BCyy&I4D6#i>rLfI+tKa~VD;32{qvYEi==C7DapL~6ix zXkqQkd18BO3{%O>mtVU+uYdPg&vwQbZtEAw3OX*=on)NI4Q+3<@?V@e>v^?a|3d*H zwT>PBp-2qTJv_lWiv9Edygq<6mIjl)ZZE2p9hVMeQ)iAWIL@u8x7J)B_J18K>{uPn zX#EbcI^{Xowu(Y~vBl9FQ%|L`UO3^E52@9o$|JV4s4gX4@&aNQznvnLxr3>^1J6Zz zTb1MfM>l=nD}7qoU-erT(`gCUPPeO4=TCKBj|d)`44a9Nw1S9PitNoTkxqNg4QtXd zX-K*Vj7w-3j5R1F!ta})*OCo^sCR!T?(aBW>U8JrPrgnJegKajs6QnBpuWE9CO&*H zOB3q}3@Q)28>{kVb{_HtBtkX2y{m*s%IQ6V5mptaEfq!1v<2|Fij~AL$bdffE=TD! z(?Ygh1myUv4>{d8#k9Waz?cE^^H|?R=F+haYCs7s9X-CN;3exNEZfK)p~Yzg2lzm7 zr&x98AD92XA#;=XCH~#_t{HIj<`0EeaO@unrQ_MRkixw1LE?bcA%bSRvG_5?=O4`! z6h=HqKMh?EFO|&lEvU-6MF`*6;phn+pKC>K=XWtb8J%Y$$`y%^qPls^UJY?V;vrfV z7Ly;5z{*`yD=h)TgqeRHU?z$8ZD(E^X+D1_idkqPz4v2(HG)Fa+v{NR_l!5I*hu~g z0`-7%qgoq(%x*(Co6=NoF<$1h=n^>Ce>yA~1 zJ`z;hT=g%}dYy~jSiEg|8N=o2StPKY0yqAzuTI92e&=0@9j}7fiD2W?gSFoS7xcgV z$X;Emu}h8L3S$eK*FchIVNOaB0yd{4c#3XnfO6nIpDeJ3G5MtPO=*@YJ$myxTw7P} z%P$IhD4UHhx2JQkzF+tVk|mkdn45%_1LtjutFF~QDJXvTIkKkU_Ot_O-aqe>@PA3t zRk%B$I_#SD$sDt}XdAEJdt4kENb9@wXd49H&=7!@M_NHrrvbr?8y0@QKTA4}(bZh~ ze2||W08IxMfb(qPQ#M$kzM%A}B6}#tqZ&Gc(MpK^!uPy3j89(818F;xoD5zsnWYDkT`swi0~j z65A6}wf={K^D<|B*Z04E=CVtb{22T`5Eo*9YPvG?|CcTA#oUY3d6OxlJ$K6Cok=o- zmX5&+_l+!`ZqkjIZ9SoWz&p*Ov)znMP6+Dv9&rylCNr#uURE{YkVv@2m0D@JF?8VF z%~VyPE)MIAl$>Dc0^Z4@4eWk5R?8!~gqK*jr5PVxB4?mfX0BPc4O(>=bu~xX&?2oJ z@YvTC2+iJV%8-2sv!1hZ8 zgPG^i#5|jfEjg|zyL+yU>u%@{`p6XDOcxVk6Ce!;C+qLS>6=o97~|+0?qFd{Umv0r z2h}$P7ftQ>IkP@YwuFR{^yVs4WA_S~g)6j&wUbgmdKP#GRSK=En@m8WfG=PL!dv6B z)MbV8(bg<^@1jt-Px{~nod)K^8Z(Ikkl*~#%B}qdIs%YAqfOH*UZwUX#tKR18+C>4 z7+&MRQQsYY(f^p`Tpg04`iYZ_9U}ms_Uy}T z-SZ?(T`|;}IpoOXVAUsQ&98}j9Y90QY4|wQ=oUOTHdV}sm4aSHivdcD%8_XR+3AZWwhteO;6N)x;!xi95X65u=wApk>fAwul@Qu#fpi#XW|vlF1(q6^$V`y?02 zESfRBLT?8va{I>;;}VKy9%bZ*TPb~bG3%mHTp&nQc4F*4-HQp!|NB37pMt`jzHSI7 zc~UTZC*d$A>y%lZM9$f<`7Wf<-=EXXhoZOa|2WayGmJ27D7HpFALDcMBpOv;+cK6% z+Job7!73+@g%WZ8S34|e7x$#g+jZZ_-PjwCnXES;RrMWVNxOZ*!urk5FOw2G-8aMy z>~jbv>+7sFWyS(?EjUO)>Hppfx}^;t_ul@^Q7wrjI<0$4C#Ls&vNB+fy3C*F!?0=Y z1tOty-4#)p1pFi059rB^J2E%(YlaNtf9h2TPmrz6KxTtpJi1*EJfVC;D1c#Mo z%oQ*F+v&EEHaef-TvgB%-SCc z@}%S4$us1T=~i)0`aSB=!L-w3k=n}FoBj5Qj$dJ%%uOmZ?K=~&R|j9U<=Ls6u#O|Q zCx08Wqa$<{vy9QHg^gpyX<-VGQa1JRw$oDS2x15{n0j{uy^6a`Y!($&PMmV+KIyMK ziUpzPYLq&Xp|pEJ!)#0s&FfP2Bj@T7AMG-qO$REBquk!t`9>a(^@^CEM~RepNXCAq z;NPBf{DqPmt$l8rbsqckX3su#jC)PYXAi=GOX%iwX^y;s1K6~1GcaM>-fSwrYOmie zaf^58M{+JO`Xo=VFY{F=;PGD(^tVqjy_M-fnX%Jnl#i3gfa*8nLn0kv(x!}0peku? zsKja(gb5^@Z#ZGVs=YptoiwapDIDD`)>AB0{KoE$c&Ss51AW)xF(foxj*4?QJ7;0` zDatoE^pP`%Lk`wzbBal0?_PWXYLO(K-EKrmplkJCx-Is}k5V}6#w8!6KXZprLvK#S zn(U zg7)Gy-ud3mO9?wBLcpp<0+=|yBX!``{GV^`x$JvRyv}um;w6+=i}*6pT#WN zk@Ebb0g!!^!D!2iHK~Uy0HF}!lkAwVD`+9rL*?c5I{9zR|0q4sWk|<)o`9=%|wh-V_kv;zLHQLcdd*VqzuX>k| z_w=ptckjT`16oN_+sRp73HnSe?R6#vkQkh@ZW>3x!3!J51~qMhN;`z!%^m}mIR?#Y z;Ei?5riRZ0chrlk?lwZ!yX3b&F}cuNaY=xIwSp6STQ#7pub{eZi>Qh*ad|DyQt==V-$w={fCA5gmRyjwJ#R=I6gSKL|qEKR%RbA?|5V#5aPYt_gO~znXg9g<==}`j6JrDM~g8Z zAmMMk`i!69ETSx3(tk|1x0CL%$!Kg?TpCMd4=!#SXe`SJ+}*;ZHHoh+Sy3N{+}X?V zc+{YP5?10NG+cRAFlVzSNP=~sNL=ltN!POT3})l$_%&WfoPNiJka(X@2Njm% z#v1=MEz0)u*gwMocw~%q`*q{iN_xZO^uNl9pim}%7dNyY;yo-B9{%9A{)M9k*gFhM z21*xI1(hv180W9T6b-ypRp_nmjnQBV=`|pJ!_*qC{anbS8Rz<^Xot~O=a#Jg`Nwl4 z4o;CD)qR@PI?Uf@1MM@0(6XV8f+(lfI8S6QHNE{?pVB@<)b5Cv_)t*4Rvyejx*&(X zclqSP0U}0+2h_A-wm4!W--eI@Agh7Cw>Dh$*kgL|L_hyG9?|4{HW{_W79Bh{qHHuICnwP-Gs_1Q~9gTcvJgCLaTFeusP z2_tc`%=s_4-ha+Y!awokb?)XIvW>SDAF%PrxEnk^cV`}LyX!ySQ2janwVUH^^WNCq&0U+?C8r4H4 zP7KBVYQEHEd9mzf&{7C0pdznxBo<|}VA-C5vKp}|3tL|SfD5)8piMgHu{}gEV=1k< zr%=q*Lw!(OCn7;Qs$(a}9Q};zYV3k*p{Gjn2~B*PLTAZ}Sl612@sWxc&&^$vw9*1& zP=^vIG>qA@=9N%jAmRCWLbK5W1G2N!O+x_z6q-ycUU_Bt!24^v-5c5OWZHiwse&jz za2(tbG$=6TKjK>sBbjTFx>egd+rE!C3wme+?~s+3KCJoCp&xxn8;ErTD(Q%~9-BnU zpnA2KrLbFmdY4qU(4R0ixp$C-5d7VKcwh6JusxM2Q{SK<+9PbolWK5T)05ts6VN-_J~Q%P~v5UrI>IaAWi0`$U-?IBU`|ldF_# z>r*pEu=Ua<4ynC9mw4#fT-^YbZr)6FM*v9i$SGU#gsMmDj+&Q*JgyV#w>I)uQyVv- zz}0B6=wOBL<&SnfHs_1iIyeA;{tpjpJs<3e2C8whHr|_TL-iuACt@Hzo70YN9Fv{u z62q9l-9HqXo`GhhOxt)s7d5dFE96GnuXljlpRwfXCjjBdS?T;b)TmMWon$gEwma>v zgN+3twt$^{mqij^QyCxe%YON~#~76MiRH`06cqflHUfD#@cqZcb_G8da9#5Ybxc1Z z8N6{OOsn9mC1l0lEzOBfn+@pgRz#=D)F~tQHfs*foj+CV-&$xD+q=6)fFFqU6)R}W zk7p*b_L}yj_CNLA@)8YIVVtkUReZA$nJRj8s?cmjtNG^8Yh9;TxeCb6o=()U_9;#q zqb|-~W4#`x7;wz|V>C1W-ZY6(`+Rh=Wn6AQeL2Q9+g@aG!8RRTKoV25rFE7NANu`a z{+xR1y`?oL&*^6IqS#q|5kjMVwQ17RS~^!5=Th@p&vU~+ae#NSTu5z<^oqxLv${2f zTub)O^9u+*(mV5VALQ@yO`Y^g^HOkK)RK(F|;J#O5zFl3(W zvgM(f+ z#lwkBmg8sllwba8T&D4_yj!U3o7y~ZSP`yo0u?ta29u4TFxM*j#3a`Rk0!&>xx&Tb zWo}l(C%)f(hlC_Qt<35|xetX$yoa;Dj+rd&cvPijwAGB8j@c6F0VG}Kq4L@jI*SRe zoR{*RVINEQ=yq=@IzHB@l@x!)7rsG!n(|s=pnWDfA)KyvvAdp2!iZND`)c#& zY>;aRpF?yP&B+nGe7wC;+K>lOh98aIz!&CuEU1gq-86q}SmI`iph zR#Er0NzO!JMiV05VCIdrPX8?NMOujS@ADI6Dl;otyiqC-g@2UoI#KyV`)i6dx0fEU!rswTn~tO>O))~_&iQx*ua8mb#TUXHm6*W=g+0~$nX53417}sCsxg1m)cyT#%1$< z-$)^)OFL{wz}%L_X~_Ybz9hoFlL~o!SgAvshzXl+tfB*ZNix~!un#<4zGG_*JMa`{ zwvpZXdz&Tm?K6`dq?e0L!fV)pPFM|eGDN$Wp{+)?IAS?hBZaqLiqTdaCY;&1xCw^~ zBm23@c}3CN@fdY1(8!_@Xp%7hx6@$MF%>fG+CUNUz5+e{C=t|iZ4+Xn029}52E<&IEuME4Qa zl3Rj)*p-w?ggBi|i`?A;-HpW4mg5cBZI>A@-Xs&A zRZ1+4>3+-E;>U_o|H6;N)ax;$rAPau?sC&Ki8arRakW{0apbbnT!gXpe)}2fKJ2^B z#KX2!+#B@U^wwCfs~5wN0N+9?@EhaA(Z5R4B0IgmZFD(RrYmAP{N3K|1wtTqqi8$4 zqK#WP(rg&K6$Q|zO;k{C1k%JZHk;44*&8Y@x-l95`-gQ)&qv&;pb9bv!wySV= zXdJ0Yv}wMREEr^#$}K=H4r=scqjMWi8jTA+S`W7nR=)J9*wf8>fp0m$+xc7fw z?tGY;$-|i6F+X$8G4dPV(pR%lCU(ehl9((#8TDPAyy8stlC(0FJ`+KVh}4!K8Z~xxWtZcz%Z}fPqyF^?z0zSw zsBEXlGx=f(OMZW<=6IDI_s;0Z<(aH6%A!iUF?AP8)W(^e9*-GrPuBfi=AplT_+R}V zAicv&t2S%c!}%rdrK`NIVncIWOF`$0S2q^vZ88U;=k7l0-@f%ZvJkJ{@p>Jlz9PS3 zm%Ot90ieUenZ4p44(M8ebz$m#WuQyG^cNKZ_mu7jxi6ORdtc-CxP0lhQQ`Zio0xBd z?M_rK`xVIDL~UqOnYb{7j+c^*_8X6l`A|2l2u2An@S3*lmx3sDw4pRKCrx;U&U&r~ zX)js(f3r3Xcaoo3;m)+%i@Os_n`}2FZU4pU-LK&2F)VYn*KKid_cLiAG1+stt*2CH zD)^QC#Kg5iP2!qTuTYn(t)yt-haCQZ$65*OahjXFtVTlP_?B8Tr<$Mj`nhg{?ZoL? znIx}e!piH|r2YrDtL5(OJW}G|a%%AILM(G(d(rf}gk$UJ@skuKmgQrvuif^Nv38K( ztax`2n+1p*EKDnDh*+N-m88?L{E!bQl^W|gqF(4Ebk#9WFwo*WZ2|Cm%+6kVCi@`h z=Zs2@pfaga9)e=6~n1c+{|6ltS2k&Sfg> zcVL%<`}%e#sAa=yn`){uq)oDVx~$o=I9<5;p2c}gh}T&0sJ%_F z{yT;Vi1$ikZ?G2#0#o)PK|HC(v+s#B#ZkD>L33U`rbx6bdka)fZBqdRg-CM7PYde^4eU(1 zCL1+(|0r=#DhXwlwBU^D(fcUB(iNDXtZ^PB+a4YLO&XGrg^Y<1&vRiBNU2d^`+&?l zZ2U0j!*3I*jcFn5vXt%DkjK$M4i2uouHMqpUMwH{BE)ze+S#+o*|M{;eQSiVbZbex z%GS4(a2V&!k$yGBn^W(W{Hh=?Y6@1zu$uqwL*M=4>e55OK6M8}$w$V9R5JW?p4)(d zS10@;$G8pS4xHnH<2v=IwE;0pq~N{*?KH-(ykVCrE|ID~Vy?gCfPSzuwVD!;Ad&dE z&Z(wunb-|@%|VdaeOA!9J6Hg#5$D&A^q1G`6MxukWhj>fxKrycN$1sCa*ZJ%d42i7 zTnx@=5Yz@(b$i|48(p?Ul(N!Mu2wQn{p!`zdQahZuWS z`Rl`_8Z8goUk6(jKH{VsvoWQ(mJ1LjH_SMH@5xh)S4e;6`2A6wSVFi*-Rq$2PzFCd zzb(^np|)!)M_#WPer|#!H#zoXx`pa&^C_^sK54X)uKOdnVBbl=*V@@k81+Z{{jhHb zvJ8RiAoy)3nY8A9$&GwfV&&#c*>Luzhmz4YfmmVuHH%(O+;;1(uQYB>W$)}^_s%#| zOI$SyU&P~eqd4vy?2?CHIo9+IlSl-08xh6PD#5G}W1})+FhKJ|_!W6R zw`%?(q zvj=qZ*e(P4+!vt z+B*cA>pJ0dKTI*%%J8TpEwZrd`O1uO3+Xg8laDM2HxhX`#XbN@yMxR{%3QA^1URD8 zAHD7RoQctB{yY+rm*=;x#MtT@2$mRCo>yTT73{=OBsD}sh8{ZThi97S9sdy zYZe{XIQL%b-*?T7uMXPnRj7HICrphBNS8ZRe3G1z5_{u4XU^Tb6Z=l6Qw&_TUwGr( zT;xs2im;1Nf6$m{SkG!)Zn<~JEuXPs3D<`VH`f`udI#c?ztturc;JqI?y*PZ;*(t7 z!OT+oWVo#VqT%i&lIYK$_uWaBukcD(@)Vy1pn= zitVrCntt)CU-M+zk7Vx;>sGWNAJ!`I6uG8^qd{X{j#3#K%MIuR@n%X@sz2NurBQ!m zyC+cNKbl87)W9s?Mm9TqK@8M+CXpZQ4%$MXW*w+i03L2tbkwj?*^a0&d$sOnOP_=e zQLOpTl-xHI%#NElW1#!JWB=A#B&pqWINmt%&gqtkU6QzoCK=e%`~(ak=G#AT8R&Ec z`C2YQ<%-| zk-`tBLvPO7OG-E%$zQ6taITde%2xLYc<@gQIabVVe9ihXp*qBmGOCwOJb+8q*e zRQe#+!~Ff3?b79fW_pvdx1bFExot^F%b*qUAjk0qAdxp*-XbWhR?US-at}Y+G zp9%7ray3r;dSs2kA5<8`{hV(6pb4o;c(9cWflZW{ARgZHOs#uTl%~-xq^UP23%+)p z7o9&C8WTx3g#i=M!!F}x!VR9Jw)*?CIB!v`X$BbLhL$$eP;xUgHEAT?tsz#ntb=q2 zZ^H~ON2W?RUE=;*{3lhw=$ND~d-RgTsc7 zLl%H3A|&0dMrYnRzuW1W_&mqSfP!z@(-X~K+Fdy{s?oD`)`tm4K17H_^kNjo&_slT zyz6w;y~=6MXo6WSBHR3Cu)T}nG$3h#+ngTw%`2mn8Gu!fl8kc-cZDH$ihmEXr{zgb zpE&;QsR8<%q&W(uuXG9Q+Ywde=Fc|_IbGM+RmJLVqFyL)5GiNwqJi*gyZ(sg6Z@y@PKE zDzk1+axa|a?7!v8{P>@zzcO?R)6E>n0?;Idt@=(UQEfpAD z33Vt-4dDS$bc9#V+^ZYm)oe!-Eku;g{7tIjO$;T>BKGEA4HMjJp6`jR&7&@;T8IQ* zOK()G!=h?eFr)O9oL86#5X6&-n$W`jW7BN^h|Bz*4rbdD67<|tvJbp~)j82%zPu3n zp|8aIUtkKMroU1RApE4LZVb!MPbh*`Ma3BCmCFY|hR$=6)K=?0D+I^Q&!>#o9CE7v zMUx)%tt^0emgT8-26S(q?tDyhtyKE=zuTkx@9`{>h5wchUsCDFa3uG0Yi~aI`0BLw zvFkVUX#_o#CJ?y2a) z{M((h-m%b+b6_gh5pvY=^_Pc}FKB22nW?_~ujp70I+3k;whWvqG1Qgpx~s9Liv*j& zsz|NgOCKx)K?O6%N^gDb)Q5Su$$jS&;h4s+5cr=tA^${U){eGp7v8NzT?Oyn2KgHy?*Y;b7-ooFXJvvW!V(ohf{xb>m} z_L`i&!H$O2w&6t)D~-)WH{d}~Ha--q`_trlsxsfQ_s2i{gS#r8YXDCm(vIsBI5v$~ zmsIWLL529`SWc4Py17+>nb}MVOHtFWy7J9L{WYIZQSbZXy)zH;*k$gtY>}qy=KVQmFGXSTYNO7VD^?vO3 z6(-G3ll3)3Z{?q2Jx4ZQAMlcn_}E%bo-hyi{jJJ};Qp=hs&rs_+jGGl*o9D$6)1Dk3 zqxhn#h8sVVyUNEgtL#n!+Q>`C2eV>mT1N>n|9hMXDw<`bAUmJ&BkQ>g;aTVv4Ly6d zx9t*5fef8?S>NoAb2$uT;=XnBE!r=9dYLH~6B%un5YZx(>m6veLF8MgjL~2h+n)Hg zp!WKo@c)0gS129hDSv+R)VgIzCX{7xO!oxDCwx4{dU`^nARmR|8vl zJDg01a>w0s3MMcWZl2XXxnFJ%6M_6FHIB5iF|o%YKdgEArOa2!+IDQ^1N&&obP zCDJdTm9@5+`Ge|GcJAAAlx<^;9+|ZsPKR?$MT$-0nxw2Y7$-c~imOuU4R53_Bo%my znz&GHLn_kJhDX3rbMDU_kj_1BCLSr43nqT^Bmu?Z94R+P^J+9yc$e^E3cye*v2P_j z-vu0i)c7*-qhQVLy2$!cYYxGm;iJ)kr6n)KR1losbd*LcQrlHN_Hu;Rn#;t7kNL$E zEf-ZE)x0Y4^r*>^RL7}!#g_u-7KAvW*8Ga6v&=G*0GS^=p<;=(G_ITB_tq@?3Q?CCXv}C(0*9`;T@>WsUm=bI{qBXO3WB4b;~&c12l z8T=C+MriCr{4P|i@c>r;IePnFDCpiOi6`E)zo2W-%wC(x$#U@jMHLO$sYCSQcO&5n z2}XEReGwG;S$6llH3h>Le1J7EG366AyFBNJ`ziL}^69?U+A7^*iWc5)HHI|Xf!ZY6 z;E!su=HpOG^?oKcyD8n}zVDh3Oj<4vE;cV_J^xGZ|DE5C)z)_-TswdA?c<5u!_Xn; z$DU{Gt*rt@ts9DA)NJ{aFp~q!k>qmdVcmQr)ryRU!RXb^znf#L@ZLIUJ(f6BS`zQc6+&is2r(00K`CLJ`4EE%8v6Vt8Tz-OTf6~+fj zrD`KKx%ZUDr=Oe*cz!IM)xJzV|4~ZDv=sG5=r^;7mZpIj(myVihA>_ zMisT8Lx9rdcQz?Xa5RziTe<6HV{#?l?O_w@B3kiUg3qnZ+^o$ha9)2_d$jhi#U($>C^Ph+&m1_0zCMVDlhS}m7}76BD;sZfKp0+`D~17g<#@8+V- zIcJ+{7DV?^gpQ4VCfdYCN?6XQez_~gN?W?kht^{2X1ZWqyOKddd+~|^Jz_a%K z77Xgd2ru~lH;!K;g6jt@N;tz_AHDy>)5UP}d7?v8&g4$1kcmsGtmKFm&?UNIlQ`!$ zXlLdHt@MJ9p?~9AL~>e3lZVTYTbcG@(?X<-!NMJ|-pY)Wc<6v%docd4p? zTKh)O{N3W?nHw|Xwa&sDuwMHhbcYEANYv%%w`D0fF2;Zd`mm^h?fO!FlGz#$5Hc`q z9PXyDH`W~k{AC$DV!=$>Q^{z+jkUy%i~%d^SEkAZ@FjK_8H@;&asEoYhskFB)Jz4R z(ph?`sp$0o_WJlIu>Y6)>l3}!FLx>k@v zmZ{-M36@iPI4vxcoqQ(~B-q8I5JG8`Q%Uk04gOnWm=Vtxca z`B`~=)#@*rO!zhR0sjQ9l7|(Q`hf|Sqhtx$gXWB`1>d{&-L*dE0KC30D5Dmwe0H~3 z|2!7tUH!9E8c3QAB06I=CyYx7YkM*Q{qxR&_WTOTy{%sCz3z(DSSQKxXw58%R=18) zM{gt#ZQ9-uik0n#AG9!J1-O=OGHu5@9Qm&JG8&->F;p{wM(OtfN587RSJic{CkAI#Q zYLn$Alg4BDSb^n8h2zX9B{JRnKqpG7VyJ8SrLAk#>Y9WsOXf+#gxV?6VLF^_ic$$K zw=l?C+Zt(JvjNlml7M~lzuh<@v=f!3k>Y*=7-=qnu9-uIKi&_?4C*F1pb)S1)BuLd zNH@J)sK$s_%9GxT!H#LL$)-ALjTbS#lQBhA;uIakziC$Y*BX+~H&$M5f!)qTA$h%t zuCmqgjzS-(Dd^TjClM75O4mbs)fd>=;~6#2%BIj1L-ZJIu_@qE(s;zd*m7 zvd<8#=w)TSXCM38f=0T#i(8xXyz4zt;-iw7GjlGP*w91k1KjEKNa966$DhB7N*$?T zd`&ig*=cuLKDGDpRAc*E@58<;PLdUP5a-Hz&BG>De&4{HZYf`VQ8EeY@1v%0kN0D8 zi%n;X^Tw(>3F5YK8>f#)q-6EfKDOaOu>BJ z8`l>gZg!0TB}x*Q-PMTbW<(^27iz1Tq~@nx$gMdxAS&#U{tKaZQjuQmiI=b?~6L2ha>b;Yv0hrUad zakD65RjgQ`%5G zH;uVunk|;PWRQsESQY957mdh`mjAp#7j%j-Kh5nHJgW5KO7^2%|I<6gYtv(lMR@5?acP@*Brc z&N^o3N*C%66LSdFxEJk7vl@Pu9YG`F;9Zdrd&MEytZsJ5@Z@r=0a)sOz5Jo30bg!e z{SwQ@v>vfBF*4P4ZXz0E3C$7@aJEXTYiU&|X#D(IAo)y~`T46$t;MX(z8<13?p?>U z`pWheDd}yruY$y~h=X5~(luI%$d{-3X7x(t*)ZizH;)Y1{d+)yr94?W+eq=ZLhIdl>zJAHj#bGuBszOH{l!WXs30$W1c#tZqP26^sGq}x+emMBZV&$4q$k_rPgVphr2Ob)M~2Uw;-?oUt(HV|MQ31K1}bwZVkme+4k#U- zHKD2t>c!ETZ%yM8EZdkCY7tqlv;%>dW}`E#(!dcvLMrX_bAX(lJm*=yUM%Q51zoSPlqesb+WFE-NM z^3yn}>!PXIKqco+b5_8NM}>79-WNb%hW)I_f$RGApExc(ss1)@Z%3|@s@a%r!(?zo z4KYK)!>`Yzo#PM5H$c6)B_#Asf_J#HMORvE;~b5MQ1hSkh6j=BCFQ5OJCdLPQWDAw z{5h?~{O0%U^pl(2( zAv~>-DDoB6IsNr#cNrh}Nt@e18V+0yJRD*gZM2sTWXySA0qhioDp1318nnHgO;D(W zAIrQ;R=`F%bU<^y;&QFfin|gve5}=Ps`f;rdN-~%Pw$x#^Od%Tphn%>?d*E0+Jff- zCgh$*WZR9LNIA4ijs>B@sYY&Jd;dIke=mHQ11=Xkn{Hiop3==Dd8GK{_EMLE^teE^ zfxl3`>{lmq*BTQu{5*LhL`r5Vb8)^nY6dUde*Yse*cpi4vA!qBtkMzqGMLL#Tk%m= z#&QJ>q5h}^h_4Gx?=otkCC`*uaDj95e4AuP=II_nox*k1nr90#ET`Rg4itVcD;d`e z3hdSzQPyzNUoJN|-;c)Hzmk2KY>q5Hn*JyciFIu_q?%@7%61L~XKTE*fz~Lr!6_&g zFtibR5H@jqhHY{YiOc!Cq`59%EQORajz0s-&^Qw)xuMQR;8?4H(uUE*5josuGoOYzf2qeZdT-LEbz$u`$pyQ}#~wH9u;;;G zx!p|B)_ckRjE7N^hg_y@)?zLZoA-xGK54l~+3GlDZ-sN}?y-9wH&u_B-7fKmssu+t zlKgoPY#R3R5$8z=qbGb(*=AofC5Z`liwy>04q(@ksk;i!%6N|N7XKU!ei<+@JqTMv zw{ez&`T9bP?E)g5joBmdIf3x!ozpqvJl4a~fKV+n!<#cT zHzo_rZANN^4JK`deqG)8Runq8df!>UgC4%4lz*(LUws^fwe>Z-7eO+&RS@0h1%R&3 zi3rs{l=b-;p0mPosdxe@y(`Xf)GgzJvtB9}-A&-ME1eS4vGt)+j$c}vUsTS7+;Ul5 z{q(PKOY-ybH5X&#;O{MRj+}MR{_OZ(~9q*7_Yj=N8R@ro0+&k4e$F zR6czMzSA5`Ego;5`2~qn`)ekijW2bYypqK|TG1tO^-72!YY#I2CNtkVU)T5iPwsLm z+`8<@(443?>K_i}*c`Jeib+LLIVU2(l=ntpMH}6F+A>1$)C>C_`J>Y?DpTErZS(}d zc<>(ug8v-*|DPX7TwU+nD?SiC3*F;6jh^hF5%?Qso?NLDHSQ>jd(5os{=XVy1Nd-< zzED0n{V_s~V)xJb+d}KV7f=4@S$_e=6ZnUVaX+h@cg{GhrU=VZKyGvD*tyksAe=rJ zHZ???Mqs9%ih0qY9KKuXQj9v;ztmcj03I$;QyhghuvKs3?e2W~orP;u&#Be36o*Z1 z!DWJ3hG9!_+fr3ShQV;{IX6=cG2K$uw_)2RAkNxf22cQizI2lPCz@Z@v&BKmGen$$ z^D&vx>z0{I6yAmaj=#KpzANUi+s6F`L%g*&{vLZ{P8UZMcrLnRJKr(a=<&K4lnPH5Q zJ1%|V*dM)`ijltSIzWxBly;4)tLCTVl(-1qPfNXEDo&<)SErBegQ&$awN158|MzwK z<==`bBt99O1@{ia)zIacD5^*waRa~Mc6pwk{1;8tI>UMG3Y{@weg;n+?50OUxCp|3 z(5PsC`o}86zn4}1Ph7131D90qzlq7W%$*rlW%@!&-~y+*d+$mr=C6Nj4*m3T*opp} zA-2Mx@ceRjtMW{|g`egIQ`;MwE0YadXMesLst|*mldt=&{dax0>hnExPe?VTWxL07 zTWz$eHEk(0?keW%l}@(dKmP;6zdpGW62CryB;L)e*W#G7;p(+r{vl8R9wBGEVbG17<4kWjBU2N7#V1~=x92E z_evFfno zCY)CDQuqIH5xJ*0IUk7M@8ai+>D9K>iYqMS;mH9UKGsxUKa_RsjFBY>lZf4JrusO? zfl)*Fgi%Lw&4GCRbi{kkXfCGDtl^#+AF;|ncnXzq=#gNgVk!M&~Fa zPPdmQyKsSU$9m9BFJb+6gLk0Xy>)-=TG_B^KnQMmm6EY}W15O>)5yHOmQ=T7cv;4j zPS3AjF22ND7~dOf^oo=xlaZOz$@@eiC!@P(a@0#dQ1|Uj6ZJ$6vw}4gz(dO zgvvmt#Nkb+1vkzwEqhV}eupdM?u{9NthEC$_7)?Z zy)KV-t9RUV42xUu^*-jXd>MGqJw%M| za!T-l$+lVg+#Ex#LV1FuaGX;mm%80h3GouQvH+XlQiRmHDdnn1vPOOOC+|$Svg6m~u{$ zozLI%Q^|Xjm#@KGjfz}Fc9XfHFD-i3Ot*KVha#*cH@~D6)Nv?nSeh zAE0B+7CGfV-d~FpTV_V~9TJ5PJohsMbO!eq3Q>4$GFo_|iByv}(=R-N-U3Z-r$P7q z+^1fXf|C>swinVhFVkiEh{PszvWOsBtU{fBT8?#cSf7Ft*Id2x`i7mc*hmd(v${n} zx_QjQRP8IaH|2=d|A9n@fyvJ~%_ozjPMMIvd*Ui=LmmEBD|arVzpYM^p}8Ypcvl~^ zAp{n(v1Vz7a^gw8?r)!pHfE#k_qM03Zt)w&YfEQ!*F5_$b<7%ZbJ$0+YV_7{QcSV~ zx^>pFa0T$fn`#IWe%lPcb}Ibm5Jb1IRdCjua3BJT0s#T2wbHB%&)XEo`A17HI>$FD zBN8R)^B5UlX?Ddv)7afw&w5{HR1;2!~j-C~<-c83SVk9LX zKteO=XAMu5Jz8jbw%mtjF5kuQ^vAE2#N}%|jb(6$@maYlenXT+w`+)|li}bDG|$}p z=;5GDc)ko;YBsu7>F22k-K*1SjNP|pildkI)y>71xstA3f%m(=z9XYc@ ztzIRQ=V56XT2_mD;#CxTrsy=p9mk<--7~e3aC;LN`ZpVWJ~8P6@0O$F_zX*UTD~=F zaKaqQFK!gL;x!3v^dH^G!HAMi_|F(b)YJk&a(qq?|DxgiZa>qJAHpQ_(_mmA!mZmp z+~*u%(bk&b*=6E!z^zDT=3KBeA{Y%&2rD!8B9rnNV$-!zD{*|+9ajwGcpNvDfCjQK z%N`tynIw+ZFt2gQsH14f+$)fm*8;htl#14jQgSOy-PBp1MqxhNrJ3QA8`ID_V2B80 z3|Qvaf9S{Jx-y5uDuX&K8a+dk--|M*qMY<*lGI)6AbILV=^`UXyC!T>$zEW2P8 z!)^n>0G%vAaoexS7v8YQx-xhX)nfU!V9Hg|m|!3=Hak~5-b?a`9pI2J z22+dh!e`HERlE6?j}0enkOH@Yf>CgbaolshnKP3u`Gx6^f1Hx1d_;HYr9KvP^rSkw zs}jIIvJ0$csrwk$p=-ZVT-%#P6qe5HNIcd%wZex8Z}?T83!BFGt4hY1ZnEY~3UXLG z>3okb2mIIyO6?CP6})12Q}?b-_ABr4C!R36E;20D$im$^a8mmwNVlHDASpK{`t?0fklw^viP#~XST2@)@vWC zv;*0S*7mxY_*^P7T8>x8L$4^~ibA;RR05VcF!X7}_Ly$Dck@eUoIXcdIXz2f0Gw@7 zTAIZZal;TfiCUQMoiTF2Od$yaG)o<5I5It$Nyu{a;81}NPL2ObsR@or zl+qTNK+Bj`cUJioiGig+BKSOMPr|+0V$^hP#>eFM@6sZ3pG5Au1^3p?C`GJ!VHx~dz_gYA9C^3WdDd0TZSvN>VN}hCNk?*_5{vfeJy}=V~Q)z{L zovT_fDs`6yz)Mhp;07`W8zKVc?+ly*7{`a&t{-7(G6?`B94l9DQgtcgA5FfNLZswTwf9T^lpZ2=Wuv zQqAb1Z$s;?;Nwt(!>OHcv9&61lVu;*d9033W`^t;K)=J)!O^m)ko~YqqGsnpJX`+6 zmtw?Rp*6Gh$8ilF=ak?fmQLmxfLc1t;wu>J zYWWh9)@|@PcX7eZATGPy*EQA_$F>GBI8rRfx#aNf^X)Q!J+*FjutjRzx}$&{mh$ zGl)j<0L>Lixe*ihmWTlLK(-YK3RZ3|5a+TCv%=|OWj)rO&E!H@2x-mQX6U%U5t))! zYqH!GRY3f#!D_>5BbKhg->Kz@sq?D@7ypXL;xUtD&2DbzoNY}$<${|#=BsU$rPW!( zfh1hkB?9z>g9`cmr+2c;(=#&%K^SdjfRei0lLNxwSJd! z4yKzTUXCZJMRwIS#D~^rf^(Hz4$?&$Lx#JV%I@6S&v|O#wt-BXc9U>jk2Z-DgZK%$ zrDMh1yb@z6c-CYi@dT#_bkx=TJbdQ3>9*<=X6I9}Vovk}ZUp+mMg?r`E@pxPPxH5I zj>q$^$`brojiy$>J>hZpkTK@sZ3LAeEj?sYD=*C60;i}W+iHgmfX9~8O1*cIUxT^k z!UoUZO)<%-A|^m)W;l0q??${Z;_yU++%UVLzamM&$1tn=W$x`Ri4lcPPg-SsoTAXO!8V4RaE%x8roCuyNu{9!7p>6xqO zj|H6rTMksbSeC&>MTX&`nt4)=J2$*r5-new_dv8C7A|YrT~w~)zh!TJ?J5b@-kMEv z?W0UTX)UbwG%120J}5&I0?8oHKt1T}-j|gY@WdJaFG7<786}HcvMu8owqmrR1dNTD zappc=;kr;4_&{(3sIU}x57HPr!ItI++V8e*e3UB~xTH`|nXk{p*k zgOtf<&2IW9YqiG}g|NEGAFE_rUuJ$*L+qECL$g!A=adOtxu?rhlHs@#)RD|v;_g^94LnlrtEyWu ziAN7quj@?3^>q<>0@|g~6{2W^^XJ zrwoOwbkmP+D#X`jKF@^8;zXDa8j&cgN1x04Iy&>ptNQx-P;1udrUI;wtBRa&j?|%` z1|vfyc~835>1Brm*eydr`mpoBJK^Us{`YWJF}?sJLln+ujZJHqkcJ2(G@qR|udc|4 zNBE#OIXk)(UYGcG0T#kc)2Ug<^_KIdPb*qBDd|K=Gr4@0syZ)uN0pmj(6LC^!VV=I zhSQ?Kx~bKL4j@qQSjoE#8y8ti-3o-7h6r2)XuR(i^!p9P%iSkQZ1bi3ZJqGB(^(cK zz$kd#SInbH+}Vkx?urwG$JKZ)z#JDnJl0c0$s?GIHI+S_Yn-qu!%*V_8k>hV2(Gh9 zXg_iEF6ZYeuizwAe7fbBh(iCFNPa&*+Y`*_Dc_!7`iMX62{Sca>rhjDQS^>=SE*vq zPG=Ob9I@5Pzb+mI(4wY?-66}ESXANDDYYowolgHMUH0; z=Zg^WM8_%lZdQHy?U!}VG2>_ZYoq8%oC0)Lbd+cJm||RNJ1+-I*|2(>t!>?0T1I`5 zgmpe}S<9ZXwpuGpuR`U?xQ*vFou~3LA~hE|)Ka`Q^@|r~GVdg;9FSs$oeZehXVQywV5 zzu#0(X-h*0Z5h?1t_pWCQQDHRKFgooW+0v(aZWquw0I4)b(~eN7RJlht8vyhZrFN@ z)#lfb-%O0gMdmAi)_!w8DnAw5&idp@Mq*SHe5IsZH446s29f%a(4?3AQ$66qek zj!0PmYQUvBW9&W{Hu4Vji-hpxO#NwMU;UyCl_F-tl47` ztTNOW*Jx~B@g1K$+1=_Ro$Dbf#}3fy@NjbkmH1hs!#Lb(I3xJ`N-5se+hqgpH5iE=nf&L_a}8>yX(OMPbC5rDxCR5vyMr1p z^)pCpc9sBq5;WI?O*Vkt`z%;umhZ$wn+8g7Hq$Z zt2wDUp(ontb=t;-Bhi^dK0q%nyh&Naw66lxra0P-4S_dn8i-xc&z(1APEPrv<~#8? zs!7e%&K?i#y~6=&e6y_k(j;p7#MY5Y#2FrK`t#RjVj>NV{Mm$f8ezw#q-DbBp;k{l z{Z7p+!`tbYRKtMitWg1iW6Cq)64Ea(vGF3`pwRiMbGaAmk;O%{)qLM5aPAx9eTtZ8 z%j~y)QTup{J#ucE$*(Yp8tnyG+ z6WxOEq!_*?XoZ@&-^pb*%!;zAkn5=78#*!NW6-v1tdI!g=ApgIFGFEnqWJ$#0hBzP zh3eqh70*kxjqVfz51`5C$k)O3`sMivO7=IM4-OE4&xx!%xqzAQ;M3>aHx(=&r3qP2 zh&|4)xR@zeLw6ES;s)}re4fFegY573Cw`n%l^hq2lj-_nDyUV?wIE7#Zl5vpO@wbk z6>uDeO0L-uf{8bhjd8=L*qz6loL)pfvfyNn-RoP~(7o!?QFWAM*93S2lBZV{_d%0n z+EP8~eSl?ZsRot1$9sKfG{cJ-kOeKIYKWbnLWy8Xe{M8jdDdZbcsRS4_+B_W?QNu) zobZ&S>!#8C&SW2z2NR*b$VDp}7-LX_I-0j52&{ukE(K?Ef9f0KgFI!&`S&B#eOf2! zL7#yY71&945Rv9$mmt?d&bGH)*SDXtD1UFLVRdqviq;*!q~KhGUXHR){W0O~yNnPT z{ppb~R>?o^Ze*cW<~ZWx&xa}!VjF3hjSp`2{S{SeWP(of0-zCrBW6o!`8fdwu{O&s z6Mxan%M6{UxtSzvwBAP=Sx8n>rEHfOpHUaB*?Hw456tsIogRaMBN576E|*8)s?GGO z;$nrq4?8?sxMqkGdTZEHIS15>HPkT_fjLq{@z?E!TO$ZbEGFRfi7v&Gh&D%$<|_&| z?yLcCe^{I^omu`+j&=5l>l3a)jkr#GJq#G1Ye;KZWvnNf>dHIayXIL3X1L9s)LRZj zfne(T9MLt08UaeDDSqiBg-n+Gi6yWehU@d1$o0$iTg{zh!igpagw1Hi4Y*t{Ynj*; z&ZFl_7vI8VBl=m$eJ;k+CLKWc*5zPG)hF6?gydUEE_Y}9BmMWm@hxDNg;cdmEeg8Y zYQKkA9`IX*uGTsv{FO~Lqi_~=UFT4a{Hnjm_N)BPJM)ZQ?P-^!w#jZYPn$b%;TId z?Sk!yiRaz^YVn%_iA(fmC$u6lJJlm!e9x1X0AX(=X@_TXT0ZAge!2SN$D&1nd=N9Y zS8<|JWQx~tvRc;UEm?4ia>8A_N9UR+y_CYn-nHbY-ffES9g4zPsqj=iaOsbiBW^A{ z7MqIe=F@i{uMO}CRPTv3{Y7I8wUV<|m^!;*<|gI3rl)fwl}}PHtNph>H2j@KET?PJ z#&%2k2z5pVIzXLOBe#oWqG&y4mTKl>3P7xzQ4KBTgGJGb;9%$HbQ4BY@@xip0VP^P z3{s_a)(QPi6M8E*mnt=X-`^khx6~Z4OoBMfV*V*LkN^1WnW}feKcwb-RyZG$$=_1* z7*Ryj6nt#R3F!pQu~q4poKx|x3}lY6Jw6XRz7?5K`=~mLOFc&c2H%*Q(6&3d%RG6h z7B#LTuV3t+qMU552T@l;coNLS@Z`M!yxizL%p422Ir09^AY@`w&cM8XmB|XFHP-aBemgNdV+Oe8( zvFfK=)G)vdBPw^aZX2vKK__IDk)Nl&H&NExx7(V{&S}GPG3p{x?|b-SmK{2`Rmd`( zrTg>vkl<_VI$Lhm&w{Czr5LF0}s-V=DpfL48 z{-bXiu8(B{fFgH8?JIv5M3l<-Y6LMN_A&c@f6j8Sx_MVq?7Re`vy`tkuX4;8_FF4r z{cTu7xbQquj2^)RA80_f{EZ zLGR7h`9^N}$jRv(rzJ{_l@#E;p-x>=f`{h^WLWy(UG;sF4&!Ccr$g#GvQ83Lq&v@* zar~Zod9u$`-t?u^Gw*%Ho;`Br-oy5mO{EFAim$%)I(#{zw(6BAsxp~z@z&qwu+`&O zqC6%)wQWQ9@@rG&nN7nv%XsI|pI6xCz9|1(A@GdhzC;K`doiaTcZtP%eN3M`l!>#S z2_wF)V%h%ig^0!&t9ZKQsL@R|YaQH6OXord-wk?lS&cqP8?;6#A*yIdj80 z(TaJXp58LW$ueJ~48rZe>H|>`N?G2kbguH^72LH8fQ_zM%9*G$8Wu)YOqh4_W(QxA z(usfmU+leSR8w2~_RH37#j>S$lq!VY5s=3MNNCat2_U_M1ZkneRz#$CB7F;l9*~xh zKwwLU07@qb0qIf#(mS5)=l!2|ywCrf5APV~oNp(C@M(?3%v_6=Iq&aPNs_EHMhT$pN%nc>jd2?;@@%(fqva^;$k(!P|CNv_6e|XDa2IvP zfaw1TKvR2oZhaa^*$95!DNCrO6 zXYujxS|V57UB3zoJM%qCNlQ!dX(rv^yjD50w6OA(XQmSA#fvl7xs%&utTkz)H*Rq{ z*1BH7{eG|i82c#n{DaD;quYO`g|_$K&1?DIZSNr4;+3Ud*|R+zF7^1%rAv2{PZ1() zci4mrB?RUL1g4y6Q{7$NweZM?M+YzN59d5L_9U?k8xkd(+Z;txTB2qs@&%I0;fK?d zlxpR-+sECHC?2$1v-a3G-onW+r((+}9jRQTp``ooqaa-tUAZ)Zk-j0s+TWFcaq26EaGTwD<1EQ`Z1 z-Qq2bD>1UJO3SP^G4NwCFr(o=Rvfnn?XPzb>1_ne%Hi1U!Hv4kjYb6;hHgWmNYL?S zuuKr$j6~mTHlQRiWRvuN-v2Pp@^A9*FY0;xlB{1t-t*7Px98Pyg*!YkC z`|w8m+64V@y72YpKY^m;;>39bEZdvAG!A0<%BxDw+K80`-hKEgU#Hq%TZb1+0?u<> z)m+%HFYtWG$t5uoZVO-f849#j4HTa9RRy5HjtvUr(1NuaV|w;89nQ2Ny`7H7y*#R+ zJy(IIDpN|7i$_CjI?NLs)WlEs3&|DxUiVnz9A$@%`8k{z46+(_t z>s^nzuCF7vy?(o^0vM`R^JiYb@b|$LU2ZHk*#Zkv4B9nz5V%C#fHfB0u-P{C$BQQ4 z&^w^ZA?cx?+7i9_#rWnm@AoVUZ}$aexsKjqc$h?ayl8p9L0fN>aGReHAd>w|ByvfB zB=D%x)rGt*@x}^y7Evb&YS`xN4G}g=sC#mra@f|`H9!m9nHV=rv_PF@{xFz)s`qv( zS_9jrpj@)O=#*pQU$&k9*gbUJYZ+8E5zrvKQeTSfcI;m^!3lawUE%mq$?u9AukEJ; zh&;w`D&+J!$;NwlDn8IuXUmnooFrlDf}MEHNYJ94d4u78AQDU%Y3521Zb-7CYB!#3_r~w9i%T zKHm4+e7_jtfKbO5OQVg({v%Gb9AVQ}NKqE;2-ZyPC6kZcD}rNBC?V;!8|w9}?%&Pp z)1m&{EuJV%d?6!fy7^*#wE1G~O&f!p`KmJ6a(VU=;xQo3i<?%8cy<-Re~uA{GP zrM_+7w+^K@k3z74vRBjkgVXnwhL&r5PCuS44pq$e=~{h6GzWyLMm3J~Lng`=WF&V) zRH_~+TNfI~J1z)(DgqZR4iAzWy*zX;;NI;+Me0v~uo!cqMcUg&Y@u(=;V#usTkTS~F<(OyiHFgfKZ~ zo!q!~8*Bv4qp9z>zN(F`I~i_oy0aCWp6`2CCRIU~O>&pDep;*EEHtB4YSAwVq+%9=Bo zjE5HFIIitXO%JFyfB$&@e^Z$9|FL8+BifTaC1#VaignDprX^IFt>H0dqMBUX*xyjE zFgThroyEKk(5nU}vA&(+Z~~l~{0u~SFH)rSAXsaPzihux;4M*(-d*)-^C~vU4}TLE zk0mJU)wSvBzyBT*1In^Ql3<7AiN~r%NVrrTakEj#3yq;#n`E?~X7j}7F3m~?@%K%? zKeV-LIaK<4ibhsPi&v@h>87*=#XDx2#7fSS7dWWHD(fsQ!TVb2j(!#R@w$qcdmsPD z>}~Y8V)ZLqPKd8X*@^&pja|xeAzAwul4>JPnIF1W4w*$Or?GDo&~=VC;-6drSc@%H zbCpn9R-a2Y$1nATuyNhiUnrPVT{^C6m2w{(ucFo_D9shB z9WolJ<3i|72)aTtBBCj3wT#RC&;RA}u}sijMRcmuw@hh1qGt!`QYQc1znR6*s> z##uwJ{0ThY>&&(#=@ZIzF{K|>kTjnV(*2K=9c zVNmfR%g&*LN=F>iI2zOq>YAB#+l%`09AO_MmiDvmWO-iria4^26UIP`7`;k}=PjUQ zMy8~1me?|)_-(c1)AY?JXPab&@NwcbS`K|Hk00cdN5sOdo86YwQeKmY{%8U#r8?oT zxK5lD>`@vvVF#O_TARt-{e6Ees#7PFY3;ZER=~`-%V+Cmn2~VJT3g0+CBZbq_|~Lp ztGQ(+xo#*Anj6Py@&x#BN&Ln5ysmnXgP%{;zb&*3ZeddyOHsRzh{!XM+yH;#canvC z?|E=)wRq!N`|<`DN=qO7T^gU^*3IO~Hd!;$c=KUeG$}r{Vi=DZsowpfmO<)TN(hV_ zUx_90#FzFpS*-N(dzZd4Ss&BA_WBep zgS{O`zcbX6BH*5SiP`erTN~S6_o7w}?DA-kk~K+(n_^e~Twn@%o`&cbgB^@7U}|F( zo$@41_VcP@#e^zit@6HPY&V2Z_58lgii|kq!HZTMzA7cRFNrt0E%eyO+(%(br~r3s(_y89p}s>G5}7+k$#)5tKRuOIJw(h8 zgxUf6cBeCq#0z}q&|=m{#~@daBPh^<@QdNL`<(j5KX<@yvxbAxjI7IE7B;`fw;0q} zQxeuymhSZ7h|j5?D{ZV!Wc4qJ^&6%c$5pxN!?1yMdV$f5{|;WY^%>Bc|gB*M3j!ucHQa2plZt zU--&W3v(9omA-%dGmifkUJ*-^U}t)ZV}j(2nOLvJH_+F+n0)SP{MwP2-alq7H1r>; z@8lk*|Gv?Z^K|cu_QACAR!zt|!PMD5J+`(@%?bEfhOLFB-DQ6Rp^9wPrwMk;6@Q^D z3H8%iln61~&W%>=mvznlt&Nn?T|Z}UCXChwZ)W?TXjKi0I_cMU0C24mc3RkM^fTAG zkdMR?eLfyq7(@R^d+)fAVy*~qCT;$VOh`RzSxSssXfc6H={T;W#MikR)DVQ{f2zb; z^dfHc1FR!sh2}+X#^i;MU{PjcfsuYv_*rhfa{r4iJHvg*v)KKJ&RLma8Go)Y+X#*^ z&pOAYM7}hw7-+gLx=KUTt$#AJJB?|`0N+ED;KfL~o~(Z^hHPKP_mVtSW`CcU!1~B} zk-qlT-aJGPv^3ZKJ+?5>uw|{+vQrJ5Zaze9gya#7D-JaZHpNC02#|T&RY#s+~|Q`r-V8ph#+1FZ*I5=6^}uiIr$uijJ_@e2*dK4KrE2L`he@j zbItG4wfxu&yNp4so)sR2bwHDK7F|6ld>3;og&rMT2^&KLtogrOyTP3Bw9_&9-k`w! zzXTTM9ynMDLJ_jwvIh4Yxq12+#q|Dg>&w25fhVM+$_q0x!9Wu-f0C)7w$j4}$r!t?Xb%6~CTrUYZ9r}s@;Hh~0!XZ<7Ow!I;@FErGx8*L7Y z>2$7GB+s92)QwF{9M_30aro%k=6PfvKGW(WC2Cj`O)#r3EDw&}0bs^;8@%~5mNvaZ zmmF1i_A+YZXXNy#r7p~%;Id;+v8$QDwPP)2Li^fO_kgp%%remKxu?0O@Oingo(X!= zccD?ROHJf;80SD$@MEOzT&lPmHFFLiP4M4_M1drVAiXuV=jFi^-1r=SDhP;8gYwgcOJ5c)?P22vFF1uk8hC zgG;J^Vyvp|Rg<~Vc^tfM6Q2VN95_(fSKk~Lt6k$a@V7WTy@A+W{+haX&_%OUikcM` z_Wjx$Nbtq6De<;xmbPV;#TTtU-~z{`Qgr%5N49T8@>rGC4Ap6HU26qe7xaW-x6 z+T!gEFKnPS@mRL1+-juhzz1%Ng6f*&Ctbu7*7~cT)E_f`qQcc1GW>(9&fnQf?H8}a z#39Ze*itJ`j@xApSGP|04oUkvZK+`9kGU}<@Tol>aVj)Ll1HWtD}VxE+bxK$&?M&e zIi}VnDMM-QD1-QCEGDHvA;|>tjN&?$5fJn^MT1@8kl8<+w<+jy&3DXQfLs%XlO@X6 zDw8=SSUV}Taa3RL&WG@vrh_aeUwt2$X~x}q1myvX8YE^~S8)67-<_8t z^kylgzKOtjla9-<7^_M#tCl-GLF-4QW#!gZ&8kXG{Z%%~Z54yoh<~C||F{TD>!k2%5m~O?Ko%+=ElT5^lPztPIJc9M$tl9DB7s z4O=etgoh((Rl>jZ!Ap3Bj!wT02BZTqo!eE{(md@8R3@7Xd81O~q?f+|yQUl|Qa_rR zN*rT>;^Pm;c@31(vpT;ami5>{b+IpGGS^>-<^1W-n6=lO;eo5UyvWU`+`D(Iv?D9O zPhyuiv1(rw^<{iC;H;;1+XZ6(!*?H-Oo+-$5?fd#;XHg2oMsjbs%F=B6hlJQIHy1| z=~AG!>qMgUNJ9d}1g$>dML~xicu}h0XTFsg)++^uJt>t=T&Ht%9Y{yt+} zn1h73;pe4o_O)ikJCt>EZogqiA7~63V~EU#ruYB;p6w*1&SnFysH)asmX=#y5`!H+ zO0iu7o2}(dS}pv?nZo(hgE}7}zh8^pzr*$*W{2imd{(&0i|>Eg-b;1p&;;C+%YcfR z-W55YwgxsLbB2RjMz&^Z?-mD_yWzil9tHa2cEr`AFDW|%O|bTHJ{TAkEw)yQdMD`H zsD{`@(2Rl{gI0Uv9qzaa_X=@8|JTB>iF}$*joG&Tf`9<1>H_$L+R$^4 zOzQ$Re_H8+LW^iMG)3ma8 z=#W;3*X>kQNGEX-U8^i6koSurc$juWLuZr`33{%UVcnYnu+>mPWGG+ha$)U_Wv z$b^p-21^yQ**ru|Qo^Kb6~^ zvFr8hnBIWDE0R!_bU(hiMi;Gwl}V;Me@%kd@4~)ntWc#E zDS0!q;`=|^w*=->zl_ZdZImXIj?5dS8O}7DzJZ(7#>6-_IC7kT>zY%1_SbXRlYB2y zyc2Adij3O+?(XWLhg{aSPnjGTkhRnE4bA&snON3&S?*6t8BNZ#T8+rWJMy9YO;!WS zaK|Wl_A2k|58+0RDhnh@y#_(DTzo-V?t3~{Ooh!^+BwEf@x2pJ^o2D^I;5u0KVMY! z?!1}DT;;~GNq9%T$i+=FmKlSX+<3a0~Gr3J`q${d&~7}~`n$dbML{Z)!{aCfDBR0O-!40~ob zpW>0Hv=vI)bq4L1WEC*2?A$QC6VBe)lYh{yUs>SG$S=yZSegU?JvuwhAS5}+_tqnn zCHuF6s5mZC{Q6QoAGvp)(~K=3X8yQU>ZDKIRj$Wgt_q>625(foF7aNmq2<{ktu?)s z!rRjVpQpf4Qm6`(e<&hu=3g5@r<&V(>v0Mfz!&R>I=6<0LPnY2k0v6``>XsP1=>_d z!eFfvO21woB!b`gwP`e!=jW3 z{3Mzfmv)XMAJYCsH-RpecUqSd$1sS=r&7_dA)jKyRG(S z%8A9LVe(s$(vZjC-G115)>Ey1&z4kM%*0CdM_`jy*O8O(x(%4a;Rs(~pGsEl1#&W*cllM!+!H^;w)kmED0v{MW4 z9~@uz$HPk53-c0FH^3;@z;y(ngD&4wV1J{N%^?#14^cseT>af$xqh zMd`X-lBj-c?as}aFFVR5aqoTfS)c-677$;p@bK+=|IxvfD&4NyE7-_W zBCL9J%+|KPbm)_83=R~Coq9&H^pKj@o#9lwE66cU_mnN!I(j&!`bx2-rrJ0w42h+Y zTN2$eo!fPx_b`)N-U+)rOA&Ojuy%?MK948_{l&2PS3^q_o&I?Wfp|nXtXHUG6OR%` z_9)z51&?{hcy+{T>%T$zntjc^T#FW(`Sjl4dpdI0UHP587A2=|ID_87Z90btF8i)r z;Ju)`wtHL~Gt3z+`HI<|dn6U++AI!jRn}T$j^ou$iOrFG_bDW%2$8vD-K{LCZA`Ph z@bcTJ-r|fo*Xi5T&{H=Lq{6c1xaI%QZC^wCx-`o@3TA$oL{|{ljOVC^le(QR-6I;|+csU_M*SJkoOu+g zK@JpiCK;T+_PxADv5Zh~zA|CpxDmT1)9_d(7|m-_VNdy5!!$jEbv;lSwa3gsA^^_e zib)>FyS{KI7rPATOu&wpj zDC+pGv0jJd<$sXm8%Lay3z>Hb+@5biSKJA;jCaI`y2fqy%}#F}N^Bj5@@_7*^4~?| z-~EGT5&n(G|#$K=lVPs=WhQy+fF()5e%ysllq+t$kxo1Y-W z0aa3eLPo2Pj?rk3lW^X@)>_Q z0hYqBV&|J%2;3GuZD&mObR{R$axBfjGc3s^q{p_tpQGI9c2X@H=VjI0#*BzgAckHY zBRSue0+Cj)eL$cZ>;rGrT<~i$s zcd5-&j?-7ty->*W1__zP``6j(RE;<|n84CXr=a8DCtr$~0qoMk02f9Wh6t#xy4lG;k27_%$|)u&0bL%qGdxCbgd9An!WhDxQz3Gl&6r8(}y zRrtN$p*D^Yq}ILKK z)m`8BN|=#2hzfOA?>F$-+Y2FsHASP| z8&zeC4)C8luHoJO$KL1?zUR_TKWaTkx&{V9^4~Twkv-*BYL2&bU*i&2Oz~d^-bbo& z?V;)iJ9~DWT_{*9NLTaCr-)Db8zG;o?2$gU!QedgJ%Sz^tY=Ne9an|C(NU+K#vb<0 z33OF5Y4Gcu4?OE}YNrUw!CEt;Ymu)Y$HVjP*%)zbpz4$z2z-^lDd( zf52&IDa_F=hZEOM#aWxAS1rBJmMKPA2UWh%mPJABB-{c;J89b2(<=wCTY9k z62p2Ez9L(w&io-D>ua{u(fH=(N*V%UTZ;S6ww_5A1KZZgl6qB zkaIZ-HXdTwQhi4CE`n^E@?_NVq$Pv2aN6)e?q(vY(!X-T1eViGe~=XE$w|H~QE3RS z5UP_-?v%UQ%IJ|Xt^)XWIaZTwTE=%TW2g)Cpr&c+ivcNhW_gxT%O@~EOlEvIlj_5W z3ZXYfqK%J%lIDdLstKJU>WtQf7FP6XAs_j+W%O4@UdXmCQP!UEblRZAC107ocE)9N zH<|hTY_w9rN(BKt=rNMZ0lSUE??S-+nNs;%#Tn}Pi6Z`K@`4|CFI4~bp9EMr*z`1} zSp_e6sX@U3KGKi`wUL*9PiT*y^b^+MNIAzPROk$p`Z_Wph3DCP_&+677!3@0CM@3wRnIyuxZ+(aRPxa|%F7U0GKlzL<3A&gwA>03E|Lw`cav=U%U5bG z@PPccoW2-kOGJ16TjJ8f1dw0Yff_u_Dl8k6t+QyXXhI+gEpUki{P=wE%_Q7aZZWIn zt7w@rCW~e17?OV|i^IQbb~a6!Art*2bA_dqq1&W>+)H#QwGU|79gty9JlFd?-=~cUUfB!4-8Mg8VB4a^AsFAy}^T*Yates?nVqcY6{2^2cD! zFOD^XpwEQ}rP!%&mVm?!l@i(p2uhK6{n({XVatiDdR?qFz*CkQZmj37S3P3H$~?j| z6+83tmZW>MPY!FFnC)U!YbO(FO0qU^Ih?8nC+LMr7unhesbxr3nAz$HrlRka#^JO? zzdF}scu2iJn#qPiDJ}P(hSCc@r-j%C}rVeW+`r7jOO}5N*QmS zucziYLhtJ2AsWs@Q7=kInh%@U%>r@-l@`krYH=Y~2TE;+aVasn%d0!ivyEbq7DlZ7 z^^v?KU#YQ(w{ekw-n^%WF@y}9e^vz_q|Fq>a_atd_|z*qE{1;xR(iDFaO)^2>-3cm zmL^KjWRXjIviWXSk)26%kz%wx>BhdlksrQyCniF(iT&*Ja#`3nFREaAm<#2CZ~V~= z!$Ve?r)`9Ayy#PZ|8GqLiLD=`vd{%!k@Xc}(=R_qvTD8h|C*yPQh><(6 zY2O>_z5=!Bu}ANpQq7Is{S2R^3a$P9uUjHGy^a}!pG7X3W9q08(lT-gRJPSSwJehe z>RKEC)oAw*&8Z3?DifEFsOuJE!@b_Rx`F;IXyc-<{0=c7)uw{U=nA?aW9s<=KH_5z zm(lN0+TEW1L(|2EKiadkMHj?#_0?|Y@Tc}Vq?DJT%ne#O#S-a0a%v+`IJmsL_VMz! z6TOr`Q<_(sKOdILaVsf!B{2M3`}W2HKf$Ba$3h(_FPN0#(*yFo7-B zv0XOvuu3`zosnW^ZRhq{K9PXg)Lz(k%fihYQ*R)k_|kS)->J|Iutt6;{=Iz#qRvk(QZvaepwUdwodXv8p!o z2{A_kr1c|ltjopc+@EQvGi9rIS0%qgd~J!zlS5d+_C4;z3T@{I39Eb}p6g-A zr5=e~4mm~l`V=;wnb8V0rVemdr7)vCO9gh;HD=Ro&=0lvb>muVEar5=u$pIVHD>4{ zKb0nk^X@*|HP`BI0mWYp@G&OYe(vKHD%@)<<{Rj+`#N`LiS^0N*5gaZojytftWQRN zyw8+$eWyh0+2Oc_xg#pt<@#X9(<5A?s)6lUQeRzGWOi(5LN%vuCsDh4>0NqAr*ti% z2Gj(qf9KWs+^m&Y?jP~0hrdV}JoLh^c$%KAAqR*Vb*XW_e_mGDz42-4)y_<0iUXCP7W%| z0q+)vW6iBLBAJN}oL?PB_08~^R9}z^lE(L77DM@wJuz|$Yt6vCd1*MMW71NSsO~nI z`^T7X_5FIdoz9{>cdCozRvNI|dw~l<7MpnBJUIQ}{$T5TKab=!k=4hiVi4)OUuAEw zKx+FSBGHM$LvdFt*u8GhAXL5{GntHRd zZbJyqmy_O^ToDcCjK%We<3;WlS<-XVRW;Iu7L4_p=%pl8s+$Zl_$CTtv0)jue@jc$|cf&8zInGiu++s`4q{l+I`p!H#)v@T{WMg)y z&i?n~wdy><>Vx0)Lz@TSk_$-w)U;%InFK0|Q8@Pb=j6P^nG)5jHH+a|qfjR&Bi^;4 zr}e${1g426{c#E%%J2ujuRQ2~RV3cHr!B|t{7NS6F12XkVbt+^-rwIrHPd%+!nxB) zHMTLai_$Odgv{0HONK76d{ebue~ZRYEMcyDydO5LmaJ^dGFvANBLvvuaS9TJTXc_c zV%@2F9oAHEs37OPQPT8}xy|*M0AFRUuc>6Pgl`yY;SF4L{M5^vPWR|2od(Q={2z*` zG08uvFe!)qu15UKVelK=f2w=a%>Nss%_^i;`t;IH8!dd{r=umihFH{ps(;^nQ2tNO zmm+v$JCvBGc~QEgd9U7A-0(khN<6Tn`D5gxh;yU^S)`A#YrN^_aikZXkv6=*u#c^H2A)qQ=%+IaW& zit1L4tN8VDx}2*)?+Po(TnfEL=aFLZ`z$7>m0^do{_xhdiS6mKPqp{8Cu!v z!|MlUr`nJaF1JsQOfxx5e{1_hsC;C=j#$LNBiPGwgC5XmjWSCeHCx@K;YjvKO@Lkg_bRaz_`EEaeCW> zuH7*5oEmDEmYl74`{an{&oY=G9lt zDsTE!Z70SmSnI+ETmkKN>*>``;+KEBBH~MZBM}8@C|aUd%grBtI5d&n>yzud&g0R? zmbgr*fp<@{t{!&5bIB&`I^IyLj=7f!pq^0);)KWGayJ=tS!rK4w8LdtsnJU?eeo`< zaeE&b`yuKLd|}bkg6|riNB0A?bn{)Zl13+;Z-xWh?^8oL*ze6GbQaT^MZ& zXHvlW_qrIIZ#?wMDdun!zud}wW+PeO{o~=a4kX=v%)SLQFI+?@Kj}lkS@2v%QjiG# zmv9f~#|9@gSLykAoUL0fis3xcIyd5mWXmPIM=QZ-Aj$W=(u*Ld-YVEki{fXY^?bsx zOY#;@h^yty-<=}@b5L$mkmyM$4>ILW*L)rE~S%5+Q)s0pZPtTb8 ziwVQl=l%si)rf3YMx9wvUi5y668YG3MJ?4G`raLX%*V^h-QHF6fZOF+hUTB$Cdn>q z?)V8x4_1u+72r4ai=p(`^`fXpzO$LmpJG8u29wNnfef>iQ(Sew2H+0xaU86!?8Y0D<%O|>?Zsz{JYThoDW4*?IeL)-9?Lc3580&~m zlN6lbTKM`^E>WObT0eprV}>l0vaF~EJHAAA%>B%;aeC!_)X73nTSN*6qIw>`T30=P z(if&i4~=6Kh>KvGmzM9D0;IOG>u%TvO&kVqtEZ+(+}F>tVPvEIiC}lt1`99>M{PvE|yetA?xBzD3@(& zlZfv9NF@UyA5TN)vk(Qr>Bc%_?}poso*`%EW)FC^{IrNb+s4HTwe z_SorlL8^4gmsEq!C+KS>{(|K}oToBa6o9>VE=x zt~N${$V+25zGLxNFUrTJqv1i34-e&mPyHd#(31yU5ArPWUWgKJo@8%BdP|Bd`Fqr7 zh~q27z+3nQL_%$~?0HY6l5HVT!dTzZ1A0b;IAMgJZ(5v1sPs71#^f+HLcM@XQi
    -x7{!;bgb8l{~wysi5c881A5(E$~{rP8) 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

    <%xUo5);1I^$q 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%0D>_@c z%ROcZ7^z&Y`}@dLK;5UZfDrP3%&|B5R_uPY4>5$kyA= zo0;>rL<8zM^Da@iqO%A{asKvbBCC=nd<9GL* z;Cb7`Cud1BFACdScrImPV^>qnr5|-Z$l}DSH#?4o zs(!R`;s>61Wp;Dm1_6dPvM;j>5-%w!#$_e=XKL3)aJ3xTZ3gzZwq~Q8#@PUfwq>({ zr;>GCJG;`C-G$~%ukcRI+_LIJ^G9ll zZv*#V>9sbQ`vdb{RQt_j0?NL+Hq3|k?VZtGK2y{->Ml49A^LE23%*N;B^`g=t}ByM zD6#io#j^poJ;XG_kD{Teb+RT!>eE7NGLIVa#2Q|u<*VkWgKrJQNDyvPDsnBUsvBs> z2{!1Bm^j{}6<{G0))v>ME=Z%`)#{2=Tt{s>v&MqL7w&DUg>-VMOT^W$v-#km4`$XX zs+X4ne6Pp5h%OB>j;T|t7Gn)tK6w6N1Q})RZKmpO!43Ir>=eNDvF6>{}Is|q+p#O>(0-W5}FnzZ_0pNyBn|Dk}{8izc z&I^~}<eqfjuY1 z9&?_&-4^RL$z%-@A&b+$2u(a20D*t(n=_@ehNWkz?Nk}i2}G?r6+V|Rsg%Cyc_+{j9|4YQ+&*GIV4oYpc4tNA>Au2 zBU$V+GNXVC^>?V^*2&8@aCXGzLM-`rxXSALZVm@^v$1@BpSoV@V~`hBPg+HHmWQ3M z9f;_iW&ApOk#7?%F8*}#W3%pUz~`P7Nz`wxuu~I;l2B5){joN4NJ9^ROV8$wZ}aB8UkMj~+GF~a zJ_h<#J7aWVu)jiX|NA`3zuv=14#P9qDaa23+i{0YxO?u5vaUC-fa%yadZn`~H=}}Y zD2{uR6Wol|6}*CqcuVDn87p6c9$wrq^dKTI6rIqbz{z`_Yp#kh0oVb8yJXjAdsnln z(ww$!cpIw zi%)j_WL@SgJ>T5;#<9@W5`Q<%ziUl;IZ>&|cTH(y%udIfv1#l)d6#7_NO2d2_*G;f=Y7?+T_9G ztKa`In*V)0=aFCF_~#NsV7KCeN%4K1d4D?f*|$T}DVwqiRJ?;6zNjP9Qx6NBVmHhNxGiYi zj%-oxx&eE}>{D)^x41w$A9$kUPKN1i!*&xbqYt<|n1poi@W=ls;bz$t78Aium;%2zC7zWn{?8}sN=pPOIV4E`LzTCP(8~Xdmqh<~A z!gAgB*=;|r*tpSIl=%%H&TG%ZkGFn!{}0c8@a!jV|KRPPJpX~`Kk@uWp8w2?A9(Q- bFMj03&piHs$Di={BOZVD|C2xbd-}ftrvUP@ From 4c65a8091a5c491375fd3770c2bfa8b8611451f4 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Tue, 29 Jul 2025 15:37:16 +0800 Subject: [PATCH 62/72] chore: base form (#23101) --- .../base/form/components/base/base-field.tsx | 37 +++++++++++++++++-- .../base/form/components/base/base-form.tsx | 13 ++++++- .../base/form/form-scenarios/auth/index.tsx | 2 + web/app/components/base/form/types.ts | 1 + web/app/components/base/radio/ui.tsx | 10 ++++- 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/web/app/components/base/form/components/base/base-field.tsx b/web/app/components/base/form/components/base/base-field.tsx index 0195f38795..8120fad6b0 100644 --- a/web/app/components/base/form/components/base/base-field.tsx +++ b/web/app/components/base/form/components/base/base-field.tsx @@ -11,6 +11,7 @@ import PureSelect from '@/app/components/base/select/pure' import type { FormSchema } from '@/app/components/base/form/types' import { FormTypeEnum } from '@/app/components/base/form/types' import { useRenderI18nObject } from '@/hooks/use-i18n' +import RadioE from '@/app/components/base/radio/ui' export type BaseFieldProps = { fieldClassName?: string @@ -57,8 +58,27 @@ const BaseField = ({ if (typeof placeholder === 'object' && placeholder !== null) return renderI18nObject(placeholder as Record) }, [placeholder, renderI18nObject]) + const optionValues = useStore(field.form.store, (s) => { + const result: Record = {} + options?.forEach((option) => { + if (option.show_on?.length) { + option.show_on.forEach((condition) => { + result[condition.variable] = s.values[condition.variable] + }) + } + }) + return result + }) const memorizedOptions = useMemo(() => { - return options?.map((option) => { + return options?.filter((option) => { + if (!option.show_on?.length) + return true + + return option.show_on.every((condition) => { + const conditionValue = optionValues[condition.variable] + return conditionValue === condition.value + }) + }).map((option) => { return { label: typeof option.label === 'string' ? option.label : renderI18nObject(option.label), value: option.value, @@ -151,17 +171,28 @@ const BaseField = ({ } { formSchema.type === FormTypeEnum.radio && ( -

    +
    { memorizedOptions.map(option => (
    field.handleChange(option.value)} > + { + formSchema.showRadioUI && ( + + ) + } {option.label}
    )) diff --git a/web/app/components/base/form/components/base/base-form.tsx b/web/app/components/base/form/components/base/base-form.tsx index 640d474b19..c056829db4 100644 --- a/web/app/components/base/form/components/base/base-form.tsx +++ b/web/app/components/base/form/components/base/base-form.tsx @@ -2,6 +2,7 @@ import { memo, useCallback, useImperativeHandle, + useMemo, } from 'react' import type { AnyFieldApi, @@ -45,8 +46,18 @@ const BaseForm = ({ disabled, formFromProps, }: BaseFormProps) => { + const initialDefaultValues = useMemo(() => { + if (defaultValues) + return defaultValues + + return formSchemas.reduce((acc, schema) => { + if (schema.default) + acc[schema.name] = schema.default + return acc + }, {} as Record) + }, [defaultValues]) const formFromHook = useForm({ - defaultValues, + defaultValues: initialDefaultValues, }) const form: any = formFromProps || formFromHook const { getFormValues } = useGetFormValues(form, formSchemas) diff --git a/web/app/components/base/form/form-scenarios/auth/index.tsx b/web/app/components/base/form/form-scenarios/auth/index.tsx index 3927f90959..f499e43f16 100644 --- a/web/app/components/base/form/form-scenarios/auth/index.tsx +++ b/web/app/components/base/form/form-scenarios/auth/index.tsx @@ -7,6 +7,7 @@ const AuthForm = ({ defaultValues, ref, formFromProps, + ...rest }: BaseFormProps) => { return ( ) } diff --git a/web/app/components/base/form/types.ts b/web/app/components/base/form/types.ts index c165d2939b..9b3beeee7f 100644 --- a/web/app/components/base/form/types.ts +++ b/web/app/components/base/form/types.ts @@ -58,6 +58,7 @@ export type FormSchema = { options?: FormOption[] labelClassName?: string validators?: AnyValidators + showRadioUI?: boolean } export type FormValues = Record diff --git a/web/app/components/base/radio/ui.tsx b/web/app/components/base/radio/ui.tsx index 178262d0b9..ea132c7d4b 100644 --- a/web/app/components/base/radio/ui.tsx +++ b/web/app/components/base/radio/ui.tsx @@ -5,13 +5,21 @@ import cn from '@/utils/classnames' type Props = { isChecked: boolean + className?: string } const RadioUI: FC = ({ isChecked, + className, }) => { return ( -
    +
    ) } From 51a6b9dc5764b93b6849731bd0897797a148c91b Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:35:33 +0800 Subject: [PATCH 63/72] hotfix: clear_all_annotations should also execute delete_annotation_index_task just like delete_app_annotation (#23093) Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- api/services/annotation_service.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/api/services/annotation_service.py b/api/services/annotation_service.py index 3239af998e..80dd63bf89 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -452,6 +452,11 @@ class AppAnnotationService: if not app: raise NotFound("App not found") + # if annotation reply is enabled, delete annotation index + app_annotation_setting = ( + db.session.query(AppAnnotationSetting).where(AppAnnotationSetting.app_id == app_id).first() + ) + annotations_query = db.session.query(MessageAnnotation).filter(MessageAnnotation.app_id == app_id) for annotation in annotations_query.yield_per(100): annotation_hit_histories_query = db.session.query(AppAnnotationHitHistory).filter( @@ -460,6 +465,12 @@ class AppAnnotationService: for annotation_hit_history in annotation_hit_histories_query.yield_per(100): db.session.delete(annotation_hit_history) + # if annotation reply is enabled, delete annotation index + if app_annotation_setting: + delete_annotation_index_task.delay( + annotation.id, app_id, current_user.current_tenant_id, app_annotation_setting.collection_binding_id + ) + db.session.delete(annotation) db.session.commit() From ae28ca0b8dad13ea30158f15be181f91cb890a48 Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:36:21 +0800 Subject: [PATCH 64/72] minor fix: wrong assignment (#23103) --- api/tests/unit_tests/core/ops/test_config_entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/unit_tests/core/ops/test_config_entity.py b/api/tests/unit_tests/core/ops/test_config_entity.py index 4bcc6cb605..209f8b7c57 100644 --- a/api/tests/unit_tests/core/ops/test_config_entity.py +++ b/api/tests/unit_tests/core/ops/test_config_entity.py @@ -118,7 +118,7 @@ class TestLangfuseConfig: assert config.host == "https://custom.langfuse.com" def test_valid_config_with_path(self): - host = host = "https://custom.langfuse.com/api/v1" + host = "https://custom.langfuse.com/api/v1" config = LangfuseConfig(public_key="public_key", secret_key="secret_key", host=host) assert config.public_key == "public_key" assert config.secret_key == "secret_key" From 1bf0df03b5eeda913293e8294c9d14ed1652fd6f Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:36:29 +0800 Subject: [PATCH 65/72] minor fix: fix some translation (#23105) --- web/i18n/hi-IN/app.ts | 2 +- web/i18n/tr-TR/app-debug.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/i18n/hi-IN/app.ts b/web/i18n/hi-IN/app.ts index 4c6bc7a8f8..9b13fdc392 100644 --- a/web/i18n/hi-IN/app.ts +++ b/web/i18n/hi-IN/app.ts @@ -261,7 +261,7 @@ const translation = { noAccessPermission: 'वेब एप्लिकेशन तक पहुँचने की अनुमति नहीं है', maxActiveRequests: 'अधिकतम समवर्ती अनुरोध', maxActiveRequestsPlaceholder: 'असीमित के लिए 0 दर्ज करें', - maxActiveRequestsTip: 'प्रति ऐप अधिकतम सक्रिय अनुरोधों की अधिकतम संख्या (असीमित के लिए 0)', + maxActiveRequestsTip: 'प्रति ऐप सक्रिय अनुरोधों की अधिकतम संख्या (असीमित के लिए 0)', } export default translation diff --git a/web/i18n/tr-TR/app-debug.ts b/web/i18n/tr-TR/app-debug.ts index 631974edb5..c9a5f7b585 100644 --- a/web/i18n/tr-TR/app-debug.ts +++ b/web/i18n/tr-TR/app-debug.ts @@ -350,6 +350,7 @@ const translation = { content: 'İçerik', required: 'Gerekli', errorMsg: { + varNameRequired: 'Değişken adı gereklidir', labelNameRequired: 'Etiket adı gereklidir', varNameCanBeRepeat: 'Değişken adı tekrar edemez', atLeastOneOption: 'En az bir seçenek gereklidir', From f4d4a32af2efe4ea9416046f3c90df36bb35de3a Mon Sep 17 00:00:00 2001 From: lyzno1 <92089059+lyzno1@users.noreply.github.com> Date: Tue, 29 Jul 2025 18:24:57 +0800 Subject: [PATCH 66/72] Feat/enhance i18n scripts (#23114) --- web/__tests__/check-i18n.test.ts | 569 ++++++++++++++++++++++++ web/i18n-config/check-i18n.js | 223 +++++++++- web/i18n/zh-Hans/app-annotation.ts | 3 - web/i18n/zh-Hans/app-debug.ts | 1 - web/i18n/zh-Hans/app.ts | 2 - web/i18n/zh-Hans/login.ts | 1 - web/i18n/zh-Hans/time.ts | 1 - web/i18n/zh-Hans/workflow.ts | 1 - web/i18n/zh-Hant/app-annotation.ts | 3 - web/i18n/zh-Hant/app.ts | 14 - web/i18n/zh-Hant/billing.ts | 26 -- web/i18n/zh-Hant/common.ts | 1 - web/i18n/zh-Hant/dataset-creation.ts | 2 - web/i18n/zh-Hant/dataset-documents.ts | 1 - web/i18n/zh-Hant/dataset-hit-testing.ts | 1 - web/i18n/zh-Hant/login.ts | 1 - web/i18n/zh-Hant/tools.ts | 1 - web/i18n/zh-Hant/workflow.ts | 3 - 18 files changed, 783 insertions(+), 71 deletions(-) create mode 100644 web/__tests__/check-i18n.test.ts diff --git a/web/__tests__/check-i18n.test.ts b/web/__tests__/check-i18n.test.ts new file mode 100644 index 0000000000..173aa96118 --- /dev/null +++ b/web/__tests__/check-i18n.test.ts @@ -0,0 +1,569 @@ +import fs from 'node:fs' +import path from 'node:path' + +// Mock functions to simulate the check-i18n functionality +const vm = require('node:vm') +const transpile = require('typescript').transpile + +describe('check-i18n script functionality', () => { + const testDir = path.join(__dirname, '../i18n-test') + const testEnDir = path.join(testDir, 'en-US') + const testZhDir = path.join(testDir, 'zh-Hans') + + // Helper function that replicates the getKeysFromLanguage logic + async function getKeysFromLanguage(language: string, testPath = testDir): Promise { + return new Promise((resolve, reject) => { + const folderPath = path.resolve(testPath, language) + const allKeys: string[] = [] + + if (!fs.existsSync(folderPath)) { + resolve([]) + return + } + + fs.readdir(folderPath, (err, files) => { + if (err) { + reject(err) + return + } + + const translationFiles = files.filter(file => /\.(ts|js)$/.test(file)) + + translationFiles.forEach((file) => { + const filePath = path.join(folderPath, file) + const fileName = file.replace(/\.[^/.]+$/, '') + const camelCaseFileName = fileName.replace(/[-_](.)/g, (_, c) => + c.toUpperCase(), + ) + + try { + const content = fs.readFileSync(filePath, 'utf8') + const moduleExports = {} + const context = { + exports: moduleExports, + module: { exports: moduleExports }, + require, + console, + __filename: filePath, + __dirname: folderPath, + } + + vm.runInNewContext(transpile(content), context) + const translationObj = moduleExports.default || moduleExports + + if(!translationObj || typeof translationObj !== 'object') + throw new Error(`Error parsing file: ${filePath}`) + + const nestedKeys: string[] = [] + const iterateKeys = (obj: any, prefix = '') => { + for (const key in obj) { + const nestedKey = prefix ? `${prefix}.${key}` : key + if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { + // This is an object (but not array), recurse into it but don't add it as a key + iterateKeys(obj[key], nestedKey) + } + else { + // This is a leaf node (string, number, boolean, array, etc.), add it as a key + nestedKeys.push(nestedKey) + } + } + } + iterateKeys(translationObj) + + const fileKeys = nestedKeys.map(key => `${camelCaseFileName}.${key}`) + allKeys.push(...fileKeys) + } + catch (error) { + reject(error) + } + }) + resolve(allKeys) + }) + }) + } + + beforeEach(() => { + // Clean up and create test directories + if (fs.existsSync(testDir)) + fs.rmSync(testDir, { recursive: true }) + + fs.mkdirSync(testDir, { recursive: true }) + fs.mkdirSync(testEnDir, { recursive: true }) + fs.mkdirSync(testZhDir, { recursive: true }) + }) + + afterEach(() => { + // Clean up test files + if (fs.existsSync(testDir)) + fs.rmSync(testDir, { recursive: true }) + }) + + describe('Key extraction logic', () => { + it('should extract only leaf node keys, not intermediate objects', async () => { + const testContent = `const translation = { + simple: 'Simple Value', + nested: { + level1: 'Level 1 Value', + deep: { + level2: 'Level 2 Value' + } + }, + array: ['not extracted'], + number: 42, + boolean: true +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'test.ts'), testContent) + + const keys = await getKeysFromLanguage('en-US') + + expect(keys).toEqual([ + 'test.simple', + 'test.nested.level1', + 'test.nested.deep.level2', + 'test.array', + 'test.number', + 'test.boolean', + ]) + + // Should not include intermediate object keys + expect(keys).not.toContain('test.nested') + expect(keys).not.toContain('test.nested.deep') + }) + + it('should handle camelCase file name conversion correctly', async () => { + const testContent = `const translation = { + key: 'value' +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'app-debug.ts'), testContent) + fs.writeFileSync(path.join(testEnDir, 'user_profile.ts'), testContent) + + const keys = await getKeysFromLanguage('en-US') + + expect(keys).toContain('appDebug.key') + expect(keys).toContain('userProfile.key') + }) + }) + + describe('Missing keys detection', () => { + it('should detect missing keys in target language', async () => { + const enContent = `const translation = { + common: { + save: 'Save', + cancel: 'Cancel', + delete: 'Delete' + }, + app: { + title: 'My App', + version: '1.0' + } +} + +export default translation +` + + const zhContent = `const translation = { + common: { + save: '保存', + cancel: '取消' + // missing 'delete' + }, + app: { + title: '我的应用' + // missing 'version' + } +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'test.ts'), enContent) + fs.writeFileSync(path.join(testZhDir, 'test.ts'), zhContent) + + const enKeys = await getKeysFromLanguage('en-US') + const zhKeys = await getKeysFromLanguage('zh-Hans') + + const missingKeys = enKeys.filter(key => !zhKeys.includes(key)) + + expect(missingKeys).toContain('test.common.delete') + expect(missingKeys).toContain('test.app.version') + expect(missingKeys).toHaveLength(2) + }) + }) + + describe('Extra keys detection', () => { + it('should detect extra keys in target language', async () => { + const enContent = `const translation = { + common: { + save: 'Save', + cancel: 'Cancel' + } +} + +export default translation +` + + const zhContent = `const translation = { + common: { + save: '保存', + cancel: '取消', + delete: '删除', // extra key + extra: '额外的' // another extra key + }, + newSection: { + someKey: '某个值' // extra section + } +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'test.ts'), enContent) + fs.writeFileSync(path.join(testZhDir, 'test.ts'), zhContent) + + const enKeys = await getKeysFromLanguage('en-US') + const zhKeys = await getKeysFromLanguage('zh-Hans') + + const extraKeys = zhKeys.filter(key => !enKeys.includes(key)) + + expect(extraKeys).toContain('test.common.delete') + expect(extraKeys).toContain('test.common.extra') + expect(extraKeys).toContain('test.newSection.someKey') + expect(extraKeys).toHaveLength(3) + }) + }) + + describe('File filtering logic', () => { + it('should filter keys by specific file correctly', async () => { + // Create multiple files + const file1Content = `const translation = { + button: 'Button', + text: 'Text' +} + +export default translation +` + + const file2Content = `const translation = { + title: 'Title', + description: 'Description' +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'components.ts'), file1Content) + fs.writeFileSync(path.join(testEnDir, 'pages.ts'), file2Content) + fs.writeFileSync(path.join(testZhDir, 'components.ts'), file1Content) + fs.writeFileSync(path.join(testZhDir, 'pages.ts'), file2Content) + + const allEnKeys = await getKeysFromLanguage('en-US') + const allZhKeys = await getKeysFromLanguage('zh-Hans') + + // Test file filtering logic + const targetFile = 'components' + const filteredEnKeys = allEnKeys.filter(key => + key.startsWith(targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())), + ) + const filteredZhKeys = allZhKeys.filter(key => + key.startsWith(targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())), + ) + + expect(allEnKeys).toHaveLength(4) // 2 keys from each file + expect(filteredEnKeys).toHaveLength(2) // only components keys + expect(filteredEnKeys).toContain('components.button') + expect(filteredEnKeys).toContain('components.text') + expect(filteredEnKeys).not.toContain('pages.title') + expect(filteredEnKeys).not.toContain('pages.description') + }) + }) + + describe('Complex nested structure handling', () => { + it('should handle deeply nested objects correctly', async () => { + const complexContent = `const translation = { + level1: { + level2: { + level3: { + level4: { + deepValue: 'Deep Value' + }, + anotherValue: 'Another Value' + }, + simpleValue: 'Simple Value' + }, + directValue: 'Direct Value' + }, + rootValue: 'Root Value' +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'complex.ts'), complexContent) + + const keys = await getKeysFromLanguage('en-US') + + expect(keys).toContain('complex.level1.level2.level3.level4.deepValue') + expect(keys).toContain('complex.level1.level2.level3.anotherValue') + expect(keys).toContain('complex.level1.level2.simpleValue') + expect(keys).toContain('complex.level1.directValue') + expect(keys).toContain('complex.rootValue') + + // Should not include intermediate objects + expect(keys).not.toContain('complex.level1') + expect(keys).not.toContain('complex.level1.level2') + expect(keys).not.toContain('complex.level1.level2.level3') + expect(keys).not.toContain('complex.level1.level2.level3.level4') + }) + }) + + describe('Edge cases', () => { + it('should handle empty objects', async () => { + const emptyContent = `const translation = { + empty: {}, + withValue: 'value' +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'empty.ts'), emptyContent) + + const keys = await getKeysFromLanguage('en-US') + + expect(keys).toContain('empty.withValue') + expect(keys).not.toContain('empty.empty') + }) + + it('should handle special characters in keys', async () => { + const specialContent = `const translation = { + 'key-with-dash': 'value1', + 'key_with_underscore': 'value2', + 'key.with.dots': 'value3', + normalKey: 'value4' +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'special.ts'), specialContent) + + const keys = await getKeysFromLanguage('en-US') + + expect(keys).toContain('special.key-with-dash') + expect(keys).toContain('special.key_with_underscore') + expect(keys).toContain('special.key.with.dots') + expect(keys).toContain('special.normalKey') + }) + + it('should handle different value types', async () => { + const typesContent = `const translation = { + stringValue: 'string', + numberValue: 42, + booleanValue: true, + nullValue: null, + undefinedValue: undefined, + arrayValue: ['array', 'values'], + objectValue: { + nested: 'nested value' + } +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'types.ts'), typesContent) + + const keys = await getKeysFromLanguage('en-US') + + expect(keys).toContain('types.stringValue') + expect(keys).toContain('types.numberValue') + expect(keys).toContain('types.booleanValue') + expect(keys).toContain('types.nullValue') + expect(keys).toContain('types.undefinedValue') + expect(keys).toContain('types.arrayValue') + expect(keys).toContain('types.objectValue.nested') + expect(keys).not.toContain('types.objectValue') + }) + }) + + describe('Real-world scenario tests', () => { + it('should handle app-debug structure like real files', async () => { + const appDebugEn = `const translation = { + pageTitle: { + line1: 'Prompt', + line2: 'Engineering' + }, + operation: { + applyConfig: 'Publish', + resetConfig: 'Reset', + debugConfig: 'Debug' + }, + generate: { + instruction: 'Instructions', + generate: 'Generate', + resTitle: 'Generated Prompt', + noDataLine1: 'Describe your use case on the left,', + noDataLine2: 'the orchestration preview will show here.' + } +} + +export default translation +` + + const appDebugZh = `const translation = { + pageTitle: { + line1: '提示词', + line2: '编排' + }, + operation: { + applyConfig: '发布', + resetConfig: '重置', + debugConfig: '调试' + }, + generate: { + instruction: '指令', + generate: '生成', + resTitle: '生成的提示词', + noData: '在左侧描述您的用例,编排预览将在此处显示。' // This is extra + } +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'app-debug.ts'), appDebugEn) + fs.writeFileSync(path.join(testZhDir, 'app-debug.ts'), appDebugZh) + + const enKeys = await getKeysFromLanguage('en-US') + const zhKeys = await getKeysFromLanguage('zh-Hans') + + const missingKeys = enKeys.filter(key => !zhKeys.includes(key)) + const extraKeys = zhKeys.filter(key => !enKeys.includes(key)) + + expect(missingKeys).toContain('appDebug.generate.noDataLine1') + expect(missingKeys).toContain('appDebug.generate.noDataLine2') + expect(extraKeys).toContain('appDebug.generate.noData') + + expect(missingKeys).toHaveLength(2) + expect(extraKeys).toHaveLength(1) + }) + + it('should handle time structure with operation nested keys', async () => { + const timeEn = `const translation = { + months: { + January: 'January', + February: 'February' + }, + operation: { + now: 'Now', + ok: 'OK', + cancel: 'Cancel', + pickDate: 'Pick Date' + }, + title: { + pickTime: 'Pick Time' + }, + defaultPlaceholder: 'Pick a time...' +} + +export default translation +` + + const timeZh = `const translation = { + months: { + January: '一月', + February: '二月' + }, + operation: { + now: '此刻', + ok: '确定', + cancel: '取消', + pickDate: '选择日期' + }, + title: { + pickTime: '选择时间' + }, + pickDate: '选择日期', // This is extra - duplicates operation.pickDate + defaultPlaceholder: '请选择时间...' +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'time.ts'), timeEn) + fs.writeFileSync(path.join(testZhDir, 'time.ts'), timeZh) + + const enKeys = await getKeysFromLanguage('en-US') + const zhKeys = await getKeysFromLanguage('zh-Hans') + + const missingKeys = enKeys.filter(key => !zhKeys.includes(key)) + const extraKeys = zhKeys.filter(key => !enKeys.includes(key)) + + expect(missingKeys).toHaveLength(0) // No missing keys + expect(extraKeys).toContain('time.pickDate') // Extra root-level pickDate + expect(extraKeys).toHaveLength(1) + + // Should have both keys available + expect(zhKeys).toContain('time.operation.pickDate') // Correct nested key + expect(zhKeys).toContain('time.pickDate') // Extra duplicate key + }) + }) + + describe('Statistics calculation', () => { + it('should calculate correct difference statistics', async () => { + const enContent = `const translation = { + key1: 'value1', + key2: 'value2', + key3: 'value3' +} + +export default translation +` + + const zhContentMissing = `const translation = { + key1: 'value1', + key2: 'value2' + // missing key3 +} + +export default translation +` + + const zhContentExtra = `const translation = { + key1: 'value1', + key2: 'value2', + key3: 'value3', + key4: 'extra', + key5: 'extra2' +} + +export default translation +` + + fs.writeFileSync(path.join(testEnDir, 'stats.ts'), enContent) + + // Test missing keys scenario + fs.writeFileSync(path.join(testZhDir, 'stats.ts'), zhContentMissing) + + const enKeys = await getKeysFromLanguage('en-US') + const zhKeysMissing = await getKeysFromLanguage('zh-Hans') + + expect(enKeys.length - zhKeysMissing.length).toBe(1) // +1 means 1 missing key + + // Test extra keys scenario + fs.writeFileSync(path.join(testZhDir, 'stats.ts'), zhContentExtra) + + const zhKeysExtra = await getKeysFromLanguage('zh-Hans') + + expect(enKeys.length - zhKeysExtra.length).toBe(-2) // -2 means 2 extra keys + }) + }) +}) diff --git a/web/i18n-config/check-i18n.js b/web/i18n-config/check-i18n.js index 7e3b725c9e..edc2566a3c 100644 --- a/web/i18n-config/check-i18n.js +++ b/web/i18n-config/check-i18n.js @@ -58,9 +58,14 @@ async function getKeysFromLanguage(language) { const iterateKeys = (obj, prefix = '') => { for (const key in obj) { const nestedKey = prefix ? `${prefix}.${key}` : key - nestedKeys.push(nestedKey) - if (typeof obj[key] === 'object' && obj[key] !== null) + if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) { + // This is an object (but not array), recurse into it but don't add it as a key iterateKeys(obj[key], nestedKey) + } + else { + // This is a leaf node (string, number, boolean, array, etc.), add it as a key + nestedKeys.push(nestedKey) + } } } iterateKeys(translationObj) @@ -79,15 +84,176 @@ async function getKeysFromLanguage(language) { }) } +function removeKeysFromObject(obj, keysToRemove, prefix = '') { + let modified = false + for (const key in obj) { + const fullKey = prefix ? `${prefix}.${key}` : key + + if (keysToRemove.includes(fullKey)) { + delete obj[key] + modified = true + console.log(`🗑️ Removed key: ${fullKey}`) + } + else if (typeof obj[key] === 'object' && obj[key] !== null) { + const subModified = removeKeysFromObject(obj[key], keysToRemove, fullKey) + modified = modified || subModified + } + } + return modified +} + +async function removeExtraKeysFromFile(language, fileName, extraKeys) { + const filePath = path.resolve(__dirname, '../i18n', language, `${fileName}.ts`) + + if (!fs.existsSync(filePath)) { + console.log(`⚠️ File not found: ${filePath}`) + return false + } + + try { + // Filter keys that belong to this file + const camelCaseFileName = fileName.replace(/[-_](.)/g, (_, c) => c.toUpperCase()) + const fileSpecificKeys = extraKeys + .filter(key => key.startsWith(`${camelCaseFileName}.`)) + .map(key => key.substring(camelCaseFileName.length + 1)) // Remove file prefix + + if (fileSpecificKeys.length === 0) + return false + + console.log(`🔄 Processing file: ${filePath}`) + + // Read the original file content + const content = fs.readFileSync(filePath, 'utf8') + const lines = content.split('\n') + + let modified = false + const linesToRemove = [] + + // Find lines to remove for each key + for (const keyToRemove of fileSpecificKeys) { + const keyParts = keyToRemove.split('.') + let targetLineIndex = -1 + + // Build regex pattern for the exact key path + if (keyParts.length === 1) { + // Simple key at root level like "pickDate: 'value'" + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + const simpleKeyPattern = new RegExp(`^\\s*${keyParts[0]}\\s*:`) + if (simpleKeyPattern.test(line)) { + targetLineIndex = i + break + } + } + } + else { + // Nested key - need to find the exact path + const currentPath = [] + let braceDepth = 0 + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + const trimmedLine = line.trim() + + // Track current object path + const keyMatch = trimmedLine.match(/^(\w+)\s*:\s*{/) + if (keyMatch) { + currentPath.push(keyMatch[1]) + braceDepth++ + } + else if (trimmedLine === '},' || trimmedLine === '}') { + if (braceDepth > 0) { + braceDepth-- + currentPath.pop() + } + } + + // Check if this line matches our target key + const leafKeyMatch = trimmedLine.match(/^(\w+)\s*:/) + if (leafKeyMatch) { + const fullPath = [...currentPath, leafKeyMatch[1]] + const fullPathString = fullPath.join('.') + + if (fullPathString === keyToRemove) { + targetLineIndex = i + break + } + } + } + } + + if (targetLineIndex !== -1) { + linesToRemove.push(targetLineIndex) + console.log(`🗑️ Found key to remove: ${keyToRemove} at line ${targetLineIndex + 1}`) + modified = true + } + else { + console.log(`⚠️ Could not find key: ${keyToRemove}`) + } + } + + if (modified) { + // Remove lines in reverse order to maintain correct indices + linesToRemove.sort((a, b) => b - a) + + for (const lineIndex of linesToRemove) { + const line = lines[lineIndex] + console.log(`🗑️ Removing line ${lineIndex + 1}: ${line.trim()}`) + lines.splice(lineIndex, 1) + + // Also remove trailing comma from previous line if it exists and the next line is a closing brace + if (lineIndex > 0 && lineIndex < lines.length) { + const prevLine = lines[lineIndex - 1] + const nextLine = lines[lineIndex] ? lines[lineIndex].trim() : '' + + if (prevLine.trim().endsWith(',') && (nextLine.startsWith('}') || nextLine === '')) + lines[lineIndex - 1] = prevLine.replace(/,\s*$/, '') + } + } + + // Write back to file + const newContent = lines.join('\n') + fs.writeFileSync(filePath, newContent) + console.log(`💾 Updated file: ${filePath}`) + return true + } + + return false + } + catch (error) { + console.error(`Error processing file ${filePath}:`, error.message) + return false + } +} + +// Add command line argument support +const targetFile = process.argv.find(arg => arg.startsWith('--file='))?.split('=')[1] +const targetLang = process.argv.find(arg => arg.startsWith('--lang='))?.split('=')[1] +const autoRemove = process.argv.includes('--auto-remove') + async function main() { const compareKeysCount = async () => { - const targetKeys = await getKeysFromLanguage(targetLanguage) - const languagesKeys = await Promise.all(languages.map(language => getKeysFromLanguage(language))) + const allTargetKeys = await getKeysFromLanguage(targetLanguage) + + // Filter target keys by file if specified + const targetKeys = targetFile + ? allTargetKeys.filter(key => key.startsWith(targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase()))) + : allTargetKeys + + // Filter languages by target language if specified + const languagesToProcess = targetLang ? [targetLang] : languages + + const allLanguagesKeys = await Promise.all(languagesToProcess.map(language => getKeysFromLanguage(language))) + + // Filter language keys by file if specified + const languagesKeys = targetFile + ? allLanguagesKeys.map(keys => keys.filter(key => key.startsWith(targetFile.replace(/[-_](.)/g, (_, c) => c.toUpperCase())))) + : allLanguagesKeys const keysCount = languagesKeys.map(keys => keys.length) const targetKeysCount = targetKeys.length - const comparison = languages.reduce((result, language, index) => { + const comparison = languagesToProcess.reduce((result, language, index) => { const languageKeysCount = keysCount[index] const difference = targetKeysCount - languageKeysCount result[language] = difference @@ -96,13 +262,52 @@ async function main() { console.log(comparison) - // Print missing keys - languages.forEach((language, index) => { - const missingKeys = targetKeys.filter(key => !languagesKeys[index].includes(key)) + // Print missing keys and extra keys + for (let index = 0; index < languagesToProcess.length; index++) { + const language = languagesToProcess[index] + const languageKeys = languagesKeys[index] + const missingKeys = targetKeys.filter(key => !languageKeys.includes(key)) + const extraKeys = languageKeys.filter(key => !targetKeys.includes(key)) + console.log(`Missing keys in ${language}:`, missingKeys) - }) + + // Show extra keys only when there are extra keys (negative difference) + if (extraKeys.length > 0) { + console.log(`Extra keys in ${language} (not in ${targetLanguage}):`, extraKeys) + + // Auto-remove extra keys if flag is set + if (autoRemove) { + console.log(`\n🤖 Auto-removing extra keys from ${language}...`) + + // Get all translation files + const i18nFolder = path.resolve(__dirname, '../i18n', language) + const files = fs.readdirSync(i18nFolder) + .filter(file => /\.ts$/.test(file)) + .map(file => file.replace(/\.ts$/, '')) + .filter(f => !targetFile || f === targetFile) // Filter by target file if specified + + let totalRemoved = 0 + for (const fileName of files) { + const removed = await removeExtraKeysFromFile(language, fileName, extraKeys) + if (removed) totalRemoved++ + } + + console.log(`✅ Auto-removal completed for ${language}. Modified ${totalRemoved} files.`) + } + } + } } + console.log('🚀 Starting check-i18n script...') + if (targetFile) + console.log(`📁 Checking file: ${targetFile}`) + + if (targetLang) + console.log(`🌍 Checking language: ${targetLang}`) + + if (autoRemove) + console.log('🤖 Auto-remove mode: ENABLED') + compareKeysCount() } diff --git a/web/i18n/zh-Hans/app-annotation.ts b/web/i18n/zh-Hans/app-annotation.ts index 44d075715f..cb2d3be0cd 100644 --- a/web/i18n/zh-Hans/app-annotation.ts +++ b/web/i18n/zh-Hans/app-annotation.ts @@ -9,8 +9,6 @@ const translation = { table: { header: { question: '提问', - match: '匹配', - response: '回复', answer: '答案', createdAt: '创建时间', hits: '命中次数', @@ -71,7 +69,6 @@ const translation = { noHitHistory: '没有命中历史', }, hitHistoryTable: { - question: '问题', query: '提问', match: '匹配', response: '回复', diff --git a/web/i18n/zh-Hans/app-debug.ts b/web/i18n/zh-Hans/app-debug.ts index 8bdb56ac64..b58eedb5b3 100644 --- a/web/i18n/zh-Hans/app-debug.ts +++ b/web/i18n/zh-Hans/app-debug.ts @@ -254,7 +254,6 @@ const translation = { noDataLine1: '在左侧描述您的用例,', noDataLine2: '编排预览将在此处显示。', apply: '应用', - noData: '在左侧描述您的用例,编排预览将在此处显示。', loading: '为您编排应用程序中…', overwriteTitle: '覆盖现有配置?', overwriteMessage: '应用此提示将覆盖现有配置。', diff --git a/web/i18n/zh-Hans/app.ts b/web/i18n/zh-Hans/app.ts index 9e577a360e..7c8b292ce4 100644 --- a/web/i18n/zh-Hans/app.ts +++ b/web/i18n/zh-Hans/app.ts @@ -35,7 +35,6 @@ const translation = { learnMore: '了解更多', startFromBlank: '创建空白应用', startFromTemplate: '从应用模版创建', - captionAppType: '想要哪种应用类型?', foundResult: '{{count}} 个结果', foundResults: '{{count}} 个结果', noAppsFound: '未找到应用', @@ -45,7 +44,6 @@ const translation = { chatbotUserDescription: '通过简单的配置快速搭建一个基于 LLM 的对话机器人。支持切换为 Chatflow 编排。', completionShortDescription: '用于文本生成任务的 AI 助手', completionUserDescription: '通过简单的配置快速搭建一个面向文本生成类任务的 AI 助手。', - completionWarning: '该类型不久后将不再支持创建', agentShortDescription: '具备推理与自主工具调用的智能助手', agentUserDescription: '能够迭代式的规划推理、自主工具调用,直至完成任务目标的智能助手。', workflowShortDescription: '面向单轮自动化任务的编排工作流', diff --git a/web/i18n/zh-Hans/login.ts b/web/i18n/zh-Hans/login.ts index b63630e288..2276436d0e 100644 --- a/web/i18n/zh-Hans/login.ts +++ b/web/i18n/zh-Hans/login.ts @@ -77,7 +77,6 @@ const translation = { activated: '现在登录', adminInitPassword: '管理员初始化密码', validate: '验证', - sso: '使用 SSO 继续', checkCode: { checkYourEmail: '验证您的电子邮件', tips: '验证码已经发送到您的邮箱 {{email}}', diff --git a/web/i18n/zh-Hans/time.ts b/web/i18n/zh-Hans/time.ts index 5158a710b5..8a223d9dd1 100644 --- a/web/i18n/zh-Hans/time.ts +++ b/web/i18n/zh-Hans/time.ts @@ -26,7 +26,6 @@ const translation = { now: '此刻', ok: '确定', cancel: '取消', - pickDate: '选择日期', }, title: { pickTime: '选择时间', diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts index 81e207f67e..1f0300ae2a 100644 --- a/web/i18n/zh-Hans/workflow.ts +++ b/web/i18n/zh-Hans/workflow.ts @@ -213,7 +213,6 @@ const translation = { startRun: '开始运行', running: '运行中', testRunIteration: '测试运行迭代', - testRunLoop: '测试运行循环', back: '返回', iteration: '迭代', loop: '循环', diff --git a/web/i18n/zh-Hant/app-annotation.ts b/web/i18n/zh-Hant/app-annotation.ts index 02eb98f5d4..538546928c 100644 --- a/web/i18n/zh-Hant/app-annotation.ts +++ b/web/i18n/zh-Hant/app-annotation.ts @@ -9,8 +9,6 @@ const translation = { table: { header: { question: '提問', - match: '匹配', - response: '回覆', answer: '答案', createdAt: '建立時間', hits: '命中次數', @@ -71,7 +69,6 @@ const translation = { noHitHistory: '沒有命中歷史', }, hitHistoryTable: { - question: '問題', query: '提問', match: '匹配', response: '回覆', diff --git a/web/i18n/zh-Hant/app.ts b/web/i18n/zh-Hant/app.ts index e6a3a0b570..0bf99d5067 100644 --- a/web/i18n/zh-Hant/app.ts +++ b/web/i18n/zh-Hant/app.ts @@ -26,21 +26,7 @@ const translation = { newApp: { startFromBlank: '建立空白應用', startFromTemplate: '從應用模版建立', - captionAppType: '想要哪種應用類型?', - chatbotDescription: '使用大型語言模型構建聊天助手', - completionDescription: '構建一個根據提示生成高品質文字的應用程式,例如生成文章、摘要、翻譯等。', - completionWarning: '該類型不久後將不再支援建立', - agentDescription: '構建一個智慧 Agent,可以自主選擇工具來完成任務', - workflowDescription: '以工作流的形式編排生成型應用,提供更多的自訂設定。它適合有經驗的使用者。', workflowWarning: '正在進行 Beta 測試', - chatbotType: '聊天助手編排方法', - basic: '基礎編排', - basicTip: '新手適用,可以切換成工作流編排', - basicFor: '新手適用', - basicDescription: '基本編排允許使用簡單的設定編排聊天機器人應用程式,而無需修改內建提示。它適合初學者。', - advanced: '工作流編排', - advancedFor: '進階使用者適用', - advancedDescription: '工作流編排以工作流的形式編排聊天機器人,提供自訂設定,包括編輯內建提示的能力。它適合有經驗的使用者。', captionName: '應用名稱 & 圖示', appNamePlaceholder: '給你的應用起個名字', captionDescription: '描述', diff --git a/web/i18n/zh-Hant/billing.ts b/web/i18n/zh-Hant/billing.ts index 6ede2c6213..f957bc4eab 100644 --- a/web/i18n/zh-Hant/billing.ts +++ b/web/i18n/zh-Hant/billing.ts @@ -23,18 +23,13 @@ const translation = { contractOwner: '聯絡團隊管理員', free: '免費', startForFree: '免費開始', - getStartedWith: '開始使用', contactSales: '聯絡銷售', talkToSales: '聯絡銷售', modelProviders: '支援的模型提供商', - teamMembers: '團隊成員', buildApps: '構建應用程式數', vectorSpace: '向量空間', vectorSpaceTooltip: '向量空間是 LLMs 理解您的資料所需的長期記憶系統。', - vectorSpaceBillingTooltip: '向量儲存是將知識庫向量化處理後為讓 LLMs 理解資料而使用的長期記憶儲存,1MB 大約能滿足 1.2 million character 的向量化後資料儲存(以 OpenAI Embedding 模型估算,不同模型計算方式有差異)。在向量化過程中,實際的壓縮或尺寸減小取決於內容的複雜性和冗餘性。', - documentsUploadQuota: '文件上傳配額', documentProcessingPriority: '文件處理優先順序', - documentProcessingPriorityTip: '如需更高的文件處理優先順序,請升級您的套餐', documentProcessingPriorityUpgrade: '以更快的速度、更高的精度處理更多的資料。', priority: { 'standard': '標準', @@ -103,19 +98,16 @@ const translation = { sandbox: { name: 'Sandbox', description: '200 次 GPT 免費試用', - includesTitle: '包括:', for: '核心功能免費試用', }, professional: { name: 'Professional', description: '讓個人和小團隊能夠以經濟實惠的方式釋放更多能力。', - includesTitle: 'Sandbox 計劃中的一切,加上:', for: '適合獨立開發者/小型團隊', }, team: { name: 'Team', description: '協作無限制並享受頂級效能。', - includesTitle: 'Professional 計劃中的一切,加上:', for: '適用於中型團隊', }, enterprise: { @@ -123,15 +115,6 @@ const translation = { description: '獲得大規模關鍵任務系統的完整功能和支援。', includesTitle: 'Team 計劃中的一切,加上:', features: { - 1: '商業許可證授權', - 6: '先進安全與控制', - 3: '多個工作區及企業管理', - 2: '專屬企業功能', - 4: '單一登入', - 8: '專業技術支援', - 0: '企業級可擴展部署解決方案', - 7: 'Dify 官方的更新和維護', - 5: '由 Dify 合作夥伴協商的服務水平協議', }, price: '自訂', btnText: '聯繫銷售', @@ -140,9 +123,6 @@ const translation = { }, community: { features: { - 0: '所有核心功能均在公共存儲庫下釋出', - 2: '遵循 Dify 開源許可證', - 1: '單一工作區域', }, includesTitle: '免費功能:', btnText: '開始使用社區', @@ -153,10 +133,6 @@ const translation = { }, premium: { features: { - 2: '網頁應用程序標誌及品牌自定義', - 0: '各種雲端服務提供商的自我管理可靠性', - 1: '單一工作區域', - 3: '優先電子郵件及聊天支持', }, for: '適用於中型組織和團隊', comingSoon: '微軟 Azure 與 Google Cloud 支持即將推出', @@ -173,8 +149,6 @@ const translation = { fullSolution: '升級您的套餐以獲得更多空間。', }, apps: { - fullTipLine1: '升級您的套餐以', - fullTipLine2: '構建更多的程式。', fullTip1: '升級以創建更多應用程序', fullTip2des: '建議清除不活躍的應用程式以釋放使用空間,或聯繫我們。', contactUs: '聯繫我們', diff --git a/web/i18n/zh-Hant/common.ts b/web/i18n/zh-Hant/common.ts index 6404d0e003..ccfca85bfe 100644 --- a/web/i18n/zh-Hant/common.ts +++ b/web/i18n/zh-Hant/common.ts @@ -197,7 +197,6 @@ const translation = { showAppLength: '顯示 {{length}} 個應用', delete: '刪除帳戶', deleteTip: '刪除您的帳戶將永久刪除您的所有資料並且無法恢復。', - deleteConfirmTip: '請將以下內容從您的註冊電子郵件發送至 ', account: '帳戶', myAccount: '我的帳戶', studio: '工作室', diff --git a/web/i18n/zh-Hant/dataset-creation.ts b/web/i18n/zh-Hant/dataset-creation.ts index fca1ff651e..e99fb0c320 100644 --- a/web/i18n/zh-Hant/dataset-creation.ts +++ b/web/i18n/zh-Hant/dataset-creation.ts @@ -1,8 +1,6 @@ const translation = { steps: { header: { - creation: '建立知識庫', - update: '上傳檔案', fallbackRoute: '知識', }, one: '選擇資料來源', diff --git a/web/i18n/zh-Hant/dataset-documents.ts b/web/i18n/zh-Hant/dataset-documents.ts index b04a339070..1b482f181f 100644 --- a/web/i18n/zh-Hant/dataset-documents.ts +++ b/web/i18n/zh-Hant/dataset-documents.ts @@ -341,7 +341,6 @@ const translation = { keywords: '關鍵詞', addKeyWord: '新增關鍵詞', keywordError: '關鍵詞最大長度為 20', - characters: '字元', hitCount: '召回次數', vectorHash: '向量雜湊:', questionPlaceholder: '在這裡新增問題', diff --git a/web/i18n/zh-Hant/dataset-hit-testing.ts b/web/i18n/zh-Hant/dataset-hit-testing.ts index 0dbe149025..4b8cc5150a 100644 --- a/web/i18n/zh-Hant/dataset-hit-testing.ts +++ b/web/i18n/zh-Hant/dataset-hit-testing.ts @@ -2,7 +2,6 @@ const translation = { title: '召回測試', desc: '基於給定的查詢文字測試知識庫的召回效果。', dateTimeFormat: 'YYYY-MM-DD HH:mm', - recents: '最近查詢', table: { header: { source: '資料來源', diff --git a/web/i18n/zh-Hant/login.ts b/web/i18n/zh-Hant/login.ts index ae617cb5c0..8187323276 100644 --- a/web/i18n/zh-Hant/login.ts +++ b/web/i18n/zh-Hant/login.ts @@ -70,7 +70,6 @@ const translation = { activated: '現在登入', adminInitPassword: '管理員初始化密碼', validate: '驗證', - sso: '繼續使用 SSO', checkCode: { verify: '驗證', resend: '發送', diff --git a/web/i18n/zh-Hant/tools.ts b/web/i18n/zh-Hant/tools.ts index fbfb09e321..9dad3a74cf 100644 --- a/web/i18n/zh-Hant/tools.ts +++ b/web/i18n/zh-Hant/tools.ts @@ -54,7 +54,6 @@ const translation = { keyTooltip: 'HTTP 頭部名稱,如果你不知道是什麼,可以將其保留為 Authorization 或設定為自定義值', types: { none: '無', - api_key: 'API Key', apiKeyPlaceholder: 'HTTP 頭部名稱,用於傳遞 API Key', apiValuePlaceholder: '輸入 API Key', api_key_query: '查詢參數', diff --git a/web/i18n/zh-Hant/workflow.ts b/web/i18n/zh-Hant/workflow.ts index 935d042fa7..bcdfbb81d3 100644 --- a/web/i18n/zh-Hant/workflow.ts +++ b/web/i18n/zh-Hant/workflow.ts @@ -107,10 +107,8 @@ const translation = { loadMore: '載入更多工作流', noHistory: '無歷史記錄', publishUpdate: '發布更新', - referenceVar: '參考變量', exportSVG: '匯出為 SVG', exportPNG: '匯出為 PNG', - noExist: '沒有這個變數', versionHistory: '版本歷史', exitVersions: '退出版本', exportImage: '匯出圖像', @@ -610,7 +608,6 @@ const translation = { }, select: '選擇', addSubVariable: '子變數', - condition: '條件', }, variableAssigner: { title: '變量賦值', From 00cb1c26a1472d2512173453fef8a7f6669c35de Mon Sep 17 00:00:00 2001 From: Shaun Date: Tue, 29 Jul 2025 19:34:46 +0800 Subject: [PATCH 67/72] refactor: pass external_trace_id to message trace (#23089) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/controllers/service_api/app/completion.py | 3 ++ api/core/ops/aliyun_trace/aliyun_trace.py | 28 ++++++++++++--- .../aliyun_trace/data_exporter/traceclient.py | 12 +++++-- .../arize_phoenix_trace.py | 28 ++++++++------- api/core/ops/entities/trace_entity.py | 1 + api/core/ops/langfuse_trace/langfuse_trace.py | 19 +++++----- .../ops/langsmith_trace/langsmith_trace.py | 17 +++++---- api/core/ops/opik_trace/opik_trace.py | 19 +++++----- api/core/ops/ops_trace_manager.py | 15 +++++--- api/core/ops/weave_trace/weave_trace.py | 35 +++++++++++++------ 10 files changed, 115 insertions(+), 62 deletions(-) diff --git a/api/controllers/service_api/app/completion.py b/api/controllers/service_api/app/completion.py index 7762672494..edc66cc5e9 100644 --- a/api/controllers/service_api/app/completion.py +++ b/api/controllers/service_api/app/completion.py @@ -47,6 +47,9 @@ class CompletionApi(Resource): parser.add_argument("retriever_from", type=str, required=False, default="dev", location="json") args = parser.parse_args() + external_trace_id = get_external_trace_id(request) + if external_trace_id: + args["external_trace_id"] = external_trace_id streaming = args["response_mode"] == "streaming" diff --git a/api/core/ops/aliyun_trace/aliyun_trace.py b/api/core/ops/aliyun_trace/aliyun_trace.py index af0e38f7ef..06050619e9 100644 --- a/api/core/ops/aliyun_trace/aliyun_trace.py +++ b/api/core/ops/aliyun_trace/aliyun_trace.py @@ -10,6 +10,7 @@ from sqlalchemy.orm import Session, sessionmaker from core.ops.aliyun_trace.data_exporter.traceclient import ( TraceClient, convert_datetime_to_nanoseconds, + convert_string_to_id, convert_to_span_id, convert_to_trace_id, generate_span_id, @@ -101,8 +102,9 @@ class AliyunDataTrace(BaseTraceInstance): raise ValueError(f"Aliyun get run url failed: {str(e)}") def workflow_trace(self, trace_info: WorkflowTraceInfo): - external_trace_id = trace_info.metadata.get("external_trace_id") - trace_id = external_trace_id or convert_to_trace_id(trace_info.workflow_run_id) + trace_id = convert_to_trace_id(trace_info.workflow_run_id) + if trace_info.trace_id: + trace_id = convert_string_to_id(trace_info.trace_id) workflow_span_id = convert_to_span_id(trace_info.workflow_run_id, "workflow") self.add_workflow_span(trace_id, workflow_span_id, trace_info) @@ -130,6 +132,9 @@ class AliyunDataTrace(BaseTraceInstance): status = Status(StatusCode.ERROR, trace_info.error) trace_id = convert_to_trace_id(message_id) + if trace_info.trace_id: + trace_id = convert_string_to_id(trace_info.trace_id) + message_span_id = convert_to_span_id(message_id, "message") message_span = SpanData( trace_id=trace_id, @@ -186,9 +191,13 @@ class AliyunDataTrace(BaseTraceInstance): return message_id = trace_info.message_id + trace_id = convert_to_trace_id(message_id) + if trace_info.trace_id: + trace_id = convert_string_to_id(trace_info.trace_id) + documents_data = extract_retrieval_documents(trace_info.documents) dataset_retrieval_span = SpanData( - trace_id=convert_to_trace_id(message_id), + trace_id=trace_id, parent_span_id=convert_to_span_id(message_id, "message"), span_id=generate_span_id(), name="dataset_retrieval", @@ -214,8 +223,12 @@ class AliyunDataTrace(BaseTraceInstance): if trace_info.error: status = Status(StatusCode.ERROR, trace_info.error) + trace_id = convert_to_trace_id(message_id) + if trace_info.trace_id: + trace_id = convert_string_to_id(trace_info.trace_id) + tool_span = SpanData( - trace_id=convert_to_trace_id(message_id), + trace_id=trace_id, parent_span_id=convert_to_span_id(message_id, "message"), span_id=generate_span_id(), name=trace_info.tool_name, @@ -451,8 +464,13 @@ class AliyunDataTrace(BaseTraceInstance): status: Status = Status(StatusCode.OK) if trace_info.error: status = Status(StatusCode.ERROR, trace_info.error) + + trace_id = convert_to_trace_id(message_id) + if trace_info.trace_id: + trace_id = convert_string_to_id(trace_info.trace_id) + suggested_question_span = SpanData( - trace_id=convert_to_trace_id(message_id), + trace_id=trace_id, parent_span_id=convert_to_span_id(message_id, "message"), span_id=convert_to_span_id(message_id, "suggested_question"), name="suggested_question", diff --git a/api/core/ops/aliyun_trace/data_exporter/traceclient.py b/api/core/ops/aliyun_trace/data_exporter/traceclient.py index 934ce95a64..bd19c8a503 100644 --- a/api/core/ops/aliyun_trace/data_exporter/traceclient.py +++ b/api/core/ops/aliyun_trace/data_exporter/traceclient.py @@ -181,15 +181,21 @@ def convert_to_trace_id(uuid_v4: Optional[str]) -> int: raise ValueError(f"Invalid UUID input: {e}") +def convert_string_to_id(string: Optional[str]) -> int: + if not string: + return generate_span_id() + hash_bytes = hashlib.sha256(string.encode("utf-8")).digest() + id = int.from_bytes(hash_bytes[:8], byteorder="big", signed=False) + return id + + def convert_to_span_id(uuid_v4: Optional[str], span_type: str) -> int: try: uuid_obj = uuid.UUID(uuid_v4) except Exception as e: raise ValueError(f"Invalid UUID input: {e}") combined_key = f"{uuid_obj.hex}-{span_type}" - hash_bytes = hashlib.sha256(combined_key.encode("utf-8")).digest() - span_id = int.from_bytes(hash_bytes[:8], byteorder="big", signed=False) - return span_id + return convert_string_to_id(combined_key) def convert_datetime_to_nanoseconds(start_time_a: Optional[datetime]) -> Optional[int]: diff --git a/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py b/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py index f252a022d8..a20f2485c8 100644 --- a/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py +++ b/api/core/ops/arize_phoenix_trace/arize_phoenix_trace.py @@ -91,16 +91,21 @@ def datetime_to_nanos(dt: Optional[datetime]) -> int: return int(dt.timestamp() * 1_000_000_000) -def uuid_to_trace_id(string: Optional[str]) -> int: - """Convert UUID string to a valid trace ID (16-byte integer).""" +def string_to_trace_id128(string: Optional[str]) -> int: + """ + Convert any input string into a stable 128-bit integer trace ID. + + This uses SHA-256 hashing and takes the first 16 bytes (128 bits) of the digest. + It's suitable for generating consistent, unique identifiers from strings. + """ if string is None: string = "" hash_object = hashlib.sha256(string.encode()) - # Take the first 16 bytes (128 bits) of the hash + # Take the first 16 bytes (128 bits) of the hash digest digest = hash_object.digest()[:16] - # Convert to integer (128 bits) + # Convert to a 128-bit integer return int.from_bytes(digest, byteorder="big") @@ -153,8 +158,7 @@ class ArizePhoenixDataTrace(BaseTraceInstance): } workflow_metadata.update(trace_info.metadata) - external_trace_id = trace_info.metadata.get("external_trace_id") - trace_id = external_trace_id or uuid_to_trace_id(trace_info.workflow_run_id) + trace_id = string_to_trace_id128(trace_info.trace_id or trace_info.workflow_run_id) span_id = RandomIdGenerator().generate_span_id() context = SpanContext( trace_id=trace_id, @@ -310,7 +314,7 @@ class ArizePhoenixDataTrace(BaseTraceInstance): SpanAttributes.SESSION_ID: trace_info.message_data.conversation_id, } - trace_id = uuid_to_trace_id(trace_info.message_id) + trace_id = string_to_trace_id128(trace_info.trace_id or trace_info.message_id) message_span_id = RandomIdGenerator().generate_span_id() span_context = SpanContext( trace_id=trace_id, @@ -406,7 +410,7 @@ class ArizePhoenixDataTrace(BaseTraceInstance): } metadata.update(trace_info.metadata) - trace_id = uuid_to_trace_id(trace_info.message_id) + trace_id = string_to_trace_id128(trace_info.message_id) span_id = RandomIdGenerator().generate_span_id() context = SpanContext( trace_id=trace_id, @@ -468,7 +472,7 @@ class ArizePhoenixDataTrace(BaseTraceInstance): } metadata.update(trace_info.metadata) - trace_id = uuid_to_trace_id(trace_info.message_id) + trace_id = string_to_trace_id128(trace_info.message_id) span_id = RandomIdGenerator().generate_span_id() context = SpanContext( trace_id=trace_id, @@ -521,7 +525,7 @@ class ArizePhoenixDataTrace(BaseTraceInstance): } metadata.update(trace_info.metadata) - trace_id = uuid_to_trace_id(trace_info.message_id) + trace_id = string_to_trace_id128(trace_info.message_id) span_id = RandomIdGenerator().generate_span_id() context = SpanContext( trace_id=trace_id, @@ -568,7 +572,7 @@ class ArizePhoenixDataTrace(BaseTraceInstance): "tool_config": json.dumps(trace_info.tool_config, ensure_ascii=False), } - trace_id = uuid_to_trace_id(trace_info.message_id) + trace_id = string_to_trace_id128(trace_info.message_id) tool_span_id = RandomIdGenerator().generate_span_id() logger.info("[Arize/Phoenix] Creating tool trace with trace_id: %s, span_id: %s", trace_id, tool_span_id) @@ -629,7 +633,7 @@ class ArizePhoenixDataTrace(BaseTraceInstance): } metadata.update(trace_info.metadata) - trace_id = uuid_to_trace_id(trace_info.message_id) + trace_id = string_to_trace_id128(trace_info.message_id) span_id = RandomIdGenerator().generate_span_id() context = SpanContext( trace_id=trace_id, diff --git a/api/core/ops/entities/trace_entity.py b/api/core/ops/entities/trace_entity.py index 151fa2aaf4..3bad5c92fb 100644 --- a/api/core/ops/entities/trace_entity.py +++ b/api/core/ops/entities/trace_entity.py @@ -14,6 +14,7 @@ class BaseTraceInfo(BaseModel): start_time: Optional[datetime] = None end_time: Optional[datetime] = None metadata: dict[str, Any] + trace_id: Optional[str] = None @field_validator("inputs", "outputs") @classmethod diff --git a/api/core/ops/langfuse_trace/langfuse_trace.py b/api/core/ops/langfuse_trace/langfuse_trace.py index d356e735ee..3a03d9f4fe 100644 --- a/api/core/ops/langfuse_trace/langfuse_trace.py +++ b/api/core/ops/langfuse_trace/langfuse_trace.py @@ -67,14 +67,13 @@ class LangFuseDataTrace(BaseTraceInstance): self.generate_name_trace(trace_info) def workflow_trace(self, trace_info: WorkflowTraceInfo): - external_trace_id = trace_info.metadata.get("external_trace_id") - trace_id = external_trace_id or trace_info.workflow_run_id + trace_id = trace_info.trace_id or trace_info.workflow_run_id user_id = trace_info.metadata.get("user_id") metadata = trace_info.metadata metadata["workflow_app_log_id"] = trace_info.workflow_app_log_id if trace_info.message_id: - trace_id = external_trace_id or trace_info.message_id + trace_id = trace_info.trace_id or trace_info.message_id name = TraceTaskName.MESSAGE_TRACE.value trace_data = LangfuseTrace( id=trace_id, @@ -250,8 +249,10 @@ class LangFuseDataTrace(BaseTraceInstance): user_id = end_user_data.session_id metadata["user_id"] = user_id + trace_id = trace_info.trace_id or message_id + trace_data = LangfuseTrace( - id=message_id, + id=trace_id, user_id=user_id, name=TraceTaskName.MESSAGE_TRACE.value, input={ @@ -285,7 +286,7 @@ class LangFuseDataTrace(BaseTraceInstance): langfuse_generation_data = LangfuseGeneration( name="llm", - trace_id=message_id, + trace_id=trace_id, start_time=trace_info.start_time, end_time=trace_info.end_time, model=message_data.model_id, @@ -311,7 +312,7 @@ class LangFuseDataTrace(BaseTraceInstance): "preset_response": trace_info.preset_response, "inputs": trace_info.inputs, }, - trace_id=trace_info.message_id, + trace_id=trace_info.trace_id or trace_info.message_id, start_time=trace_info.start_time or trace_info.message_data.created_at, end_time=trace_info.end_time or trace_info.message_data.created_at, metadata=trace_info.metadata, @@ -334,7 +335,7 @@ class LangFuseDataTrace(BaseTraceInstance): name=TraceTaskName.SUGGESTED_QUESTION_TRACE.value, input=trace_info.inputs, output=str(trace_info.suggested_question), - trace_id=trace_info.message_id, + trace_id=trace_info.trace_id or trace_info.message_id, start_time=trace_info.start_time, end_time=trace_info.end_time, metadata=trace_info.metadata, @@ -352,7 +353,7 @@ class LangFuseDataTrace(BaseTraceInstance): name=TraceTaskName.DATASET_RETRIEVAL_TRACE.value, input=trace_info.inputs, output={"documents": trace_info.documents}, - trace_id=trace_info.message_id, + trace_id=trace_info.trace_id or trace_info.message_id, start_time=trace_info.start_time or trace_info.message_data.created_at, end_time=trace_info.end_time or trace_info.message_data.updated_at, metadata=trace_info.metadata, @@ -365,7 +366,7 @@ class LangFuseDataTrace(BaseTraceInstance): name=trace_info.tool_name, input=trace_info.tool_inputs, output=trace_info.tool_outputs, - trace_id=trace_info.message_id, + trace_id=trace_info.trace_id or trace_info.message_id, start_time=trace_info.start_time, end_time=trace_info.end_time, metadata=trace_info.metadata, diff --git a/api/core/ops/langsmith_trace/langsmith_trace.py b/api/core/ops/langsmith_trace/langsmith_trace.py index fb3f6ecf0d..f9e5128e89 100644 --- a/api/core/ops/langsmith_trace/langsmith_trace.py +++ b/api/core/ops/langsmith_trace/langsmith_trace.py @@ -65,8 +65,7 @@ class LangSmithDataTrace(BaseTraceInstance): self.generate_name_trace(trace_info) def workflow_trace(self, trace_info: WorkflowTraceInfo): - external_trace_id = trace_info.metadata.get("external_trace_id") - trace_id = external_trace_id or trace_info.message_id or trace_info.workflow_run_id + trace_id = trace_info.trace_id or trace_info.message_id or trace_info.workflow_run_id if trace_info.start_time is None: trace_info.start_time = datetime.now() message_dotted_order = ( @@ -290,7 +289,7 @@ class LangSmithDataTrace(BaseTraceInstance): reference_example_id=None, input_attachments={}, output_attachments={}, - trace_id=None, + trace_id=trace_info.trace_id, dotted_order=None, parent_run_id=None, ) @@ -319,7 +318,7 @@ class LangSmithDataTrace(BaseTraceInstance): reference_example_id=None, input_attachments={}, output_attachments={}, - trace_id=None, + trace_id=trace_info.trace_id, dotted_order=None, id=str(uuid.uuid4()), ) @@ -351,7 +350,7 @@ class LangSmithDataTrace(BaseTraceInstance): reference_example_id=None, input_attachments={}, output_attachments={}, - trace_id=None, + trace_id=trace_info.trace_id, dotted_order=None, error="", file_list=[], @@ -381,7 +380,7 @@ class LangSmithDataTrace(BaseTraceInstance): reference_example_id=None, input_attachments={}, output_attachments={}, - trace_id=None, + trace_id=trace_info.trace_id, dotted_order=None, error="", file_list=[], @@ -410,7 +409,7 @@ class LangSmithDataTrace(BaseTraceInstance): reference_example_id=None, input_attachments={}, output_attachments={}, - trace_id=None, + trace_id=trace_info.trace_id, dotted_order=None, error="", file_list=[], @@ -440,7 +439,7 @@ class LangSmithDataTrace(BaseTraceInstance): reference_example_id=None, input_attachments={}, output_attachments={}, - trace_id=None, + trace_id=trace_info.trace_id, dotted_order=None, error=trace_info.error or "", ) @@ -465,7 +464,7 @@ class LangSmithDataTrace(BaseTraceInstance): reference_example_id=None, input_attachments={}, output_attachments={}, - trace_id=None, + trace_id=trace_info.trace_id, dotted_order=None, error="", file_list=[], diff --git a/api/core/ops/opik_trace/opik_trace.py b/api/core/ops/opik_trace/opik_trace.py index 1e52f28350..dd6a424ddb 100644 --- a/api/core/ops/opik_trace/opik_trace.py +++ b/api/core/ops/opik_trace/opik_trace.py @@ -96,8 +96,7 @@ class OpikDataTrace(BaseTraceInstance): self.generate_name_trace(trace_info) def workflow_trace(self, trace_info: WorkflowTraceInfo): - external_trace_id = trace_info.metadata.get("external_trace_id") - dify_trace_id = external_trace_id or trace_info.workflow_run_id + dify_trace_id = trace_info.trace_id or trace_info.workflow_run_id opik_trace_id = prepare_opik_uuid(trace_info.start_time, dify_trace_id) workflow_metadata = wrap_metadata( trace_info.metadata, message_id=trace_info.message_id, workflow_app_log_id=trace_info.workflow_app_log_id @@ -105,7 +104,7 @@ class OpikDataTrace(BaseTraceInstance): root_span_id = None if trace_info.message_id: - dify_trace_id = external_trace_id or trace_info.message_id + dify_trace_id = trace_info.trace_id or trace_info.message_id opik_trace_id = prepare_opik_uuid(trace_info.start_time, dify_trace_id) trace_data = { @@ -276,7 +275,7 @@ class OpikDataTrace(BaseTraceInstance): return metadata = trace_info.metadata - message_id = trace_info.message_id + dify_trace_id = trace_info.trace_id or trace_info.message_id user_id = message_data.from_account_id metadata["user_id"] = user_id @@ -291,7 +290,7 @@ class OpikDataTrace(BaseTraceInstance): metadata["end_user_id"] = end_user_id trace_data = { - "id": prepare_opik_uuid(trace_info.start_time, message_id), + "id": prepare_opik_uuid(trace_info.start_time, dify_trace_id), "name": TraceTaskName.MESSAGE_TRACE.value, "start_time": trace_info.start_time, "end_time": trace_info.end_time, @@ -330,7 +329,7 @@ class OpikDataTrace(BaseTraceInstance): start_time = trace_info.start_time or trace_info.message_data.created_at span_data = { - "trace_id": prepare_opik_uuid(start_time, trace_info.message_id), + "trace_id": prepare_opik_uuid(start_time, trace_info.trace_id or trace_info.message_id), "name": TraceTaskName.MODERATION_TRACE.value, "type": "tool", "start_time": start_time, @@ -356,7 +355,7 @@ class OpikDataTrace(BaseTraceInstance): start_time = trace_info.start_time or message_data.created_at span_data = { - "trace_id": prepare_opik_uuid(start_time, trace_info.message_id), + "trace_id": prepare_opik_uuid(start_time, trace_info.trace_id or trace_info.message_id), "name": TraceTaskName.SUGGESTED_QUESTION_TRACE.value, "type": "tool", "start_time": start_time, @@ -376,7 +375,7 @@ class OpikDataTrace(BaseTraceInstance): start_time = trace_info.start_time or trace_info.message_data.created_at span_data = { - "trace_id": prepare_opik_uuid(start_time, trace_info.message_id), + "trace_id": prepare_opik_uuid(start_time, trace_info.trace_id or trace_info.message_id), "name": TraceTaskName.DATASET_RETRIEVAL_TRACE.value, "type": "tool", "start_time": start_time, @@ -391,7 +390,7 @@ class OpikDataTrace(BaseTraceInstance): def tool_trace(self, trace_info: ToolTraceInfo): span_data = { - "trace_id": prepare_opik_uuid(trace_info.start_time, trace_info.message_id), + "trace_id": prepare_opik_uuid(trace_info.start_time, trace_info.trace_id or trace_info.message_id), "name": trace_info.tool_name, "type": "tool", "start_time": trace_info.start_time, @@ -406,7 +405,7 @@ class OpikDataTrace(BaseTraceInstance): def generate_name_trace(self, trace_info: GenerateNameTraceInfo): trace_data = { - "id": prepare_opik_uuid(trace_info.start_time, trace_info.message_id), + "id": prepare_opik_uuid(trace_info.start_time, trace_info.trace_id or trace_info.message_id), "name": TraceTaskName.GENERATE_NAME_TRACE.value, "start_time": trace_info.start_time, "end_time": trace_info.end_time, diff --git a/api/core/ops/ops_trace_manager.py b/api/core/ops/ops_trace_manager.py index 91cdc937a6..a607c76beb 100644 --- a/api/core/ops/ops_trace_manager.py +++ b/api/core/ops/ops_trace_manager.py @@ -407,6 +407,7 @@ class TraceTask: def __init__( self, trace_type: Any, + trace_id: Optional[str] = None, message_id: Optional[str] = None, workflow_execution: Optional[WorkflowExecution] = None, conversation_id: Optional[str] = None, @@ -424,6 +425,9 @@ class TraceTask: self.app_id = None self.kwargs = kwargs + external_trace_id = kwargs.get("external_trace_id") + if external_trace_id: + self.trace_id = external_trace_id def execute(self): return self.preprocess() @@ -520,11 +524,8 @@ class TraceTask: "app_id": workflow_run.app_id, } - external_trace_id = self.kwargs.get("external_trace_id") - if external_trace_id: - metadata["external_trace_id"] = external_trace_id - workflow_trace_info = WorkflowTraceInfo( + trace_id=self.trace_id, workflow_data=workflow_run.to_dict(), conversation_id=conversation_id, workflow_id=workflow_id, @@ -584,6 +585,7 @@ class TraceTask: message_tokens = message_data.message_tokens message_trace_info = MessageTraceInfo( + trace_id=self.trace_id, message_id=message_id, message_data=message_data.to_dict(), conversation_model=conversation_mode, @@ -627,6 +629,7 @@ class TraceTask: workflow_app_log_id = str(workflow_app_log_data.id) if workflow_app_log_data else None moderation_trace_info = ModerationTraceInfo( + trace_id=self.trace_id, message_id=workflow_app_log_id or message_id, inputs=inputs, message_data=message_data.to_dict(), @@ -667,6 +670,7 @@ class TraceTask: workflow_app_log_id = str(workflow_app_log_data.id) if workflow_app_log_data else None suggested_question_trace_info = SuggestedQuestionTraceInfo( + trace_id=self.trace_id, message_id=workflow_app_log_id or message_id, message_data=message_data.to_dict(), inputs=message_data.message, @@ -708,6 +712,7 @@ class TraceTask: } dataset_retrieval_trace_info = DatasetRetrievalTraceInfo( + trace_id=self.trace_id, message_id=message_id, inputs=message_data.query or message_data.inputs, documents=[doc.model_dump() for doc in documents] if documents else [], @@ -772,6 +777,7 @@ class TraceTask: ) tool_trace_info = ToolTraceInfo( + trace_id=self.trace_id, message_id=message_id, message_data=message_data.to_dict(), tool_name=tool_name, @@ -807,6 +813,7 @@ class TraceTask: } generate_name_trace_info = GenerateNameTraceInfo( + trace_id=self.trace_id, conversation_id=conversation_id, inputs=inputs, outputs=generate_conversation_name, diff --git a/api/core/ops/weave_trace/weave_trace.py b/api/core/ops/weave_trace/weave_trace.py index 470601b17a..8089860481 100644 --- a/api/core/ops/weave_trace/weave_trace.py +++ b/api/core/ops/weave_trace/weave_trace.py @@ -87,8 +87,7 @@ class WeaveDataTrace(BaseTraceInstance): self.generate_name_trace(trace_info) def workflow_trace(self, trace_info: WorkflowTraceInfo): - external_trace_id = trace_info.metadata.get("external_trace_id") - trace_id = external_trace_id or trace_info.message_id or trace_info.workflow_run_id + trace_id = trace_info.trace_id or trace_info.message_id or trace_info.workflow_run_id if trace_info.start_time is None: trace_info.start_time = datetime.now() @@ -245,8 +244,12 @@ class WeaveDataTrace(BaseTraceInstance): attributes["start_time"] = trace_info.start_time attributes["end_time"] = trace_info.end_time attributes["tags"] = ["message", str(trace_info.conversation_mode)] + + trace_id = trace_info.trace_id or message_id + attributes["trace_id"] = trace_id + message_run = WeaveTraceModel( - id=message_id, + id=trace_id, op=str(TraceTaskName.MESSAGE_TRACE.value), input_tokens=trace_info.message_tokens, output_tokens=trace_info.answer_tokens, @@ -274,7 +277,7 @@ class WeaveDataTrace(BaseTraceInstance): ) self.start_call( llm_run, - parent_run_id=message_id, + parent_run_id=trace_id, ) self.finish_call(llm_run) self.finish_call(message_run) @@ -289,6 +292,9 @@ class WeaveDataTrace(BaseTraceInstance): attributes["start_time"] = trace_info.start_time or trace_info.message_data.created_at attributes["end_time"] = trace_info.end_time or trace_info.message_data.updated_at + trace_id = trace_info.trace_id or trace_info.message_id + attributes["trace_id"] = trace_id + moderation_run = WeaveTraceModel( id=str(uuid.uuid4()), op=str(TraceTaskName.MODERATION_TRACE.value), @@ -303,7 +309,7 @@ class WeaveDataTrace(BaseTraceInstance): exception=getattr(trace_info, "error", None), file_list=[], ) - self.start_call(moderation_run, parent_run_id=trace_info.message_id) + self.start_call(moderation_run, parent_run_id=trace_id) self.finish_call(moderation_run) def suggested_question_trace(self, trace_info: SuggestedQuestionTraceInfo): @@ -316,6 +322,9 @@ class WeaveDataTrace(BaseTraceInstance): attributes["start_time"] = (trace_info.start_time or message_data.created_at,) attributes["end_time"] = (trace_info.end_time or message_data.updated_at,) + trace_id = trace_info.trace_id or trace_info.message_id + attributes["trace_id"] = trace_id + suggested_question_run = WeaveTraceModel( id=str(uuid.uuid4()), op=str(TraceTaskName.SUGGESTED_QUESTION_TRACE.value), @@ -326,7 +335,7 @@ class WeaveDataTrace(BaseTraceInstance): file_list=[], ) - self.start_call(suggested_question_run, parent_run_id=trace_info.message_id) + self.start_call(suggested_question_run, parent_run_id=trace_id) self.finish_call(suggested_question_run) def dataset_retrieval_trace(self, trace_info: DatasetRetrievalTraceInfo): @@ -338,6 +347,9 @@ class WeaveDataTrace(BaseTraceInstance): attributes["start_time"] = (trace_info.start_time or trace_info.message_data.created_at,) attributes["end_time"] = (trace_info.end_time or trace_info.message_data.updated_at,) + trace_id = trace_info.trace_id or trace_info.message_id + attributes["trace_id"] = trace_id + dataset_retrieval_run = WeaveTraceModel( id=str(uuid.uuid4()), op=str(TraceTaskName.DATASET_RETRIEVAL_TRACE.value), @@ -348,7 +360,7 @@ class WeaveDataTrace(BaseTraceInstance): file_list=[], ) - self.start_call(dataset_retrieval_run, parent_run_id=trace_info.message_id) + self.start_call(dataset_retrieval_run, parent_run_id=trace_id) self.finish_call(dataset_retrieval_run) def tool_trace(self, trace_info: ToolTraceInfo): @@ -357,6 +369,11 @@ class WeaveDataTrace(BaseTraceInstance): attributes["start_time"] = trace_info.start_time attributes["end_time"] = trace_info.end_time + message_id = trace_info.message_id or getattr(trace_info, "conversation_id", None) + message_id = message_id or None + trace_id = trace_info.trace_id or message_id + attributes["trace_id"] = trace_id + tool_run = WeaveTraceModel( id=str(uuid.uuid4()), op=trace_info.tool_name, @@ -366,9 +383,7 @@ class WeaveDataTrace(BaseTraceInstance): attributes=attributes, exception=trace_info.error, ) - message_id = trace_info.message_id or getattr(trace_info, "conversation_id", None) - message_id = message_id or None - self.start_call(tool_run, parent_run_id=message_id) + self.start_call(tool_run, parent_run_id=trace_id) self.finish_call(tool_run) def generate_name_trace(self, trace_info: GenerateNameTraceInfo): From cba5bd588ccb1a10ab35377d14e6a39563230e7e Mon Sep 17 00:00:00 2001 From: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:54:37 +0800 Subject: [PATCH 68/72] minor fix: wrong position of retry_document_indexing_task time elapsed (#23099) --- api/tasks/retry_document_indexing_task.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/tasks/retry_document_indexing_task.py b/api/tasks/retry_document_indexing_task.py index 2576d7b051..26b41aff2e 100644 --- a/api/tasks/retry_document_indexing_task.py +++ b/api/tasks/retry_document_indexing_task.py @@ -95,8 +95,8 @@ def retry_document_indexing_task(dataset_id: str, document_ids: list[str]): logging.info(click.style(str(ex), fg="yellow")) redis_client.delete(retry_indexing_cache_key) logging.exception("retry_document_indexing_task failed, document_id: %s", document_id) - end_at = time.perf_counter() - logging.info(click.style(f"Retry dataset: {dataset_id} latency: {end_at - start_at}", fg="green")) + end_at = time.perf_counter() + logging.info(click.style(f"Retry dataset: {dataset_id} latency: {end_at - start_at}", fg="green")) except Exception as e: logging.exception( "retry_document_indexing_task failed, dataset_id: %s, document_ids: %s", dataset_id, document_ids From ea542d42ca8d28270396baee26fe25247f98d197 Mon Sep 17 00:00:00 2001 From: crazywoola <100913391+crazywoola@users.noreply.github.com> Date: Tue, 29 Jul 2025 21:36:32 +0800 Subject: [PATCH 69/72] fix: i18n link in README.md (#23121) --- README.md | 2 +- README_AR.md | 2 +- README_BN.md | 2 +- README_CN.md | 2 +- README_DE.md | 2 +- README_ES.md | 2 +- README_FR.md | 2 +- README_JA.md | 2 +- README_KL.md | 2 +- README_KR.md | 2 +- README_PT.md | 2 +- README_TR.md | 2 +- README_TW.md | 2 +- README_VI.md | 2 +- web/i18n-config/README.md | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 2909e0e6cf..16a1268cb1 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ One-Click deploy Dify to Alibaba Cloud with [Alibaba Cloud Data Management](http For those who'd like to contribute code, see our [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). At the same time, please consider supporting Dify by sharing it on social media and at events and conferences. -> We are looking for contributors to help translate Dify into languages other than Mandarin or English. If you are interested in helping, please see the [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) for more information, and leave us a comment in the `global-users` channel of our [Discord Community Server](https://discord.gg/8Tpq4AcN9c). +> We are looking for contributors to help translate Dify into languages other than Mandarin or English. If you are interested in helping, please see the [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) for more information, and leave us a comment in the `global-users` channel of our [Discord Community Server](https://discord.gg/8Tpq4AcN9c). ## Community & contact diff --git a/README_AR.md b/README_AR.md index e959ca0f78..d2cb0098a3 100644 --- a/README_AR.md +++ b/README_AR.md @@ -223,7 +223,7 @@ docker compose up -d لأولئك الذين يرغبون في المساهمة، انظر إلى [دليل المساهمة](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) لدينا. في الوقت نفسه، يرجى النظر في دعم Dify عن طريق مشاركته على وسائل التواصل الاجتماعي وفي الفعاليات والمؤتمرات. -> نحن نبحث عن مساهمين لمساعدة في ترجمة Dify إلى لغات أخرى غير اللغة الصينية المندرين أو الإنجليزية. إذا كنت مهتمًا بالمساعدة، يرجى الاطلاع على [README للترجمة](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) لمزيد من المعلومات، واترك لنا تعليقًا في قناة `global-users` على [خادم المجتمع على Discord](https://discord.gg/8Tpq4AcN9c). +> نحن نبحث عن مساهمين لمساعدة في ترجمة Dify إلى لغات أخرى غير اللغة الصينية المندرين أو الإنجليزية. إذا كنت مهتمًا بالمساعدة، يرجى الاطلاع على [README للترجمة](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) لمزيد من المعلومات، واترك لنا تعليقًا في قناة `global-users` على [خادم المجتمع على Discord](https://discord.gg/8Tpq4AcN9c). **المساهمون** diff --git a/README_BN.md b/README_BN.md index 29d7374ea5..f57413ec8b 100644 --- a/README_BN.md +++ b/README_BN.md @@ -241,7 +241,7 @@ GitHub-এ ডিফাইকে স্টার দিয়ে রাখুন যারা কোড অবদান রাখতে চান, তাদের জন্য আমাদের [অবদান নির্দেশিকা] দেখুন (https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)। একই সাথে, সোশ্যাল মিডিয়া এবং ইভেন্ট এবং কনফারেন্সে এটি শেয়ার করে Dify কে সমর্থন করুন। -> আমরা ম্যান্ডারিন বা ইংরেজি ছাড়া অন্য ভাষায় Dify অনুবাদ করতে সাহায্য করার জন্য অবদানকারীদের খুঁজছি। আপনি যদি সাহায্য করতে আগ্রহী হন, তাহলে আরও তথ্যের জন্য [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) দেখুন এবং আমাদের [ডিসকর্ড কমিউনিটি সার্ভার](https://discord.gg/8Tpq4AcN9c) এর `গ্লোবাল-ইউজারস` চ্যানেলে আমাদের একটি মন্তব্য করুন। +> আমরা ম্যান্ডারিন বা ইংরেজি ছাড়া অন্য ভাষায় Dify অনুবাদ করতে সাহায্য করার জন্য অবদানকারীদের খুঁজছি। আপনি যদি সাহায্য করতে আগ্রহী হন, তাহলে আরও তথ্যের জন্য [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) দেখুন এবং আমাদের [ডিসকর্ড কমিউনিটি সার্ভার](https://discord.gg/8Tpq4AcN9c) এর `গ্লোবাল-ইউজারস` চ্যানেলে আমাদের একটি মন্তব্য করুন। ## কমিউনিটি এবং যোগাযোগ diff --git a/README_CN.md b/README_CN.md index 486a368c09..e9c73eb48b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -244,7 +244,7 @@ docker compose up -d 对于那些想要贡献代码的人,请参阅我们的[贡献指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)。 同时,请考虑通过社交媒体、活动和会议来支持 Dify 的分享。 -> 我们正在寻找贡献者来帮助将 Dify 翻译成除了中文和英文之外的其他语言。如果您有兴趣帮助,请参阅我们的[i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md)获取更多信息,并在我们的[Discord 社区服务器](https://discord.gg/8Tpq4AcN9c)的`global-users`频道中留言。 +> 我们正在寻找贡献者来帮助将 Dify 翻译成除了中文和英文之外的其他语言。如果您有兴趣帮助,请参阅我们的[i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)获取更多信息,并在我们的[Discord 社区服务器](https://discord.gg/8Tpq4AcN9c)的`global-users`频道中留言。 **Contributors** diff --git a/README_DE.md b/README_DE.md index fce52c34c2..d31a56542d 100644 --- a/README_DE.md +++ b/README_DE.md @@ -236,7 +236,7 @@ Ein-Klick-Bereitstellung von Dify in der Alibaba Cloud mit [Alibaba Cloud Data M Falls Sie Code beitragen möchten, lesen Sie bitte unseren [Contribution Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). Gleichzeitig bitten wir Sie, Dify zu unterstützen, indem Sie es in den sozialen Medien teilen und auf Veranstaltungen und Konferenzen präsentieren. -> Wir suchen Mitwirkende, die dabei helfen, Dify in weitere Sprachen zu übersetzen – außer Mandarin oder Englisch. Wenn Sie Interesse an einer Mitarbeit haben, lesen Sie bitte die [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) für weitere Informationen und hinterlassen Sie einen Kommentar im `global-users`-Kanal unseres [Discord Community Servers](https://discord.gg/8Tpq4AcN9c). +> Wir suchen Mitwirkende, die dabei helfen, Dify in weitere Sprachen zu übersetzen – außer Mandarin oder Englisch. Wenn Sie Interesse an einer Mitarbeit haben, lesen Sie bitte die [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) für weitere Informationen und hinterlassen Sie einen Kommentar im `global-users`-Kanal unseres [Discord Community Servers](https://discord.gg/8Tpq4AcN9c). ## Gemeinschaft & Kontakt diff --git a/README_ES.md b/README_ES.md index 6fd6dfcee8..918bfe2286 100644 --- a/README_ES.md +++ b/README_ES.md @@ -237,7 +237,7 @@ Para aquellos que deseen contribuir con código, consulten nuestra [Guía de con Al mismo tiempo, considera apoyar a Dify compartiéndolo en redes sociales y en eventos y conferencias. -> Estamos buscando colaboradores para ayudar con la traducción de Dify a idiomas que no sean el mandarín o el inglés. Si estás interesado en ayudar, consulta el [README de i18n](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) para obtener más información y déjanos un comentario en el canal `global-users` de nuestro [Servidor de Comunidad en Discord](https://discord.gg/8Tpq4AcN9c). +> Estamos buscando colaboradores para ayudar con la traducción de Dify a idiomas que no sean el mandarín o el inglés. Si estás interesado en ayudar, consulta el [README de i18n](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) para obtener más información y déjanos un comentario en el canal `global-users` de nuestro [Servidor de Comunidad en Discord](https://discord.gg/8Tpq4AcN9c). **Contribuidores** diff --git a/README_FR.md b/README_FR.md index b2209fb495..56ca878aae 100644 --- a/README_FR.md +++ b/README_FR.md @@ -235,7 +235,7 @@ Pour ceux qui souhaitent contribuer du code, consultez notre [Guide de contribut Dans le même temps, veuillez envisager de soutenir Dify en le partageant sur les réseaux sociaux et lors d'événements et de conférences. -> Nous recherchons des contributeurs pour aider à traduire Dify dans des langues autres que le mandarin ou l'anglais. Si vous êtes intéressé à aider, veuillez consulter le [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) pour plus d'informations, et laissez-nous un commentaire dans le canal `global-users` de notre [Serveur communautaire Discord](https://discord.gg/8Tpq4AcN9c). +> Nous recherchons des contributeurs pour aider à traduire Dify dans des langues autres que le mandarin ou l'anglais. Si vous êtes intéressé à aider, veuillez consulter le [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) pour plus d'informations, et laissez-nous un commentaire dans le canal `global-users` de notre [Serveur communautaire Discord](https://discord.gg/8Tpq4AcN9c). **Contributeurs** diff --git a/README_JA.md b/README_JA.md index c658225f90..6d277a36ed 100644 --- a/README_JA.md +++ b/README_JA.md @@ -234,7 +234,7 @@ docker compose up -d 同時に、DifyをSNSやイベント、カンファレンスで共有してサポートしていただけると幸いです。 -> Difyを英語または中国語以外の言語に翻訳してくれる貢献者を募集しています。興味がある場合は、詳細については[i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md)を参照してください。また、[Discordコミュニティサーバー](https://discord.gg/8Tpq4AcN9c)の`global-users`チャンネルにコメントを残してください。 +> Difyを英語または中国語以外の言語に翻訳してくれる貢献者を募集しています。興味がある場合は、詳細については[i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)を参照してください。また、[Discordコミュニティサーバー](https://discord.gg/8Tpq4AcN9c)の`global-users`チャンネルにコメントを残してください。 **貢献者** diff --git a/README_KL.md b/README_KL.md index bfafcc7407..dac67eeb29 100644 --- a/README_KL.md +++ b/README_KL.md @@ -235,7 +235,7 @@ For those who'd like to contribute code, see our [Contribution Guide](https://gi At the same time, please consider supporting Dify by sharing it on social media and at events and conferences. -> We are looking for contributors to help with translating Dify to languages other than Mandarin or English. If you are interested in helping, please see the [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) for more information, and leave us a comment in the `global-users` channel of our [Discord Community Server](https://discord.gg/8Tpq4AcN9c). +> We are looking for contributors to help with translating Dify to languages other than Mandarin or English. If you are interested in helping, please see the [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) for more information, and leave us a comment in the `global-users` channel of our [Discord Community Server](https://discord.gg/8Tpq4AcN9c). **Contributors** diff --git a/README_KR.md b/README_KR.md index 282117e776..072481da02 100644 --- a/README_KR.md +++ b/README_KR.md @@ -229,7 +229,7 @@ Dify를 Kubernetes에 배포하고 프리미엄 스케일링 설정을 구성했 동시에 Dify를 소셜 미디어와 행사 및 컨퍼런스에 공유하여 지원하는 것을 고려해 주시기 바랍니다. -> 우리는 Dify를 중국어나 영어 이외의 언어로 번역하는 데 도움을 줄 수 있는 기여자를 찾고 있습니다. 도움을 주고 싶으시다면 [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md)에서 더 많은 정보를 확인하시고 [Discord 커뮤니티 서버](https://discord.gg/8Tpq4AcN9c)의 `global-users` 채널에 댓글을 남겨주세요. +> 우리는 Dify를 중국어나 영어 이외의 언어로 번역하는 데 도움을 줄 수 있는 기여자를 찾고 있습니다. 도움을 주고 싶으시다면 [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md)에서 더 많은 정보를 확인하시고 [Discord 커뮤니티 서버](https://discord.gg/8Tpq4AcN9c)의 `global-users` 채널에 댓글을 남겨주세요. **기여자** diff --git a/README_PT.md b/README_PT.md index 576f6b48f7..1260f8e6fd 100644 --- a/README_PT.md +++ b/README_PT.md @@ -233,7 +233,7 @@ Implante o Dify na Alibaba Cloud com um clique usando o [Alibaba Cloud Data Mana Para aqueles que desejam contribuir com código, veja nosso [Guia de Contribuição](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md). Ao mesmo tempo, considere apoiar o Dify compartilhando-o nas redes sociais e em eventos e conferências. -> Estamos buscando contribuidores para ajudar na tradução do Dify para idiomas além de Mandarim e Inglês. Se você tiver interesse em ajudar, consulte o [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) para mais informações e deixe-nos um comentário no canal `global-users` em nosso [Servidor da Comunidade no Discord](https://discord.gg/8Tpq4AcN9c). +> Estamos buscando contribuidores para ajudar na tradução do Dify para idiomas além de Mandarim e Inglês. Se você tiver interesse em ajudar, consulte o [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) para mais informações e deixe-nos um comentário no canal `global-users` em nosso [Servidor da Comunidade no Discord](https://discord.gg/8Tpq4AcN9c). **Contribuidores** diff --git a/README_TR.md b/README_TR.md index 6e94e54fa0..37953f0de1 100644 --- a/README_TR.md +++ b/README_TR.md @@ -227,7 +227,7 @@ Dify'ı bulut platformuna tek tıklamayla dağıtın [terraform](https://www.ter Kod katkısında bulunmak isteyenler için [Katkı Kılavuzumuza](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) bakabilirsiniz. Aynı zamanda, lütfen Dify'ı sosyal medyada, etkinliklerde ve konferanslarda paylaşarak desteklemeyi düşünün. -> Dify'ı Mandarin veya İngilizce dışındaki dillere çevirmemize yardımcı olacak katkıda bulunanlara ihtiyacımız var. Yardımcı olmakla ilgileniyorsanız, lütfen daha fazla bilgi için [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) dosyasına bakın ve [Discord Topluluk Sunucumuzdaki](https://discord.gg/8Tpq4AcN9c) `global-users` kanalında bize bir yorum bırakın. +> Dify'ı Mandarin veya İngilizce dışındaki dillere çevirmemize yardımcı olacak katkıda bulunanlara ihtiyacımız var. Yardımcı olmakla ilgileniyorsanız, lütfen daha fazla bilgi için [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) dosyasına bakın ve [Discord Topluluk Sunucumuzdaki](https://discord.gg/8Tpq4AcN9c) `global-users` kanalında bize bir yorum bırakın. **Katkıda Bulunanlar** diff --git a/README_TW.md b/README_TW.md index 6e3e22b5c1..f70d6a25f6 100644 --- a/README_TW.md +++ b/README_TW.md @@ -239,7 +239,7 @@ Dify 的所有功能都提供相應的 API,因此您可以輕鬆地將 Dify 對於想要貢獻程式碼的開發者,請參閱我們的[貢獻指南](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md)。 同時,也請考慮透過在社群媒體和各種活動與會議上分享 Dify 來支持我們。 -> 我們正在尋找貢獻者協助將 Dify 翻譯成中文和英文以外的語言。如果您有興趣幫忙,請查看 [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) 獲取更多資訊,並在我們的 [Discord 社群伺服器](https://discord.gg/8Tpq4AcN9c) 的 `global-users` 頻道留言給我們。 +> 我們正在尋找貢獻者協助將 Dify 翻譯成中文和英文以外的語言。如果您有興趣幫忙,請查看 [i18n README](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) 獲取更多資訊,並在我們的 [Discord 社群伺服器](https://discord.gg/8Tpq4AcN9c) 的 `global-users` 頻道留言給我們。 ## 社群與聯絡方式 diff --git a/README_VI.md b/README_VI.md index 51314e6de5..ddd9aa95f6 100644 --- a/README_VI.md +++ b/README_VI.md @@ -231,7 +231,7 @@ Triển khai Dify lên Alibaba Cloud chỉ với một cú nhấp chuột bằng Đồng thời, vui lòng xem xét hỗ trợ Dify bằng cách chia sẻ nó trên mạng xã hội và tại các sự kiện và hội nghị. -> Chúng tôi đang tìm kiếm người đóng góp để giúp dịch Dify sang các ngôn ngữ khác ngoài tiếng Trung hoặc tiếng Anh. Nếu bạn quan tâm đến việc giúp đỡ, vui lòng xem [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n/README.md) để biết thêm thông tin và để lại bình luận cho chúng tôi trong kênh `global-users` của [Máy chủ Cộng đồng Discord](https://discord.gg/8Tpq4AcN9c) của chúng tôi. +> Chúng tôi đang tìm kiếm người đóng góp để giúp dịch Dify sang các ngôn ngữ khác ngoài tiếng Trung hoặc tiếng Anh. Nếu bạn quan tâm đến việc giúp đỡ, vui lòng xem [README i18n](https://github.com/langgenius/dify/blob/main/web/i18n-config/README.md) để biết thêm thông tin và để lại bình luận cho chúng tôi trong kênh `global-users` của [Máy chủ Cộng đồng Discord](https://discord.gg/8Tpq4AcN9c) của chúng tôi. **Người đóng góp** diff --git a/web/i18n-config/README.md b/web/i18n-config/README.md index 5e7058d829..dacda966dd 100644 --- a/web/i18n-config/README.md +++ b/web/i18n-config/README.md @@ -8,7 +8,6 @@ This directory contains the internationalization (i18n) files for this project. ``` ├── [ 24] README.md -├── [ 0] README_CN.md ├── [ 704] en-US │   ├── [2.4K] app-annotation.ts │   ├── [5.2K] app-api.ts @@ -48,6 +47,7 @@ By default we will use `LanguagesSupported` to determine which languages are sup 1. Create a new folder for the new language. ``` +cd web/i18n cp -r en-US fr-FR ``` From 6914c1c85e0aa6583af344f8f1c35b804ff46fa1 Mon Sep 17 00:00:00 2001 From: lyzno1 <92089059+lyzno1@users.noreply.github.com> Date: Tue, 29 Jul 2025 21:39:40 +0800 Subject: [PATCH 70/72] fix(web): make iteration panel respect MAX_PARALLEL_LIMIT environment variable (#23083) (#23104) --- .../workflow-parallel-limit.test.tsx | 301 ++++++++++++++++++ web/app/components/workflow/constants.ts | 8 - .../components/workflow/hooks/use-workflow.ts | 8 +- .../workflow/nodes/iteration/panel.tsx | 7 +- web/config/index.ts | 15 +- 5 files changed, 319 insertions(+), 20 deletions(-) create mode 100644 web/__tests__/workflow-parallel-limit.test.tsx diff --git a/web/__tests__/workflow-parallel-limit.test.tsx b/web/__tests__/workflow-parallel-limit.test.tsx new file mode 100644 index 0000000000..0843122ab4 --- /dev/null +++ b/web/__tests__/workflow-parallel-limit.test.tsx @@ -0,0 +1,301 @@ +/** + * MAX_PARALLEL_LIMIT Configuration Bug Test + * + * This test reproduces and verifies the fix for issue #23083: + * MAX_PARALLEL_LIMIT environment variable does not take effect in iteration panel + */ + +import { render, screen } from '@testing-library/react' +import React from 'react' + +// Mock environment variables before importing constants +const originalEnv = process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT + +// Test with different environment values +function setupEnvironment(value?: string) { + if (value) + process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT = value + else + delete process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT + + // Clear module cache to force re-evaluation + jest.resetModules() +} + +function restoreEnvironment() { + if (originalEnv) + process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT = originalEnv + else + delete process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT + + jest.resetModules() +} + +// Mock i18next with proper implementation +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => { + if (key.includes('MaxParallelismTitle')) return 'Max Parallelism' + if (key.includes('MaxParallelismDesc')) return 'Maximum number of parallel executions' + if (key.includes('parallelMode')) return 'Parallel Mode' + if (key.includes('parallelPanelDesc')) return 'Enable parallel execution' + if (key.includes('errorResponseMethod')) return 'Error Response Method' + return key + }, + }), + initReactI18next: { + type: '3rdParty', + init: jest.fn(), + }, +})) + +// Mock i18next module completely to prevent initialization issues +jest.mock('i18next', () => ({ + use: jest.fn().mockReturnThis(), + init: jest.fn().mockReturnThis(), + t: jest.fn(key => key), + isInitialized: true, +})) + +// Mock the useConfig hook +jest.mock('@/app/components/workflow/nodes/iteration/use-config', () => ({ + __esModule: true, + default: () => ({ + inputs: { + is_parallel: true, + parallel_nums: 5, + error_handle_mode: 'terminated', + }, + changeParallel: jest.fn(), + changeParallelNums: jest.fn(), + changeErrorHandleMode: jest.fn(), + }), +})) + +// Mock other components +jest.mock('@/app/components/workflow/nodes/_base/components/variable/var-reference-picker', () => { + return function MockVarReferencePicker() { + return
    VarReferencePicker
    + } +}) + +jest.mock('@/app/components/workflow/nodes/_base/components/split', () => { + return function MockSplit() { + return
    Split
    + } +}) + +jest.mock('@/app/components/workflow/nodes/_base/components/field', () => { + return function MockField({ title, children }: { title: string, children: React.ReactNode }) { + return ( +
    + + {children} +
    + ) + } +}) + +jest.mock('@/app/components/base/switch', () => { + return function MockSwitch({ defaultValue }: { defaultValue: boolean }) { + return + } +}) + +jest.mock('@/app/components/base/select', () => { + return function MockSelect() { + return + } +}) + +// Use defaultValue to avoid controlled input warnings +jest.mock('@/app/components/base/slider', () => { + return function MockSlider({ value, max, min }: { value: number, max: number, min: number }) { + return ( + + ) + } +}) + +// Use defaultValue to avoid controlled input warnings +jest.mock('@/app/components/base/input', () => { + return function MockInput({ type, max, min, value }: { type: string, max: number, min: number, value: number }) { + return ( + + ) + } +}) + +describe('MAX_PARALLEL_LIMIT Configuration Bug', () => { + const mockNodeData = { + id: 'test-iteration-node', + type: 'iteration' as const, + data: { + title: 'Test Iteration', + desc: 'Test iteration node', + iterator_selector: ['test'], + output_selector: ['output'], + is_parallel: true, + parallel_nums: 5, + error_handle_mode: 'terminated' as const, + }, + } + + beforeEach(() => { + jest.clearAllMocks() + }) + + afterEach(() => { + restoreEnvironment() + }) + + afterAll(() => { + restoreEnvironment() + }) + + describe('Environment Variable Parsing', () => { + it('should parse MAX_PARALLEL_LIMIT from NEXT_PUBLIC_MAX_PARALLEL_LIMIT environment variable', () => { + setupEnvironment('25') + const { MAX_PARALLEL_LIMIT } = require('@/config') + expect(MAX_PARALLEL_LIMIT).toBe(25) + }) + + it('should fallback to default when environment variable is not set', () => { + setupEnvironment() // No environment variable + const { MAX_PARALLEL_LIMIT } = require('@/config') + expect(MAX_PARALLEL_LIMIT).toBe(10) + }) + + it('should handle invalid environment variable values', () => { + setupEnvironment('invalid') + const { MAX_PARALLEL_LIMIT } = require('@/config') + + // Should fall back to default when parsing fails + expect(MAX_PARALLEL_LIMIT).toBe(10) + }) + + it('should handle empty environment variable', () => { + setupEnvironment('') + const { MAX_PARALLEL_LIMIT } = require('@/config') + + // Should fall back to default when empty + expect(MAX_PARALLEL_LIMIT).toBe(10) + }) + + // Edge cases for boundary values + it('should clamp MAX_PARALLEL_LIMIT to MIN when env is 0 or negative', () => { + setupEnvironment('0') + let { MAX_PARALLEL_LIMIT } = require('@/config') + expect(MAX_PARALLEL_LIMIT).toBe(10) // Falls back to default + + setupEnvironment('-5') + ;({ MAX_PARALLEL_LIMIT } = require('@/config')) + expect(MAX_PARALLEL_LIMIT).toBe(10) // Falls back to default + }) + + it('should handle float numbers by parseInt behavior', () => { + setupEnvironment('12.7') + const { MAX_PARALLEL_LIMIT } = require('@/config') + // parseInt truncates to integer + expect(MAX_PARALLEL_LIMIT).toBe(12) + }) + }) + + describe('UI Component Integration (Main Fix Verification)', () => { + it('should render iteration panel with environment-configured max value', () => { + // Set environment variable to a different value + setupEnvironment('30') + + // Import Panel after setting environment + const Panel = require('@/app/components/workflow/nodes/iteration/panel').default + const { MAX_PARALLEL_LIMIT } = require('@/config') + + render( + , + ) + + // Behavior-focused assertion: UI max should equal MAX_PARALLEL_LIMIT + const numberInput = screen.getByTestId('number-input') + expect(numberInput).toHaveAttribute('data-max', String(MAX_PARALLEL_LIMIT)) + + const slider = screen.getByTestId('slider') + expect(slider).toHaveAttribute('data-max', String(MAX_PARALLEL_LIMIT)) + + // Verify the actual values + expect(MAX_PARALLEL_LIMIT).toBe(30) + expect(numberInput.getAttribute('data-max')).toBe('30') + expect(slider.getAttribute('data-max')).toBe('30') + }) + + it('should maintain UI consistency with different environment values', () => { + setupEnvironment('15') + const Panel = require('@/app/components/workflow/nodes/iteration/panel').default + const { MAX_PARALLEL_LIMIT } = require('@/config') + + render( + , + ) + + // Both input and slider should use the same max value from MAX_PARALLEL_LIMIT + const numberInput = screen.getByTestId('number-input') + const slider = screen.getByTestId('slider') + + expect(numberInput.getAttribute('data-max')).toBe(slider.getAttribute('data-max')) + expect(numberInput.getAttribute('data-max')).toBe(String(MAX_PARALLEL_LIMIT)) + }) + }) + + describe('Legacy Constant Verification (For Transition Period)', () => { + // Marked as transition/deprecation tests + it('should maintain MAX_ITERATION_PARALLEL_NUM for backward compatibility', () => { + const { MAX_ITERATION_PARALLEL_NUM } = require('@/app/components/workflow/constants') + expect(typeof MAX_ITERATION_PARALLEL_NUM).toBe('number') + expect(MAX_ITERATION_PARALLEL_NUM).toBe(10) // Hardcoded legacy value + }) + + it('should demonstrate MAX_PARALLEL_LIMIT vs legacy constant difference', () => { + setupEnvironment('50') + const { MAX_PARALLEL_LIMIT } = require('@/config') + const { MAX_ITERATION_PARALLEL_NUM } = require('@/app/components/workflow/constants') + + // MAX_PARALLEL_LIMIT is configurable, MAX_ITERATION_PARALLEL_NUM is not + expect(MAX_PARALLEL_LIMIT).toBe(50) + expect(MAX_ITERATION_PARALLEL_NUM).toBe(10) + expect(MAX_PARALLEL_LIMIT).not.toBe(MAX_ITERATION_PARALLEL_NUM) + }) + }) + + describe('Constants Validation', () => { + it('should validate that required constants exist and have correct types', () => { + const { MAX_PARALLEL_LIMIT } = require('@/config') + const { MIN_ITERATION_PARALLEL_NUM } = require('@/app/components/workflow/constants') + expect(typeof MAX_PARALLEL_LIMIT).toBe('number') + expect(typeof MIN_ITERATION_PARALLEL_NUM).toBe('number') + expect(MAX_PARALLEL_LIMIT).toBeGreaterThanOrEqual(MIN_ITERATION_PARALLEL_NUM) + }) + }) +}) diff --git a/web/app/components/workflow/constants.ts b/web/app/components/workflow/constants.ts index 0ef4dc9dea..5bf053e2c5 100644 --- a/web/app/components/workflow/constants.ts +++ b/web/app/components/workflow/constants.ts @@ -437,14 +437,6 @@ export const NODE_LAYOUT_HORIZONTAL_PADDING = 60 export const NODE_LAYOUT_VERTICAL_PADDING = 60 export const NODE_LAYOUT_MIN_DISTANCE = 100 -let maxParallelLimit = 10 - -if (process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT && process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT !== '') - maxParallelLimit = Number.parseInt(process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT) -else if (globalThis.document?.body?.getAttribute('data-public-max-parallel-limit') && globalThis.document.body.getAttribute('data-public-max-parallel-limit') !== '') - maxParallelLimit = Number.parseInt(globalThis.document.body.getAttribute('data-public-max-parallel-limit') as string) - -export const PARALLEL_LIMIT = maxParallelLimit export const PARALLEL_DEPTH_LIMIT = 3 export const RETRIEVAL_OUTPUT_STRUCT = `{ diff --git a/web/app/components/workflow/hooks/use-workflow.ts b/web/app/components/workflow/hooks/use-workflow.ts index 8bc9d3436f..f9120f45b1 100644 --- a/web/app/components/workflow/hooks/use-workflow.ts +++ b/web/app/components/workflow/hooks/use-workflow.ts @@ -30,7 +30,6 @@ import { } from '../utils' import { PARALLEL_DEPTH_LIMIT, - PARALLEL_LIMIT, SUPPORT_OUTPUT_VARS_NODE, } from '../constants' import { CUSTOM_NOTE_NODE } from '../note-node/constants' @@ -48,6 +47,7 @@ import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/ite import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants' import { basePath } from '@/utils/var' import { canFindTool } from '@/utils' +import { MAX_PARALLEL_LIMIT } from '@/config' export const useIsChatMode = () => { const appDetail = useAppStore(s => s.appDetail) @@ -270,8 +270,6 @@ export const useWorkflow = () => { }) setNodes(newNodes) } - - // eslint-disable-next-line react-hooks/exhaustive-deps }, [store]) const isVarUsedInNodes = useCallback((varSelector: ValueSelector) => { @@ -310,9 +308,9 @@ export const useWorkflow = () => { edges, } = store.getState() const connectedEdges = edges.filter(edge => edge.source === nodeId && edge.sourceHandle === nodeHandle) - if (connectedEdges.length > PARALLEL_LIMIT - 1) { + if (connectedEdges.length > MAX_PARALLEL_LIMIT - 1) { const { setShowTips } = workflowStore.getState() - setShowTips(t('workflow.common.parallelTip.limit', { num: PARALLEL_LIMIT })) + setShowTips(t('workflow.common.parallelTip.limit', { num: MAX_PARALLEL_LIMIT })) return false } diff --git a/web/app/components/workflow/nodes/iteration/panel.tsx b/web/app/components/workflow/nodes/iteration/panel.tsx index 4b529f0785..23e93b0dd5 100644 --- a/web/app/components/workflow/nodes/iteration/panel.tsx +++ b/web/app/components/workflow/nodes/iteration/panel.tsx @@ -3,7 +3,7 @@ import React from 'react' import { useTranslation } from 'react-i18next' import VarReferencePicker from '../_base/components/variable/var-reference-picker' import Split from '../_base/components/split' -import { MAX_ITERATION_PARALLEL_NUM, MIN_ITERATION_PARALLEL_NUM } from '../../constants' +import { MIN_ITERATION_PARALLEL_NUM } from '../../constants' import type { IterationNodeType } from './types' import useConfig from './use-config' import { ErrorHandleMode, type NodePanelProps } from '@/app/components/workflow/types' @@ -12,6 +12,7 @@ import Switch from '@/app/components/base/switch' import Select from '@/app/components/base/select' import Slider from '@/app/components/base/slider' import Input from '@/app/components/base/input' +import { MAX_PARALLEL_LIMIT } from '@/config' const i18nPrefix = 'workflow.nodes.iteration' @@ -96,11 +97,11 @@ const Panel: FC> = ({ inputs.is_parallel && (
    {t(`${i18nPrefix}.MaxParallelismDesc`)}
    }>
    - { changeParallelNums(Number(e.target.value)) }} /> + { changeParallelNums(Number(e.target.value)) }} /> diff --git a/web/config/index.ts b/web/config/index.ts index 667723aaaf..4a8b07d6e4 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -13,12 +13,18 @@ const getBooleanConfig = (envVar: string | undefined, dataAttrKey: DatasetAttr, } const getNumberConfig = (envVar: string | undefined, dataAttrKey: DatasetAttr, defaultValue: number) => { - if (envVar) - return Number.parseInt(envVar) + if (envVar) { + const parsed = Number.parseInt(envVar) + if (!Number.isNaN(parsed) && parsed > 0) + return parsed + } const attrValue = globalThis.document?.body?.getAttribute(dataAttrKey) - if (attrValue) - return Number.parseInt(attrValue) + if (attrValue) { + const parsed = Number.parseInt(attrValue) + if (!Number.isNaN(parsed) && parsed > 0) + return parsed + } return defaultValue } @@ -265,6 +271,7 @@ export const FULL_DOC_PREVIEW_LENGTH = 50 export const JSON_SCHEMA_MAX_DEPTH = 10 export const MAX_TOOLS_NUM = getNumberConfig(process.env.NEXT_PUBLIC_MAX_TOOLS_NUM, DatasetAttr.DATA_PUBLIC_MAX_TOOLS_NUM, 10) +export const MAX_PARALLEL_LIMIT = getNumberConfig(process.env.NEXT_PUBLIC_MAX_PARALLEL_LIMIT, DatasetAttr.DATA_PUBLIC_MAX_PARALLEL_LIMIT, 10) export const TEXT_GENERATION_TIMEOUT_MS = getNumberConfig(process.env.NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS, DatasetAttr.DATA_PUBLIC_TEXT_GENERATION_TIMEOUT_MS, 60000) export const LOOP_NODE_MAX_COUNT = getNumberConfig(process.env.NEXT_PUBLIC_LOOP_NODE_MAX_COUNT, DatasetAttr.DATA_PUBLIC_LOOP_NODE_MAX_COUNT, 100) export const MAX_ITERATIONS_NUM = getNumberConfig(process.env.NEXT_PUBLIC_MAX_ITERATIONS_NUM, DatasetAttr.DATA_PUBLIC_MAX_ITERATIONS_NUM, 99) From ab7c2cf000b1567a8ed5c28a9ff8c06dae1e579c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=86=E8=90=8C=E9=97=B7=E6=B2=B9=E7=93=B6?= <253605712@qq.com> Date: Tue, 29 Jul 2025 21:40:03 +0800 Subject: [PATCH 71/72] minor fix: Object of type int64 is not JSON serializable (#23109) --- api/services/annotation_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/services/annotation_service.py b/api/services/annotation_service.py index 80dd63bf89..cfa917daf6 100644 --- a/api/services/annotation_service.py +++ b/api/services/annotation_service.py @@ -280,7 +280,7 @@ class AppAnnotationService: try: # Skip the first row - df = pd.read_csv(file) + df = pd.read_csv(file, dtype=str) result = [] for index, row in df.iterrows(): content = {"question": row.iloc[0], "answer": row.iloc[1]} From 72a2c3decf2b53d1c74c8f04ddabe4762b76827c Mon Sep 17 00:00:00 2001 From: baonudesifeizhai <85092850+baonudesifeizhai@users.noreply.github.com> Date: Tue, 29 Jul 2025 09:40:15 -0400 Subject: [PATCH 72/72] Fix/http node timeout validation#23077 (#23117) Co-authored-by: crazywoola <427733928@qq.com> --- .../nodes/http/components/timeout/index.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/web/app/components/workflow/nodes/http/components/timeout/index.tsx b/web/app/components/workflow/nodes/http/components/timeout/index.tsx index b0fd3b229e..40ebab0e2a 100644 --- a/web/app/components/workflow/nodes/http/components/timeout/index.tsx +++ b/web/app/components/workflow/nodes/http/components/timeout/index.tsx @@ -20,7 +20,7 @@ const InputField: FC<{ description: string placeholder: string value?: number - onChange: (value: number) => void + onChange: (value: number | undefined) => void readOnly?: boolean min: number max: number @@ -35,8 +35,18 @@ const InputField: FC<{ type='number' value={value} onChange={(e) => { - const value = Math.max(min, Math.min(max, Number.parseInt(e.target.value, 10))) - onChange(value) + const inputValue = e.target.value + if (inputValue === '') { + // When user clears the input, set to undefined to let backend use default values + onChange(undefined) + } + else { + const parsedValue = Number.parseInt(inputValue, 10) + if (!Number.isNaN(parsedValue)) { + const value = Math.max(min, Math.min(max, parsedValue)) + onChange(value) + } + } }} placeholder={placeholder} readOnly={readOnly}