From f601093ccc07d1e8c1d3b91572ad9b4c70ef0b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Wed, 11 Jun 2025 15:38:51 +0800 Subject: [PATCH 1/5] fix: only enterprise version request app access mode (#20785) --- web/app/(shareLayout)/layout.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/app/(shareLayout)/layout.tsx b/web/app/(shareLayout)/layout.tsx index 8db336a17d..7de5d51edb 100644 --- a/web/app/(shareLayout)/layout.tsx +++ b/web/app/(shareLayout)/layout.tsx @@ -12,12 +12,18 @@ const Layout: FC<{ }> = ({ children }) => { const isGlobalPending = useGlobalPublicStore(s => s.isGlobalPending) const setWebAppAccessMode = useGlobalPublicStore(s => s.setWebAppAccessMode) + const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) const pathname = usePathname() const searchParams = useSearchParams() const redirectUrl = searchParams.get('redirect_url') const [isLoading, setIsLoading] = useState(true) useEffect(() => { (async () => { + if (!systemFeatures.webapp_auth.enabled) { + setIsLoading(false) + return + } + let appCode: string | null = null if (redirectUrl) appCode = redirectUrl?.split('/').pop() || null From d6d8cca053b074b17ea754821e9a52121102218d Mon Sep 17 00:00:00 2001 From: Yeuoly <45712896+Yeuoly@users.noreply.github.com> Date: Wed, 11 Jun 2025 16:01:50 +0800 Subject: [PATCH 2/5] refactor: replace compact response generation with length-prefixed response for backwards invocation api (#20903) --- api/controllers/inner_api/plugin/plugin.py | 10 ++-- api/core/plugin/backwards_invocation/base.py | 8 ++- api/libs/helper.py | 56 ++++++++++++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/api/controllers/inner_api/plugin/plugin.py b/api/controllers/inner_api/plugin/plugin.py index f3a1bd8fa5..41063b35a5 100644 --- a/api/controllers/inner_api/plugin/plugin.py +++ b/api/controllers/inner_api/plugin/plugin.py @@ -29,7 +29,7 @@ from core.plugin.entities.request import ( RequestRequestUploadFile, ) from core.tools.entities.tool_entities import ToolProviderType -from libs.helper import compact_generate_response +from libs.helper import length_prefixed_response from models.account import Account, Tenant from models.model import EndUser @@ -44,7 +44,7 @@ class PluginInvokeLLMApi(Resource): response = PluginModelBackwardsInvocation.invoke_llm(user_model.id, tenant_model, payload) return PluginModelBackwardsInvocation.convert_to_event_stream(response) - return compact_generate_response(generator()) + return length_prefixed_response(0xF, generator()) class PluginInvokeTextEmbeddingApi(Resource): @@ -101,7 +101,7 @@ class PluginInvokeTTSApi(Resource): ) return PluginModelBackwardsInvocation.convert_to_event_stream(response) - return compact_generate_response(generator()) + return length_prefixed_response(0xF, generator()) class PluginInvokeSpeech2TextApi(Resource): @@ -162,7 +162,7 @@ class PluginInvokeToolApi(Resource): ), ) - return compact_generate_response(generator()) + return length_prefixed_response(0xF, generator()) class PluginInvokeParameterExtractorNodeApi(Resource): @@ -228,7 +228,7 @@ class PluginInvokeAppApi(Resource): files=payload.files, ) - return compact_generate_response(PluginAppBackwardsInvocation.convert_to_event_stream(response)) + return length_prefixed_response(0xF, PluginAppBackwardsInvocation.convert_to_event_stream(response)) class PluginInvokeEncryptApi(Resource): diff --git a/api/core/plugin/backwards_invocation/base.py b/api/core/plugin/backwards_invocation/base.py index 3214e07469..2a5f857576 100644 --- a/api/core/plugin/backwards_invocation/base.py +++ b/api/core/plugin/backwards_invocation/base.py @@ -11,14 +11,12 @@ class BaseBackwardsInvocation: try: for chunk in response: if isinstance(chunk, BaseModel | dict): - yield BaseBackwardsInvocationResponse(data=chunk).model_dump_json().encode() + b"\n\n" - elif isinstance(chunk, str): - yield f"event: {chunk}\n\n".encode() + yield BaseBackwardsInvocationResponse(data=chunk).model_dump_json().encode() except Exception as e: error_message = BaseBackwardsInvocationResponse(error=str(e)).model_dump_json() - yield f"{error_message}\n\n".encode() + yield error_message.encode() else: - yield BaseBackwardsInvocationResponse(data=response).model_dump_json().encode() + b"\n\n" + yield BaseBackwardsInvocationResponse(data=response).model_dump_json().encode() T = TypeVar("T", bound=dict | Mapping | str | bool | int | BaseModel) diff --git a/api/libs/helper.py b/api/libs/helper.py index e78a782fbe..070e575dbc 100644 --- a/api/libs/helper.py +++ b/api/libs/helper.py @@ -3,6 +3,7 @@ import logging import re import secrets import string +import struct import subprocess import time import uuid @@ -14,6 +15,7 @@ from zoneinfo import available_timezones from flask import Response, stream_with_context from flask_restful import fields +from pydantic import BaseModel from configs import dify_config from core.app.features.rate_limiting.rate_limit import RateLimitGenerator @@ -206,6 +208,60 @@ def compact_generate_response(response: Union[Mapping, Generator, RateLimitGener return Response(stream_with_context(generate()), status=200, mimetype="text/event-stream") +def length_prefixed_response(magic_number: int, response: Union[Mapping, Generator, RateLimitGenerator]) -> Response: + """ + This function is used to return a response with a length prefix. + Magic number is a one byte number that indicates the type of the response. + + For a compatibility with latest plugin daemon https://github.com/langgenius/dify-plugin-daemon/pull/341 + Avoid using line-based response, it leads a memory issue. + + We uses following format: + | Field | Size | Description | + |---------------|----------|---------------------------------| + | Magic Number | 1 byte | Magic number identifier | + | Reserved | 1 byte | Reserved field | + | Header Length | 2 bytes | Header length (usually 0xa) | + | Data Length | 4 bytes | Length of the data | + | Reserved | 6 bytes | Reserved fields | + | Data | Variable | Actual data content | + + | Reserved Fields | Header | Data | + |-----------------|----------|----------| + | 4 bytes total | Variable | Variable | + + all data is in little endian + """ + + def pack_response_with_length_prefix(response: bytes) -> bytes: + header_length = 0xA + data_length = len(response) + # | Magic Number 1byte | Reserved 1byte | Header Length 2bytes | Data Length 4bytes | Reserved 6bytes | Data + return struct.pack(" Generator: + for chunk in response: + if isinstance(chunk, str): + yield pack_response_with_length_prefix(chunk.encode("utf-8")) + else: + yield pack_response_with_length_prefix(chunk) + + return Response(stream_with_context(generate()), status=200, mimetype="text/event-stream") + + class TokenManager: @classmethod def generate_token( From acb2488fc8f9ba66cea5a4d99571a9f2eeb862b1 Mon Sep 17 00:00:00 2001 From: -LAN- Date: Wed, 11 Jun 2025 16:28:36 +0800 Subject: [PATCH 3/5] chore(package): Bump version to 1.4.2 (#20897) Signed-off-by: -LAN- --- api/configs/packaging/__init__.py | 2 +- docker/docker-compose-template.yaml | 8 ++++---- docker/docker-compose.middleware.yaml | 2 +- docker/docker-compose.yaml | 8 ++++---- web/package.json | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/configs/packaging/__init__.py b/api/configs/packaging/__init__.py index ae4875d591..5f209736a0 100644 --- a/api/configs/packaging/__init__.py +++ b/api/configs/packaging/__init__.py @@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings): CURRENT_VERSION: str = Field( description="Dify version", - default="1.4.1", + default="1.4.2", ) COMMIT_SHA: str = Field( diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index 75bdab1a06..a409a729ce 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -2,7 +2,7 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:1.4.1 + image: langgenius/dify-api:1.4.2 restart: always environment: # Use the shared environment variables. @@ -31,7 +31,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:1.4.1 + image: langgenius/dify-api:1.4.2 restart: always environment: # Use the shared environment variables. @@ -57,7 +57,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.4.1 + image: langgenius/dify-web:1.4.2 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -142,7 +142,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.1.1-local + image: langgenius/dify-plugin-daemon:0.1.2-local restart: always environment: # Use the shared environment variables. diff --git a/docker/docker-compose.middleware.yaml b/docker/docker-compose.middleware.yaml index 8276e2977f..dceee484ca 100644 --- a/docker/docker-compose.middleware.yaml +++ b/docker/docker-compose.middleware.yaml @@ -71,7 +71,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.1.1-local + image: langgenius/dify-plugin-daemon:0.1.2-local restart: always env_file: - ./middleware.env diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index e559021684..d927334118 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -508,7 +508,7 @@ x-shared-env: &shared-api-worker-env services: # API service api: - image: langgenius/dify-api:1.4.1 + image: langgenius/dify-api:1.4.2 restart: always environment: # Use the shared environment variables. @@ -537,7 +537,7 @@ services: # worker service # The Celery worker for processing the queue. worker: - image: langgenius/dify-api:1.4.1 + image: langgenius/dify-api:1.4.2 restart: always environment: # Use the shared environment variables. @@ -563,7 +563,7 @@ services: # Frontend web application. web: - image: langgenius/dify-web:1.4.1 + image: langgenius/dify-web:1.4.2 restart: always environment: CONSOLE_API_URL: ${CONSOLE_API_URL:-} @@ -648,7 +648,7 @@ services: # plugin daemon plugin_daemon: - image: langgenius/dify-plugin-daemon:0.1.1-local + image: langgenius/dify-plugin-daemon:0.1.2-local restart: always environment: # Use the shared environment variables. diff --git a/web/package.json b/web/package.json index ff4214f966..f42233a5b3 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "dify-web", - "version": "1.4.1", + "version": "1.4.2", "private": true, "engines": { "node": ">=v22.11.0" From 41e3ecc8376125034a89914ad8f74dc695eb910f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B9=9B=E9=9C=B2=E5=85=88=E7=94=9F?= Date: Wed, 11 Jun 2025 16:57:24 +0800 Subject: [PATCH 4/5] fix remote ip header CF-Connecting-IP (#20846) --- api/libs/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/libs/helper.py b/api/libs/helper.py index 070e575dbc..3f2a630956 100644 --- a/api/libs/helper.py +++ b/api/libs/helper.py @@ -185,7 +185,7 @@ def generate_string(n): def extract_remote_ip(request) -> str: if request.headers.get("CF-Connecting-IP"): - return cast(str, request.headers.get("Cf-Connecting-Ip")) + return cast(str, request.headers.get("CF-Connecting-IP")) elif request.headers.getlist("X-Forwarded-For"): return cast(str, request.headers.getlist("X-Forwarded-For")[0]) else: From 4d9b15e519e3162a0b3860dafacd3d36020ca152 Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Wed, 11 Jun 2025 17:11:28 +0800 Subject: [PATCH 5/5] fix --- .../plugins/component-picker-block/index.tsx | 4 +- .../components/base/prompt-editor/types.ts | 2 + .../components/input-field/dialog-wrapper.tsx | 2 +- .../input-field/editor/dialog-wrapper.tsx | 2 +- .../workflow/hooks/use-nodes-interactions.ts | 2 +- .../components/input-support-select-var.tsx | 6 +++ .../nodes/_base/components/prompt/editor.tsx | 2 +- .../variable/manage-input-field.tsx | 38 +++++++++++++++++++ .../variable/var-reference-picker.tsx | 1 + .../variable/var-reference-popup.tsx | 8 +++- .../variable/var-reference-vars.tsx | 12 ++++++ .../condition-list/condition-input.tsx | 4 ++ .../workflow/nodes/knowledge-base/default.ts | 2 +- .../condition-list/condition-input.tsx | 4 ++ web/i18n/en-US/pipeline.ts | 4 ++ web/i18n/zh-Hans/pipeline.ts | 4 ++ 16 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 web/app/components/workflow/nodes/_base/components/variable/manage-input-field.tsx diff --git a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx index b43d2c8117..b7444be38c 100644 --- a/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx +++ b/web/app/components/base/prompt-editor/plugins/component-picker-block/index.tsx @@ -165,6 +165,8 @@ const ComponentPicker = ({ isSupportFileVar={isSupportFileVar} onClose={handleClose} onBlur={handleClose} + showManageInputField={workflowVariableBlock.showManageInputField} + onManageInputField={workflowVariableBlock.onManageInputField} /> ) @@ -205,7 +207,7 @@ const ComponentPicker = ({ } ) - }, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable, handleClose, isSupportFileVar]) + }, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable, handleClose, isSupportFileVar, workflowVariableBlock?.showManageInputField, workflowVariableBlock?.onManageInputField]) return ( void onDelete?: () => void getVarType?: GetVarType + showManageInputField?: boolean + onManageInputField?: () => void } export type MenuTextMatch = { diff --git a/web/app/components/rag-pipeline/components/input-field/dialog-wrapper.tsx b/web/app/components/rag-pipeline/components/input-field/dialog-wrapper.tsx index aa118f8840..b0273962b4 100644 --- a/web/app/components/rag-pipeline/components/input-field/dialog-wrapper.tsx +++ b/web/app/components/rag-pipeline/components/input-field/dialog-wrapper.tsx @@ -21,7 +21,7 @@ const DialogWrapper = ({ const close = useCallback(() => onClose?.(), [onClose]) return ( - +
onClose?.(), [onClose]) return ( - +
{ } else { // If no nodeId is provided, fall back to the current behavior - const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.DataSource + const bundledNodes = nodes.filter(node => node.data._isBundled && node.data.type !== BlockEnum.Start && node.data.type !== BlockEnum.DataSource && node.data.type !== BlockEnum.KnowledgeBase && !node.data.isInIteration && !node.data.isInLoop) if (bundledNodes.length) { diff --git a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx index a741629da8..e8e2645a46 100644 --- a/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx +++ b/web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx @@ -13,6 +13,7 @@ import PromptEditor from '@/app/components/base/prompt-editor' import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' import Tooltip from '@/app/components/base/tooltip' import { noop } from 'lodash-es' +import { useStore } from '@/app/components/workflow/store' type Props = { instanceId?: string @@ -56,6 +57,9 @@ const Editor: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [isFocus]) + const pipelineId = useStore(s => s.pipelineId) + const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog) + return (
<> @@ -103,6 +107,8 @@ const Editor: FC = ({ } return acc }, {} as any), + showManageInputField: !!pipelineId, + onManageInputField: () => setShowInputFieldDialog?.(true), }} onChange={onChange} editable={!readOnly} diff --git a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx index 0a7ebc2a09..51993d0397 100644 --- a/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx +++ b/web/app/components/workflow/nodes/_base/components/prompt/editor.tsx @@ -254,7 +254,7 @@ const Editor: FC = ({ workflowVariableBlock={{ show: true, variables: nodesOutputVars || [], - getVarType, + getVarType: getVarType as any, workflowNodesMap: availableNodes.reduce((acc, node) => { acc[node.id] = { title: node.data.title, diff --git a/web/app/components/workflow/nodes/_base/components/variable/manage-input-field.tsx b/web/app/components/workflow/nodes/_base/components/variable/manage-input-field.tsx new file mode 100644 index 0000000000..929e585157 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/variable/manage-input-field.tsx @@ -0,0 +1,38 @@ +import { useTranslation } from 'react-i18next' +import { RiAddLine } from '@remixicon/react' + +type ManageInputFieldProps = { + onManage: () => void +} + +const ManageInputField = ({ + onManage, +}: ManageInputFieldProps) => { + const { t } = useTranslation() + + return ( +
+
+ +
+ {t('pipeline.inputField.create')} +
+
+
+
+ {t('pipeline.inputField.manage')} +
+
+ ) +} + +export default ManageInputField diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index f0b2b6c572..18ee82d865 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -319,6 +319,7 @@ const VarReferencePicker: FC = ({ return null }, [isValidVar, isShowAPart, hasValue, t, outputVarNode?.title, outputVarNode?.type, value, type]) + return (
= ({ }) => { const { t } = useTranslation() const { locale } = useContext(I18n) + const pipelineId = useStore(s => s.pipelineId) + const showManageRagInputFields = useMemo(() => !!pipelineId, [pipelineId]) + const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog) // max-h-[300px] overflow-y-auto todo: use portal to handle long list return (
= ({ onChange={onChange} itemWidth={itemWidth} isSupportFileVar={isSupportFileVar} + showManageInputField={showManageRagInputFields} + onManageInputField={() => setShowInputFieldDialog?.(true)} /> }
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index b77c9f005b..329b14b9f2 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -24,6 +24,7 @@ import { FILE_STRUCT } from '@/app/components/workflow/constants' import { Loop } from '@/app/components/base/icons/src/vender/workflow' import { noop } from 'lodash-es' import { InputField } from '@/app/components/base/icons/src/vender/pipeline' +import ManageInputField from './manage-input-field' type ObjectChildrenProps = { nodeId: string @@ -266,6 +267,8 @@ type Props = { maxHeightClass?: string onClose?: () => void onBlur?: () => void + showManageInputField?: boolean + onManageInputField?: () => void } const VarReferenceVars: FC = ({ hideSearch, @@ -277,6 +280,8 @@ const VarReferenceVars: FC = ({ maxHeightClass, onClose, onBlur, + showManageInputField, + onManageInputField, }) => { const { t } = useTranslation() const [searchText, setSearchText] = useState('') @@ -367,6 +372,13 @@ const VarReferenceVars: FC = ({ }
:
{t('workflow.common.noVar')}
} + { + showManageInputField && ( + + ) + } ) } diff --git a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx index e6f08184c8..ea8f6a5b5d 100644 --- a/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx +++ b/web/app/components/workflow/nodes/if-else/components/condition-list/condition-input.tsx @@ -23,6 +23,8 @@ const ConditionInput = ({ }: ConditionInputProps) => { const { t } = useTranslation() const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey) + const pipelineId = useStore(s => s.pipelineId) + const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog) return ( setShowInputFieldDialog?.(true), }} onChange={onChange} editable={!disabled} diff --git a/web/app/components/workflow/nodes/knowledge-base/default.ts b/web/app/components/workflow/nodes/knowledge-base/default.ts index a7132df364..e43c08778f 100644 --- a/web/app/components/workflow/nodes/knowledge-base/default.ts +++ b/web/app/components/workflow/nodes/knowledge-base/default.ts @@ -13,7 +13,7 @@ const nodeDefault: NodeDefault = { index_chunk_variable_selector: [], keyword_number: 10, retrieval_model: { - top_k: 2, + top_k: 3, score_threshold_enabled: false, score_threshold: 0.5, }, diff --git a/web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx b/web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx index 5c02f140dc..4df537ee43 100644 --- a/web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx +++ b/web/app/components/workflow/nodes/loop/components/condition-list/condition-input.tsx @@ -20,6 +20,8 @@ const ConditionInput = ({ }: ConditionInputProps) => { const { t } = useTranslation() const controlPromptEditorRerenderKey = useStore(s => s.controlPromptEditorRerenderKey) + const pipelineId = useStore(s => s.pipelineId) + const setShowInputFieldDialog = useStore(s => s.setShowInputFieldDialog) return ( setShowInputFieldDialog?.(true), }} onChange={onChange} editable={!disabled} diff --git a/web/i18n/en-US/pipeline.ts b/web/i18n/en-US/pipeline.ts index 7a3d24b5ca..5f895eb250 100644 --- a/web/i18n/en-US/pipeline.ts +++ b/web/i18n/en-US/pipeline.ts @@ -11,6 +11,10 @@ const translation = { descriptionPlaceholder: 'Please enter the description of this Knowledge Pipeline. (Optional) ', }, }, + inputField: { + create: 'Create user input field', + manage: 'Manage', + }, } export default translation diff --git a/web/i18n/zh-Hans/pipeline.ts b/web/i18n/zh-Hans/pipeline.ts index b02fc001a1..3197e66170 100644 --- a/web/i18n/zh-Hans/pipeline.ts +++ b/web/i18n/zh-Hans/pipeline.ts @@ -11,6 +11,10 @@ const translation = { descriptionPlaceholder: '请输入此 Pipeline 的描述。 (可选)', }, }, + inputField: { + create: '创建用户输入字段', + manage: '管理', + }, } export default translation