Merge remote-tracking branch 'origin/feat/trigger' into feat/trigger

This commit is contained in:
zhsama 2025-10-23 15:33:31 +08:00
commit aea3fc6281
21 changed files with 180 additions and 19 deletions

View File

@ -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:

View File

@ -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")
)
}
)

View File

@ -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.")

View File

@ -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

View File

@ -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,

View File

@ -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):

View File

@ -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)

View File

@ -221,7 +221,7 @@ function AppCard({
</div>
<div
className="cursor-pointer text-xs font-normal text-text-accent hover:underline"
onClick={() => window.open(docLink('/guides/workflow/node/start'), '_blank')}
onClick={() => window.open(docLink('/guides/workflow/node/user-input'), '_blank')}
>
{t('appOverview.overview.appInfo.enableTooltip.learnMore')}
</div>

View File

@ -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<AppSSO>
@ -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 (
<div className="w-full max-w-full rounded-xl border-l-[0.5px] border-t border-effects-highlight">
@ -209,8 +207,9 @@ function TriggerCard({ appInfo, onToggleResult }: ITriggerCardProps) {
<div className="system-xs-regular leading-4 text-text-tertiary">
{t('appOverview.overview.triggerInfo.triggerStatusDescription')}{' '}
<Link
href="#"
onClick={handleLearnMoreClick}
href={docLink('/guides/workflow/node/trigger')}
target="_blank"
rel="noopener noreferrer"
className="text-text-accent hover:underline"
>
{t('appOverview.overview.triggerInfo.learnAboutTriggers')}

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path 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="#676F83"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

View File

@ -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"
}

View File

@ -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<SVGSVGElement> & {
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>;
},
) => <IconBase {...props} ref={ref} data={data as IconData} />
Icon.displayName = 'ArrowUpDoubleLine'
export default Icon

View File

@ -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'

View File

@ -63,7 +63,7 @@ const WorkflowOnboardingModal: FC<WorkflowOnboardingModalProps> = ({
<div className="body-xs-regular leading-4 text-text-tertiary">
{t('workflow.onboarding.description')}{' '}
<a
href={docLink('guides/workflow/node/start')}
href={docLink('/guides/workflow/node/start')}
target="_blank"
rel="noopener noreferrer"
className="hover:text-text-accent-hover cursor-pointer text-text-accent underline"

View File

@ -229,7 +229,7 @@ const AllTools = ({
</div>
</>
)}
{(hasToolsListContent || hasPluginContent) && (
{(hasToolsListContent || enable_marketplace) && (
<>
<div className='px-3 pb-1 pt-2'>
<span className='system-xs-medium text-text-primary'>{t('tools.allTools')}</span>
@ -249,7 +249,7 @@ const AllTools = ({
isShowRAGRecommendations={isShowRAGRecommendations}
/>
)}
{hasPluginContent && (
{enable_marketplace && (
<PluginList
ref={pluginRef}
wrapElemRef={wrapElemRef as RefObject<HTMLElement>}

View File

@ -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 && (
<div
className='group mt-1 flex cursor-pointer items-center gap-x-2 rounded-lg py-1 pl-3 pr-2 text-text-tertiary transition-colors hover:bg-state-base-hover hover:text-text-secondary'
onClick={() => {
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)
})
}}
>
<div className='flex items-center px-1 text-text-tertiary transition-colors group-hover:text-text-secondary'>
<RiMoreLine className='size-4 group-hover:hidden' />
<ArrowDownDoubleLine className='hidden size-4 group-hover:block' />
{isExpanded ? (
<ArrowUpDoubleLine className='hidden size-4 group-hover:block' />
) : (
<ArrowDownDoubleLine className='hidden size-4 group-hover:block' />
)}
</div>
<div className='system-xs-regular'>
{t('workflow.tabs.showMoreFeatured')}
{t(isExpanded ? 'workflow.tabs.showLessFeatured' : 'workflow.tabs.showMoreFeatured')}
</div>
</div>
)}

View File

@ -164,6 +164,7 @@ const convertJsonSchemaToField = (schema: any, schemaTypeDefinitions?: SchemaTyp
const metaData = genNodeMetaData({
sort: 1,
type: BlockEnum.TriggerPlugin,
helpLinkUri: 'plugin-trigger',
isStart: true,
})

View File

@ -107,6 +107,7 @@ const validateVisualConfig = (payload: ScheduleTriggerNodeType, t: any): string
const metaData = genNodeMetaData({
sort: 2,
type: BlockEnum.TriggerSchedule,
helpLinkUri: 'schedule-trigger',
isStart: true,
})

View File

@ -8,6 +8,7 @@ import { createWebhookRawVariable } from './utils/raw-variable'
const metaData = genNodeMetaData({
sort: 3,
type: BlockEnum.TriggerWebhook,
helpLinkUri: 'webhook-trigger',
isStart: true,
})

View File

@ -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',

View File

@ -265,6 +265,7 @@ const translation = {
'start': '开始',
'featuredTools': '精选推荐',
'showMoreFeatured': '查看更多',
'showLessFeatured': '收起',
'installed': '已安装',
'pluginByAuthor': '来自 {{author}}',
'usePlugin': '选择工具',