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': '选择工具',