mirror of
https://github.com/langgenius/dify.git
synced 2026-01-07 23:04:12 +00:00
fix(trigger): show event detail
This commit is contained in:
@@ -12,7 +12,7 @@ import EndpointList from './endpoint-list'
|
||||
import ModelList from './model-list'
|
||||
import { SubscriptionList } from './subscription-list'
|
||||
import { usePluginStore } from './subscription-list/store'
|
||||
import { TriggerEventsList } from './trigger-events-list'
|
||||
import { TriggerEventsList } from './trigger/event-list'
|
||||
|
||||
type Props = {
|
||||
detail?: PluginDetail
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContextSelector } from 'use-context-selector'
|
||||
import I18n from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n-config/language'
|
||||
import ToolItem from '@/app/components/tools/provider/tool-item'
|
||||
import { useTriggerProviderInfo } from '@/service/use-triggers'
|
||||
import type { Tool, ToolParameter } from '@/app/components/tools/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import type { ToolWithProvider } from '@/app/components/workflow/types'
|
||||
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { Trigger } from '@/app/components/plugins/types'
|
||||
import { usePluginStore } from './subscription-list/store'
|
||||
|
||||
type TriggerOption = {
|
||||
value: string
|
||||
label: TypeWithI18N
|
||||
icon?: string | null
|
||||
}
|
||||
|
||||
const pickLocaleText = (value?: TypeWithI18N | string, fallback = '', language = 'en_US'): string => {
|
||||
if (!value)
|
||||
return fallback
|
||||
if (typeof value === 'string')
|
||||
return value
|
||||
const typedValue = value as Record<string, string>
|
||||
return typedValue[language] ?? typedValue.en_US ?? Object.values(typedValue)[0] ?? fallback
|
||||
}
|
||||
|
||||
const getTriggerDescription = (description: TypeWithI18N | { human?: TypeWithI18N | string } | string | undefined, language = 'en_US'): string => {
|
||||
if (!description)
|
||||
return ''
|
||||
|
||||
if (typeof description === 'string')
|
||||
return description
|
||||
|
||||
if (typeof description === 'object' && 'human' in description) {
|
||||
const human = (description as { human?: TypeWithI18N | string }).human
|
||||
return pickLocaleText(human, '', language)
|
||||
}
|
||||
|
||||
return pickLocaleText(description as TypeWithI18N, '', language)
|
||||
}
|
||||
|
||||
const toToolParameter = (parameter: any): ToolParameter => {
|
||||
const paramLabel = parameter.label as TypeWithI18N
|
||||
const paramDescription = parameter.description as TypeWithI18N | undefined
|
||||
|
||||
return {
|
||||
name: parameter.name,
|
||||
label: paramLabel,
|
||||
human_description: paramDescription || paramLabel,
|
||||
type: parameter.type,
|
||||
form: 'setting',
|
||||
llm_description: typeof paramDescription === 'object' ? (paramDescription?.en_US || '') : (paramDescription || ''),
|
||||
required: parameter.required ?? false,
|
||||
multiple: parameter.multiple ?? false,
|
||||
default: parameter.default ?? '',
|
||||
options: parameter.options?.map((option: TriggerOption) => ({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
})) || [],
|
||||
}
|
||||
}
|
||||
|
||||
const toTool = (trigger: Trigger, fallbackAuthor: string): Tool => {
|
||||
const name = trigger.identity?.name || ''
|
||||
const label = trigger.identity?.label || { en_US: name }
|
||||
const description = trigger.description?.human || trigger.description || { en_US: '' }
|
||||
return {
|
||||
name,
|
||||
author: trigger.identity?.author || fallbackAuthor,
|
||||
label: label as TypeWithI18N,
|
||||
description: description as TypeWithI18N,
|
||||
parameters: (trigger.parameters || []).map((param: any) => toToolParameter(param)),
|
||||
labels: [],
|
||||
output_schema: trigger.output_schema || {},
|
||||
}
|
||||
}
|
||||
|
||||
export const TriggerEventsList = () => {
|
||||
const { t } = useTranslation()
|
||||
const locale = useContextSelector(I18n, state => state.locale)
|
||||
const language = getLanguage(locale)
|
||||
const detail = usePluginStore(state => state.detail)
|
||||
const events = detail?.declaration.trigger?.events || []
|
||||
|
||||
const { data: providerInfo } = useTriggerProviderInfo(detail?.provider || '')
|
||||
|
||||
const collection = useMemo<ToolWithProvider | undefined>(() => {
|
||||
if (!detail || !providerInfo)
|
||||
return undefined
|
||||
|
||||
const tools = (providerInfo.events || []).map((trigger: any) => toTool(trigger, providerInfo.author))
|
||||
|
||||
const metaVersion = detail.declaration.meta?.version || detail.declaration.version || '1.0'
|
||||
|
||||
return {
|
||||
id: providerInfo.plugin_id || providerInfo.name,
|
||||
name: providerInfo.name,
|
||||
author: providerInfo.author,
|
||||
description: providerInfo.description,
|
||||
icon: providerInfo.icon || providerInfo.icon_dark || '',
|
||||
label: providerInfo.label,
|
||||
type: CollectionType.builtIn,
|
||||
team_credentials: {},
|
||||
is_team_authorization: false,
|
||||
allow_delete: false,
|
||||
labels: providerInfo.tags || [],
|
||||
plugin_id: providerInfo.plugin_id || detail.plugin_id,
|
||||
tools,
|
||||
meta: { version: metaVersion },
|
||||
}
|
||||
}, [detail, providerInfo])
|
||||
|
||||
if (!events.length)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='px-4 pb-4 pt-2'>
|
||||
<div className='mb-1 py-1'>
|
||||
<div className='system-sm-semibold-uppercase mb-1 flex h-6 items-center justify-between text-text-secondary'>
|
||||
{t('pluginTrigger.events.actionNum', { num: events.length, event: t(`pluginTrigger.events.${events.length > 1 ? 'events' : 'event'}`) })}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
{collection
|
||||
? events.map((triggerEvent: Trigger) => {
|
||||
const triggerName = triggerEvent.identity?.name || ''
|
||||
const tool = collection.tools.find(item => item.name === triggerName)
|
||||
|| toTool(triggerEvent, collection.author)
|
||||
|
||||
return (
|
||||
<ToolItem
|
||||
key={`${detail?.plugin_id}${triggerEvent.identity?.name || ''}`}
|
||||
disabled={false}
|
||||
collection={collection}
|
||||
tool={tool}
|
||||
isBuiltIn={false}
|
||||
isModel={false}
|
||||
/>
|
||||
)
|
||||
})
|
||||
: events.map((triggerEvent: Trigger) => (
|
||||
<div
|
||||
key={`${detail?.plugin_id}${triggerEvent.identity?.name || ''}`}
|
||||
className='bg-components-panel-item-bg rounded-xl border-[0.5px] border-components-panel-border-subtle px-4 py-3 shadow-xs'
|
||||
>
|
||||
<div className='system-md-semibold pb-0.5 text-text-secondary'>
|
||||
{pickLocaleText(triggerEvent.identity?.label as TypeWithI18N, triggerEvent.identity?.name || '', language)}
|
||||
</div>
|
||||
<div className='system-xs-regular line-clamp-2 text-text-tertiary'>
|
||||
{getTriggerDescription(triggerEvent.description, language)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
'use client'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import Icon from '@/app/components/plugins/card/base/card-icon'
|
||||
import Description from '@/app/components/plugins/card/base/description'
|
||||
import OrgInfo from '@/app/components/plugins/card/base/org-info'
|
||||
import { triggerEventParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema'
|
||||
import type { TriggerProviderApiEntity } from '@/app/components/workflow/block-selector/types'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
// import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
RiArrowLeftLine,
|
||||
RiCloseLine,
|
||||
} from '@remixicon/react'
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { TriggerEvent } from '../../types'
|
||||
|
||||
type EventDetailDrawerProps = {
|
||||
eventInfo: TriggerEvent
|
||||
providerInfo: TriggerProviderApiEntity
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const getType = (type: string, t: TFunction) => {
|
||||
if (type === 'number-input')
|
||||
return t('tools.setBuiltInTools.number')
|
||||
if (type === 'text-input')
|
||||
return t('tools.setBuiltInTools.string')
|
||||
if (type === 'checkbox')
|
||||
return 'boolean'
|
||||
if (type === 'file')
|
||||
return t('tools.setBuiltInTools.file')
|
||||
return type
|
||||
}
|
||||
|
||||
export const EventDetailDrawer: FC<EventDetailDrawerProps> = (props) => {
|
||||
const { eventInfo, providerInfo, onClose } = props
|
||||
const language = useLanguage()
|
||||
const { t } = useTranslation()
|
||||
const parametersSchemas = triggerEventParametersToFormSchemas(eventInfo.parameters)
|
||||
const outputVars = Object.entries(eventInfo.output_schema?.properties || {}).map(([name, schema]: [string, any]) => ({
|
||||
name,
|
||||
type: schema.type || 'string',
|
||||
description: schema.description || '',
|
||||
}))
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
isOpen
|
||||
clickOutsideNotOpen={false}
|
||||
onClose={onClose}
|
||||
footer={null}
|
||||
mask={false}
|
||||
positionCenter={false}
|
||||
panelClassName={cn('mb-2 mr-2 mt-[64px] !w-[420px] !max-w-[420px] justify-start rounded-2xl border-[0.5px] border-components-panel-border !bg-components-panel-bg !p-0 shadow-xl')}
|
||||
>
|
||||
<div className='relative border-b border-divider-subtle p-4 pb-3'>
|
||||
<div className='absolute right-3 top-3'>
|
||||
<ActionButton onClick={onClose}>
|
||||
<RiCloseLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div
|
||||
className='system-xs-semibold-uppercase mb-2 flex cursor-pointer items-center gap-1 text-text-accent-secondary'
|
||||
onClick={onClose}
|
||||
>
|
||||
<RiArrowLeftLine className='h-4 w-4' />
|
||||
BACK
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Icon size='tiny' className='h-6 w-6' src={providerInfo.icon!} />
|
||||
<OrgInfo
|
||||
packageNameClassName='w-auto'
|
||||
orgName={providerInfo.author}
|
||||
packageName={providerInfo.name.split('/').pop() || ''}
|
||||
/>
|
||||
</div>
|
||||
<div className='system-md-semibold mt-1 text-text-primary'>{eventInfo?.identity?.label[language]}</div>
|
||||
<Description className='mb-2 mt-3 h-auto' text={eventInfo.description[language]} descriptionLineRows={2}></Description>
|
||||
</div>
|
||||
<div className='flex h-full flex-col'>
|
||||
<div className='system-sm-semibold-uppercase p-4 pb-1 text-text-primary'>{t('tools.setBuiltInTools.parameters')}</div>
|
||||
<div className='h-0 grow overflow-y-auto px-4'>
|
||||
{parametersSchemas.length > 0 && (
|
||||
<div className='space-y-1 py-2'>
|
||||
{parametersSchemas.map((item, index) => (
|
||||
<div key={index} className='py-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='code-sm-semibold text-text-secondary'>{item.label[language]}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>
|
||||
{getType(item.type, t)}
|
||||
</div>
|
||||
{item.required && (
|
||||
<div className='system-xs-medium text-text-warning-secondary'>{t('tools.setBuiltInTools.required')}</div>
|
||||
)}
|
||||
</div>
|
||||
{item.description && (
|
||||
<div className='system-xs-regular mt-0.5 text-text-tertiary'>
|
||||
{item.description?.[language]}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* <Split /> */}
|
||||
<div className='system-sm-semibold-uppercase p-4 pb-1 pt-0 text-text-primary'>{t('pluginTrigger.events.output')}</div>
|
||||
<OutputVars collapsed={false}>
|
||||
{outputVars.map(varItem => (
|
||||
<VarItem
|
||||
key={varItem.name}
|
||||
name={varItem.name}
|
||||
type={varItem.type}
|
||||
description={varItem.description}
|
||||
// isIndent={hasObjectOutput}
|
||||
/>
|
||||
))}
|
||||
</OutputVars>
|
||||
</div>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import type { TriggerEvent } from '@/app/components/plugins/types'
|
||||
import type { TriggerProviderApiEntity } from '@/app/components/workflow/block-selector/types'
|
||||
import { useTriggerProviderInfo } from '@/service/use-triggers'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePluginStore } from '../subscription-list/store'
|
||||
import { EventDetailDrawer } from './event-detail-drawer'
|
||||
|
||||
type TriggerEventCardProps = {
|
||||
eventInfo: TriggerEvent
|
||||
providerInfo: TriggerProviderApiEntity
|
||||
}
|
||||
|
||||
const TriggerEventCard = ({ eventInfo, providerInfo }: TriggerEventCardProps) => {
|
||||
const { identity, description = {} } = eventInfo
|
||||
const language = useLanguage()
|
||||
const [showDetail, setShowDetail] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn('bg-components-panel-item-bg cursor-pointer rounded-xl border-[0.5px] border-components-panel-border-subtle px-4 py-3 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover')}
|
||||
onClick={() => setShowDetail(true)}
|
||||
>
|
||||
<div className='system-md-semibold pb-0.5 text-text-secondary'>{identity.label[language]}</div>
|
||||
<div className='system-xs-regular line-clamp-2 text-text-tertiary' title={description[language]}>{description[language]}</div>
|
||||
</div>
|
||||
{showDetail && (
|
||||
<EventDetailDrawer
|
||||
eventInfo={eventInfo}
|
||||
providerInfo={providerInfo}
|
||||
onClose={() => setShowDetail(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const TriggerEventsList = () => {
|
||||
const { t } = useTranslation()
|
||||
const detail = usePluginStore(state => state.detail)
|
||||
|
||||
const { data: providerInfo } = useTriggerProviderInfo(detail?.provider || '')
|
||||
const triggerEvents = providerInfo?.events || []
|
||||
|
||||
if (!providerInfo || !triggerEvents.length)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='px-4 pb-4 pt-2'>
|
||||
<div className='mb-1 py-1'>
|
||||
<div className='system-sm-semibold-uppercase mb-1 flex h-6 items-center justify-between text-text-secondary'>
|
||||
{t('pluginTrigger.events.actionNum', { num: triggerEvents.length, event: t(`pluginTrigger.events.${triggerEvents.length > 1 ? 'events' : 'event'}`) })}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
{
|
||||
triggerEvents.map((triggerEvent: TriggerEvent) => (
|
||||
<TriggerEventCard
|
||||
key={`${detail?.plugin_id}${triggerEvent.identity?.name || ''}`}
|
||||
eventInfo={triggerEvent}
|
||||
providerInfo={providerInfo}
|
||||
/>))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ export type PluginTriggerSubscriptionConstructor = {
|
||||
}
|
||||
|
||||
export type PluginTriggerDefinition = {
|
||||
events: Trigger[]
|
||||
events: TriggerEvent[]
|
||||
identity: Identity
|
||||
subscription_constructor: PluginTriggerSubscriptionConstructor
|
||||
subscription_schema: ParametersSchema[]
|
||||
@@ -158,29 +158,31 @@ export type PropertiesSchema = {
|
||||
placeholder: any
|
||||
}
|
||||
|
||||
export type Trigger = {
|
||||
export type TriggerEventParameter = {
|
||||
name: string
|
||||
label: Record<Locale, string>
|
||||
type: string
|
||||
auto_generate: any
|
||||
template: any
|
||||
scope: any
|
||||
required: boolean
|
||||
multiple: boolean
|
||||
default: any
|
||||
min: any
|
||||
max: any
|
||||
precision: any
|
||||
options?: Array<{
|
||||
value: string
|
||||
label: Record<Locale, string>
|
||||
icon?: string
|
||||
}>
|
||||
description?: Record<Locale, string>
|
||||
}
|
||||
|
||||
export type TriggerEvent = {
|
||||
identity: Identity
|
||||
description: Record<Locale, string>
|
||||
parameters: {
|
||||
name: string
|
||||
label: Record<Locale, string>
|
||||
type: string
|
||||
auto_generate: any
|
||||
template: any
|
||||
scope: any
|
||||
required: boolean
|
||||
multiple: boolean
|
||||
default: any
|
||||
min: any
|
||||
max: any
|
||||
precision: any
|
||||
options?: Array<{
|
||||
value: string
|
||||
label: Record<Locale, string>
|
||||
icon?: string
|
||||
}>
|
||||
description?: Record<Locale, string>
|
||||
}[]
|
||||
parameters: TriggerEventParameter[]
|
||||
output_schema: Record<string, any>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ToolCredential, ToolParameter } from '../types'
|
||||
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import type { TriggerEventParameter } from '../../plugins/types'
|
||||
import type { ToolCredential, ToolParameter } from '../types'
|
||||
|
||||
export const toType = (type: string) => {
|
||||
switch (type) {
|
||||
@@ -14,6 +15,21 @@ export const toType = (type: string) => {
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
export const triggerEventParametersToFormSchemas = (parameters: TriggerEventParameter[]) => {
|
||||
if (!parameters?.length)
|
||||
return []
|
||||
|
||||
return parameters.map((parameter) => {
|
||||
return {
|
||||
...parameter,
|
||||
type: toType(parameter.type),
|
||||
_type: parameter.type,
|
||||
tooltip: parameter.description,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const toolParametersToFormSchemas = (parameters: ToolParameter[]) => {
|
||||
if (!parameters)
|
||||
return []
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TypeWithI18N } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { ParametersSchema, PluginMeta, PluginTriggerSubscriptionConstructor, SupportedCreationMethods } from '../../plugins/types'
|
||||
import type { ParametersSchema, PluginMeta, PluginTriggerSubscriptionConstructor, SupportedCreationMethods, TriggerEvent } from '../../plugins/types'
|
||||
import type { Collection, Event } from '../../tools/types'
|
||||
|
||||
export enum TabsEnum {
|
||||
@@ -200,10 +200,10 @@ export type TriggerProviderApiEntity = {
|
||||
plugin_id?: string
|
||||
plugin_unique_identifier: string
|
||||
supported_creation_methods: SupportedCreationMethods[]
|
||||
credentials_schema: TriggerCredentialField[]
|
||||
subscription_constructor: PluginTriggerSubscriptionConstructor
|
||||
credentials_schema?: TriggerCredentialField[]
|
||||
subscription_constructor?: PluginTriggerSubscriptionConstructor | null
|
||||
subscription_schema: ParametersSchema[]
|
||||
events: TriggerApiEntity[]
|
||||
events: TriggerEvent[]
|
||||
}
|
||||
|
||||
// Frontend types - compatible with ToolWithProvider
|
||||
|
||||
@@ -165,6 +165,7 @@ const translation = {
|
||||
item: {
|
||||
parameters: '{{count}} parameters',
|
||||
},
|
||||
output: 'Output',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ const translation = {
|
||||
item: {
|
||||
parameters: '{{count}}个参数',
|
||||
},
|
||||
output: '输出',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user