diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py
index f07fd32a27..a3b75555cd 100644
--- a/api/controllers/console/app/workflow.py
+++ b/api/controllers/console/app/workflow.py
@@ -1056,15 +1056,23 @@ class DraftWorkflowTriggerNodeApi(Resource):
raise e
if not event:
return jsonable_encoder({"status": "waiting", "retry_in": LISTENING_RETRY_IN})
+
+ workflow_args = dict(event.workflow_args or {})
+ raw_files = workflow_args.get("files")
+ files = _parse_file(draft_workflow, raw_files if isinstance(raw_files, list) else None)
+ if node_type == NodeType.TRIGGER_WEBHOOK:
+ user_inputs = workflow_args.get("inputs") or {}
+ else:
+ user_inputs = workflow_args
try:
node_execution = workflow_service.run_draft_workflow_node(
app_model=app_model,
draft_workflow=draft_workflow,
node_id=node_id,
- user_inputs=event.workflow_args,
+ user_inputs=user_inputs,
account=current_user,
query="",
- files=[],
+ files=files,
)
return jsonable_encoder(node_execution)
except Exception as e:
diff --git a/api/controllers/console/workspace/plugin.py b/api/controllers/console/workspace/plugin.py
index 19d7ce8722..2f38832b05 100644
--- a/api/controllers/console/workspace/plugin.py
+++ b/api/controllers/console/workspace/plugin.py
@@ -114,6 +114,24 @@ class PluginIconApi(Resource):
return send_file(io.BytesIO(icon_bytes), mimetype=mimetype, max_age=icon_cache_max_age)
+class PluginAssetApi(Resource):
+ @setup_required
+ @login_required
+ @account_initialization_required
+ def get(self):
+ req = reqparse.RequestParser()
+ req.add_argument("plugin_unique_identifier", type=str, required=True, location="args")
+ req.add_argument("file_name", type=str, required=True, location="args")
+ args = req.parse_args()
+
+ current_user, tenant_id = current_account_with_tenant()
+ try:
+ binary = PluginService.extract_asset(tenant_id, args["plugin_unique_identifier"], args["file_name"])
+ return send_file(io.BytesIO(binary), mimetype="application/octet-stream")
+ except PluginDaemonClientSideError as e:
+ raise ValueError(e)
+
+
@console_ns.route("/workspaces/current/plugin/upload/pkg")
class PluginUploadFromPkgApi(Resource):
@setup_required
@@ -688,3 +706,25 @@ class PluginAutoUpgradeExcludePluginApi(Resource):
args = req.parse_args()
return jsonable_encoder({"success": PluginAutoUpgradeService.exclude_plugin(tenant_id, args["plugin_id"])})
+
+
+@console_ns.route("/workspaces/current/plugin/readme")
+class PluginReadmeApi(Resource):
+ @setup_required
+ @login_required
+ @account_initialization_required
+ def get(self):
+ current_user, tenant_id = current_account_with_tenant()
+ parser = reqparse.RequestParser()
+ parser.add_argument("plugin_unique_identifier", type=str, required=True, location="args")
+ parser.add_argument("language", type=str, required=False, location="args")
+ args = parser.parse_args()
+ return jsonable_encoder(
+ {
+ "readme": PluginService.fetch_plugin_readme(
+ tenant_id,
+ args["plugin_unique_identifier"],
+ args.get("language", "en-US")
+ )
+ }
+ )
diff --git a/api/core/plugin/entities/plugin_daemon.py b/api/core/plugin/entities/plugin_daemon.py
index 97b8a8c5ba..a3ab65c8f9 100644
--- a/api/core/plugin/entities/plugin_daemon.py
+++ b/api/core/plugin/entities/plugin_daemon.py
@@ -252,3 +252,7 @@ class CredentialType(enum.StrEnum):
return cls.UNAUTHORIZED
else:
raise ValueError(f"Invalid credential type: {credential_type}")
+
+class PluginReadmeResponse(BaseModel):
+ content: str = Field(description="The readme of the plugin.")
+ language: str = Field(description="The language of the readme.")
diff --git a/api/core/plugin/impl/asset.py b/api/core/plugin/impl/asset.py
index b9bfe2d2cf..0c6a744bb5 100644
--- a/api/core/plugin/impl/asset.py
+++ b/api/core/plugin/impl/asset.py
@@ -10,3 +10,9 @@ class PluginAssetManager(BasePluginClient):
if response.status_code != 200:
raise ValueError(f"can not found asset {id}")
return response.content
+
+ def extract_asset(self, tenant_id: str, plugin_unique_identifier: str, filename: str) -> bytes:
+ response = self._request(method="GET", path=f"plugin/{tenant_id}/asset/{plugin_unique_identifier}")
+ if response.status_code != 200:
+ raise ValueError(f"can not found asset {plugin_unique_identifier}, {str(response.status_code)}")
+ return response.content
diff --git a/api/core/plugin/impl/plugin.py b/api/core/plugin/impl/plugin.py
index 18b5fa8af6..6124b0dd8d 100644
--- a/api/core/plugin/impl/plugin.py
+++ b/api/core/plugin/impl/plugin.py
@@ -1,5 +1,7 @@
from collections.abc import Sequence
+from requests import HTTPError
+
from core.plugin.entities.bundle import PluginBundleDependency
from core.plugin.entities.plugin import (
MissingPluginDependency,
@@ -13,12 +15,35 @@ from core.plugin.entities.plugin_daemon import (
PluginInstallTask,
PluginInstallTaskStartResponse,
PluginListResponse,
+ PluginReadmeResponse,
)
from core.plugin.impl.base import BasePluginClient
from models.provider_ids import GenericProviderID
class PluginInstaller(BasePluginClient):
+ def fetch_plugin_readme(self, tenant_id: str, plugin_unique_identifier: str, language: str) -> str:
+ """
+ Fetch plugin readme
+ """
+ try:
+ response = self._request_with_plugin_daemon_response(
+ "GET",
+ f"plugin/{tenant_id}/management/fetch/readme",
+ PluginReadmeResponse,
+ params={
+ "tenant_id":tenant_id,
+ "plugin_unique_identifier": plugin_unique_identifier,
+ "language": language
+ }
+ )
+ return response.content
+ except HTTPError as e:
+ message = e.args[0]
+ if "404" in message:
+ return ""
+ raise e
+
def fetch_plugin_by_identifier(
self,
tenant_id: str,
diff --git a/api/services/app_dsl_service.py b/api/services/app_dsl_service.py
index 9c3cc48961..15fefd6116 100644
--- a/api/services/app_dsl_service.py
+++ b/api/services/app_dsl_service.py
@@ -44,7 +44,7 @@ IMPORT_INFO_REDIS_KEY_PREFIX = "app_import_info:"
CHECK_DEPENDENCIES_REDIS_KEY_PREFIX = "app_check_dependencies:"
IMPORT_INFO_REDIS_EXPIRY = 10 * 60 # 10 minutes
DSL_MAX_SIZE = 10 * 1024 * 1024 # 10MB
-CURRENT_DSL_VERSION = "0.4.0"
+CURRENT_DSL_VERSION = "0.5.0"
class ImportMode(StrEnum):
diff --git a/api/services/plugin/plugin_service.py b/api/services/plugin/plugin_service.py
index 3ac02428a4..b8303eb724 100644
--- a/api/services/plugin/plugin_service.py
+++ b/api/services/plugin/plugin_service.py
@@ -193,6 +193,11 @@ class PluginService:
mime_type, _ = guess_type(asset_file)
return manager.fetch_asset(tenant_id, asset_file), mime_type or "application/octet-stream"
+ @staticmethod
+ def extract_asset(tenant_id: str, plugin_unique_identifier: str, file_name: str) -> bytes:
+ manager = PluginAssetManager()
+ return manager.extract_asset(tenant_id, plugin_unique_identifier, file_name)
+
@staticmethod
def check_plugin_unique_identifier(tenant_id: str, plugin_unique_identifier: str) -> bool:
"""
@@ -510,3 +515,11 @@ class PluginService:
"""
manager = PluginInstaller()
return manager.check_tools_existence(tenant_id, provider_ids)
+
+ @staticmethod
+ def fetch_plugin_readme(tenant_id: str, plugin_unique_identifier: str, language: str) -> str:
+ """
+ Fetch plugin readme
+ """
+ manager = PluginInstaller()
+ return manager.fetch_plugin_readme(tenant_id, plugin_unique_identifier, language)
diff --git a/web/app/components/app/overview/app-card.tsx b/web/app/components/app/overview/app-card.tsx
index a2d71436c1..43293ab53b 100644
--- a/web/app/components/app/overview/app-card.tsx
+++ b/web/app/components/app/overview/app-card.tsx
@@ -221,7 +221,7 @@ function AppCard({
window.open(docLink('/guides/workflow/node/start'), '_blank')}
+ onClick={() => window.open(docLink('/guides/workflow/node/user-input'), '_blank')}
>
{t('appOverview.overview.appInfo.enableTooltip.learnMore')}
diff --git a/web/app/components/app/overview/trigger-card.tsx b/web/app/components/app/overview/trigger-card.tsx
index 856f17c595..8946bdc0a5 100644
--- a/web/app/components/app/overview/trigger-card.tsx
+++ b/web/app/components/app/overview/trigger-card.tsx
@@ -18,6 +18,7 @@ import { canFindTool } from '@/utils'
import { useTriggerStatusStore } from '@/app/components/workflow/store/trigger-status'
import BlockIcon from '@/app/components/workflow/block-icon'
import { BlockEnum } from '@/app/components/workflow/types'
+import { useDocLink } from '@/context/i18n'
export type ITriggerCardProps = {
appInfo: AppDetailResponse & Partial
@@ -83,6 +84,7 @@ const getTriggerIcon = (trigger: AppTrigger, triggerPlugins: any[]) => {
function TriggerCard({ appInfo, onToggleResult }: ITriggerCardProps) {
const { t } = useTranslation()
+ const docLink = useDocLink()
const appId = appInfo.id
const { isCurrentWorkspaceEditor } = useAppContext()
const { data: triggersResponse, isLoading } = useAppTriggers(appId)
@@ -136,10 +138,6 @@ function TriggerCard({ appInfo, onToggleResult }: ITriggerCardProps) {
}
}
- const handleLearnMoreClick = () => {
- console.log('Learn about Triggers clicked')
- }
-
if (isLoading) {
return (
@@ -209,8 +207,9 @@ function TriggerCard({ appInfo, onToggleResult }: ITriggerCardProps) {
{t('appOverview.overview.triggerInfo.triggerStatusDescription')}{' '}
{t('appOverview.overview.triggerInfo.learnAboutTriggers')}
diff --git a/web/app/components/base/icons/assets/vender/solid/arrows/arrow-up-double-line.svg b/web/app/components/base/icons/assets/vender/solid/arrows/arrow-up-double-line.svg
new file mode 100644
index 0000000000..1f0b9858e1
--- /dev/null
+++ b/web/app/components/base/icons/assets/vender/solid/arrows/arrow-up-double-line.svg
@@ -0,0 +1,3 @@
+
diff --git a/web/app/components/base/icons/src/vender/solid/arrows/ArrowUpDoubleLine.json b/web/app/components/base/icons/src/vender/solid/arrows/ArrowUpDoubleLine.json
new file mode 100644
index 0000000000..b76fc3e80c
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/solid/arrows/ArrowUpDoubleLine.json
@@ -0,0 +1,26 @@
+{
+ "icon": {
+ "type": "element",
+ "isRootNode": true,
+ "name": "svg",
+ "attributes": {
+ "width": "16",
+ "height": "16",
+ "viewBox": "0 0 16 16",
+ "fill": "none",
+ "xmlns": "http://www.w3.org/2000/svg"
+ },
+ "children": [
+ {
+ "type": "element",
+ "name": "path",
+ "attributes": {
+ "d": "M8 3.22388L3.86194 7.36193L4.80475 8.30473L8 5.10949L11.1953 8.30473L12.1381 7.36193L8 3.22388ZM8 6.99046L3.86194 11.1285L4.80475 12.0713L8 8.87606L11.1953 12.0713L12.1381 11.1285L8 6.99046Z",
+ "fill": "currentColor"
+ },
+ "children": []
+ }
+ ]
+ },
+ "name": "ArrowUpDoubleLine"
+}
diff --git a/web/app/components/base/icons/src/vender/solid/arrows/ArrowUpDoubleLine.tsx b/web/app/components/base/icons/src/vender/solid/arrows/ArrowUpDoubleLine.tsx
new file mode 100644
index 0000000000..06ba38ec70
--- /dev/null
+++ b/web/app/components/base/icons/src/vender/solid/arrows/ArrowUpDoubleLine.tsx
@@ -0,0 +1,20 @@
+// GENERATE BY script
+// DON NOT EDIT IT MANUALLY
+
+import * as React from 'react'
+import data from './ArrowUpDoubleLine.json'
+import IconBase from '@/app/components/base/icons/IconBase'
+import type { IconData } from '@/app/components/base/icons/IconBase'
+
+const Icon = (
+ {
+ ref,
+ ...props
+ }: React.SVGProps
& {
+ ref?: React.RefObject>;
+ },
+) =>
+
+Icon.displayName = 'ArrowUpDoubleLine'
+
+export default Icon
diff --git a/web/app/components/base/icons/src/vender/solid/arrows/index.ts b/web/app/components/base/icons/src/vender/solid/arrows/index.ts
index 44522622dc..58ce9aa8ac 100644
--- a/web/app/components/base/icons/src/vender/solid/arrows/index.ts
+++ b/web/app/components/base/icons/src/vender/solid/arrows/index.ts
@@ -1,4 +1,5 @@
export { default as ArrowDownDoubleLine } from './ArrowDownDoubleLine'
export { default as ArrowDownRoundFill } from './ArrowDownRoundFill'
+export { default as ArrowUpDoubleLine } from './ArrowUpDoubleLine'
export { default as ChevronDown } from './ChevronDown'
export { default as HighPriority } from './HighPriority'
diff --git a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx
index 7e8c76d67a..747a232ca7 100644
--- a/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx
+++ b/web/app/components/workflow-app/components/workflow-onboarding-modal/index.tsx
@@ -63,7 +63,7 @@ const WorkflowOnboardingModal: FC = ({
{t('workflow.onboarding.description')}{' '}
>
)}
- {(hasToolsListContent || hasPluginContent) && (
+ {(hasToolsListContent || enable_marketplace) && (
<>
{t('tools.allTools')}
@@ -249,7 +249,7 @@ const AllTools = ({
isShowRAGRecommendations={isShowRAGRecommendations}
/>
)}
- {hasPluginContent && (
+ {enable_marketplace && (
}
diff --git a/web/app/components/workflow/block-selector/featured-tools.tsx b/web/app/components/workflow/block-selector/featured-tools.tsx
index a4d4655988..fe5c561362 100644
--- a/web/app/components/workflow/block-selector/featured-tools.tsx
+++ b/web/app/components/workflow/block-selector/featured-tools.tsx
@@ -16,7 +16,7 @@ import { ViewType } from './view-type-select'
import Tools from './tools'
import { formatNumber } from '@/utils/format'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
-import { ArrowDownDoubleLine, ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/arrows'
+import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
const MAX_RECOMMENDED_COUNT = 15
@@ -119,7 +119,9 @@ const FeaturedTools = ({
const totalVisible = visibleInstalledProviders.length + visibleUninstalledPlugins.length
const maxAvailable = Math.min(MAX_RECOMMENDED_COUNT, installedProviders.length + uninstalledPlugins.length)
- const showMore = totalVisible < maxAvailable
+ const hasMoreToShow = totalVisible < maxAvailable
+ const canToggleVisibility = maxAvailable > INITIAL_VISIBLE_COUNT
+ const isExpanded = canToggleVisibility && !hasMoreToShow
const showEmptyState = !isLoading && totalVisible === 0
return (
@@ -183,19 +185,28 @@ const FeaturedTools = ({
>
)}
- {!isLoading && totalVisible > 0 && showMore && (
+ {!isLoading && totalVisible > 0 && canToggleVisibility && (
{
- setVisibleCount(count => Math.min(count + INITIAL_VISIBLE_COUNT, maxAvailable))
+ setVisibleCount((count) => {
+ if (count >= maxAvailable)
+ return INITIAL_VISIBLE_COUNT
+
+ return Math.min(count + INITIAL_VISIBLE_COUNT, maxAvailable)
+ })
}}
>
-
+ {isExpanded ? (
+
+ ) : (
+
+ )}
- {t('workflow.tabs.showMoreFeatured')}
+ {t(isExpanded ? 'workflow.tabs.showLessFeatured' : 'workflow.tabs.showMoreFeatured')}
)}
diff --git a/web/app/components/workflow/nodes/trigger-plugin/default.ts b/web/app/components/workflow/nodes/trigger-plugin/default.ts
index e1e0665b07..e49204f112 100644
--- a/web/app/components/workflow/nodes/trigger-plugin/default.ts
+++ b/web/app/components/workflow/nodes/trigger-plugin/default.ts
@@ -164,6 +164,7 @@ const convertJsonSchemaToField = (schema: any, schemaTypeDefinitions?: SchemaTyp
const metaData = genNodeMetaData({
sort: 1,
type: BlockEnum.TriggerPlugin,
+ helpLinkUri: 'plugin-trigger',
isStart: true,
})
diff --git a/web/app/components/workflow/nodes/trigger-schedule/default.ts b/web/app/components/workflow/nodes/trigger-schedule/default.ts
index e47518cc25..69f93c33f4 100644
--- a/web/app/components/workflow/nodes/trigger-schedule/default.ts
+++ b/web/app/components/workflow/nodes/trigger-schedule/default.ts
@@ -107,6 +107,7 @@ const validateVisualConfig = (payload: ScheduleTriggerNodeType, t: any): string
const metaData = genNodeMetaData({
sort: 2,
type: BlockEnum.TriggerSchedule,
+ helpLinkUri: 'schedule-trigger',
isStart: true,
})
diff --git a/web/app/components/workflow/nodes/trigger-webhook/default.ts b/web/app/components/workflow/nodes/trigger-webhook/default.ts
index 176b365b03..5071a79913 100644
--- a/web/app/components/workflow/nodes/trigger-webhook/default.ts
+++ b/web/app/components/workflow/nodes/trigger-webhook/default.ts
@@ -8,6 +8,7 @@ import { createWebhookRawVariable } from './utils/raw-variable'
const metaData = genNodeMetaData({
sort: 3,
type: BlockEnum.TriggerWebhook,
+ helpLinkUri: 'webhook-trigger',
isStart: true,
})
diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts
index 150b9a8902..61d1d010f0 100644
--- a/web/i18n/en-US/workflow.ts
+++ b/web/i18n/en-US/workflow.ts
@@ -279,6 +279,7 @@ const translation = {
'searchDataSource': 'Search Data Source',
'featuredTools': 'Featured',
'showMoreFeatured': 'Show more',
+ 'showLessFeatured': 'Show less',
'installed': 'Installed',
'pluginByAuthor': 'By {{author}}',
'usePlugin': 'Select tool',
diff --git a/web/i18n/zh-Hans/workflow.ts b/web/i18n/zh-Hans/workflow.ts
index 51ae3b8050..759b628144 100644
--- a/web/i18n/zh-Hans/workflow.ts
+++ b/web/i18n/zh-Hans/workflow.ts
@@ -265,6 +265,7 @@ const translation = {
'start': '开始',
'featuredTools': '精选推荐',
'showMoreFeatured': '查看更多',
+ 'showLessFeatured': '收起',
'installed': '已安装',
'pluginByAuthor': '来自 {{author}}',
'usePlugin': '选择工具',