From e576b989b8eca4a7d8cfe6bdc91e9a451580489b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AF=97=E6=B5=93?= Date: Fri, 11 Jul 2025 10:39:20 +0800 Subject: [PATCH 01/25] feat(tool): add support for API key authentication via query parameter (#21656) --- api/core/tools/custom_tool/provider.py | 28 ++++++++-- api/core/tools/custom_tool/tool.py | 18 ++++++- api/core/tools/entities/tool_entities.py | 3 +- api/core/tools/tool_manager.py | 18 ++++++- api/services/tools/tools_transform_service.py | 11 ++-- .../config-credentials.tsx | 52 ++++++++++++++++--- web/app/components/tools/types.ts | 4 +- web/i18n/en-US/tools.ts | 6 ++- web/i18n/zh-Hans/tools.ts | 6 ++- 9 files changed, 126 insertions(+), 20 deletions(-) diff --git a/api/core/tools/custom_tool/provider.py b/api/core/tools/custom_tool/provider.py index 3137d32013..fbe1d79137 100644 --- a/api/core/tools/custom_tool/provider.py +++ b/api/core/tools/custom_tool/provider.py @@ -39,19 +39,22 @@ class ApiToolProviderController(ToolProviderController): type=ProviderConfig.Type.SELECT, options=[ ProviderConfig.Option(value="none", label=I18nObject(en_US="None", zh_Hans="无")), - ProviderConfig.Option(value="api_key", label=I18nObject(en_US="api_key", zh_Hans="api_key")), + ProviderConfig.Option(value="api_key_header", label=I18nObject(en_US="Header", zh_Hans="请求头")), + ProviderConfig.Option( + value="api_key_query", label=I18nObject(en_US="Query Param", zh_Hans="查询参数") + ), ], default="none", help=I18nObject(en_US="The auth type of the api provider", zh_Hans="api provider 的认证类型"), ) ] - if auth_type == ApiProviderAuthType.API_KEY: + if auth_type == ApiProviderAuthType.API_KEY_HEADER: credentials_schema = [ *credentials_schema, ProviderConfig( name="api_key_header", required=False, - default="api_key", + default="Authorization", type=ProviderConfig.Type.TEXT_INPUT, help=I18nObject(en_US="The header name of the api key", zh_Hans="携带 api key 的 header 名称"), ), @@ -74,6 +77,25 @@ class ApiToolProviderController(ToolProviderController): ], ), ] + elif auth_type == ApiProviderAuthType.API_KEY_QUERY: + credentials_schema = [ + *credentials_schema, + ProviderConfig( + name="api_key_query_param", + required=False, + default="key", + type=ProviderConfig.Type.TEXT_INPUT, + help=I18nObject( + en_US="The query parameter name of the api key", zh_Hans="携带 api key 的查询参数名称" + ), + ), + ProviderConfig( + name="api_key_value", + required=True, + type=ProviderConfig.Type.SECRET_INPUT, + help=I18nObject(en_US="The api key", zh_Hans="api key 的值"), + ), + ] elif auth_type == ApiProviderAuthType.NONE: pass diff --git a/api/core/tools/custom_tool/tool.py b/api/core/tools/custom_tool/tool.py index 5cba4cf7f5..10653b9948 100644 --- a/api/core/tools/custom_tool/tool.py +++ b/api/core/tools/custom_tool/tool.py @@ -78,8 +78,8 @@ class ApiTool(Tool): if "auth_type" not in credentials: raise ToolProviderCredentialValidationError("Missing auth_type") - if credentials["auth_type"] == "api_key": - api_key_header = "api_key" + if credentials["auth_type"] in ("api_key_header", "api_key"): # backward compatibility: + api_key_header = "Authorization" if "api_key_header" in credentials: api_key_header = credentials["api_key_header"] @@ -100,6 +100,11 @@ class ApiTool(Tool): headers[api_key_header] = credentials["api_key_value"] + elif credentials["auth_type"] == "api_key_query": + # For query parameter authentication, we don't add anything to headers + # The query parameter will be added in do_http_request method + pass + needed_parameters = [parameter for parameter in (self.api_bundle.parameters or []) if parameter.required] for parameter in needed_parameters: if parameter.required and parameter.name not in parameters: @@ -154,6 +159,15 @@ class ApiTool(Tool): cookies = {} files = [] + # Add API key to query parameters if auth_type is api_key_query + if self.runtime and self.runtime.credentials: + credentials = self.runtime.credentials + if credentials.get("auth_type") == "api_key_query": + api_key_query_param = credentials.get("api_key_query_param", "key") + api_key_value = credentials.get("api_key_value") + if api_key_value: + params[api_key_query_param] = api_key_value + # check parameters for parameter in self.api_bundle.openapi.get("parameters", []): value = self.get_parameter_value(parameter, parameters) diff --git a/api/core/tools/entities/tool_entities.py b/api/core/tools/entities/tool_entities.py index bd216dad64..b5148e245f 100644 --- a/api/core/tools/entities/tool_entities.py +++ b/api/core/tools/entities/tool_entities.py @@ -96,7 +96,8 @@ class ApiProviderAuthType(Enum): """ NONE = "none" - API_KEY = "api_key" + API_KEY_HEADER = "api_key_header" + API_KEY_QUERY = "api_key_query" @classmethod def value_of(cls, value: str) -> "ApiProviderAuthType": diff --git a/api/core/tools/tool_manager.py b/api/core/tools/tool_manager.py index adae56cd27..22a9853b41 100644 --- a/api/core/tools/tool_manager.py +++ b/api/core/tools/tool_manager.py @@ -684,9 +684,16 @@ class ToolManager: if provider is None: raise ToolProviderNotFoundError(f"api provider {provider_id} not found") + auth_type = ApiProviderAuthType.NONE + provider_auth_type = provider.credentials.get("auth_type") + if provider_auth_type in ("api_key_header", "api_key"): # backward compatibility + auth_type = ApiProviderAuthType.API_KEY_HEADER + elif provider_auth_type == "api_key_query": + auth_type = ApiProviderAuthType.API_KEY_QUERY + controller = ApiToolProviderController.from_db( provider, - ApiProviderAuthType.API_KEY if provider.credentials["auth_type"] == "api_key" else ApiProviderAuthType.NONE, + auth_type, ) controller.load_bundled_tools(provider.tools) @@ -745,9 +752,16 @@ class ToolManager: credentials = {} # package tool provider controller + auth_type = ApiProviderAuthType.NONE + credentials_auth_type = credentials.get("auth_type") + if credentials_auth_type in ("api_key_header", "api_key"): # backward compatibility + auth_type = ApiProviderAuthType.API_KEY_HEADER + elif credentials_auth_type == "api_key_query": + auth_type = ApiProviderAuthType.API_KEY_QUERY + controller = ApiToolProviderController.from_db( provider_obj, - ApiProviderAuthType.API_KEY if credentials["auth_type"] == "api_key" else ApiProviderAuthType.NONE, + auth_type, ) # init tool configuration tool_configuration = ProviderConfigEncrypter( diff --git a/api/services/tools/tools_transform_service.py b/api/services/tools/tools_transform_service.py index ac127ae93e..3d0c35cd9b 100644 --- a/api/services/tools/tools_transform_service.py +++ b/api/services/tools/tools_transform_service.py @@ -159,11 +159,16 @@ class ToolTransformService: convert provider controller to user provider """ # package tool provider controller + auth_type = ApiProviderAuthType.NONE + credentials_auth_type = db_provider.credentials.get("auth_type") + if credentials_auth_type in ("api_key_header", "api_key"): # backward compatibility + auth_type = ApiProviderAuthType.API_KEY_HEADER + elif credentials_auth_type == "api_key_query": + auth_type = ApiProviderAuthType.API_KEY_QUERY + controller = ApiToolProviderController.from_db( db_provider=db_provider, - auth_type=ApiProviderAuthType.API_KEY - if db_provider.credentials["auth_type"] == "api_key" - else ApiProviderAuthType.NONE, + auth_type=auth_type, ) return controller diff --git a/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx b/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx index cbf1048b09..f0ad13f9b1 100644 --- a/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx +++ b/web/app/components/tools/edit-custom-collection-modal/config-credentials.tsx @@ -68,23 +68,34 @@ const ConfigCredential: FC = ({ text={t('tools.createTool.authMethod.types.none')} value={AuthType.none} isChecked={tempCredential.auth_type === AuthType.none} - onClick={value => setTempCredential({ ...tempCredential, auth_type: value as AuthType })} + onClick={value => setTempCredential({ + auth_type: value as AuthType, + })} /> setTempCredential({ - ...tempCredential, auth_type: value as AuthType, api_key_header: tempCredential.api_key_header || 'Authorization', api_key_value: tempCredential.api_key_value || '', api_key_header_prefix: tempCredential.api_key_header_prefix || AuthHeaderPrefix.custom, })} /> + setTempCredential({ + auth_type: value as AuthType, + api_key_query_param: tempCredential.api_key_query_param || 'key', + api_key_value: tempCredential.api_key_value || '', + })} + /> - {tempCredential.auth_type === AuthType.apiKey && ( + {tempCredential.auth_type === AuthType.apiKeyHeader && ( <>
{t('tools.createTool.authHeaderPrefix.title')}
@@ -136,6 +147,35 @@ const ConfigCredential: FC = ({ />
)} + {tempCredential.auth_type === AuthType.apiKeyQuery && ( + <> +
+
+ {t('tools.createTool.authMethod.queryParam')} + + {t('tools.createTool.authMethod.queryParamTooltip')} +
+ } + triggerClassName='ml-0.5 w-4 h-4' + /> +
+ setTempCredential({ ...tempCredential, api_key_query_param: e.target.value })} + placeholder={t('tools.createTool.authMethod.types.queryParamPlaceholder')!} + /> + +
+
{t('tools.createTool.authMethod.value')}
+ setTempCredential({ ...tempCredential, api_key_value: e.target.value })} + placeholder={t('tools.createTool.authMethod.types.apiValuePlaceholder')!} + /> +
+ )} diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index d444ee1f38..b83919ad18 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -7,7 +7,8 @@ export enum LOC { export enum AuthType { none = 'none', - apiKey = 'api_key', + apiKeyHeader = 'api_key_header', + apiKeyQuery = 'api_key_query', } export enum AuthHeaderPrefix { @@ -21,6 +22,7 @@ export type Credential = { api_key_header?: string api_key_value?: string api_key_header_prefix?: AuthHeaderPrefix + api_key_query_param?: string } export enum CollectionType { diff --git a/web/i18n/en-US/tools.ts b/web/i18n/en-US/tools.ts index 418d1cb076..4e1ce1308a 100644 --- a/web/i18n/en-US/tools.ts +++ b/web/i18n/en-US/tools.ts @@ -80,11 +80,15 @@ const translation = { title: 'Authorization method', type: 'Authorization type', keyTooltip: 'Http Header Key, You can leave it with "Authorization" if you have no idea what it is or set it to a custom value', + queryParam: 'Query Parameter', + queryParamTooltip: 'The name of the API key query parameter to pass, e.g. "key" in "https://example.com/test?key=API_KEY".', types: { none: 'None', - api_key: 'API Key', + api_key_header: 'Header', + api_key_query: 'Query Param', apiKeyPlaceholder: 'HTTP header name for API Key', apiValuePlaceholder: 'Enter API Key', + queryParamPlaceholder: 'Query parameter name for API Key', }, key: 'Key', value: 'Value', diff --git a/web/i18n/zh-Hans/tools.ts b/web/i18n/zh-Hans/tools.ts index 4e0ccf476f..5c1eb13236 100644 --- a/web/i18n/zh-Hans/tools.ts +++ b/web/i18n/zh-Hans/tools.ts @@ -80,11 +80,15 @@ const translation = { title: '鉴权方法', type: '鉴权类型', keyTooltip: 'HTTP 头部名称,如果你不知道是什么,可以将其保留为 Authorization 或设置为自定义值', + queryParam: '查询参数', + queryParamTooltip: '用于传递 API 密钥查询参数的名称, 如 "https://example.com/test?key=API_KEY" 中的 "key"参数', types: { none: '无', - api_key: 'API Key', + api_key_header: '请求头', + api_key_query: '查询参数', apiKeyPlaceholder: 'HTTP 头部名称,用于传递 API Key', apiValuePlaceholder: '输入 API Key', + queryParamPlaceholder: '查询参数名称,用于传递 API Key', }, key: '键', value: '值', From c805238471eb4f15daa830730dbc828bd468f051 Mon Sep 17 00:00:00 2001 From: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:17:28 +0800 Subject: [PATCH 02/25] fix: adjust layout styles for header and dataset update (#22182) --- web/app/components/datasets/create/index.tsx | 2 +- .../notion-page-preview/index.module.css | 2 +- .../datasets/create/step-one/index.tsx | 43 ++++++++++++------- web/app/components/header/index.tsx | 2 +- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/web/app/components/datasets/create/index.tsx b/web/app/components/datasets/create/index.tsx index b1e4087226..a1ff2f5d87 100644 --- a/web/app/components/datasets/create/index.tsx +++ b/web/app/components/datasets/create/index.tsx @@ -122,7 +122,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => { return return ( -
+
{step === 1 && setShowModal(true) const modalCloseHandle = () => setShowModal(false) - const updateCurrentFile = (file: File) => { + const updateCurrentFile = useCallback((file: File) => { setCurrentFile(file) - } - const hideFilePreview = () => { + }, []) + + const hideFilePreview = useCallback(() => { setCurrentFile(undefined) - } + }, []) - const updateCurrentPage = (page: NotionPage) => { + const updateCurrentPage = useCallback((page: NotionPage) => { setCurrentNotionPage(page) - } + }, []) - const hideNotionPagePreview = () => { + const hideNotionPagePreview = useCallback(() => { setCurrentNotionPage(undefined) - } + }, []) + + const updateWebsite = useCallback((website: CrawlResultItem) => { + setCurrentWebsite(website) + }, []) - const hideWebsitePreview = () => { + const hideWebsitePreview = useCallback(() => { setCurrentWebsite(undefined) - } + }, []) const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type) const isInCreatePage = shouldShowDataSourceTypeList @@ -139,7 +144,7 @@ const StepOne = ({
{ shouldShowDataSourceTypeList && ( -
+
{t('datasetCreation.steps.one')}
) @@ -158,8 +163,8 @@ const StepOne = ({ if (dataSourceTypeDisable) return changeType(DataSourceType.FILE) - hideFilePreview() hideNotionPagePreview() + hideWebsitePreview() }} > @@ -182,7 +187,7 @@ const StepOne = ({ return changeType(DataSourceType.NOTION) hideFilePreview() - hideNotionPagePreview() + hideWebsitePreview() }} > @@ -201,7 +206,13 @@ const StepOne = ({ dataSourceType === DataSourceType.WEB && s.active, dataSourceTypeDisable && dataSourceType !== DataSourceType.WEB && s.disabled, )} - onClick={() => changeType(DataSourceType.WEB)} + onClick={() => { + if (dataSourceTypeDisable) + return + changeType(DataSourceType.WEB) + hideFilePreview() + hideNotionPagePreview() + }} >
{ } return ( -
+
{systemFeatures.branding.enabled && systemFeatures.branding.workspace_logo From d5624ba671201137c32b80ad6e2131feb85fed19 Mon Sep 17 00:00:00 2001 From: K <84141602+krikera@users.noreply.github.com> Date: Fri, 11 Jul 2025 09:41:59 +0530 Subject: [PATCH 03/25] fix: resolve Docker file URL networking issue for plugins (#21334) (#21382) Co-authored-by: crazywoola <427733928@qq.com> --- api/.env.example | 5 +++++ api/configs/feature/__init__.py | 7 +++++++ api/core/file/helpers.py | 4 +++- api/core/tools/signature.py | 5 +++-- api/core/tools/tool_file_manager.py | 5 +++-- docker/.env.example | 5 +++++ docker/docker-compose.yaml | 1 + 7 files changed, 27 insertions(+), 5 deletions(-) diff --git a/api/.env.example b/api/.env.example index baa9c382c8..a7ea6cf937 100644 --- a/api/.env.example +++ b/api/.env.example @@ -17,6 +17,11 @@ APP_WEB_URL=http://127.0.0.1:3000 # Files URL FILES_URL=http://127.0.0.1:5001 +# INTERNAL_FILES_URL is used for plugin daemon communication within Docker network. +# Set this to the internal Docker service URL for proper plugin file access. +# Example: INTERNAL_FILES_URL=http://api:5001 +INTERNAL_FILES_URL=http://127.0.0.1:5001 + # The time in seconds after the signature is rejected FILES_ACCESS_TIMEOUT=300 diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index df15b92c35..963fcbedf9 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -237,6 +237,13 @@ class FileAccessConfig(BaseSettings): default="", ) + INTERNAL_FILES_URL: str = Field( + description="Internal base URL for file access within Docker network," + " used for plugin daemon and internal service communication." + " Falls back to FILES_URL if not specified.", + default="", + ) + FILES_ACCESS_TIMEOUT: int = Field( description="Expiration time in seconds for file access URLs", default=300, diff --git a/api/core/file/helpers.py b/api/core/file/helpers.py index 73fabdb11b..335ad2266a 100644 --- a/api/core/file/helpers.py +++ b/api/core/file/helpers.py @@ -21,7 +21,9 @@ def get_signed_file_url(upload_file_id: str) -> str: def get_signed_file_url_for_plugin(filename: str, mimetype: str, tenant_id: str, user_id: str) -> str: - url = f"{dify_config.FILES_URL}/files/upload/for-plugin" + # Plugin access should use internal URL for Docker network communication + base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL + url = f"{base_url}/files/upload/for-plugin" if user_id is None: user_id = "DEFAULT-USER" diff --git a/api/core/tools/signature.py b/api/core/tools/signature.py index e80005d7bf..5cdf473542 100644 --- a/api/core/tools/signature.py +++ b/api/core/tools/signature.py @@ -9,9 +9,10 @@ from configs import dify_config def sign_tool_file(tool_file_id: str, extension: str) -> str: """ - sign file to get a temporary url + sign file to get a temporary url for plugin access """ - base_url = dify_config.FILES_URL + # Use internal URL for plugin/tool file access in Docker environments + base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL file_preview_url = f"{base_url}/files/tools/{tool_file_id}{extension}" timestamp = str(int(time.time())) diff --git a/api/core/tools/tool_file_manager.py b/api/core/tools/tool_file_manager.py index b849f51064..ece02f9d59 100644 --- a/api/core/tools/tool_file_manager.py +++ b/api/core/tools/tool_file_manager.py @@ -35,9 +35,10 @@ class ToolFileManager: @staticmethod def sign_file(tool_file_id: str, extension: str) -> str: """ - sign file to get a temporary url + sign file to get a temporary url for plugin access """ - base_url = dify_config.FILES_URL + # Use internal URL for plugin/tool file access in Docker environments + base_url = dify_config.INTERNAL_FILES_URL or dify_config.FILES_URL file_preview_url = f"{base_url}/files/tools/{tool_file_id}{extension}" timestamp = str(int(time.time())) diff --git a/docker/.env.example b/docker/.env.example index a403f25cb2..84b6152f0a 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -47,6 +47,11 @@ APP_WEB_URL= # ensuring port 5001 is externally accessible (see docker-compose.yaml). FILES_URL= +# INTERNAL_FILES_URL is used for plugin daemon communication within Docker network. +# Set this to the internal Docker service URL for proper plugin file access. +# Example: INTERNAL_FILES_URL=http://api:5001 +INTERNAL_FILES_URL= + # ------------------------------ # Server Configuration # ------------------------------ diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 647af62d96..ac9953aa33 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -11,6 +11,7 @@ x-shared-env: &shared-api-worker-env APP_API_URL: ${APP_API_URL:-} APP_WEB_URL: ${APP_WEB_URL:-} FILES_URL: ${FILES_URL:-} + INTERNAL_FILES_URL: ${INTERNAL_FILES_URL:-} LOG_LEVEL: ${LOG_LEVEL:-INFO} LOG_FILE: ${LOG_FILE:-/app/logs/server.log} LOG_FILE_MAX_SIZE: ${LOG_FILE_MAX_SIZE:-20} From 9a9ec0c99bd7ddbf4b1aaa8767e4280510f8c67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcelo=20D=C3=ADaz?= <40875838+marcelodiaz558@users.noreply.github.com> Date: Fri, 11 Jul 2025 02:04:42 -0400 Subject: [PATCH 04/25] feat: Add Audio configuration setting to app configuration UI (#21957) --- .../app/configuration/config/config-audio.tsx | 78 +++++++++++++++++++ .../app/configuration/config/index.tsx | 3 + .../components/app/configuration/index.tsx | 2 + web/context/debug-configuration.ts | 2 + web/i18n/en-US/app-debug.ts | 4 + 5 files changed, 89 insertions(+) create mode 100644 web/app/components/app/configuration/config/config-audio.tsx diff --git a/web/app/components/app/configuration/config/config-audio.tsx b/web/app/components/app/configuration/config/config-audio.tsx new file mode 100644 index 0000000000..5600f8cbb6 --- /dev/null +++ b/web/app/components/app/configuration/config/config-audio.tsx @@ -0,0 +1,78 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import produce from 'immer' +import { useContext } from 'use-context-selector' + +import { Microphone01 } from '@/app/components/base/icons/src/vender/features' +import Tooltip from '@/app/components/base/tooltip' +import ConfigContext from '@/context/debug-configuration' +import { SupportUploadFileTypes } from '@/app/components/workflow/types' +import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks' +import Switch from '@/app/components/base/switch' + +const ConfigAudio: FC = () => { + const { t } = useTranslation() + const file = useFeatures(s => s.features.file) + const featuresStore = useFeaturesStore() + const { isShowAudioConfig } = useContext(ConfigContext) + + const isAudioEnabled = file?.allowed_file_types?.includes(SupportUploadFileTypes.audio) ?? false + + const handleChange = useCallback((value: boolean) => { + const { + features, + setFeatures, + } = featuresStore!.getState() + + const newFeatures = produce(features, (draft) => { + if (value) { + draft.file!.allowed_file_types = Array.from(new Set([ + ...(draft.file?.allowed_file_types || []), + SupportUploadFileTypes.audio, + ])) + } + else { + draft.file!.allowed_file_types = draft.file!.allowed_file_types?.filter( + type => type !== SupportUploadFileTypes.audio, + ) + } + if (draft.file) + draft.file.enabled = (draft.file.allowed_file_types?.length ?? 0) > 0 + }) + setFeatures(newFeatures) + }, [featuresStore]) + + if (!isShowAudioConfig) + return null + + return ( +
+
+
+ +
+
+
+
{t('appDebug.feature.audioUpload.title')}
+ + {t('appDebug.feature.audioUpload.description')} +
+ } + /> +
+
+
+ +
+
+ ) +} +export default React.memo(ConfigAudio) diff --git a/web/app/components/app/configuration/config/index.tsx b/web/app/components/app/configuration/config/index.tsx index dc2095502e..d0375c6de9 100644 --- a/web/app/components/app/configuration/config/index.tsx +++ b/web/app/components/app/configuration/config/index.tsx @@ -8,6 +8,7 @@ import DatasetConfig from '../dataset-config' import HistoryPanel from '../config-prompt/conversation-history/history-panel' import ConfigVision from '../config-vision' import ConfigDocument from './config-document' +import ConfigAudio from './config-audio' import AgentTools from './agent/agent-tools' import ConfigContext from '@/context/debug-configuration' import ConfigPrompt from '@/app/components/app/configuration/config-prompt' @@ -85,6 +86,8 @@ const Config: FC = () => { + + {/* Chat History */} {isAdvancedMode && isChatApp && modelModeType === ModelModeType.completion && ( { const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision) const isShowDocumentConfig = !!currModel?.features?.includes(ModelFeatureEnum.document) + const isShowAudioConfig = !!currModel?.features?.includes(ModelFeatureEnum.audio) const isAllowVideoUpload = !!currModel?.features?.includes(ModelFeatureEnum.video) // *** web app features *** const featuresData: FeaturesData = useMemo(() => { @@ -920,6 +921,7 @@ const Configuration: FC = () => { setVisionConfig: handleSetVisionConfig, isAllowVideoUpload, isShowDocumentConfig, + isShowAudioConfig, rerankSettingModalOpen, setRerankSettingModalOpen, }} diff --git a/web/context/debug-configuration.ts b/web/context/debug-configuration.ts index 47710c8fc1..cb737c5288 100644 --- a/web/context/debug-configuration.ts +++ b/web/context/debug-configuration.ts @@ -102,6 +102,7 @@ type IDebugConfiguration = { setVisionConfig: (visionConfig: VisionSettings, noNotice?: boolean) => void isAllowVideoUpload: boolean isShowDocumentConfig: boolean + isShowAudioConfig: boolean rerankSettingModalOpen: boolean setRerankSettingModalOpen: (rerankSettingModalOpen: boolean) => void } @@ -254,6 +255,7 @@ const DebugConfigurationContext = createContext({ setVisionConfig: noop, isAllowVideoUpload: false, isShowDocumentConfig: false, + isShowAudioConfig: false, rerankSettingModalOpen: false, setRerankSettingModalOpen: noop, }) diff --git a/web/i18n/en-US/app-debug.ts b/web/i18n/en-US/app-debug.ts index 349ff37118..5282dab360 100644 --- a/web/i18n/en-US/app-debug.ts +++ b/web/i18n/en-US/app-debug.ts @@ -222,6 +222,10 @@ const translation = { title: 'Document', description: 'Enable Document will allows the model to take in documents and answer questions about them.', }, + audioUpload: { + title: 'Audio', + description: 'Enable Audio will allow the model to process audio files for transcription and analysis.', + }, }, codegen: { title: 'Code Generator', From fe4e2f7921e4362f699cb07f2bcd75f5774632ba Mon Sep 17 00:00:00 2001 From: le0zh Date: Fri, 11 Jul 2025 15:07:32 +0800 Subject: [PATCH 05/25] feat: support var in suggested questions (#17340) Co-authored-by: crazywoola <427733928@qq.com> --- web/app/components/base/chat/chat/hooks.ts | 5 ++--- .../features/new-feature-panel/conversation-opener/modal.tsx | 1 + web/app/components/workflow/panel/debug-and-preview/hooks.ts | 4 ++-- web/i18n/de-DE/app-debug.ts | 1 + web/i18n/en-US/app-debug.ts | 1 + web/i18n/es-ES/app-debug.ts | 1 + web/i18n/fa-IR/app-debug.ts | 1 + web/i18n/fr-FR/app-debug.ts | 1 + web/i18n/hi-IN/app-debug.ts | 1 + web/i18n/it-IT/app-debug.ts | 1 + web/i18n/ja-JP/app-debug.ts | 1 + web/i18n/ko-KR/app-debug.ts | 1 + web/i18n/pl-PL/app-debug.ts | 1 + web/i18n/pt-BR/app-debug.ts | 1 + web/i18n/ru-RU/app-debug.ts | 1 + web/i18n/tr-TR/app-debug.ts | 1 + web/i18n/uk-UA/app-debug.ts | 1 + web/i18n/vi-VN/app-debug.ts | 1 + web/i18n/zh-Hans/app-debug.ts | 1 + web/i18n/zh-Hant/app-debug.ts | 1 + 20 files changed, 22 insertions(+), 5 deletions(-) diff --git a/web/app/components/base/chat/chat/hooks.ts b/web/app/components/base/chat/chat/hooks.ts index 10fb455d33..17373cec9d 100644 --- a/web/app/components/base/chat/chat/hooks.ts +++ b/web/app/components/base/chat/chat/hooks.ts @@ -83,12 +83,11 @@ export const useChat = ( const ret = [...threadMessages] if (config?.opening_statement) { const index = threadMessages.findIndex(item => item.isOpeningStatement) - if (index > -1) { ret[index] = { ...ret[index], content: getIntroduction(config.opening_statement), - suggestedQuestions: config.suggested_questions, + suggestedQuestions: config.suggested_questions?.map(item => getIntroduction(item)), } } else { @@ -97,7 +96,7 @@ export const useChat = ( content: getIntroduction(config.opening_statement), isAnswer: true, isOpeningStatement: true, - suggestedQuestions: config.suggested_questions, + suggestedQuestions: config.suggested_questions?.map(item => getIntroduction(item)), }) } } diff --git a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx index 117c8a5558..51e33c43d2 100644 --- a/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx +++ b/web/app/components/base/features/new-feature-panel/conversation-opener/modal.tsx @@ -130,6 +130,7 @@ const OpeningSettingModal = ({ { const value = e.target.value setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => { diff --git a/web/app/components/workflow/panel/debug-and-preview/hooks.ts b/web/app/components/workflow/panel/debug-and-preview/hooks.ts index 26d604ef11..d98cf49812 100644 --- a/web/app/components/workflow/panel/debug-and-preview/hooks.ts +++ b/web/app/components/workflow/panel/debug-and-preview/hooks.ts @@ -92,7 +92,7 @@ export const useChat = ( ret[index] = { ...ret[index], content: getIntroduction(config.opening_statement), - suggestedQuestions: config.suggested_questions, + suggestedQuestions: config.suggested_questions?.map((item: string) => getIntroduction(item)), } } else { @@ -101,7 +101,7 @@ export const useChat = ( content: getIntroduction(config.opening_statement), isAnswer: true, isOpeningStatement: true, - suggestedQuestions: config.suggested_questions, + suggestedQuestions: config.suggested_questions?.map((item: string) => getIntroduction(item)), }) } } diff --git a/web/i18n/de-DE/app-debug.ts b/web/i18n/de-DE/app-debug.ts index 4022e755e9..1adaf6f05d 100644 --- a/web/i18n/de-DE/app-debug.ts +++ b/web/i18n/de-DE/app-debug.ts @@ -298,6 +298,7 @@ const translation = { add: 'Hinzufügen', writeOpener: 'Eröffnung schreiben', placeholder: 'Schreiben Sie hier Ihre Eröffnungsnachricht, Sie können Variablen verwenden, versuchen Sie {{Variable}} zu tippen.', + openingQuestionPlaceholder: 'Sie können Variablen verwenden, versuchen Sie {{variable}} einzugeben.', openingQuestion: 'Eröffnungsfragen', noDataPlaceHolder: 'Den Dialog mit dem Benutzer zu beginnen, kann helfen, in konversationellen Anwendungen eine engere Verbindung mit ihnen herzustellen.', diff --git a/web/i18n/en-US/app-debug.ts b/web/i18n/en-US/app-debug.ts index 5282dab360..938cb27c12 100644 --- a/web/i18n/en-US/app-debug.ts +++ b/web/i18n/en-US/app-debug.ts @@ -446,6 +446,7 @@ const translation = { writeOpener: 'Edit opener', placeholder: 'Write your opener message here, you can use variables, try type {{variable}}.', openingQuestion: 'Opening Questions', + openingQuestionPlaceholder: 'You can use variables, try typing {{variable}}.', noDataPlaceHolder: 'Starting the conversation with the user can help AI establish a closer connection with them in conversational applications.', varTip: 'You can use variables, try type {{variable}}', diff --git a/web/i18n/es-ES/app-debug.ts b/web/i18n/es-ES/app-debug.ts index 8c986bf669..afdea66338 100644 --- a/web/i18n/es-ES/app-debug.ts +++ b/web/i18n/es-ES/app-debug.ts @@ -329,6 +329,7 @@ const translation = { writeOpener: 'Escribir apertura', placeholder: 'Escribe tu mensaje de apertura aquí, puedes usar variables, intenta escribir {{variable}}.', openingQuestion: 'Preguntas de Apertura', + openingQuestionPlaceholder: 'Puede usar variables, intente escribir {{variable}}.', noDataPlaceHolder: 'Iniciar la conversación con el usuario puede ayudar a la IA a establecer una conexión más cercana con ellos en aplicaciones de conversación.', varTip: 'Puedes usar variables, intenta escribir {{variable}}', tooShort: 'Se requieren al menos 20 palabras en la indicación inicial para generar una apertura de conversación.', diff --git a/web/i18n/fa-IR/app-debug.ts b/web/i18n/fa-IR/app-debug.ts index 5cf9c15efe..75085ef30e 100644 --- a/web/i18n/fa-IR/app-debug.ts +++ b/web/i18n/fa-IR/app-debug.ts @@ -364,6 +364,7 @@ const translation = { writeOpener: 'نوشتن آغازگر', placeholder: 'پیام آغازگر خود را اینجا بنویسید، می‌توانید از متغیرها استفاده کنید، سعی کنید {{variable}} را تایپ کنید.', openingQuestion: 'سوالات آغازین', + openingQuestionPlaceholder: 'می‌توانید از متغیرها استفاده کنید، سعی کنید {{variable}} را تایپ کنید.', noDataPlaceHolder: 'شروع مکالمه با کاربر می‌تواند به AI کمک کند تا ارتباط نزدیک‌تری با آنها برقرار کند.', varTip: 'می‌توانید از متغیرها استفاده کنید، سعی کنید {{variable}} را تایپ کنید', tooShort: 'حداقل 20 کلمه از پرسش اولیه برای تولید نظرات آغازین مکالمه مورد نیاز است.', diff --git a/web/i18n/fr-FR/app-debug.ts b/web/i18n/fr-FR/app-debug.ts index 6671092930..f3984c0435 100644 --- a/web/i18n/fr-FR/app-debug.ts +++ b/web/i18n/fr-FR/app-debug.ts @@ -317,6 +317,7 @@ const translation = { writeOpener: 'Écrire l\'introduction', placeholder: 'Rédigez votre message d\'ouverture ici, vous pouvez utiliser des variables, essayez de taper {{variable}}.', openingQuestion: 'Questions d\'ouverture', + openingQuestionPlaceholder: 'Vous pouvez utiliser des variables, essayez de taper {{variable}}.', noDataPlaceHolder: 'Commencer la conversation avec l\'utilisateur peut aider l\'IA à établir une connexion plus proche avec eux dans les applications conversationnelles.', varTip: 'Vous pouvez utiliser des variables, essayez de taper {{variable}}', diff --git a/web/i18n/hi-IN/app-debug.ts b/web/i18n/hi-IN/app-debug.ts index 3f4b06c08b..ded2af4132 100644 --- a/web/i18n/hi-IN/app-debug.ts +++ b/web/i18n/hi-IN/app-debug.ts @@ -362,6 +362,7 @@ const translation = { placeholder: 'यहां अपना प्रारंभक संदेश लिखें, आप वेरिएबल्स का उपयोग कर सकते हैं, {{variable}} टाइप करने का प्रयास करें।', openingQuestion: 'प्रारंभिक प्रश्न', + openingQuestionPlaceholder: 'आप वेरिएबल्स का उपयोग कर सकते हैं, {{variable}} टाइप करके देखें।', noDataPlaceHolder: 'उपयोगकर्ता के साथ संवाद प्रारंभ करने से एआई को संवादात्मक अनुप्रयोगों में उनके साथ निकट संबंध स्थापित करने में मदद मिल सकती है।', varTip: diff --git a/web/i18n/it-IT/app-debug.ts b/web/i18n/it-IT/app-debug.ts index c8b3c08302..bfa75b282b 100644 --- a/web/i18n/it-IT/app-debug.ts +++ b/web/i18n/it-IT/app-debug.ts @@ -365,6 +365,7 @@ const translation = { placeholder: 'Scrivi qui il tuo messaggio introduttivo, puoi usare variabili, prova a scrivere {{variable}}.', openingQuestion: 'Domande iniziali', + openingQuestionPlaceholder: 'Puoi usare variabili, prova a digitare {{variable}}.', noDataPlaceHolder: 'Iniziare la conversazione con l\'utente può aiutare l\'IA a stabilire un legame più stretto con loro nelle applicazioni conversazionali.', varTip: 'Puoi usare variabili, prova a scrivere {{variable}}', diff --git a/web/i18n/ja-JP/app-debug.ts b/web/i18n/ja-JP/app-debug.ts index f862f3f2f7..decbe4863e 100644 --- a/web/i18n/ja-JP/app-debug.ts +++ b/web/i18n/ja-JP/app-debug.ts @@ -434,6 +434,7 @@ const translation = { writeOpener: 'オープナーを書く', placeholder: 'ここにオープナーメッセージを書いてください。変数を使用できます。{{variable}} を入力してみてください。', openingQuestion: '開始質問', + openingQuestionPlaceholder: '変数を使用できます。{{variable}} と入力してみてください。', noDataPlaceHolder: 'ユーザーとの会話を開始すると、会話アプリケーションで彼らとのより密接な関係を築くのに役立ちます。', varTip: '変数を使用できます。{{variable}} を入力してみてください', diff --git a/web/i18n/ko-KR/app-debug.ts b/web/i18n/ko-KR/app-debug.ts index 3c5a3f4b1f..b84946841f 100644 --- a/web/i18n/ko-KR/app-debug.ts +++ b/web/i18n/ko-KR/app-debug.ts @@ -328,6 +328,7 @@ const translation = { writeOpener: '오프너 작성', placeholder: '여기에 오프너 메시지를 작성하세요. 변수를 사용할 수 있습니다. {{variable}}를 입력해보세요.', openingQuestion: '시작 질문', + openingQuestionPlaceholder: '변수를 사용할 수 있습니다. {{variable}}을(를) 입력해 보세요.', noDataPlaceHolder: '사용자와의 대화를 시작하면 대화 애플리케이션에서 그들과 더 밀접한 관계를 구축하는 데 도움이 됩니다.', varTip: '변수를 사용할 수 있습니다. {{variable}}를 입력해보세요.', tooShort: '대화 시작에는 최소 20 단어의 초기 프롬프트가 필요합니다.', diff --git a/web/i18n/pl-PL/app-debug.ts b/web/i18n/pl-PL/app-debug.ts index 48b44c0cbb..dcd286d351 100644 --- a/web/i18n/pl-PL/app-debug.ts +++ b/web/i18n/pl-PL/app-debug.ts @@ -360,6 +360,7 @@ const translation = { placeholder: 'Tutaj napisz swoją wiadomość wprowadzającą, możesz użyć zmiennych, spróbuj wpisać {{variable}}.', openingQuestion: 'Pytania otwierające', + openingQuestionPlaceholder: 'Możesz używać zmiennych, spróbuj wpisać {{variable}}.', noDataPlaceHolder: 'Rozpoczynanie rozmowy z użytkownikiem może pomóc AI nawiązać bliższe połączenie z nim w aplikacjach konwersacyjnych.', varTip: 'Możesz używać zmiennych, spróbuj wpisać {{variable}}', diff --git a/web/i18n/pt-BR/app-debug.ts b/web/i18n/pt-BR/app-debug.ts index 64f7a85fe7..96d78dc9a3 100644 --- a/web/i18n/pt-BR/app-debug.ts +++ b/web/i18n/pt-BR/app-debug.ts @@ -334,6 +334,7 @@ const translation = { writeOpener: 'Escrever abertura', placeholder: 'Escreva sua mensagem de abertura aqui, você pode usar variáveis, tente digitar {{variável}}.', openingQuestion: 'Perguntas de Abertura', + openingQuestionPlaceholder: 'Você pode usar variáveis, tente digitar {{variable}}.', noDataPlaceHolder: 'Iniciar a conversa com o usuário pode ajudar a IA a estabelecer uma conexão mais próxima com eles em aplicativos de conversação.', varTip: 'Você pode usar variáveis, tente digitar {{variável}}', diff --git a/web/i18n/ru-RU/app-debug.ts b/web/i18n/ru-RU/app-debug.ts index 00cd6e8a75..5d4dbb53d3 100644 --- a/web/i18n/ru-RU/app-debug.ts +++ b/web/i18n/ru-RU/app-debug.ts @@ -370,6 +370,7 @@ const translation = { writeOpener: 'Написать начальное сообщение', placeholder: 'Напишите здесь свое начальное сообщение, вы можете использовать переменные, попробуйте ввести {{variable}}.', openingQuestion: 'Начальные вопросы', + openingQuestionPlaceholder: 'Вы можете использовать переменные, попробуйте ввести {{variable}}.', noDataPlaceHolder: 'Начало разговора с пользователем может помочь ИИ установить более тесную связь с ним в диалоговых приложениях.', varTip: 'Вы можете использовать переменные, попробуйте ввести {{variable}}', diff --git a/web/i18n/tr-TR/app-debug.ts b/web/i18n/tr-TR/app-debug.ts index f08d221d45..6ed0e0a5eb 100644 --- a/web/i18n/tr-TR/app-debug.ts +++ b/web/i18n/tr-TR/app-debug.ts @@ -368,6 +368,7 @@ const translation = { writeOpener: 'Başlangıç mesajı yaz', placeholder: 'Başlangıç mesajınızı buraya yazın, değişkenler kullanabilirsiniz, örneğin {{variable}} yazmayı deneyin.', openingQuestion: 'Açılış Soruları', + openingQuestionPlaceholder: 'Değişkenler kullanabilirsiniz, {{variable}} yazmayı deneyin.', noDataPlaceHolder: 'Kullanıcı ile konuşmayı başlatmak, AI\'ın konuşma uygulamalarında onlarla daha yakın bir bağlantı kurmasına yardımcı olabilir.', varTip: 'Değişkenler kullanabilirsiniz, örneğin {{variable}} yazmayı deneyin', diff --git a/web/i18n/uk-UA/app-debug.ts b/web/i18n/uk-UA/app-debug.ts index 7e410ffef9..70bbebe37e 100644 --- a/web/i18n/uk-UA/app-debug.ts +++ b/web/i18n/uk-UA/app-debug.ts @@ -328,6 +328,7 @@ const translation = { writeOpener: 'Напишіть вступне повідомлення', // Write opener placeholder: 'Напишіть тут своє вступне повідомлення, ви можете використовувати змінні, спробуйте ввести {{variable}}.', // Write your opener message here... openingQuestion: 'Відкриваючі питання', // Opening Questions + openingQuestionPlaceholder: 'Ви можете використовувати змінні, спробуйте ввести {{variable}}.', noDataPlaceHolder: 'Початок розмови з користувачем може допомогти ШІ встановити більш тісний зв’язок з ним у розмовних застосунках.', // ... conversational applications. varTip: 'Ви можете використовувати змінні, спробуйте ввести {{variable}}', // You can use variables, try type {{variable}} tooShort: 'Для створення вступних зауважень для розмови потрібно принаймні 20 слів вступного запиту.', // ... are required to generate an opening remarks for the conversation. diff --git a/web/i18n/vi-VN/app-debug.ts b/web/i18n/vi-VN/app-debug.ts index c091cb5abb..cd57f78e79 100644 --- a/web/i18n/vi-VN/app-debug.ts +++ b/web/i18n/vi-VN/app-debug.ts @@ -328,6 +328,7 @@ const translation = { writeOpener: 'Viết câu mở đầu', placeholder: 'Viết thông điệp mở đầu của bạn ở đây, bạn có thể sử dụng biến, hãy thử nhập {{biến}}.', openingQuestion: 'Câu hỏi mở đầu', + openingQuestionPlaceholder: 'Bạn có thể sử dụng biến, hãy thử nhập {{variable}}.', noDataPlaceHolder: 'Bắt đầu cuộc trò chuyện với người dùng có thể giúp AI thiết lập mối quan hệ gần gũi hơn với họ trong các ứng dụng trò chuyện.', varTip: 'Bạn có thể sử dụng biến, hãy thử nhập {{biến}}', tooShort: 'Cần ít nhất 20 từ trong lời nhắc ban đầu để tạo ra các câu mở đầu cho cuộc trò chuyện.', diff --git a/web/i18n/zh-Hans/app-debug.ts b/web/i18n/zh-Hans/app-debug.ts index 4f84b396d0..3728d86e58 100644 --- a/web/i18n/zh-Hans/app-debug.ts +++ b/web/i18n/zh-Hans/app-debug.ts @@ -436,6 +436,7 @@ const translation = { writeOpener: '编写开场白', placeholder: '在这里写下你的开场白,你可以使用变量,尝试输入 {{variable}}。', openingQuestion: '开场问题', + openingQuestionPlaceholder: '可以使用变量,尝试输入 {{variable}}。', noDataPlaceHolder: '在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。', varTip: '你可以使用变量,试试输入 {{variable}}', diff --git a/web/i18n/zh-Hant/app-debug.ts b/web/i18n/zh-Hant/app-debug.ts index 16374992b5..b31b9a9d66 100644 --- a/web/i18n/zh-Hant/app-debug.ts +++ b/web/i18n/zh-Hant/app-debug.ts @@ -313,6 +313,7 @@ const translation = { writeOpener: '編寫開場白', placeholder: '在這裡寫下你的開場白,你可以使用變數,嘗試輸入 {{variable}}。', openingQuestion: '開場問題', + openingQuestionPlaceholder: '可以使用變量,嘗試輸入 {{variable}}。', noDataPlaceHolder: '在對話型應用中,讓 AI 主動說第一段話可以拉近與使用者間的距離。', varTip: '你可以使用變數,試試輸入 {{variable}}', From 2a85f28963b6e055ec0b439704d10d66475c4784 Mon Sep 17 00:00:00 2001 From: Garden12138 <847686279@qq.com> Date: Fri, 11 Jul 2025 15:18:42 +0800 Subject: [PATCH 06/25] =?UTF-8?q?fix=EF=BC=9AFixed=20the=20problem=20of=20?= =?UTF-8?q?plugin=20installation=20failure=20caused=20by=20incons=E2=80=A6?= =?UTF-8?q?=20(#22156)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/services/plugin/plugin_service.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/api/services/plugin/plugin_service.py b/api/services/plugin/plugin_service.py index d7fb4a7c1b..0f22afd8dd 100644 --- a/api/services/plugin/plugin_service.py +++ b/api/services/plugin/plugin_service.py @@ -427,6 +427,9 @@ class PluginService: manager = PluginInstaller() + # collect actual plugin_unique_identifiers + actual_plugin_unique_identifiers = [] + metas = [] features = FeatureService.get_system_features() # check if already downloaded @@ -437,6 +440,8 @@ class PluginService: # check if the plugin is available to install PluginService._check_plugin_installation_scope(plugin_decode_response.verification) # already downloaded, skip + actual_plugin_unique_identifiers.append(plugin_unique_identifier) + metas.append({"plugin_unique_identifier": plugin_unique_identifier}) except Exception: # plugin not installed, download and upload pkg pkg = download_plugin_pkg(plugin_unique_identifier) @@ -447,17 +452,15 @@ class PluginService: ) # check if the plugin is available to install PluginService._check_plugin_installation_scope(response.verification) + # use response plugin_unique_identifier + actual_plugin_unique_identifiers.append(response.unique_identifier) + metas.append({"plugin_unique_identifier": response.unique_identifier}) return manager.install_from_identifiers( tenant_id, - plugin_unique_identifiers, + actual_plugin_unique_identifiers, PluginInstallationSource.Marketplace, - [ - { - "plugin_unique_identifier": plugin_unique_identifier, - } - for plugin_unique_identifier in plugin_unique_identifiers - ], + metas, ) @staticmethod From 1d85979a7464d8d4d01e582eb6083d4206ea46ba Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 11 Jul 2025 16:41:25 +0800 Subject: [PATCH 07/25] chore:extract last run common logic (#22214) --- .../workflow-app/components/workflow-main.tsx | 8 +- .../components/workflow-app/hooks/index.ts | 2 +- .../hooks/use-inspect-vars-crud.ts | 234 +---------------- .../workflow-app/hooks/use-workflow-run.ts | 9 +- .../hooks/use-fetch-workflow-inspect-vars.ts | 19 +- .../hooks/use-inspect-vars-crud-common.ts | 240 ++++++++++++++++++ 6 files changed, 275 insertions(+), 237 deletions(-) rename web/app/components/{workflow-app => workflow}/hooks/use-fetch-workflow-inspect-vars.ts (85%) create mode 100644 web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts diff --git a/web/app/components/workflow-app/components/workflow-main.tsx b/web/app/components/workflow-app/components/workflow-main.tsx index f0b9bab2cf..d425e6f595 100644 --- a/web/app/components/workflow-app/components/workflow-main.tsx +++ b/web/app/components/workflow-app/components/workflow-main.tsx @@ -15,7 +15,7 @@ import { useWorkflowRun, useWorkflowStartRun, } from '../hooks' -import { useWorkflowStore } from '@/app/components/workflow/store' +import { useStore, useWorkflowStore } from '@/app/components/workflow/store' type WorkflowMainProps = Pick const WorkflowMain = ({ @@ -64,7 +64,11 @@ const WorkflowMain = ({ handleWorkflowStartRunInChatflow, handleWorkflowStartRunInWorkflow, } = useWorkflowStartRun() - const { fetchInspectVars } = useSetWorkflowVarsWithValue() + const appId = useStore(s => s.appId) + const { fetchInspectVars } = useSetWorkflowVarsWithValue({ + flowId: appId, + ...useConfigsMap(), + }) const { hasNodeInspectVars, hasSetInspectVar, diff --git a/web/app/components/workflow-app/hooks/index.ts b/web/app/components/workflow-app/hooks/index.ts index 1ee7c030b9..9e4f94965b 100644 --- a/web/app/components/workflow-app/hooks/index.ts +++ b/web/app/components/workflow-app/hooks/index.ts @@ -5,6 +5,6 @@ export * from './use-workflow-run' export * from './use-workflow-start-run' export * from './use-is-chat-mode' export * from './use-workflow-refresh-draft' -export * from './use-fetch-workflow-inspect-vars' +export * from '../../workflow/hooks/use-fetch-workflow-inspect-vars' export * from './use-inspect-vars-crud' export * from './use-configs-map' diff --git a/web/app/components/workflow-app/hooks/use-inspect-vars-crud.ts b/web/app/components/workflow-app/hooks/use-inspect-vars-crud.ts index ce052b7ed4..109ee85f96 100644 --- a/web/app/components/workflow-app/hooks/use-inspect-vars-crud.ts +++ b/web/app/components/workflow-app/hooks/use-inspect-vars-crud.ts @@ -1,234 +1,16 @@ -import { fetchNodeInspectVars } from '@/service/workflow' -import { useStore, useWorkflowStore } from '@/app/components/workflow/store' -import type { ValueSelector } from '@/app/components/workflow/types' -import type { VarInInspect } from '@/types/workflow' -import { VarInInspectType } from '@/types/workflow' -import { - useDeleteAllInspectorVars, - useDeleteInspectVar, - useDeleteNodeInspectorVars, - useEditInspectorVar, - useInvalidateConversationVarValues, - useInvalidateSysVarValues, - useResetConversationVar, - useResetToLastRunValue, -} from '@/service/use-workflow' -import { useCallback } from 'react' -import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' -import produce from 'immer' -import type { Node } from '@/app/components/workflow/types' -import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' -import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' +import { useStore } from '@/app/components/workflow/store' +import { useInspectVarsCrudCommon } from '../../workflow/hooks/use-inspect-vars-crud-common' import { useConfigsMap } from './use-configs-map' export const useInspectVarsCrud = () => { - const workflowStore = useWorkflowStore() const appId = useStore(s => s.appId) - const { conversationVarsUrl, systemVarsUrl } = useConfigsMap() - const invalidateConversationVarValues = useInvalidateConversationVarValues(conversationVarsUrl) - const { mutateAsync: doResetConversationVar } = useResetConversationVar(appId) - const { mutateAsync: doResetToLastRunValue } = useResetToLastRunValue(appId) - const invalidateSysVarValues = useInvalidateSysVarValues(systemVarsUrl) - - const { mutateAsync: doDeleteAllInspectorVars } = useDeleteAllInspectorVars(appId) - const { mutate: doDeleteNodeInspectorVars } = useDeleteNodeInspectorVars(appId) - const { mutate: doDeleteInspectVar } = useDeleteInspectVar(appId) - - const { mutateAsync: doEditInspectorVar } = useEditInspectorVar(appId) - const { handleCancelNodeSuccessStatus } = useNodesInteractionsWithoutSync() - const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync() - const getNodeInspectVars = useCallback((nodeId: string) => { - const { nodesWithInspectVars } = workflowStore.getState() - const node = nodesWithInspectVars.find(node => node.nodeId === nodeId) - return node - }, [workflowStore]) - - const getVarId = useCallback((nodeId: string, varName: string) => { - const node = getNodeInspectVars(nodeId) - if (!node) - return undefined - const varId = node.vars.find((varItem) => { - return varItem.selector[1] === varName - })?.id - return varId - }, [getNodeInspectVars]) - - const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => { - const node = getNodeInspectVars(nodeId) - if (!node) - return undefined - - const variable = node.vars.find((varItem) => { - return varItem.name === name - }) - return variable - }, [getNodeInspectVars]) - - const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => { - const isEnv = isENV([nodeId]) - if (isEnv) // always have value - return true - const isSys = isSystemVar([nodeId]) - if (isSys) - return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name) - const isChatVar = isConversationVar([nodeId]) - if (isChatVar) - return conversationVars.some(varItem => varItem.selector?.[1] === name) - return getInspectVar(nodeId, name) !== undefined - }, [getInspectVar]) - - const hasNodeInspectVars = useCallback((nodeId: string) => { - return !!getNodeInspectVars(nodeId) - }, [getNodeInspectVars]) - - const fetchInspectVarValue = useCallback(async (selector: ValueSelector) => { - const { - appId, - setNodeInspectVars, - } = workflowStore.getState() - const nodeId = selector[0] - const isSystemVar = nodeId === 'sys' - const isConversationVar = nodeId === 'conversation' - if (isSystemVar) { - invalidateSysVarValues() - return - } - if (isConversationVar) { - invalidateConversationVarValues() - return - } - const vars = await fetchNodeInspectVars(appId, nodeId) - setNodeInspectVars(nodeId, vars) - }, [workflowStore, invalidateSysVarValues, invalidateConversationVarValues]) - - // after last run would call this - const appendNodeInspectVars = useCallback((nodeId: string, payload: VarInInspect[], allNodes: Node[]) => { - const { - nodesWithInspectVars, - setNodesWithInspectVars, - } = workflowStore.getState() - const nodes = produce(nodesWithInspectVars, (draft) => { - const nodeInfo = allNodes.find(node => node.id === nodeId) - if (nodeInfo) { - const index = draft.findIndex(node => node.nodeId === nodeId) - if (index === -1) { - draft.unshift({ - nodeId, - nodeType: nodeInfo.data.type, - title: nodeInfo.data.title, - vars: payload, - nodePayload: nodeInfo.data, - }) - } - else { - draft[index].vars = payload - // put the node to the topAdd commentMore actions - draft.unshift(draft.splice(index, 1)[0]) - } - } - }) - setNodesWithInspectVars(nodes) - handleCancelNodeSuccessStatus(nodeId) - }, [workflowStore, handleCancelNodeSuccessStatus]) - - const hasNodeInspectVar = useCallback((nodeId: string, varId: string) => { - const { nodesWithInspectVars } = workflowStore.getState() - const targetNode = nodesWithInspectVars.find(item => item.nodeId === nodeId) - if(!targetNode || !targetNode.vars) - return false - return targetNode.vars.some(item => item.id === varId) - }, [workflowStore]) - - const deleteInspectVar = useCallback(async (nodeId: string, varId: string) => { - const { deleteInspectVar } = workflowStore.getState() - if(hasNodeInspectVar(nodeId, varId)) { - await doDeleteInspectVar(varId) - deleteInspectVar(nodeId, varId) - } - }, [doDeleteInspectVar, workflowStore, hasNodeInspectVar]) - - const resetConversationVar = useCallback(async (varId: string) => { - await doResetConversationVar(varId) - invalidateConversationVarValues() - }, [doResetConversationVar, invalidateConversationVarValues]) - - const deleteNodeInspectorVars = useCallback(async (nodeId: string) => { - const { deleteNodeInspectVars } = workflowStore.getState() - if (hasNodeInspectVars(nodeId)) { - await doDeleteNodeInspectorVars(nodeId) - deleteNodeInspectVars(nodeId) - } - }, [doDeleteNodeInspectorVars, workflowStore, hasNodeInspectVars]) - - const deleteAllInspectorVars = useCallback(async () => { - const { deleteAllInspectVars } = workflowStore.getState() - await doDeleteAllInspectorVars() - await invalidateConversationVarValues() - await invalidateSysVarValues() - deleteAllInspectVars() - handleEdgeCancelRunningStatus() - }, [doDeleteAllInspectorVars, invalidateConversationVarValues, invalidateSysVarValues, workflowStore, handleEdgeCancelRunningStatus]) - - const editInspectVarValue = useCallback(async (nodeId: string, varId: string, value: any) => { - const { setInspectVarValue } = workflowStore.getState() - await doEditInspectorVar({ - varId, - value, - }) - setInspectVarValue(nodeId, varId, value) - if (nodeId === VarInInspectType.conversation) - invalidateConversationVarValues() - if (nodeId === VarInInspectType.system) - invalidateSysVarValues() - }, [doEditInspectorVar, invalidateConversationVarValues, invalidateSysVarValues, workflowStore]) - - const renameInspectVarName = useCallback(async (nodeId: string, oldName: string, newName: string) => { - const { renameInspectVarName } = workflowStore.getState() - const varId = getVarId(nodeId, oldName) - if (!varId) - return - - const newSelector = [nodeId, newName] - await doEditInspectorVar({ - varId, - name: newName, - }) - renameInspectVarName(nodeId, varId, newSelector) - }, [doEditInspectorVar, getVarId, workflowStore]) - - const isInspectVarEdited = useCallback((nodeId: string, name: string) => { - const inspectVar = getInspectVar(nodeId, name) - if (!inspectVar) - return false - - return inspectVar.edited - }, [getInspectVar]) - - const resetToLastRunVar = useCallback(async (nodeId: string, varId: string) => { - const { resetToLastRunVar } = workflowStore.getState() - const isSysVar = nodeId === 'sys' - const data = await doResetToLastRunValue(varId) - - if(isSysVar) - invalidateSysVarValues() - else - resetToLastRunVar(nodeId, varId, data.value) - }, [doResetToLastRunValue, invalidateSysVarValues, workflowStore]) + const configsMap = useConfigsMap() + const apis = useInspectVarsCrudCommon({ + flowId: appId, + ...configsMap, + }) return { - hasNodeInspectVars, - hasSetInspectVar, - fetchInspectVarValue, - editInspectVarValue, - renameInspectVarName, - appendNodeInspectVars, - deleteInspectVar, - deleteNodeInspectorVars, - deleteAllInspectorVars, - isInspectVarEdited, - resetToLastRunVar, - invalidateSysVarValues, - resetConversationVar, - invalidateConversationVarValues, + ...apis, } } diff --git a/web/app/components/workflow-app/hooks/use-workflow-run.ts b/web/app/components/workflow-app/hooks/use-workflow-run.ts index 4c34d2ffb1..c303211715 100644 --- a/web/app/components/workflow-app/hooks/use-workflow-run.ts +++ b/web/app/components/workflow-app/hooks/use-workflow-run.ts @@ -20,7 +20,8 @@ import type { VersionHistory } from '@/types/workflow' import { noop } from 'lodash-es' import { useNodesSyncDraft } from './use-nodes-sync-draft' import { useInvalidAllLastRun } from '@/service/use-workflow' -import { useSetWorkflowVarsWithValue } from './use-fetch-workflow-inspect-vars' +import { useSetWorkflowVarsWithValue } from '../../workflow/hooks/use-fetch-workflow-inspect-vars' +import { useConfigsMap } from './use-configs-map' export const useWorkflowRun = () => { const store = useStoreApi() @@ -32,7 +33,11 @@ export const useWorkflowRun = () => { const pathname = usePathname() const appId = useAppStore.getState().appDetail?.id const invalidAllLastRun = useInvalidAllLastRun(appId as string) - const { fetchInspectVars } = useSetWorkflowVarsWithValue() + const configsMap = useConfigsMap() + const { fetchInspectVars } = useSetWorkflowVarsWithValue({ + flowId: appId as string, + ...configsMap, + }) const { handleWorkflowStarted, diff --git a/web/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars.ts b/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts similarity index 85% rename from web/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars.ts rename to web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts index 07580c097e..27a9ea9d2d 100644 --- a/web/app/components/workflow-app/hooks/use-fetch-workflow-inspect-vars.ts +++ b/web/app/components/workflow/hooks/use-fetch-workflow-inspect-vars.ts @@ -6,12 +6,20 @@ import type { Node } from '@/app/components/workflow/types' import { fetchAllInspectVars } from '@/service/workflow' import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow' import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' -import { useConfigsMap } from './use-configs-map' -export const useSetWorkflowVarsWithValue = () => { +type Params = { + flowId: string + conversationVarsUrl: string + systemVarsUrl: string +} + +export const useSetWorkflowVarsWithValue = ({ + flowId, + conversationVarsUrl, + systemVarsUrl, +}: Params) => { const workflowStore = useWorkflowStore() const store = useStoreApi() - const { conversationVarsUrl, systemVarsUrl } = useConfigsMap() const invalidateConversationVarValues = useInvalidateConversationVarValues(conversationVarsUrl) const invalidateSysVarValues = useInvalidateSysVarValues(systemVarsUrl) const { handleCancelAllNodeSuccessStatus } = useNodesInteractionsWithoutSync() @@ -58,13 +66,12 @@ export const useSetWorkflowVarsWithValue = () => { }, [workflowStore, store]) const fetchInspectVars = useCallback(async () => { - const { appId } = workflowStore.getState() invalidateConversationVarValues() invalidateSysVarValues() - const data = await fetchAllInspectVars(appId) + const data = await fetchAllInspectVars(flowId) setInspectVarsToStore(data) handleCancelAllNodeSuccessStatus() // to make sure clear node output show the unset status - }, [workflowStore, invalidateConversationVarValues, invalidateSysVarValues, setInspectVarsToStore, handleCancelAllNodeSuccessStatus]) + }, [invalidateConversationVarValues, invalidateSysVarValues, flowId, setInspectVarsToStore, handleCancelAllNodeSuccessStatus]) return { fetchInspectVars, } diff --git a/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts b/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts new file mode 100644 index 0000000000..ffcfd81666 --- /dev/null +++ b/web/app/components/workflow/hooks/use-inspect-vars-crud-common.ts @@ -0,0 +1,240 @@ +import { fetchNodeInspectVars } from '@/service/workflow' +import { useWorkflowStore } from '@/app/components/workflow/store' +import type { ValueSelector } from '@/app/components/workflow/types' +import type { VarInInspect } from '@/types/workflow' +import { VarInInspectType } from '@/types/workflow' +import { + useDeleteAllInspectorVars, + useDeleteInspectVar, + useDeleteNodeInspectorVars, + useEditInspectorVar, + useInvalidateConversationVarValues, + useInvalidateSysVarValues, + useResetConversationVar, + useResetToLastRunValue, +} from '@/service/use-workflow' +import { useCallback } from 'react' +import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' +import produce from 'immer' +import type { Node } from '@/app/components/workflow/types' +import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync' +import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync' + +type Params = { + flowId: string + conversationVarsUrl: string + systemVarsUrl: string +} +export const useInspectVarsCrudCommon = ({ + flowId, + conversationVarsUrl, + systemVarsUrl, +}: Params) => { + const workflowStore = useWorkflowStore() + const invalidateConversationVarValues = useInvalidateConversationVarValues(conversationVarsUrl!) + const { mutateAsync: doResetConversationVar } = useResetConversationVar(flowId) + const { mutateAsync: doResetToLastRunValue } = useResetToLastRunValue(flowId) + const invalidateSysVarValues = useInvalidateSysVarValues(systemVarsUrl!) + + const { mutateAsync: doDeleteAllInspectorVars } = useDeleteAllInspectorVars(flowId) + const { mutate: doDeleteNodeInspectorVars } = useDeleteNodeInspectorVars(flowId) + const { mutate: doDeleteInspectVar } = useDeleteInspectVar(flowId) + + const { mutateAsync: doEditInspectorVar } = useEditInspectorVar(flowId) + const { handleCancelNodeSuccessStatus } = useNodesInteractionsWithoutSync() + const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync() + const getNodeInspectVars = useCallback((nodeId: string) => { + const { nodesWithInspectVars } = workflowStore.getState() + const node = nodesWithInspectVars.find(node => node.nodeId === nodeId) + return node + }, [workflowStore]) + + const getVarId = useCallback((nodeId: string, varName: string) => { + const node = getNodeInspectVars(nodeId) + if (!node) + return undefined + const varId = node.vars.find((varItem) => { + return varItem.selector[1] === varName + })?.id + return varId + }, [getNodeInspectVars]) + + const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => { + const node = getNodeInspectVars(nodeId) + if (!node) + return undefined + + const variable = node.vars.find((varItem) => { + return varItem.name === name + }) + return variable + }, [getNodeInspectVars]) + + const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => { + const isEnv = isENV([nodeId]) + if (isEnv) // always have value + return true + const isSys = isSystemVar([nodeId]) + if (isSys) + return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name) + const isChatVar = isConversationVar([nodeId]) + if (isChatVar) + return conversationVars.some(varItem => varItem.selector?.[1] === name) + return getInspectVar(nodeId, name) !== undefined + }, [getInspectVar]) + + const hasNodeInspectVars = useCallback((nodeId: string) => { + return !!getNodeInspectVars(nodeId) + }, [getNodeInspectVars]) + + const fetchInspectVarValue = useCallback(async (selector: ValueSelector) => { + const { + appId, + setNodeInspectVars, + } = workflowStore.getState() + const nodeId = selector[0] + const isSystemVar = nodeId === 'sys' + const isConversationVar = nodeId === 'conversation' + if (isSystemVar) { + invalidateSysVarValues() + return + } + if (isConversationVar) { + invalidateConversationVarValues() + return + } + const vars = await fetchNodeInspectVars(appId, nodeId) + setNodeInspectVars(nodeId, vars) + }, [workflowStore, invalidateSysVarValues, invalidateConversationVarValues]) + + // after last run would call this + const appendNodeInspectVars = useCallback((nodeId: string, payload: VarInInspect[], allNodes: Node[]) => { + const { + nodesWithInspectVars, + setNodesWithInspectVars, + } = workflowStore.getState() + const nodes = produce(nodesWithInspectVars, (draft) => { + const nodeInfo = allNodes.find(node => node.id === nodeId) + if (nodeInfo) { + const index = draft.findIndex(node => node.nodeId === nodeId) + if (index === -1) { + draft.unshift({ + nodeId, + nodeType: nodeInfo.data.type, + title: nodeInfo.data.title, + vars: payload, + nodePayload: nodeInfo.data, + }) + } + else { + draft[index].vars = payload + // put the node to the topAdd commentMore actions + draft.unshift(draft.splice(index, 1)[0]) + } + } + }) + setNodesWithInspectVars(nodes) + handleCancelNodeSuccessStatus(nodeId) + }, [workflowStore, handleCancelNodeSuccessStatus]) + + const hasNodeInspectVar = useCallback((nodeId: string, varId: string) => { + const { nodesWithInspectVars } = workflowStore.getState() + const targetNode = nodesWithInspectVars.find(item => item.nodeId === nodeId) + if(!targetNode || !targetNode.vars) + return false + return targetNode.vars.some(item => item.id === varId) + }, [workflowStore]) + + const deleteInspectVar = useCallback(async (nodeId: string, varId: string) => { + const { deleteInspectVar } = workflowStore.getState() + if(hasNodeInspectVar(nodeId, varId)) { + await doDeleteInspectVar(varId) + deleteInspectVar(nodeId, varId) + } + }, [doDeleteInspectVar, workflowStore, hasNodeInspectVar]) + + const resetConversationVar = useCallback(async (varId: string) => { + await doResetConversationVar(varId) + invalidateConversationVarValues() + }, [doResetConversationVar, invalidateConversationVarValues]) + + const deleteNodeInspectorVars = useCallback(async (nodeId: string) => { + const { deleteNodeInspectVars } = workflowStore.getState() + if (hasNodeInspectVars(nodeId)) { + await doDeleteNodeInspectorVars(nodeId) + deleteNodeInspectVars(nodeId) + } + }, [doDeleteNodeInspectorVars, workflowStore, hasNodeInspectVars]) + + const deleteAllInspectorVars = useCallback(async () => { + const { deleteAllInspectVars } = workflowStore.getState() + await doDeleteAllInspectorVars() + await invalidateConversationVarValues() + await invalidateSysVarValues() + deleteAllInspectVars() + handleEdgeCancelRunningStatus() + }, [doDeleteAllInspectorVars, invalidateConversationVarValues, invalidateSysVarValues, workflowStore, handleEdgeCancelRunningStatus]) + + const editInspectVarValue = useCallback(async (nodeId: string, varId: string, value: any) => { + const { setInspectVarValue } = workflowStore.getState() + await doEditInspectorVar({ + varId, + value, + }) + setInspectVarValue(nodeId, varId, value) + if (nodeId === VarInInspectType.conversation) + invalidateConversationVarValues() + if (nodeId === VarInInspectType.system) + invalidateSysVarValues() + }, [doEditInspectorVar, invalidateConversationVarValues, invalidateSysVarValues, workflowStore]) + + const renameInspectVarName = useCallback(async (nodeId: string, oldName: string, newName: string) => { + const { renameInspectVarName } = workflowStore.getState() + const varId = getVarId(nodeId, oldName) + if (!varId) + return + + const newSelector = [nodeId, newName] + await doEditInspectorVar({ + varId, + name: newName, + }) + renameInspectVarName(nodeId, varId, newSelector) + }, [doEditInspectorVar, getVarId, workflowStore]) + + const isInspectVarEdited = useCallback((nodeId: string, name: string) => { + const inspectVar = getInspectVar(nodeId, name) + if (!inspectVar) + return false + + return inspectVar.edited + }, [getInspectVar]) + + const resetToLastRunVar = useCallback(async (nodeId: string, varId: string) => { + const { resetToLastRunVar } = workflowStore.getState() + const isSysVar = nodeId === 'sys' + const data = await doResetToLastRunValue(varId) + + if(isSysVar) + invalidateSysVarValues() + else + resetToLastRunVar(nodeId, varId, data.value) + }, [doResetToLastRunValue, invalidateSysVarValues, workflowStore]) + + return { + hasNodeInspectVars, + hasSetInspectVar, + fetchInspectVarValue, + editInspectVarValue, + renameInspectVarName, + appendNodeInspectVars, + deleteInspectVar, + deleteNodeInspectorVars, + deleteAllInspectorVars, + isInspectVarEdited, + resetToLastRunVar, + invalidateSysVarValues, + resetConversationVar, + invalidateConversationVarValues, + } +} From 2d3c5b3b7c3365a5e037e85dbbb9ced174a51862 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:52:16 +0800 Subject: [PATCH 08/25] fix(emoji-picker): Adjust the style of the emoji picker (#22161) (#22231) --- web/app/components/base/emoji-picker/Inner.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/app/components/base/emoji-picker/Inner.tsx b/web/app/components/base/emoji-picker/Inner.tsx index 8d05967f33..6fc4b67181 100644 --- a/web/app/components/base/emoji-picker/Inner.tsx +++ b/web/app/components/base/emoji-picker/Inner.tsx @@ -101,7 +101,7 @@ const EmojiPickerInner: FC = ({
-
+
{isSearching && <>

Search

@@ -170,7 +170,7 @@ const EmojiPickerInner: FC = ({ 'flex h-8 w-8 items-center justify-center rounded-lg p-1', ) } style={{ background: color }}> - {selectedEmoji !== '' && } + {selectedEmoji !== '' && }
})} From 76d21743fd9f164bbca1e97f224246750b5e83d3 Mon Sep 17 00:00:00 2001 From: HyaCinth <88471803+HyaCiovo@users.noreply.github.com> Date: Fri, 11 Jul 2025 17:54:09 +0800 Subject: [PATCH 09/25] fix(web): Optimize AppInfo Component Layout (#22212) (#22218) --- web/app/components/app-sidebar/app-info.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index c28cc20df5..3817ebf5a4 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -308,13 +308,11 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx operations={operations} />
-
- -
+