This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import type { TriggerWithProvider } from '../types'
import type { Event } from '@/app/components/tools/types'
import { BlockEnum } from '../../types'
import type { TriggerDefaultValue } from '../types'
import Tooltip from '@/app/components/base/tooltip'
import { useGetLanguage } from '@/context/i18n'
import BlockIcon from '../../block-icon'
import cn from '@/utils/classnames'
import { useTranslation } from 'react-i18next'
type Props = {
provider: TriggerWithProvider
payload: Event
disabled?: boolean
isAdded?: boolean
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void
}
const TriggerPluginActionItem: FC<Props> = ({
provider,
payload,
onSelect,
disabled,
isAdded,
}) => {
const { t } = useTranslation()
const language = useGetLanguage()
return (
<Tooltip
key={payload.name}
position='right'
needsDelay={false}
popupClassName='!p-0 !px-3 !py-2.5 !w-[224px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg'
popupContent={(
<div>
<BlockIcon
size='md'
className='mb-2'
type={BlockEnum.TriggerPlugin}
toolIcon={provider.icon}
/>
<div className='mb-1 text-sm leading-5 text-text-primary'>{payload.label[language]}</div>
<div className='text-xs leading-[18px] text-text-secondary'>{payload.description[language]}</div>
</div>
)}
>
<div
key={payload.name}
className='flex cursor-pointer items-center justify-between rounded-lg pl-[21px] pr-1 hover:bg-state-base-hover'
onClick={() => {
if (disabled) return
const params: Record<string, string> = {}
if (payload.parameters) {
payload.parameters.forEach((item: any) => {
params[item.name] = ''
})
}
onSelect(BlockEnum.TriggerPlugin, {
plugin_id: provider.plugin_id,
provider_id: provider.name,
provider_type: provider.type as string,
provider_name: provider.name,
event_name: payload.name,
event_label: payload.label[language],
event_description: payload.description[language],
plugin_unique_identifier: provider.plugin_unique_identifier,
title: payload.label[language],
is_team_authorization: provider.is_team_authorization,
output_schema: payload.output_schema || {},
paramSchemas: payload.parameters,
params,
meta: provider.meta,
})
}}
>
<div className={cn('system-sm-medium h-8 truncate border-l-2 border-divider-subtle pl-4 leading-8 text-text-secondary')}>
<span className={cn(disabled && 'opacity-30')}>{payload.label[language]}</span>
</div>
{isAdded && (
<div className='system-xs-regular mr-4 text-text-tertiary'>{t('tools.addToolModal.added')}</div>
)}
</div>
</Tooltip >
)
}
export default React.memo(TriggerPluginActionItem)

View File

@@ -0,0 +1,133 @@
'use client'
import { useGetLanguage } from '@/context/i18n'
import cn from '@/utils/classnames'
import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react'
import type { FC } from 'react'
import React, { useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { CollectionType } from '@/app/components/tools/types'
import BlockIcon from '@/app/components/workflow/block-icon'
import { BlockEnum } from '@/app/components/workflow/types'
import type { TriggerDefaultValue, TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
import TriggerPluginActionItem from './action-item'
type Props = {
className?: string
payload: TriggerWithProvider
hasSearchText: boolean
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void
}
const TriggerPluginItem: FC<Props> = ({
className,
payload,
hasSearchText,
onSelect,
}) => {
const { t } = useTranslation()
const language = useGetLanguage()
const notShowProvider = payload.type === CollectionType.workflow
const actions = payload.events
const hasAction = !notShowProvider
const [isFold, setFold] = React.useState<boolean>(true)
const ref = useRef(null)
useEffect(() => {
if (hasSearchText && isFold) {
setFold(false)
return
}
if (!hasSearchText && !isFold)
setFold(true)
}, [hasSearchText])
const FoldIcon = isFold ? RiArrowRightSLine : RiArrowDownSLine
const groupName = useMemo(() => {
if (payload.type === CollectionType.builtIn)
return payload.author
if (payload.type === CollectionType.custom)
return t('workflow.tabs.customTool')
if (payload.type === CollectionType.workflow)
return t('workflow.tabs.workflowTool')
return payload.author || ''
}, [payload.author, payload.type, t])
return (
<div
key={payload.id}
className={cn('mb-1 last-of-type:mb-0')}
ref={ref}
>
<div className={cn(className)}>
<div
className='group/item flex w-full cursor-pointer select-none items-center justify-between rounded-lg pl-3 pr-1 hover:bg-state-base-hover'
onClick={() => {
if (hasAction) {
setFold(!isFold)
return
}
const event = actions[0]
const params: Record<string, string> = {}
if (event.parameters) {
event.parameters.forEach((item: any) => {
params[item.name] = ''
})
}
onSelect(BlockEnum.TriggerPlugin, {
plugin_id: payload.plugin_id,
provider_id: payload.name,
provider_type: payload.type,
provider_name: payload.name,
event_name: event.name,
event_label: event.label[language],
event_description: event.description[language],
title: event.label[language],
plugin_unique_identifier: payload.plugin_unique_identifier,
is_team_authorization: payload.is_team_authorization,
output_schema: event.output_schema || {},
paramSchemas: event.parameters,
params,
})
}}
>
<div className='flex h-8 grow items-center'>
<BlockIcon
className='shrink-0'
type={BlockEnum.TriggerPlugin}
toolIcon={payload.icon}
/>
<div className='ml-2 flex min-w-0 flex-1 items-center text-sm text-text-primary'>
<span className='max-w-[200px] truncate'>{notShowProvider ? actions[0]?.label[language] : payload.label[language]}</span>
<span className='system-xs-regular ml-2 truncate text-text-quaternary'>{groupName}</span>
</div>
</div>
<div className='ml-2 flex items-center'>
{hasAction && (
<FoldIcon className={cn('h-4 w-4 shrink-0 text-text-tertiary group-hover/item:text-text-tertiary', isFold && 'text-text-quaternary')} />
)}
</div>
</div>
{!notShowProvider && hasAction && !isFold && (
actions.map(action => (
<TriggerPluginActionItem
key={action.name}
provider={payload}
payload={action}
onSelect={onSelect}
disabled={false}
isAdded={false}
/>
))
)}
</div>
</div>
)
}
export default React.memo(TriggerPluginItem)

View File

@@ -0,0 +1,105 @@
'use client'
import { memo, useEffect, useMemo } from 'react'
import { useAllTriggerPlugins } from '@/service/use-triggers'
import TriggerPluginItem from './item'
import type { BlockEnum } from '../../types'
import type { TriggerDefaultValue, TriggerWithProvider } from '../types'
import { useGetLanguage } from '@/context/i18n'
type TriggerPluginListProps = {
onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void
searchText: string
onContentStateChange?: (hasContent: boolean) => void
tags?: string[]
}
const TriggerPluginList = ({
onSelect,
searchText,
onContentStateChange,
}: TriggerPluginListProps) => {
const { data: triggerPluginsData } = useAllTriggerPlugins()
const language = useGetLanguage()
const normalizedSearch = searchText.trim().toLowerCase()
const triggerPlugins = useMemo(() => {
const plugins = triggerPluginsData || []
const getLocalizedText = (text?: Record<string, string> | null) => {
if (!text)
return ''
if (text[language])
return text[language]
if (text['en-US'])
return text['en-US']
const firstValue = Object.values(text).find(Boolean)
return (typeof firstValue === 'string') ? firstValue : ''
}
const getSearchableTexts = (name: string, label?: Record<string, string> | null) => {
const localized = getLocalizedText(label)
const values = [localized, name].filter(Boolean)
return values.length > 0 ? values : ['']
}
const isMatchingKeywords = (value: string) => value.toLowerCase().includes(normalizedSearch)
if (!normalizedSearch)
return plugins.filter(triggerWithProvider => triggerWithProvider.events.length > 0)
return plugins.reduce<TriggerWithProvider[]>((acc, triggerWithProvider) => {
if (triggerWithProvider.events.length === 0)
return acc
const providerMatches = getSearchableTexts(
triggerWithProvider.name,
triggerWithProvider.label,
).some(text => isMatchingKeywords(text))
if (providerMatches) {
acc.push(triggerWithProvider)
return acc
}
const matchedEvents = triggerWithProvider.events.filter((event) => {
return getSearchableTexts(
event.name,
event.label,
).some(text => isMatchingKeywords(text))
})
if (matchedEvents.length > 0) {
acc.push({
...triggerWithProvider,
events: matchedEvents,
})
}
return acc
}, [])
}, [triggerPluginsData, normalizedSearch, language])
const hasContent = triggerPlugins.length > 0
useEffect(() => {
onContentStateChange?.(hasContent)
}, [hasContent, onContentStateChange])
if (!hasContent)
return null
return (
<div className="p-1">
{triggerPlugins.map(plugin => (
<TriggerPluginItem
key={plugin.id}
payload={plugin}
onSelect={onSelect}
hasSearchText={!!searchText}
/>
))}
</div>
)
}
export default memo(TriggerPluginList)