mirror of
https://github.com/langgenius/dify.git
synced 2026-05-03 17:08:03 +08:00
feat(trigger): enhance plugin and trigger integration with updated naming conventions
- Refactored `PluginFetchDynamicSelectOptionsApi` to replace the `extra` argument with `credential_id`, improving clarity in dynamic option fetching. - Updated `ProviderConfigEncrypter` to rename `mask_tool_credentials` to `mask_credentials` for consistency, and added a new method to maintain backward compatibility. - Enhanced `PluginParameterService` to utilize `credential_id` for fetching subscriptions, improving the handling of trigger credentials. - Adjusted various components and types in the frontend to replace `tool_name` with `trigger_name`, ensuring consistency across the application. - Introduced `multiple` property in `TriggerParameter` to support multi-select functionality. These changes improve the integration of triggers and plugins, enhance code clarity, and align naming conventions across the codebase.
This commit is contained in:
@ -209,7 +209,7 @@ export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) =>
|
||||
viewType={viewType}
|
||||
onSelect={(_, tool) => {
|
||||
onChange({
|
||||
agent_strategy_name: tool!.tool_name,
|
||||
agent_strategy_name: tool!.trigger_name,
|
||||
agent_strategy_provider_name: tool!.provider_name,
|
||||
agent_strategy_label: tool!.tool_label,
|
||||
agent_output_schema: tool!.output_schema,
|
||||
|
||||
@ -1,20 +1,19 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { ToolVarInputs } from '@/app/components/workflow/nodes/tool/types'
|
||||
import { type BaseResource, type BaseResourceProvider, type ResourceVarInputs, VarKindType } from '../types'
|
||||
import type { CredentialFormSchema, FormOption } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import { VarType } from '@/app/components/workflow/types'
|
||||
import { useFetchDynamicOptions } from '@/service/use-plugins'
|
||||
|
||||
import type { ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import FormInputTypeSwitch from './form-input-type-switch'
|
||||
import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use-available-var-list'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
import MixedVariableTextInput from '@/app/components/workflow/nodes/tool/components/mixed-variable-text-input'
|
||||
import MixedVariableTextInput from './mixed-variable-text-input'
|
||||
import FormInputBoolean from './form-input-boolean'
|
||||
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
|
||||
import ModelParameterModal from '@/app/components/plugins/plugin-detail-panel/model-selector'
|
||||
@ -22,19 +21,20 @@ import VarReferencePicker from '@/app/components/workflow/nodes/_base/components
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { Tool } from '@/app/components/tools/types'
|
||||
|
||||
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'
|
||||
import { ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { RiCheckLine } from '@remixicon/react'
|
||||
type Props = {
|
||||
readOnly: boolean
|
||||
nodeId: string
|
||||
schema: CredentialFormSchema
|
||||
value: ToolVarInputs
|
||||
value: ResourceVarInputs
|
||||
onChange: (value: any) => void
|
||||
inPanel?: boolean
|
||||
currentTool?: Tool
|
||||
currentProvider?: ToolWithProvider
|
||||
currentResource?: BaseResource
|
||||
currentProvider?: BaseResourceProvider
|
||||
extraParams?: Record<string, any>
|
||||
providerType?: 'tool' | 'trigger'
|
||||
providerType?: string
|
||||
}
|
||||
|
||||
const FormInputItem: FC<Props> = ({
|
||||
@ -44,10 +44,10 @@ const FormInputItem: FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
inPanel,
|
||||
currentTool,
|
||||
currentResource,
|
||||
currentProvider,
|
||||
extraParams,
|
||||
providerType = 'tool',
|
||||
providerType,
|
||||
}) => {
|
||||
const language = useLanguage()
|
||||
const [dynamicOptions, setDynamicOptions] = useState<FormOption[] | null>(null)
|
||||
@ -59,6 +59,7 @@ const FormInputItem: FC<Props> = ({
|
||||
type,
|
||||
default: defaultValue,
|
||||
options,
|
||||
multiple,
|
||||
scope,
|
||||
} = schema as any
|
||||
const varInput = value[variable]
|
||||
@ -76,6 +77,7 @@ const FormInputItem: FC<Props> = ({
|
||||
const showTypeSwitch = isNumber || isBoolean || isObject || isArray
|
||||
const isConstant = varInput?.type === VarKindType.constant || !varInput?.type
|
||||
const showVariableSelector = isFile || varInput?.type === VarKindType.variable
|
||||
const isMultipleSelect = multiple && (isSelect || isDynamicSelect)
|
||||
|
||||
const { availableVars, availableNodesWithParent } = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
@ -138,7 +140,7 @@ const FormInputItem: FC<Props> = ({
|
||||
const { mutateAsync: fetchDynamicOptions } = useFetchDynamicOptions(
|
||||
currentProvider?.plugin_id || '',
|
||||
currentProvider?.name || '',
|
||||
currentTool?.name || '',
|
||||
currentResource?.name || '',
|
||||
variable || '',
|
||||
providerType,
|
||||
extraParams,
|
||||
@ -147,7 +149,7 @@ const FormInputItem: FC<Props> = ({
|
||||
// Fetch dynamic options when component mounts or dependencies change
|
||||
useEffect(() => {
|
||||
const fetchOptions = async () => {
|
||||
if (isDynamicSelect && currentTool && currentProvider) {
|
||||
if (isDynamicSelect && currentResource && currentProvider) {
|
||||
setIsLoadingOptions(true)
|
||||
try {
|
||||
const data = await fetchDynamicOptions()
|
||||
@ -164,7 +166,7 @@ const FormInputItem: FC<Props> = ({
|
||||
}
|
||||
|
||||
fetchOptions()
|
||||
}, [isDynamicSelect, currentTool?.name, currentProvider?.name, variable, extraParams])
|
||||
}, [isDynamicSelect, currentResource?.name, currentProvider?.name, variable, extraParams])
|
||||
|
||||
const handleTypeChange = (newType: string) => {
|
||||
if (newType === VarKindType.variable) {
|
||||
@ -200,6 +202,24 @@ const FormInputItem: FC<Props> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const getSelectedLabels = (selectedValues: any[]) => {
|
||||
if (!selectedValues || selectedValues.length === 0)
|
||||
return ''
|
||||
|
||||
const optionsList = isDynamicSelect ? (dynamicOptions || options || []) : (options || [])
|
||||
const selectedOptions = optionsList.filter((opt: any) =>
|
||||
selectedValues.includes(opt.value),
|
||||
)
|
||||
|
||||
if (selectedOptions.length <= 2) {
|
||||
return selectedOptions
|
||||
.map((opt: any) => opt.label?.[language] || opt.label?.en_US || opt.value)
|
||||
.join(', ')
|
||||
}
|
||||
|
||||
return `${selectedOptions.length} selected`
|
||||
}
|
||||
|
||||
const handleAppOrModelSelect = (newValue: any) => {
|
||||
onChange({
|
||||
...value,
|
||||
@ -250,7 +270,7 @@ const FormInputItem: FC<Props> = ({
|
||||
onChange={handleValueChange}
|
||||
/>
|
||||
)}
|
||||
{isSelect && (
|
||||
{isSelect && !isMultipleSelect && (
|
||||
<SimpleSelect
|
||||
wrapperClassName='h-8 grow'
|
||||
disabled={readOnly}
|
||||
@ -277,7 +297,64 @@ const FormInputItem: FC<Props> = ({
|
||||
) : undefined}
|
||||
/>
|
||||
)}
|
||||
{isDynamicSelect && (
|
||||
{isSelect && isMultipleSelect && (
|
||||
<Listbox
|
||||
multiple
|
||||
value={varInput?.value || []}
|
||||
onChange={handleValueChange}
|
||||
disabled={readOnly}
|
||||
>
|
||||
<div className="relative">
|
||||
<ListboxButton className="relative h-8 w-full cursor-pointer rounded-lg bg-components-input-bg-normal px-3 py-1.5 text-left text-sm focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300">
|
||||
<span className="block truncate text-components-input-text-filled">
|
||||
{getSelectedLabels(varInput?.value) || placeholder?.[language] || placeholder?.en_US || 'Select options'}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronDownIcon
|
||||
className="h-4 w-4 text-text-tertiary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-components-panel-bg-blur py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm">
|
||||
{options.filter((option: { show_on: any[] }) => {
|
||||
if (option.show_on?.length)
|
||||
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
|
||||
return true
|
||||
}).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => (
|
||||
<ListboxOption
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className={({ focus }) =>
|
||||
`relative cursor-pointer select-none py-2 pl-10 pr-4 ${
|
||||
focus ? 'bg-state-base-hover text-text-secondary' : 'text-text-primary'
|
||||
}`
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
{option.icon && (
|
||||
<img src={option.icon} alt="" className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
<span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>
|
||||
{option.label[language] || option.label.en_US}
|
||||
</span>
|
||||
</div>
|
||||
{selected && (
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-primary-600">
|
||||
<RiCheckLine className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ListboxOption>
|
||||
))}
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
</Listbox>
|
||||
)}
|
||||
{isDynamicSelect && !isMultipleSelect && (
|
||||
<SimpleSelect
|
||||
wrapperClassName='h-8 grow'
|
||||
disabled={readOnly || isLoadingOptions}
|
||||
@ -304,6 +381,65 @@ const FormInputItem: FC<Props> = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{isDynamicSelect && isMultipleSelect && (
|
||||
<Listbox
|
||||
multiple
|
||||
value={varInput?.value || []}
|
||||
onChange={handleValueChange}
|
||||
disabled={readOnly || isLoadingOptions}
|
||||
>
|
||||
<div className="relative">
|
||||
<ListboxButton className="relative h-8 w-full cursor-pointer rounded-lg bg-components-input-bg-normal px-3 py-1.5 text-left text-sm focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300">
|
||||
<span className="block truncate text-components-input-text-filled">
|
||||
{isLoadingOptions
|
||||
? 'Loading...'
|
||||
: getSelectedLabels(varInput?.value) || placeholder?.[language] || placeholder?.en_US || 'Select options'}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronDownIcon
|
||||
className="h-4 w-4 text-text-tertiary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-components-panel-bg-blur py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm">
|
||||
{(dynamicOptions || options || []).filter((option: { show_on?: any[] }) => {
|
||||
if (option.show_on?.length)
|
||||
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
|
||||
return true
|
||||
}).map((option: { value: any; label: { [x: string]: any; en_US: any }; icon?: string }) => (
|
||||
<ListboxOption
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
className={({ focus }) =>
|
||||
`relative cursor-pointer select-none py-2 pl-10 pr-4 ${
|
||||
focus ? 'bg-state-base-hover text-text-secondary' : 'text-text-primary'
|
||||
}`
|
||||
}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
{option.icon && (
|
||||
<img src={option.icon} alt="" className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
<span className={`block truncate ${selected ? 'font-medium' : 'font-normal'}`}>
|
||||
{option.label[language] || option.label.en_US}
|
||||
</span>
|
||||
</div>
|
||||
{selected && (
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-primary-600">
|
||||
<RiCheckLine className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ListboxOption>
|
||||
))}
|
||||
</ListboxOptions>
|
||||
</div>
|
||||
</Listbox>
|
||||
)}
|
||||
{isShowJSONEditor && isConstant && (
|
||||
<div className='mt-1 w-full'>
|
||||
<CodeEditor
|
||||
@ -349,7 +485,7 @@ const FormInputItem: FC<Props> = ({
|
||||
filterVar={getFilterVar()}
|
||||
schema={schema}
|
||||
valueTypePlaceHolder={targetVarType()}
|
||||
currentTool={currentTool}
|
||||
currentResource={currentResource}
|
||||
currentProvider={currentProvider}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
import {
|
||||
memo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import Placeholder from './placeholder'
|
||||
import type {
|
||||
Node,
|
||||
NodeOutPutVar,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type MixedVariableTextInputProps = {
|
||||
readOnly?: boolean
|
||||
nodesOutputVars?: NodeOutPutVar[]
|
||||
availableNodes?: Node[]
|
||||
value?: string
|
||||
onChange?: (text: string) => void
|
||||
}
|
||||
const MixedVariableTextInput = ({
|
||||
readOnly = false,
|
||||
nodesOutputVars,
|
||||
availableNodes = [],
|
||||
value = '',
|
||||
onChange,
|
||||
}: MixedVariableTextInputProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<PromptEditor
|
||||
wrapperClassName={cn(
|
||||
'w-full rounded-lg border border-transparent bg-components-input-bg-normal px-2 py-1',
|
||||
'hover:border-components-input-border-hover hover:bg-components-input-bg-hover',
|
||||
'focus-within:border-components-input-border-active focus-within:bg-components-input-bg-active focus-within:shadow-xs',
|
||||
)}
|
||||
className='caret:text-text-accent'
|
||||
editable={!readOnly}
|
||||
value={value}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: nodesOutputVars || [],
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
}}
|
||||
placeholder={<Placeholder />}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(MixedVariableTextInput)
|
||||
@ -0,0 +1,52 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { FOCUS_COMMAND } from 'lexical'
|
||||
import { $insertNodes } from 'lexical'
|
||||
import { CustomTextNode } from '@/app/components/base/prompt-editor/plugins/custom-text/node'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
|
||||
const Placeholder = () => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const handleInsert = useCallback((text: string) => {
|
||||
editor.update(() => {
|
||||
const textNode = new CustomTextNode(text)
|
||||
$insertNodes([textNode])
|
||||
})
|
||||
editor.dispatchCommand(FOCUS_COMMAND, undefined as any)
|
||||
}, [editor])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='pointer-events-auto flex h-full w-full cursor-text items-center px-2'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleInsert('')
|
||||
}}
|
||||
>
|
||||
<div className='flex grow items-center'>
|
||||
{t('workflow.nodes.tool.insertPlaceholder1')}
|
||||
<div className='system-kbd mx-0.5 flex h-4 w-4 items-center justify-center rounded bg-components-kbd-bg-gray text-text-placeholder'>/</div>
|
||||
<div
|
||||
className='system-sm-regular cursor-pointer text-components-input-text-placeholder underline decoration-dotted decoration-auto underline-offset-auto hover:text-text-tertiary'
|
||||
onMouseDown={((e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleInsert('/')
|
||||
})}
|
||||
>
|
||||
{t('workflow.nodes.tool.insertPlaceholder2')}
|
||||
</div>
|
||||
</div>
|
||||
<Badge
|
||||
className='shrink-0'
|
||||
text='String'
|
||||
uppercase={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Placeholder
|
||||
@ -17,7 +17,7 @@ import VarReferencePopup from './var-reference-popup'
|
||||
import { getNodeInfoById, isConversationVar, isENV, isSystemVar, varTypeToStructType } from './utils'
|
||||
import ConstantField from './constant-field'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { Node, NodeOutPutVar, ToolWithProvider, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { Node, NodeOutPutVar, ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import type { CredentialFormSchemaSelect } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { type CredentialFormSchema, type FormOption, FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
@ -34,6 +34,7 @@ import {
|
||||
useWorkflowVariables,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
|
||||
import type { BaseResource, BaseResourceProvider } from '@/app/components/workflow/nodes/_base/types'
|
||||
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
@ -42,7 +43,6 @@ import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
import VarFullPathPanel from './var-full-path-panel'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useFetchDynamicOptions } from '@/service/use-plugins'
|
||||
import type { Tool } from '@/app/components/tools/types'
|
||||
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
|
||||
|
||||
const TRIGGER_DEFAULT_WIDTH = 227
|
||||
@ -72,8 +72,8 @@ type Props = {
|
||||
minWidth?: number
|
||||
popupFor?: 'assigned' | 'toAssigned'
|
||||
zIndex?: number
|
||||
currentTool?: Tool
|
||||
currentProvider?: ToolWithProvider
|
||||
currentResource?: BaseResource
|
||||
currentProvider?: BaseResourceProvider
|
||||
}
|
||||
|
||||
const DEFAULT_VALUE_SELECTOR: Props['value'] = []
|
||||
@ -103,7 +103,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
minWidth,
|
||||
popupFor,
|
||||
zIndex,
|
||||
currentTool,
|
||||
currentResource,
|
||||
currentProvider,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@ -328,11 +328,11 @@ const VarReferencePicker: FC<Props> = ({
|
||||
const [dynamicOptions, setDynamicOptions] = useState<FormOption[] | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { mutateAsync: fetchDynamicOptions } = useFetchDynamicOptions(
|
||||
currentProvider?.plugin_id || '', currentProvider?.name || '', currentTool?.name || '', (schema as CredentialFormSchemaSelect)?.variable || '',
|
||||
currentProvider?.plugin_id || '', currentProvider?.name || '', currentResource?.name || '', (schema as CredentialFormSchemaSelect)?.variable || '',
|
||||
'tool',
|
||||
)
|
||||
const handleFetchDynamicOptions = async () => {
|
||||
if (schema?.type !== FormTypeEnum.dynamicSelect || !currentTool || !currentProvider)
|
||||
if (schema?.type !== FormTypeEnum.dynamicSelect || !currentResource || !currentProvider)
|
||||
return
|
||||
setIsLoading(true)
|
||||
try {
|
||||
@ -345,7 +345,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
}
|
||||
useEffect(() => {
|
||||
handleFetchDynamicOptions()
|
||||
}, [currentTool, currentProvider, schema])
|
||||
}, [currentResource, currentProvider, schema])
|
||||
|
||||
const schemaWithDynamicSelect = useMemo(() => {
|
||||
if (schema?.type !== FormTypeEnum.dynamicSelect)
|
||||
|
||||
27
web/app/components/workflow/nodes/_base/types.ts
Normal file
27
web/app/components/workflow/nodes/_base/types.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
|
||||
// Generic variable types for all resource forms
|
||||
export enum VarKindType {
|
||||
variable = 'variable',
|
||||
constant = 'constant',
|
||||
mixed = 'mixed',
|
||||
}
|
||||
|
||||
// Generic resource variable inputs
|
||||
export type ResourceVarInputs = Record<string, {
|
||||
type: VarKindType
|
||||
value?: string | ValueSelector | any
|
||||
}>
|
||||
|
||||
// Base resource interface
|
||||
export type BaseResource = {
|
||||
name: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
// Base resource provider interface
|
||||
export type BaseResourceProvider = {
|
||||
plugin_id?: string
|
||||
name: string
|
||||
[key: string]: any
|
||||
}
|
||||
@ -44,7 +44,7 @@ const ImportFromTool: FC<Props> = ({
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
|
||||
const handleSelectTool = useCallback((_type: BlockEnum, toolInfo?: ToolDefaultValue) => {
|
||||
const { provider_id, provider_type, tool_name } = toolInfo!
|
||||
const { provider_id, provider_type, trigger_name: tool_name } = toolInfo!
|
||||
const currentTools = (() => {
|
||||
switch (provider_type) {
|
||||
case CollectionType.builtIn:
|
||||
|
||||
@ -17,7 +17,6 @@ type Props = {
|
||||
currentTool?: Tool
|
||||
currentProvider?: ToolWithProvider
|
||||
extraParams?: Record<string, any>
|
||||
providerType?: 'tool' | 'trigger'
|
||||
}
|
||||
|
||||
const ToolForm: FC<Props> = ({
|
||||
@ -30,7 +29,6 @@ const ToolForm: FC<Props> = ({
|
||||
currentTool,
|
||||
currentProvider,
|
||||
extraParams,
|
||||
providerType = 'tool',
|
||||
}) => {
|
||||
return (
|
||||
<div className='space-y-1'>
|
||||
@ -47,7 +45,7 @@ const ToolForm: FC<Props> = ({
|
||||
currentTool={currentTool}
|
||||
currentProvider={currentProvider}
|
||||
extraParams={extraParams}
|
||||
providerType={providerType}
|
||||
providerType='tool'
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ const ToolFormItem: FC<Props> = ({
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
inPanel={inPanel}
|
||||
currentTool={currentTool}
|
||||
currentResource={currentTool}
|
||||
currentProvider={currentProvider}
|
||||
extraParams={extraParams}
|
||||
providerType={providerType}
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
import type { CollectionType } from '@/app/components/tools/types'
|
||||
import type { CommonNodeType, ValueSelector } from '@/app/components/workflow/types'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import type { ResourceVarInputs } from '../_base/types'
|
||||
|
||||
export enum VarType {
|
||||
variable = 'variable',
|
||||
constant = 'constant',
|
||||
mixed = 'mixed',
|
||||
}
|
||||
|
||||
export type ToolVarInputs = Record<string, {
|
||||
type: VarType
|
||||
value?: string | ValueSelector | any
|
||||
}>
|
||||
// Use base types directly
|
||||
export { VarKindType as VarType } from '../_base/types'
|
||||
export type ToolVarInputs = ResourceVarInputs
|
||||
|
||||
export type ToolNodeType = CommonNodeType & {
|
||||
provider_id: string
|
||||
|
||||
@ -32,7 +32,7 @@ const useConfig = (id: string, payload: ToolNodeType) => {
|
||||
* tool_parameters: tool dynamic setting(form type = llm)
|
||||
* output_schema: tool dynamic output
|
||||
*/
|
||||
const { provider_id, provider_type, tool_name, tool_configurations, output_schema, tool_parameters } = inputs
|
||||
const { provider_id, provider_type, trigger_name: tool_name, tool_configurations, output_schema, tool_parameters } = inputs
|
||||
const isBuiltIn = provider_type === CollectionType.builtIn
|
||||
const buildInTools = useStore(s => s.buildInTools)
|
||||
const customTools = useStore(s => s.customTools)
|
||||
|
||||
@ -6,7 +6,7 @@ import { ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/block
|
||||
const nodeDefault: NodeDefault<PluginTriggerNodeType> = {
|
||||
defaultValue: {
|
||||
plugin_id: '',
|
||||
tool_name: '',
|
||||
trigger_name: '',
|
||||
event_type: '',
|
||||
config: {},
|
||||
},
|
||||
|
||||
@ -5,7 +5,7 @@ import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import useConfig from './use-config'
|
||||
import ToolForm from '@/app/components/workflow/nodes/tool/components/tool-form'
|
||||
import TriggerForm from './trigger-form'
|
||||
import StructureOutputItem from '@/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/show'
|
||||
import { Type } from '../llm/types'
|
||||
|
||||
@ -21,6 +21,8 @@ const Panel: FC<NodePanelProps<PluginTriggerNodeType>> = ({
|
||||
outputSchema,
|
||||
hasObjectOutput,
|
||||
isAuthenticated,
|
||||
currentProvider,
|
||||
currentTrigger,
|
||||
} = useConfig(id, data)
|
||||
|
||||
// Convert output schema to VarItem format
|
||||
@ -36,13 +38,14 @@ const Panel: FC<NodePanelProps<PluginTriggerNodeType>> = ({
|
||||
{isAuthenticated && triggerParameterSchema.length > 0 && (
|
||||
<>
|
||||
<div className='px-4 pb-4'>
|
||||
<ToolForm
|
||||
<TriggerForm
|
||||
readOnly={readOnly}
|
||||
nodeId={id}
|
||||
schema={triggerParameterSchema as any}
|
||||
value={triggerParameterValue}
|
||||
onChange={setTriggerParameterValue}
|
||||
providerType="trigger"
|
||||
currentProvider={currentProvider}
|
||||
currentTrigger={currentTrigger}
|
||||
/>
|
||||
</div>
|
||||
<Split />
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
'use client'
|
||||
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { Trigger } from '@/app/components/tools/types'
|
||||
import type { FC } from 'react'
|
||||
import type { PluginTriggerVarInputs } from '../types'
|
||||
import TriggerFormItem from './item'
|
||||
import type { TriggerWithProvider } from '../../../block-selector/types'
|
||||
|
||||
type Props = {
|
||||
readOnly: boolean
|
||||
nodeId: string
|
||||
schema: CredentialFormSchema[]
|
||||
value: PluginTriggerVarInputs
|
||||
onChange: (value: PluginTriggerVarInputs) => void
|
||||
onOpen?: (index: number) => void
|
||||
inPanel?: boolean
|
||||
currentTrigger?: Trigger
|
||||
currentProvider?: TriggerWithProvider
|
||||
extraParams?: Record<string, any>
|
||||
}
|
||||
|
||||
const TriggerForm: FC<Props> = ({
|
||||
readOnly,
|
||||
nodeId,
|
||||
schema,
|
||||
value,
|
||||
onChange,
|
||||
inPanel,
|
||||
currentTrigger,
|
||||
currentProvider,
|
||||
extraParams,
|
||||
}) => {
|
||||
return (
|
||||
<div className='space-y-1'>
|
||||
{
|
||||
schema.map((schema, index) => (
|
||||
<TriggerFormItem
|
||||
key={index}
|
||||
readOnly={readOnly}
|
||||
nodeId={nodeId}
|
||||
schema={schema}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
inPanel={inPanel}
|
||||
currentTrigger={currentTrigger}
|
||||
currentProvider={currentProvider}
|
||||
extraParams={extraParams}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default TriggerForm
|
||||
@ -0,0 +1,109 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import {
|
||||
RiBracesLine,
|
||||
} from '@remixicon/react'
|
||||
import type { PluginTriggerVarInputs } from '../types'
|
||||
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import FormInputItem from '@/app/components/workflow/nodes/_base/components/form-input-item'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import SchemaModal from '@/app/components/plugins/plugin-detail-panel/tool-selector/schema-modal'
|
||||
import type { Trigger } from '@/app/components/tools/types'
|
||||
import type { TriggerWithProvider } from '../../../block-selector/types'
|
||||
|
||||
type Props = {
|
||||
readOnly: boolean
|
||||
nodeId: string
|
||||
schema: CredentialFormSchema
|
||||
value: PluginTriggerVarInputs
|
||||
onChange: (value: PluginTriggerVarInputs) => void
|
||||
inPanel?: boolean
|
||||
currentTrigger?: Trigger
|
||||
currentProvider?: TriggerWithProvider
|
||||
extraParams?: Record<string, any>
|
||||
}
|
||||
|
||||
const TriggerFormItem: FC<Props> = ({
|
||||
readOnly,
|
||||
nodeId,
|
||||
schema,
|
||||
value,
|
||||
onChange,
|
||||
inPanel,
|
||||
currentTrigger,
|
||||
currentProvider,
|
||||
extraParams,
|
||||
}) => {
|
||||
const language = useLanguage()
|
||||
const { name, label, type, required, tooltip, input_schema } = schema
|
||||
const showSchemaButton = type === FormTypeEnum.object || type === FormTypeEnum.array
|
||||
const showDescription = type === FormTypeEnum.textInput || type === FormTypeEnum.secretInput
|
||||
const [isShowSchema, {
|
||||
setTrue: showSchema,
|
||||
setFalse: hideSchema,
|
||||
}] = useBoolean(false)
|
||||
return (
|
||||
<div className='space-y-0.5 py-1'>
|
||||
<div>
|
||||
<div className='flex h-6 items-center'>
|
||||
<div className='system-sm-medium text-text-secondary'>{label[language] || label.en_US}</div>
|
||||
{required && (
|
||||
<div className='system-xs-regular ml-1 text-text-destructive-secondary'>*</div>
|
||||
)}
|
||||
{!showDescription && tooltip && (
|
||||
<Tooltip
|
||||
popupContent={<div className='w-[200px]'>
|
||||
{tooltip[language] || tooltip.en_US}
|
||||
</div>}
|
||||
triggerClassName='ml-1 w-4 h-4'
|
||||
asChild={false}
|
||||
/>
|
||||
)}
|
||||
{showSchemaButton && (
|
||||
<>
|
||||
<div className='system-xs-regular ml-1 mr-0.5 text-text-quaternary'>·</div>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
onClick={showSchema}
|
||||
className='system-xs-regular px-1 text-text-tertiary'
|
||||
>
|
||||
<RiBracesLine className='mr-1 size-3.5' />
|
||||
<span>JSON Schema</span>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{showDescription && tooltip && (
|
||||
<div className='body-xs-regular pb-0.5 text-text-tertiary'>{tooltip[language] || tooltip.en_US}</div>
|
||||
)}
|
||||
</div>
|
||||
<FormInputItem
|
||||
readOnly={readOnly}
|
||||
nodeId={nodeId}
|
||||
schema={schema}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
inPanel={inPanel}
|
||||
currentResource={currentTrigger}
|
||||
currentProvider={currentProvider}
|
||||
providerType='trigger'
|
||||
extraParams={extraParams}
|
||||
/>
|
||||
|
||||
{isShowSchema && (
|
||||
<SchemaModal
|
||||
isShow
|
||||
onClose={hideSchema}
|
||||
rootName={name}
|
||||
schema={input_schema!}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default TriggerFormItem
|
||||
@ -1,12 +1,23 @@
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
import type { CollectionType } from '@/app/components/tools/types'
|
||||
import type { ResourceVarInputs } from '../_base/types'
|
||||
|
||||
export type PluginTriggerNodeType = CommonNodeType & {
|
||||
provider_id: string
|
||||
provider_type: CollectionType
|
||||
provider_name: string
|
||||
trigger_name: string
|
||||
trigger_label: string
|
||||
trigger_parameters: PluginTriggerVarInputs
|
||||
trigger_configurations: Record<string, any>
|
||||
output_schema: Record<string, any>
|
||||
parameters_schema?: Record<string, any>[]
|
||||
version?: string
|
||||
trigger_node_version?: string
|
||||
plugin_id?: string
|
||||
tool_name?: string
|
||||
event_type?: string
|
||||
config?: Record<string, any>
|
||||
provider_id?: string
|
||||
provider_type?: CollectionType
|
||||
provider_name?: string
|
||||
}
|
||||
|
||||
// Use base types directly
|
||||
export { VarKindType as PluginTriggerVarType } from '../_base/types'
|
||||
export type PluginTriggerVarInputs = ResourceVarInputs
|
||||
|
||||
@ -3,27 +3,32 @@ import produce from 'immer'
|
||||
import type { PluginTriggerNodeType } from './types'
|
||||
import useNodeCrud from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import { useNodesReadOnly } from '@/app/components/workflow/hooks'
|
||||
import { useAllTriggerPlugins, useTriggerSubscriptions } from '@/service/use-triggers'
|
||||
import {
|
||||
useAllTriggerPlugins,
|
||||
useTriggerSubscriptions,
|
||||
} from '@/service/use-triggers'
|
||||
import {
|
||||
addDefaultValue,
|
||||
toolParametersToFormSchemas,
|
||||
} from '@/app/components/tools/utils/to-form-schema'
|
||||
import type { InputVar } from '@/app/components/workflow/types'
|
||||
import type { TriggerWithProvider } from '@/app/components/workflow/block-selector/types'
|
||||
import type { Tool } from '@/app/components/tools/types'
|
||||
import type { Trigger } from '@/app/components/tools/types'
|
||||
|
||||
const useConfig = (id: string, payload: PluginTriggerNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const { data: triggerPlugins = [] } = useAllTriggerPlugins()
|
||||
|
||||
const { inputs, setInputs: doSetInputs } = useNodeCrud<PluginTriggerNodeType>(id, payload)
|
||||
const { inputs, setInputs: doSetInputs } = useNodeCrud<PluginTriggerNodeType>(
|
||||
id,
|
||||
payload,
|
||||
)
|
||||
|
||||
const { provider_id, provider_name, tool_name, config } = inputs
|
||||
const { provider_id, provider_name, trigger_name, config } = inputs
|
||||
|
||||
// Construct provider for authentication check
|
||||
const authProvider = useMemo(() => {
|
||||
if (provider_id && provider_name)
|
||||
return `${provider_id}/${provider_name}`
|
||||
if (provider_id && provider_name) return `${provider_id}/${provider_name}`
|
||||
return provider_id || ''
|
||||
}, [provider_id, provider_name])
|
||||
|
||||
@ -33,21 +38,26 @@ const useConfig = (id: string, payload: PluginTriggerNodeType) => {
|
||||
)
|
||||
|
||||
const currentProvider = useMemo<TriggerWithProvider | undefined>(() => {
|
||||
return triggerPlugins.find(provider =>
|
||||
provider.name === provider_name
|
||||
|| provider.id === provider_id
|
||||
|| (provider_id && provider.plugin_id === provider_id),
|
||||
return triggerPlugins.find(
|
||||
provider =>
|
||||
provider.name === provider_name
|
||||
|| provider.id === provider_id
|
||||
|| (provider_id && provider.plugin_id === provider_id),
|
||||
)
|
||||
}, [triggerPlugins, provider_name, provider_id])
|
||||
|
||||
const currentTrigger = useMemo<Tool | undefined>(() => {
|
||||
return currentProvider?.tools.find(tool => tool.name === tool_name)
|
||||
}, [currentProvider, tool_name])
|
||||
const currentTrigger = useMemo<Trigger | undefined>(() => {
|
||||
return currentProvider?.triggers.find(
|
||||
trigger => trigger.name === trigger_name,
|
||||
)
|
||||
}, [currentProvider, trigger_name])
|
||||
|
||||
// Dynamic subscription parameters (from subscription_schema.parameters_schema)
|
||||
const subscriptionParameterSchema = useMemo(() => {
|
||||
if (!currentProvider?.subscription_schema?.parameters_schema) return []
|
||||
return toolParametersToFormSchemas(currentProvider.subscription_schema.parameters_schema as any)
|
||||
return toolParametersToFormSchemas(
|
||||
currentProvider.subscription_schema.parameters_schema as any,
|
||||
)
|
||||
}, [currentProvider])
|
||||
|
||||
// Dynamic trigger parameters (from specific trigger.parameters)
|
||||
@ -66,22 +76,28 @@ const useConfig = (id: string, payload: PluginTriggerNodeType) => {
|
||||
return addDefaultValue(config || {}, triggerParameterSchema)
|
||||
}, [triggerParameterSchema, config])
|
||||
|
||||
const setTriggerParameterValue = useCallback((value: Record<string, any>) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.config = value
|
||||
})
|
||||
doSetInputs(newInputs)
|
||||
}, [inputs, doSetInputs])
|
||||
const setTriggerParameterValue = useCallback(
|
||||
(value: Record<string, any>) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.config = value
|
||||
})
|
||||
doSetInputs(newInputs)
|
||||
},
|
||||
[inputs, doSetInputs],
|
||||
)
|
||||
|
||||
const setInputVar = useCallback((variable: InputVar, varDetail: InputVar) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.config = {
|
||||
...draft.config,
|
||||
[variable.variable]: varDetail.variable,
|
||||
}
|
||||
})
|
||||
doSetInputs(newInputs)
|
||||
}, [inputs, doSetInputs])
|
||||
const setInputVar = useCallback(
|
||||
(variable: InputVar, varDetail: InputVar) => {
|
||||
const newInputs = produce(inputs, (draft) => {
|
||||
draft.config = {
|
||||
...draft.config,
|
||||
[variable.variable]: varDetail.variable,
|
||||
}
|
||||
})
|
||||
doSetInputs(newInputs)
|
||||
},
|
||||
[inputs, doSetInputs],
|
||||
)
|
||||
|
||||
// Get output schema
|
||||
const outputSchema = useMemo(() => {
|
||||
@ -91,7 +107,9 @@ const useConfig = (id: string, payload: PluginTriggerNodeType) => {
|
||||
// Check if trigger has complex output structure
|
||||
const hasObjectOutput = useMemo(() => {
|
||||
const properties = outputSchema.properties || {}
|
||||
return Object.values(properties).some((prop: any) => prop.type === 'object')
|
||||
return Object.values(properties).some(
|
||||
(prop: any) => prop.type === 'object',
|
||||
)
|
||||
}, [outputSchema])
|
||||
|
||||
// Authentication status check
|
||||
@ -109,10 +127,16 @@ const useConfig = (id: string, payload: PluginTriggerNodeType) => {
|
||||
|
||||
const methods = []
|
||||
|
||||
if (currentProvider.oauth_client_schema && currentProvider.oauth_client_schema.length > 0)
|
||||
if (
|
||||
currentProvider.oauth_client_schema
|
||||
&& currentProvider.oauth_client_schema.length > 0
|
||||
)
|
||||
methods.push('oauth')
|
||||
|
||||
if (currentProvider.credentials_schema && currentProvider.credentials_schema.length > 0)
|
||||
if (
|
||||
currentProvider.credentials_schema
|
||||
&& currentProvider.credentials_schema.length > 0
|
||||
)
|
||||
methods.push('api_key')
|
||||
|
||||
return methods
|
||||
|
||||
Reference in New Issue
Block a user