feat: custom credential form

pull/12372/head
AkaraChen 1 year ago
parent e0b7f48b6b
commit b7e56a23a0

@ -1,5 +1,5 @@
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import type { FC } from 'react' import type { ReactNode } from 'react'
import { ValidatingTip } from '../../key-validator/ValidateStatus' import { ValidatingTip } from '../../key-validator/ValidateStatus'
import type { import type {
CredentialFormSchema, CredentialFormSchema,
@ -22,13 +22,15 @@ import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-sele
import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector' import AppSelector from '@/app/components/plugins/plugin-detail-panel/app-selector'
import RadioE from '@/app/components/base/radio/ui' import RadioE from '@/app/components/base/radio/ui'
type FormProps = { type FormProps<
CustomFormSchema extends Omit<CredentialFormSchema, 'type'> & { type: string } = never,
> = {
className?: string className?: string
itemClassName?: string itemClassName?: string
fieldLabelClassName?: string fieldLabelClassName?: string
value: FormValue value: FormValue
onChange: (val: FormValue) => void onChange: (val: FormValue) => void
formSchemas: CredentialFormSchema[] formSchemas: Array<CredentialFormSchema | CustomFormSchema>
validating: boolean validating: boolean
validatedSuccess?: boolean validatedSuccess?: boolean
showOnVariableMap: Record<string, string[]> showOnVariableMap: Record<string, string[]>
@ -36,10 +38,13 @@ type FormProps = {
readonly?: boolean readonly?: boolean
inputClassName?: string inputClassName?: string
isShowDefaultValue?: boolean isShowDefaultValue?: boolean
fieldMoreInfo?: (payload: CredentialFormSchema) => JSX.Element | null fieldMoreInfo?: (payload: CredentialFormSchema | CustomFormSchema) => ReactNode
customRenderField?: (formSchema: CustomFormSchema, props: FormProps<CustomFormSchema>) => ReactNode
} }
const Form: FC<FormProps> = ({ function Form<
CustomFormSchema extends Omit<CredentialFormSchema, 'type'> & { type: string } = never,
>({
className, className,
itemClassName, itemClassName,
fieldLabelClassName, fieldLabelClassName,
@ -54,7 +59,8 @@ const Form: FC<FormProps> = ({
inputClassName, inputClassName,
isShowDefaultValue = false, isShowDefaultValue = false,
fieldMoreInfo, fieldMoreInfo,
}) => { customRenderField,
}: FormProps<CustomFormSchema>) {
const language = useLanguage() const language = useLanguage()
const [changeKey, setChangeKey] = useState('') const [changeKey, setChangeKey] = useState('')
@ -81,25 +87,19 @@ const Form: FC<FormProps> = ({
onChange({ ...value, [key]: newValue }) onChange({ ...value, [key]: newValue })
}, [onChange, value]) }, [onChange, value])
const renderField = (formSchema: CredentialFormSchema) => { const renderField = (formSchema: CredentialFormSchema | CustomFormSchema) => {
const tooltip = formSchema.tooltip const tooltip = formSchema.tooltip
const tooltipContent = (tooltip && ( const tooltipContent = (tooltip && (
<Tooltip <Tooltip
popupContent={ popupContent={<div className='w-[200px]'>
<div className='w-[200px]'> {tooltip[language] || tooltip.en_US}
{tooltip[language] || tooltip.en_US} </div>}
</div>}
triggerClassName='ml-1 w-4 h-4' triggerClassName='ml-1 w-4 h-4'
asChild={false} asChild={false} />
/>
)) ))
if (formSchema.type === FormTypeEnum.textInput || formSchema.type === FormTypeEnum.secretInput || formSchema.type === FormTypeEnum.textNumber) { if (formSchema.type === FormTypeEnum.textInput || formSchema.type === FormTypeEnum.secretInput || formSchema.type === FormTypeEnum.textNumber) {
const { const {
variable, variable, label, placeholder, required, show_on,
label,
placeholder,
required,
show_on,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)) if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
@ -110,11 +110,9 @@ const Form: FC<FormProps> = ({
<div key={variable} className={cn(itemClassName, 'py-3')}> <div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}> <div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
{label[language] || label.en_US} {label[language] || label.en_US}
{ {required && (
required && ( <span className='ml-1 text-red-500'>*</span>
<span className='ml-1 text-red-500'>*</span> )}
)
}
{tooltipContent} {tooltipContent}
</div> </div>
<Input <Input
@ -125,8 +123,7 @@ const Form: FC<FormProps> = ({
placeholder={placeholder?.[language] || placeholder?.en_US} placeholder={placeholder?.[language] || placeholder?.en_US}
disabled={disabled} disabled={disabled}
type={formSchema.type === FormTypeEnum.textNumber ? 'number' : 'text'} type={formSchema.type === FormTypeEnum.textNumber ? 'number' : 'text'}
{...(formSchema.type === FormTypeEnum.textNumber ? { min: (formSchema as CredentialFormSchemaNumberInput).min, max: (formSchema as CredentialFormSchemaNumberInput).max } : {})} {...(formSchema.type === FormTypeEnum.textNumber ? { min: (formSchema as CredentialFormSchemaNumberInput).min, max: (formSchema as CredentialFormSchemaNumberInput).max } : {})} />
/>
{fieldMoreInfo?.(formSchema)} {fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />} {validating && changeKey === variable && <ValidatingTip />}
</div> </div>
@ -135,11 +132,7 @@ const Form: FC<FormProps> = ({
if (formSchema.type === FormTypeEnum.radio) { if (formSchema.type === FormTypeEnum.radio) {
const { const {
options, options, variable, label, show_on, required,
variable,
label,
show_on,
required,
} = formSchema as CredentialFormSchemaRadio } = formSchema as CredentialFormSchemaRadio
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)) if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
@ -151,36 +144,32 @@ const Form: FC<FormProps> = ({
<div key={variable} className={cn(itemClassName, 'py-3')}> <div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}> <div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
{label[language] || label.en_US} {label[language] || label.en_US}
{ {required && (
required && ( <span className='ml-1 text-red-500'>*</span>
<span className='ml-1 text-red-500'>*</span> )}
)
}
{tooltipContent} {tooltipContent}
</div> </div>
<div className={`grid grid-cols-${options?.length} gap-3`}> <div className={`grid grid-cols-${options?.length} gap-3`}>
{ {options.filter((option) => {
options.filter((option) => { if (option.show_on.length)
if (option.show_on.length) return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
return option.show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)
return true return true
}).map(option => ( }).map(option => (
<div <div
className={` className={`
flex items-center gap-2 px-3 py-2 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer flex items-center gap-2 px-3 py-2 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg cursor-pointer
${value[variable] === option.value && 'bg-components-option-card-option-selected-bg border-[1.5px] border-components-option-card-option-selected-border shadow-sm'} ${value[variable] === option.value && 'bg-components-option-card-option-selected-bg border-[1.5px] border-components-option-card-option-selected-border shadow-sm'}
${disabled && '!cursor-not-allowed opacity-60'} ${disabled && '!cursor-not-allowed opacity-60'}
`} `}
onClick={() => handleFormChange(variable, option.value)} onClick={() => handleFormChange(variable, option.value)}
key={`${variable}-${option.value}`} key={`${variable}-${option.value}`}
> >
<RadioE isChecked={value[variable] === option.value} /> <RadioE isChecked={value[variable] === option.value} />
<div className='system-sm-regular text-text-secondary'>{option.label[language] || option.label.en_US}</div> <div className='system-sm-regular text-text-secondary'>{option.label[language] || option.label.en_US}</div>
</div> </div>
)) ))}
}
</div> </div>
{fieldMoreInfo?.(formSchema)} {fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />} {validating && changeKey === variable && <ValidatingTip />}
@ -190,12 +179,7 @@ const Form: FC<FormProps> = ({
if (formSchema.type === FormTypeEnum.select) { if (formSchema.type === FormTypeEnum.select) {
const { const {
options, options, variable, label, show_on, required, placeholder,
variable,
label,
show_on,
required,
placeholder,
} = formSchema as CredentialFormSchemaSelect } = formSchema as CredentialFormSchemaSelect
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)) if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
@ -206,11 +190,9 @@ const Form: FC<FormProps> = ({
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}> <div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
{label[language] || label.en_US} {label[language] || label.en_US}
{ {required && (
required && ( <span className='ml-1 text-red-500'>*</span>
<span className='ml-1 text-red-500'>*</span> )}
)
}
{tooltipContent} {tooltipContent}
</div> </div>
<SimpleSelect <SimpleSelect
@ -225,8 +207,7 @@ const Form: FC<FormProps> = ({
return true return true
}).map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))} }).map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))}
onSelect={item => handleFormChange(variable, item.value as string)} onSelect={item => handleFormChange(variable, item.value as string)}
placeholder={placeholder?.[language] || placeholder?.en_US} placeholder={placeholder?.[language] || placeholder?.en_US} />
/>
{fieldMoreInfo?.(formSchema)} {fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />} {validating && changeKey === variable && <ValidatingTip />}
</div> </div>
@ -235,10 +216,7 @@ const Form: FC<FormProps> = ({
if (formSchema.type === FormTypeEnum.boolean) { if (formSchema.type === FormTypeEnum.boolean) {
const { const {
variable, variable, label, show_on, required,
label,
show_on,
required,
} = formSchema as CredentialFormSchemaRadio } = formSchema as CredentialFormSchemaRadio
if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value)) if (show_on.length && !show_on.every(showOnItem => value[showOnItem.variable] === showOnItem.value))
@ -249,11 +227,9 @@ const Form: FC<FormProps> = ({
<div className='flex items-center justify-between py-2 system-sm-semibold text-text-secondary'> <div className='flex items-center justify-between py-2 system-sm-semibold text-text-secondary'>
<div className='flex items-center space-x-2'> <div className='flex items-center space-x-2'>
<span className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-regular text-text-secondary')}>{label[language] || label.en_US}</span> <span className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-regular text-text-secondary')}>{label[language] || label.en_US}</span>
{ {required && (
required && ( <span className='ml-1 text-red-500'>*</span>
<span className='ml-1 text-red-500'>*</span> )}
)
}
{tooltipContent} {tooltipContent}
</div> </div>
<Radio.Group <Radio.Group
@ -272,20 +248,15 @@ const Form: FC<FormProps> = ({
if (formSchema.type === FormTypeEnum.modelSelector) { if (formSchema.type === FormTypeEnum.modelSelector) {
const { const {
variable, variable, label, required, scope,
label,
required,
scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return ( return (
<div key={variable} className={cn(itemClassName, 'py-3')}> <div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}> <div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
{label[language] || label.en_US} {label[language] || label.en_US}
{ {required && (
required && ( <span className='ml-1 text-red-500'>*</span>
<span className='ml-1 text-red-500'>*</span> )}
)
}
{tooltipContent} {tooltipContent}
</div> </div>
<ModelParameterModal <ModelParameterModal
@ -295,8 +266,7 @@ const Form: FC<FormProps> = ({
value={value[variable]} value={value[variable]}
setModel={model => handleModelChanged(variable, model)} setModel={model => handleModelChanged(variable, model)}
readonly={readonly} readonly={readonly}
scope={scope} scope={scope} />
/>
{fieldMoreInfo?.(formSchema)} {fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />} {validating && changeKey === variable && <ValidatingTip />}
</div> </div>
@ -305,27 +275,22 @@ const Form: FC<FormProps> = ({
if (formSchema.type === FormTypeEnum.toolSelector) { if (formSchema.type === FormTypeEnum.toolSelector) {
const { const {
variable, variable, label, required,
label,
required,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return ( return (
<div key={variable} className={cn(itemClassName, 'py-3')}> <div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}> <div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
{label[language] || label.en_US} {label[language] || label.en_US}
{ {required && (
required && ( <span className='ml-1 text-red-500'>*</span>
<span className='ml-1 text-red-500'>*</span> )}
)
}
{tooltipContent} {tooltipContent}
</div> </div>
<ToolSelector <ToolSelector
disabled={readonly} disabled={readonly}
value={value[variable]} value={value[variable]}
onSelect={item => handleFormChange(variable, item as any)} onSelect={item => handleFormChange(variable, item as any)} />
/>
{fieldMoreInfo?.(formSchema)} {fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />} {validating && changeKey === variable && <ValidatingTip />}
</div> </div>
@ -334,41 +299,54 @@ const Form: FC<FormProps> = ({
if (formSchema.type === FormTypeEnum.appSelector) { if (formSchema.type === FormTypeEnum.appSelector) {
const { const {
variable, variable, label, required, scope,
label,
required,
scope,
} = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput) } = formSchema as (CredentialFormSchemaTextInput | CredentialFormSchemaSecretInput)
return ( return (
<div key={variable} className={cn(itemClassName, 'py-3')}> <div key={variable} className={cn(itemClassName, 'py-3')}>
<div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}> <div className={cn(fieldLabelClassName, 'flex items-center py-2 system-sm-semibold text-text-secondary')}>
{label[language] || label.en_US} {label[language] || label.en_US}
{ {required && (
required && ( <span className='ml-1 text-red-500'>*</span>
<span className='ml-1 text-red-500'>*</span> )}
)
}
{tooltipContent} {tooltipContent}
</div> </div>
<AppSelector <AppSelector
disabled={readonly} disabled={readonly}
scope={scope} scope={scope}
value={value[variable]} value={value[variable]}
onSelect={item => handleFormChange(variable, { ...item, type: FormTypeEnum.appSelector } as any)} onSelect={item => handleFormChange(variable, { ...item, type: FormTypeEnum.appSelector } as any)} />
/>
{fieldMoreInfo?.(formSchema)} {fieldMoreInfo?.(formSchema)}
{validating && changeKey === variable && <ValidatingTip />} {validating && changeKey === variable && <ValidatingTip />}
</div> </div>
) )
} }
// @ts-expect-error it work
if (!Object.values(FormTypeEnum).includes(formSchema.type)) {
return customRenderField?.(formSchema as CustomFormSchema, {
className,
itemClassName,
fieldLabelClassName,
value,
onChange,
formSchemas,
validating,
validatedSuccess,
showOnVariableMap,
isEditMode,
readonly,
inputClassName,
isShowDefaultValue,
fieldMoreInfo,
customRenderField,
})
}
} }
return ( return (
<div className={className}> <div className={className}>
{ {formSchemas.map(formSchema => renderField(formSchema))}
formSchemas.map(formSchema => renderField(formSchema))
}
</div> </div>
) )
} }

@ -6,6 +6,10 @@ import Link from 'next/link'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
import { Agent } from '@/app/components/base/icons/src/vender/workflow' import { Agent } from '@/app/components/base/icons/src/vender/workflow'
import { InputNumber } from '@/app/components/base/input-number'
import Slider from '@/app/components/base/slider'
import Field from './field'
import type { ComponentProps } from 'react'
export type Strategy = { export type Strategy = {
agent_strategy_provider_name: string agent_strategy_provider_name: string
@ -22,22 +26,65 @@ export type AgentStrategyProps = {
onFormValueChange: (value: ToolVarInputs) => void onFormValueChange: (value: ToolVarInputs) => void
} }
type MaxIterFormSchema = Omit<CredentialFormSchema, 'type'> & { type: 'max-iter' }
export const AgentStrategy = (props: AgentStrategyProps) => { export const AgentStrategy = (props: AgentStrategyProps) => {
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props
const { t } = useTranslation() const { t } = useTranslation()
const renderField: ComponentProps<typeof Form<MaxIterFormSchema>>['customRenderField'] = (schema, props) => {
switch (schema.type) {
case 'max-iter': {
const value = props.value[schema.variable]
const onChange = (value: number) => {
props.onChange({ ...props.value, [schema.variable]: value })
}
return <Field title={t('workflow.nodes.agent.maxIterations')} tooltip={'max iter'} inline>
<div className='flex w-[200px] items-center gap-3'>
<Slider value={value} onChange={onChange} className='w-full' min={1} max={10} />
<InputNumber
value={value}
// TODO: maybe empty, handle this
onChange={onChange as any}
defaultValue={3}
size='sm'
min={1}
max={10}
className='w-12'
placeholder=''
/>
</div>
</Field>
}
}
}
console.log(formSchema)
return <div className='space-y-2'> return <div className='space-y-2'>
<AgentStrategySelector value={strategy} onChange={onStrategyChange} /> <AgentStrategySelector value={strategy} onChange={onStrategyChange} />
{ {
strategy strategy
? <div> ? <div>
<Form <Form<MaxIterFormSchema>
formSchemas={formSchema} formSchemas={[
...formSchema,
{
type: 'max-iter',
variable: 'max_iterations',
label: {
en_US: 'Max Iterations',
zh_Hans: '最大迭代次数',
},
name: 'max iter',
required: true,
show_on: [],
} as MaxIterFormSchema,
]}
value={formValue} value={formValue}
onChange={onFormValueChange} onChange={onFormValueChange}
validating={false} validating={false}
showOnVariableMap={{}} showOnVariableMap={{}}
isEditMode={true} isEditMode={true}
fieldLabelClassName='uppercase' fieldLabelClassName='uppercase'
customRenderField={renderField}
/> />
</div> </div>
: <ListEmpty : <ListEmpty

@ -2,8 +2,6 @@ import type { FC } from 'react'
import type { NodePanelProps } from '../../types' import type { NodePanelProps } from '../../types'
import type { AgentNodeType } from './types' import type { AgentNodeType } from './types'
import Field from '../_base/components/field' import Field from '../_base/components/field'
import { InputNumber } from '@/app/components/base/input-number'
import Slider from '@/app/components/base/slider'
import { AgentStrategy } from '../_base/components/agent-strategy' import { AgentStrategy } from '../_base/components/agent-strategy'
import useConfig from './use-config' import useConfig from './use-config'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -43,25 +41,6 @@ const AgentPanel: FC<NodePanelProps<AgentNodeType>> = (props) => {
})} })}
/> />
</Field> </Field>
<Field title={t('workflow.nodes.agent.tools')} className='px-4'>
</Field>
<Field title={t('workflow.nodes.agent.maxIterations')} tooltip={'max iter'} inline className='px-4'>
<div className='flex w-[200px] items-center gap-3'>
<Slider value={iter} onChange={setIter} className='w-full' min={1} max={10} />
<InputNumber
value={iter}
// TODO: maybe empty, handle this
onChange={setIter as any}
defaultValue={3}
size='sm'
min={1}
max={10}
className='w-12'
placeholder=''
/>
</div>
</Field>
</div> </div>
} }

Loading…
Cancel
Save