mirror of https://github.com/langgenius/dify.git
Feat: Add "Open Workflow" link in workflow side panel (#28898)
This commit is contained in:
parent
95528ad8e5
commit
0a2d478749
|
|
@ -54,6 +54,8 @@ class ToolProviderApiEntity(BaseModel):
|
||||||
configuration: MCPConfiguration | None = Field(
|
configuration: MCPConfiguration | None = Field(
|
||||||
default=None, description="The timeout and sse_read_timeout of the MCP tool"
|
default=None, description="The timeout and sse_read_timeout of the MCP tool"
|
||||||
)
|
)
|
||||||
|
# Workflow
|
||||||
|
workflow_app_id: str | None = Field(default=None, description="The app id of the workflow tool")
|
||||||
|
|
||||||
@field_validator("tools", mode="before")
|
@field_validator("tools", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -87,6 +89,8 @@ class ToolProviderApiEntity(BaseModel):
|
||||||
optional_fields.update(self.optional_field("is_dynamic_registration", self.is_dynamic_registration))
|
optional_fields.update(self.optional_field("is_dynamic_registration", self.is_dynamic_registration))
|
||||||
optional_fields.update(self.optional_field("masked_headers", self.masked_headers))
|
optional_fields.update(self.optional_field("masked_headers", self.masked_headers))
|
||||||
optional_fields.update(self.optional_field("original_headers", self.original_headers))
|
optional_fields.update(self.optional_field("original_headers", self.original_headers))
|
||||||
|
elif self.type == ToolProviderType.WORKFLOW:
|
||||||
|
optional_fields.update(self.optional_field("workflow_app_id", self.workflow_app_id))
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"author": self.author,
|
"author": self.author,
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,9 @@ class ToolTransformService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def workflow_provider_to_user_provider(
|
def workflow_provider_to_user_provider(
|
||||||
provider_controller: WorkflowToolProviderController, labels: list[str] | None = None
|
provider_controller: WorkflowToolProviderController,
|
||||||
|
labels: list[str] | None = None,
|
||||||
|
workflow_app_id: str | None = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
convert provider controller to user provider
|
convert provider controller to user provider
|
||||||
|
|
@ -221,6 +223,7 @@ class ToolTransformService:
|
||||||
plugin_unique_identifier=None,
|
plugin_unique_identifier=None,
|
||||||
tools=[],
|
tools=[],
|
||||||
labels=labels or [],
|
labels=labels or [],
|
||||||
|
workflow_app_id=workflow_app_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,9 @@ class WorkflowToolManageService:
|
||||||
select(WorkflowToolProvider).where(WorkflowToolProvider.tenant_id == tenant_id)
|
select(WorkflowToolProvider).where(WorkflowToolProvider.tenant_id == tenant_id)
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
# Create a mapping from provider_id to app_id
|
||||||
|
provider_id_to_app_id = {provider.id: provider.app_id for provider in db_tools}
|
||||||
|
|
||||||
tools: list[WorkflowToolProviderController] = []
|
tools: list[WorkflowToolProviderController] = []
|
||||||
for provider in db_tools:
|
for provider in db_tools:
|
||||||
try:
|
try:
|
||||||
|
|
@ -202,8 +205,11 @@ class WorkflowToolManageService:
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
|
workflow_app_id = provider_id_to_app_id.get(tool.provider_id)
|
||||||
user_tool_provider = ToolTransformService.workflow_provider_to_user_provider(
|
user_tool_provider = ToolTransformService.workflow_provider_to_user_provider(
|
||||||
provider_controller=tool, labels=labels.get(tool.provider_id, [])
|
provider_controller=tool,
|
||||||
|
labels=labels.get(tool.provider_id, []),
|
||||||
|
workflow_app_id=workflow_app_id,
|
||||||
)
|
)
|
||||||
ToolTransformService.repack_provider(tenant_id=tenant_id, provider=user_tool_provider)
|
ToolTransformService.repack_provider(tenant_id=tenant_id, provider=user_tool_provider)
|
||||||
user_tool_provider.tools = [
|
user_tool_provider.tools = [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
"""
|
||||||
|
Unit tests for ToolProviderApiEntity workflow_app_id field.
|
||||||
|
|
||||||
|
This test suite covers:
|
||||||
|
- ToolProviderApiEntity workflow_app_id field creation and default value
|
||||||
|
- ToolProviderApiEntity.to_dict() method behavior with workflow_app_id
|
||||||
|
"""
|
||||||
|
|
||||||
|
from core.tools.entities.api_entities import ToolProviderApiEntity
|
||||||
|
from core.tools.entities.common_entities import I18nObject
|
||||||
|
from core.tools.entities.tool_entities import ToolProviderType
|
||||||
|
|
||||||
|
|
||||||
|
class TestToolProviderApiEntityWorkflowAppId:
|
||||||
|
"""Test suite for ToolProviderApiEntity workflow_app_id field."""
|
||||||
|
|
||||||
|
def test_workflow_app_id_field_default_none(self):
|
||||||
|
"""Test that workflow_app_id defaults to None when not provided."""
|
||||||
|
entity = ToolProviderApiEntity(
|
||||||
|
id="test_id",
|
||||||
|
author="test_author",
|
||||||
|
name="test_name",
|
||||||
|
description=I18nObject(en_US="Test description"),
|
||||||
|
icon="test_icon",
|
||||||
|
label=I18nObject(en_US="Test label"),
|
||||||
|
type=ToolProviderType.WORKFLOW,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert entity.workflow_app_id is None
|
||||||
|
|
||||||
|
def test_to_dict_includes_workflow_app_id_when_workflow_type_and_has_value(self):
|
||||||
|
"""Test that to_dict() includes workflow_app_id when type is WORKFLOW and value is set."""
|
||||||
|
workflow_app_id = "app_123"
|
||||||
|
entity = ToolProviderApiEntity(
|
||||||
|
id="test_id",
|
||||||
|
author="test_author",
|
||||||
|
name="test_name",
|
||||||
|
description=I18nObject(en_US="Test description"),
|
||||||
|
icon="test_icon",
|
||||||
|
label=I18nObject(en_US="Test label"),
|
||||||
|
type=ToolProviderType.WORKFLOW,
|
||||||
|
workflow_app_id=workflow_app_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = entity.to_dict()
|
||||||
|
|
||||||
|
assert "workflow_app_id" in result
|
||||||
|
assert result["workflow_app_id"] == workflow_app_id
|
||||||
|
|
||||||
|
def test_to_dict_excludes_workflow_app_id_when_workflow_type_and_none(self):
|
||||||
|
"""Test that to_dict() excludes workflow_app_id when type is WORKFLOW but value is None."""
|
||||||
|
entity = ToolProviderApiEntity(
|
||||||
|
id="test_id",
|
||||||
|
author="test_author",
|
||||||
|
name="test_name",
|
||||||
|
description=I18nObject(en_US="Test description"),
|
||||||
|
icon="test_icon",
|
||||||
|
label=I18nObject(en_US="Test label"),
|
||||||
|
type=ToolProviderType.WORKFLOW,
|
||||||
|
workflow_app_id=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = entity.to_dict()
|
||||||
|
|
||||||
|
assert "workflow_app_id" not in result
|
||||||
|
|
||||||
|
def test_to_dict_excludes_workflow_app_id_when_not_workflow_type(self):
|
||||||
|
"""Test that to_dict() excludes workflow_app_id when type is not WORKFLOW."""
|
||||||
|
workflow_app_id = "app_123"
|
||||||
|
entity = ToolProviderApiEntity(
|
||||||
|
id="test_id",
|
||||||
|
author="test_author",
|
||||||
|
name="test_name",
|
||||||
|
description=I18nObject(en_US="Test description"),
|
||||||
|
icon="test_icon",
|
||||||
|
label=I18nObject(en_US="Test label"),
|
||||||
|
type=ToolProviderType.BUILT_IN,
|
||||||
|
workflow_app_id=workflow_app_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
result = entity.to_dict()
|
||||||
|
|
||||||
|
assert "workflow_app_id" not in result
|
||||||
|
|
||||||
|
def test_to_dict_includes_workflow_app_id_for_workflow_type_with_empty_string(self):
|
||||||
|
"""Test that to_dict() excludes workflow_app_id when value is empty string (falsy)."""
|
||||||
|
entity = ToolProviderApiEntity(
|
||||||
|
id="test_id",
|
||||||
|
author="test_author",
|
||||||
|
name="test_name",
|
||||||
|
description=I18nObject(en_US="Test description"),
|
||||||
|
icon="test_icon",
|
||||||
|
label=I18nObject(en_US="Test label"),
|
||||||
|
type=ToolProviderType.WORKFLOW,
|
||||||
|
workflow_app_id="",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = entity.to_dict()
|
||||||
|
|
||||||
|
assert "workflow_app_id" not in result
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from core.tools.__base.tool import Tool
|
from core.tools.__base.tool import Tool
|
||||||
from core.tools.entities.api_entities import ToolApiEntity
|
from core.tools.entities.api_entities import ToolApiEntity, ToolProviderApiEntity
|
||||||
from core.tools.entities.common_entities import I18nObject
|
from core.tools.entities.common_entities import I18nObject
|
||||||
from core.tools.entities.tool_entities import ToolParameter
|
from core.tools.entities.tool_entities import ToolParameter, ToolProviderType
|
||||||
from services.tools.tools_transform_service import ToolTransformService
|
from services.tools.tools_transform_service import ToolTransformService
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -299,3 +299,154 @@ class TestToolTransformService:
|
||||||
param2 = result.parameters[1]
|
param2 = result.parameters[1]
|
||||||
assert param2.name == "param2"
|
assert param2.name == "param2"
|
||||||
assert param2.label == "Runtime Param 2"
|
assert param2.label == "Runtime Param 2"
|
||||||
|
|
||||||
|
|
||||||
|
class TestWorkflowProviderToUserProvider:
|
||||||
|
"""Test cases for ToolTransformService.workflow_provider_to_user_provider method"""
|
||||||
|
|
||||||
|
def test_workflow_provider_to_user_provider_with_workflow_app_id(self):
|
||||||
|
"""Test that workflow_provider_to_user_provider correctly sets workflow_app_id."""
|
||||||
|
from core.tools.workflow_as_tool.provider import WorkflowToolProviderController
|
||||||
|
|
||||||
|
# Create mock workflow tool provider controller
|
||||||
|
workflow_app_id = "app_123"
|
||||||
|
provider_id = "provider_123"
|
||||||
|
mock_controller = Mock(spec=WorkflowToolProviderController)
|
||||||
|
mock_controller.provider_id = provider_id
|
||||||
|
mock_controller.entity = Mock()
|
||||||
|
mock_controller.entity.identity = Mock()
|
||||||
|
mock_controller.entity.identity.author = "test_author"
|
||||||
|
mock_controller.entity.identity.name = "test_workflow_tool"
|
||||||
|
mock_controller.entity.identity.description = I18nObject(en_US="Test description")
|
||||||
|
mock_controller.entity.identity.icon = {"type": "emoji", "content": "🔧"}
|
||||||
|
mock_controller.entity.identity.icon_dark = None
|
||||||
|
mock_controller.entity.identity.label = I18nObject(en_US="Test Workflow Tool")
|
||||||
|
|
||||||
|
# Call the method
|
||||||
|
result = ToolTransformService.workflow_provider_to_user_provider(
|
||||||
|
provider_controller=mock_controller,
|
||||||
|
labels=["label1", "label2"],
|
||||||
|
workflow_app_id=workflow_app_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify the result
|
||||||
|
assert isinstance(result, ToolProviderApiEntity)
|
||||||
|
assert result.id == provider_id
|
||||||
|
assert result.author == "test_author"
|
||||||
|
assert result.name == "test_workflow_tool"
|
||||||
|
assert result.type == ToolProviderType.WORKFLOW
|
||||||
|
assert result.workflow_app_id == workflow_app_id
|
||||||
|
assert result.labels == ["label1", "label2"]
|
||||||
|
assert result.is_team_authorization is True
|
||||||
|
assert result.plugin_id is None
|
||||||
|
assert result.plugin_unique_identifier is None
|
||||||
|
assert result.tools == []
|
||||||
|
|
||||||
|
def test_workflow_provider_to_user_provider_without_workflow_app_id(self):
|
||||||
|
"""Test that workflow_provider_to_user_provider works when workflow_app_id is not provided."""
|
||||||
|
from core.tools.workflow_as_tool.provider import WorkflowToolProviderController
|
||||||
|
|
||||||
|
# Create mock workflow tool provider controller
|
||||||
|
provider_id = "provider_123"
|
||||||
|
mock_controller = Mock(spec=WorkflowToolProviderController)
|
||||||
|
mock_controller.provider_id = provider_id
|
||||||
|
mock_controller.entity = Mock()
|
||||||
|
mock_controller.entity.identity = Mock()
|
||||||
|
mock_controller.entity.identity.author = "test_author"
|
||||||
|
mock_controller.entity.identity.name = "test_workflow_tool"
|
||||||
|
mock_controller.entity.identity.description = I18nObject(en_US="Test description")
|
||||||
|
mock_controller.entity.identity.icon = {"type": "emoji", "content": "🔧"}
|
||||||
|
mock_controller.entity.identity.icon_dark = None
|
||||||
|
mock_controller.entity.identity.label = I18nObject(en_US="Test Workflow Tool")
|
||||||
|
|
||||||
|
# Call the method without workflow_app_id
|
||||||
|
result = ToolTransformService.workflow_provider_to_user_provider(
|
||||||
|
provider_controller=mock_controller,
|
||||||
|
labels=["label1"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify the result
|
||||||
|
assert isinstance(result, ToolProviderApiEntity)
|
||||||
|
assert result.id == provider_id
|
||||||
|
assert result.workflow_app_id is None
|
||||||
|
assert result.labels == ["label1"]
|
||||||
|
|
||||||
|
def test_workflow_provider_to_user_provider_workflow_app_id_none(self):
|
||||||
|
"""Test that workflow_provider_to_user_provider handles None workflow_app_id explicitly."""
|
||||||
|
from core.tools.workflow_as_tool.provider import WorkflowToolProviderController
|
||||||
|
|
||||||
|
# Create mock workflow tool provider controller
|
||||||
|
provider_id = "provider_123"
|
||||||
|
mock_controller = Mock(spec=WorkflowToolProviderController)
|
||||||
|
mock_controller.provider_id = provider_id
|
||||||
|
mock_controller.entity = Mock()
|
||||||
|
mock_controller.entity.identity = Mock()
|
||||||
|
mock_controller.entity.identity.author = "test_author"
|
||||||
|
mock_controller.entity.identity.name = "test_workflow_tool"
|
||||||
|
mock_controller.entity.identity.description = I18nObject(en_US="Test description")
|
||||||
|
mock_controller.entity.identity.icon = {"type": "emoji", "content": "🔧"}
|
||||||
|
mock_controller.entity.identity.icon_dark = None
|
||||||
|
mock_controller.entity.identity.label = I18nObject(en_US="Test Workflow Tool")
|
||||||
|
|
||||||
|
# Call the method with explicit None values
|
||||||
|
result = ToolTransformService.workflow_provider_to_user_provider(
|
||||||
|
provider_controller=mock_controller,
|
||||||
|
labels=None,
|
||||||
|
workflow_app_id=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify the result
|
||||||
|
assert isinstance(result, ToolProviderApiEntity)
|
||||||
|
assert result.id == provider_id
|
||||||
|
assert result.workflow_app_id is None
|
||||||
|
assert result.labels == []
|
||||||
|
|
||||||
|
def test_workflow_provider_to_user_provider_preserves_other_fields(self):
|
||||||
|
"""Test that workflow_provider_to_user_provider preserves all other entity fields."""
|
||||||
|
from core.tools.workflow_as_tool.provider import WorkflowToolProviderController
|
||||||
|
|
||||||
|
# Create mock workflow tool provider controller with various fields
|
||||||
|
workflow_app_id = "app_456"
|
||||||
|
provider_id = "provider_456"
|
||||||
|
mock_controller = Mock(spec=WorkflowToolProviderController)
|
||||||
|
mock_controller.provider_id = provider_id
|
||||||
|
mock_controller.entity = Mock()
|
||||||
|
mock_controller.entity.identity = Mock()
|
||||||
|
mock_controller.entity.identity.author = "another_author"
|
||||||
|
mock_controller.entity.identity.name = "another_workflow_tool"
|
||||||
|
mock_controller.entity.identity.description = I18nObject(
|
||||||
|
en_US="Another description", zh_Hans="Another description"
|
||||||
|
)
|
||||||
|
mock_controller.entity.identity.icon = {"type": "emoji", "content": "⚙️"}
|
||||||
|
mock_controller.entity.identity.icon_dark = {"type": "emoji", "content": "🔧"}
|
||||||
|
mock_controller.entity.identity.label = I18nObject(
|
||||||
|
en_US="Another Workflow Tool", zh_Hans="Another Workflow Tool"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Call the method
|
||||||
|
result = ToolTransformService.workflow_provider_to_user_provider(
|
||||||
|
provider_controller=mock_controller,
|
||||||
|
labels=["automation", "workflow"],
|
||||||
|
workflow_app_id=workflow_app_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify all fields are preserved correctly
|
||||||
|
assert isinstance(result, ToolProviderApiEntity)
|
||||||
|
assert result.id == provider_id
|
||||||
|
assert result.author == "another_author"
|
||||||
|
assert result.name == "another_workflow_tool"
|
||||||
|
assert result.description.en_US == "Another description"
|
||||||
|
assert result.description.zh_Hans == "Another description"
|
||||||
|
assert result.icon == {"type": "emoji", "content": "⚙️"}
|
||||||
|
assert result.icon_dark == {"type": "emoji", "content": "🔧"}
|
||||||
|
assert result.label.en_US == "Another Workflow Tool"
|
||||||
|
assert result.label.zh_Hans == "Another Workflow Tool"
|
||||||
|
assert result.type == ToolProviderType.WORKFLOW
|
||||||
|
assert result.workflow_app_id == workflow_app_id
|
||||||
|
assert result.labels == ["automation", "workflow"]
|
||||||
|
assert result.masked_credentials == {}
|
||||||
|
assert result.is_team_authorization is True
|
||||||
|
assert result.allow_delete is True
|
||||||
|
assert result.plugin_id is None
|
||||||
|
assert result.plugin_unique_identifier is None
|
||||||
|
assert result.tools == []
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,8 @@ export type Collection = {
|
||||||
timeout?: number
|
timeout?: number
|
||||||
sse_read_timeout?: number
|
sse_read_timeout?: number
|
||||||
}
|
}
|
||||||
|
// Workflow
|
||||||
|
workflow_app_id?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolParameter = {
|
export type ToolParameter = {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
|
useMemo,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useEdges } from 'reactflow'
|
import { useEdges } from 'reactflow'
|
||||||
|
|
@ -16,6 +17,10 @@ import {
|
||||||
} from '@/app/components/workflow/hooks'
|
} from '@/app/components/workflow/hooks'
|
||||||
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
|
import ShortcutsName from '@/app/components/workflow/shortcuts-name'
|
||||||
import type { Node } from '@/app/components/workflow/types'
|
import type { Node } from '@/app/components/workflow/types'
|
||||||
|
import { BlockEnum } from '@/app/components/workflow/types'
|
||||||
|
import { CollectionType } from '@/app/components/tools/types'
|
||||||
|
import { useAllWorkflowTools } from '@/service/use-tools'
|
||||||
|
import { canFindTool } from '@/utils'
|
||||||
|
|
||||||
type PanelOperatorPopupProps = {
|
type PanelOperatorPopupProps = {
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -45,6 +50,14 @@ const PanelOperatorPopup = ({
|
||||||
const showChangeBlock = !nodeMetaData.isTypeFixed && !nodesReadOnly
|
const showChangeBlock = !nodeMetaData.isTypeFixed && !nodesReadOnly
|
||||||
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
const isChildNode = !!(data.isInIteration || data.isInLoop)
|
||||||
|
|
||||||
|
const { data: workflowTools } = useAllWorkflowTools()
|
||||||
|
const isWorkflowTool = data.type === BlockEnum.Tool && data.provider_type === CollectionType.workflow
|
||||||
|
const workflowAppId = useMemo(() => {
|
||||||
|
if (!isWorkflowTool || !workflowTools || !data.provider_id) return undefined
|
||||||
|
const workflowTool = workflowTools.find(item => canFindTool(item.id, data.provider_id))
|
||||||
|
return workflowTool?.workflow_app_id
|
||||||
|
}, [isWorkflowTool, workflowTools, data.provider_id])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'>
|
<div className='w-[240px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'>
|
||||||
{
|
{
|
||||||
|
|
@ -137,6 +150,22 @@ const PanelOperatorPopup = ({
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
isWorkflowTool && workflowAppId && (
|
||||||
|
<>
|
||||||
|
<div className='p-1'>
|
||||||
|
<a
|
||||||
|
href={`/app/${workflowAppId}/workflow`}
|
||||||
|
target='_blank'
|
||||||
|
className='flex h-8 cursor-pointer items-center rounded-lg px-3 text-sm text-text-secondary hover:bg-state-base-hover'
|
||||||
|
>
|
||||||
|
{t('workflow.panel.openWorkflow')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className='h-px bg-divider-regular'></div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
{
|
{
|
||||||
showHelpLink && nodeMetaData.helpLinkUri && (
|
showHelpLink && nodeMetaData.helpLinkUri && (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,7 @@ const translation = {
|
||||||
userInputField: 'User Input Field',
|
userInputField: 'User Input Field',
|
||||||
changeBlock: 'Change Node',
|
changeBlock: 'Change Node',
|
||||||
helpLink: 'View Docs',
|
helpLink: 'View Docs',
|
||||||
|
openWorkflow: 'Open Workflow',
|
||||||
about: 'About',
|
about: 'About',
|
||||||
createdBy: 'Created By ',
|
createdBy: 'Created By ',
|
||||||
nextStep: 'Next Step',
|
nextStep: 'Next Step',
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,7 @@ const translation = {
|
||||||
userInputField: '用户输入字段',
|
userInputField: '用户输入字段',
|
||||||
changeBlock: '更改节点',
|
changeBlock: '更改节点',
|
||||||
helpLink: '查看帮助文档',
|
helpLink: '查看帮助文档',
|
||||||
|
openWorkflow: '打开工作流',
|
||||||
about: '关于',
|
about: '关于',
|
||||||
createdBy: '作者',
|
createdBy: '作者',
|
||||||
nextStep: '下一步',
|
nextStep: '下一步',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue