feat: var picker support choose type

This commit is contained in:
Joel 2024-03-14 16:04:09 +08:00
parent 2af2e2be67
commit 8bd74d5abf
8 changed files with 189 additions and 56 deletions

View File

@ -249,7 +249,7 @@ export const LLM_OUTPUT_STRUCT: Var[] = [
variable: 'text',
type: VarType.string,
},
USAGE,
]
export const KNOWLEDGE_RETRIEVAL_OUTPUT_STRUCT: Var[] = [

View File

@ -11,6 +11,8 @@ type Item = {
}
type Props = {
trigger?: JSX.Element
DropDownIcon?: any
noLeft?: boolean
options: Item[]
value: string
onChange: (value: any) => void
@ -24,6 +26,8 @@ type Props = {
const TypeSelector: FC<Props> = ({
trigger,
DropDownIcon = ChevronSelectorVertical,
noLeft,
options: list,
value,
onChange,
@ -41,7 +45,7 @@ const TypeSelector: FC<Props> = ({
setHide()
}, ref)
return (
<div className={cn(!trigger && 'left-[-8px]', 'relative')} ref={ref}>
<div className={cn(!trigger && !noLeft && 'left-[-8px]', 'relative')} ref={ref}>
{trigger
? (
<div
@ -55,7 +59,7 @@ const TypeSelector: FC<Props> = ({
onClick={toggleShow}
className={cn(showOption && 'bg-black/5', 'flex items-center h-5 pl-1 pr-0.5 rounded-md text-xs font-semibold text-gray-700 cursor-pointer hover:bg-black/5')}>
<div className={cn(triggerClassName, 'text-sm font-semibold', uppercase && 'uppercase')}>{item?.label}</div>
<ChevronSelectorVertical className='w-3 h-3 ' />
<DropDownIcon className='w-3 h-3 ' />
</div>
)}

View File

@ -4,13 +4,15 @@ import React, { useCallback } from 'react'
import produce from 'immer'
import RemoveButton from '../remove-button'
import VarReferencePicker from './var-reference-picker'
import type { ValueSelector, Variable } from '@/app/components/workflow/types'
import { type ValueSelector, type Variable } from '@/app/components/workflow/types'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
type Props = {
nodeId: string
readonly: boolean
list: Variable[]
onChange: (list: Variable[]) => void
isSupportConstantValue?: boolean
}
const VarList: FC<Props> = ({
@ -18,6 +20,7 @@ const VarList: FC<Props> = ({
readonly,
list,
onChange,
isSupportConstantValue,
}) => {
const handleVarNameChange = useCallback((index: number) => {
return (e: React.ChangeEvent<HTMLInputElement>) => {
@ -29,11 +32,21 @@ const VarList: FC<Props> = ({
}, [list, onChange])
const handleVarReferenceChange = useCallback((index: number) => {
return (value: ValueSelector) => {
return (value: ValueSelector | string, varKindType: VarKindType) => {
const newList = produce(list, (draft) => {
draft[index].value_selector = value
if (!draft[index].variable)
draft[index].variable = value[value.length - 1]
if (!isSupportConstantValue || varKindType === VarKindType.selector) {
draft[index].value_selector = value as ValueSelector
if (isSupportConstantValue)
draft[index].variable_type = VarKindType.selector
if (!draft[index].variable)
draft[index].variable = value[value.length - 1]
}
else {
draft[index].variable_type = VarKindType.static
draft[index].value_selector = value as ValueSelector
draft[index].value = value as string
}
})
onChange(newList)
}
@ -63,8 +76,10 @@ const VarList: FC<Props> = ({
readonly={readonly}
isShowNodeName
className='grow'
value={item.value_selector}
value={item.variable_type === VarKindType.static ? (item.value || '') : (item.value_selector || [])}
isSupportConstantValue={isSupportConstantValue}
onChange={handleVarReferenceChange(index)}
defaultVarKindType={item.variable_type}
/>
<RemoveButton
className='!p-2 !bg-gray-100 hover:!bg-gray-200'

View File

@ -1,6 +1,6 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import cn from 'classnames'
import { isArray } from 'lodash-es'
import VarReferencePopup from './var-reference-popup'
@ -19,14 +19,20 @@ import {
useIsChatMode,
useWorkflow,
} from '@/app/components/workflow/hooks'
import { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
type Props = {
className?: string
width?: number
nodeId: string
isShowNodeName: boolean
readonly: boolean
value: ValueSelector
onChange: (value: ValueSelector) => void
value: ValueSelector | string
onChange: (value: ValueSelector | string, varKindType: VarKindType) => void
isSupportConstantValue?: boolean
defaultVarKindType?: VarKindType
}
export const getNodeInfoById = (nodes: any, id: string) => {
@ -38,31 +44,38 @@ export const getNodeInfoById = (nodes: any, id: string) => {
const VarReferencePicker: FC<Props> = ({
nodeId,
width,
readonly,
className,
isShowNodeName,
value,
onChange,
isSupportConstantValue,
defaultVarKindType = VarKindType.static,
}) => {
const isChatMode = useIsChatMode()
const [varKindType, setVarKindType] = useState<VarKindType>(defaultVarKindType)
const isConstant = isSupportConstantValue && varKindType === VarKindType.static
const { getTreeLeafNodes, getBeforeNodesInSameBranch } = useWorkflow()
const availableNodes = getBeforeNodesInSameBranch(nodeId)
const outputVars = toNodeOutputVars(availableNodes, isChatMode)
const [open, setOpen] = useState(false)
const hasValue = value.length > 0
const hasValue = !isConstant && value.length > 0
const outputVarNodeId = hasValue ? value[0] : ''
const outputVarNode = hasValue ? getNodeInfoById(availableNodes, outputVarNodeId)?.data : null
const varName = hasValue ? value[value.length - 1] : ''
const getVarType = () => {
if (isConstant)
return 'undefined'
const targetVar = outputVars.find(v => v.nodeId === outputVarNodeId)
if (!targetVar)
return 'undefined'
let type: VarType = VarType.string
let curr: any = targetVar.vars
value.slice(1).forEach((key, i) => {
let curr: any = targetVar.vars;
(value as ValueSelector).slice(1).forEach((key, i) => {
const isLast = i === value.length - 2
curr = curr.find((v: any) => v.variable === key)
if (isLast) {
@ -76,6 +89,38 @@ const VarReferencePicker: FC<Props> = ({
return type
}
const varKindTypes = [
{
label: 'Variable',
value: VarKindType.selector,
},
{
label: 'Constant',
value: VarKindType.static,
},
]
const handleVarKindTypeChange = useCallback((value: VarKindType) => {
setVarKindType(value)
if (value === VarKindType.static)
onChange('', value)
else
onChange([], value)
}, [varKindType])
const inputRef = useRef<HTMLInputElement>(null)
const [isFocus, setIsFocus] = useState(false)
const [controlFocus, setControlFocus] = useState(0)
useEffect(() => {
if (controlFocus && inputRef.current) {
inputRef.current.focus()
setIsFocus(true)
}
}, [controlFocus])
const handleStaticChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
onChange(e.target.value as string, varKindType)
}, [onChange, varKindType])
return (
<div className={cn(className, !readonly && 'cursor-pointer')}>
<PortalToFollowElem
@ -83,47 +128,81 @@ const VarReferencePicker: FC<Props> = ({
onOpenChange={setOpen}
placement='bottom-start'
>
<PortalToFollowElemTrigger onClick={() => setOpen(!open)} className='!flex'>
<div className={cn('w-full h-8 p-1 rounded-lg bg-gray-100')}>
<div className={cn('inline-flex h-full items-center px-1.5 rounded-[5px]', hasValue && 'bg-white')}>
{hasValue && (
<>
{isShowNodeName && (
<div className='flex items-center'>
<div className='p-[1px]'>
<VarBlockIcon
className='!text-gray-900'
type={outputVarNode?.type}
/>
<PortalToFollowElemTrigger onClick={() => !isConstant && setOpen(!open)} className='!flex'>
<div className={cn((open || isFocus) && 'border border-gray-300', 'flex items-center w-full h-8 p-1 rounded-lg bg-gray-100')}>
{isSupportConstantValue
? <div onClick={(e) => {
e.stopPropagation()
setOpen(false)
}} className='mr-1 flex items-center space-x-1'>
<TypeSelector
noLeft
triggerClassName='!text-xs'
DropDownIcon={ChevronDown}
value={varKindType}
options={varKindTypes}
onChange={handleVarKindTypeChange}
/>
<div className='h-4 w-px bg-black/5'></div>
</div>
: <div className='ml-1.5 mr-1'>
<Variable02 className='w-3.5 h-3.5 text-gray-400' />
</div>}
{isConstant
? (
<input
type='text'
className='w-full h-8 leading-8 pl-0.5 bg-transparent text-[13px] font-normal text-gray-900 placeholder:text-gray-400 focus:outline-none'
value={isConstant ? value : ''}
onChange={handleStaticChange}
onFocus={() => setIsFocus(true)}
onBlur={() => setIsFocus(false)}
/>
)
: (
<div className={cn('inline-flex h-full items-center px-1.5 rounded-[5px]', hasValue && 'bg-white')}>
{hasValue && (
<>
{isShowNodeName && (
<div className='flex items-center'>
<div className='p-[1px]'>
<VarBlockIcon
className='!text-gray-900'
type={outputVarNode?.type}
/>
</div>
<div className='mx-0.5 text-xs font-medium text-gray-700'>{outputVarNode?.title}</div>
<Line3 className='mr-0.5'></Line3>
</div>
)}
<div className='flex items-center text-primary-600'>
<Variable02 className='w-3.5 h-3.5' />
<div className='ml-0.5 text-xs font-medium'>{varName}</div>
</div>
<div className='mx-0.5 text-xs font-medium text-gray-700'>{outputVarNode?.title}</div>
<Line3 className='mr-0.5'></Line3>
</div>
<div className='ml-0.5 text-xs font-normal text-gray-500 capitalize'>{getVarType()}</div>
</>
)}
<div className='flex items-center text-primary-600'>
<Variable02 className='w-3.5 h-3.5' />
<div className='ml-0.5 text-xs font-medium'>{varName}</div>
</div>
<div className='ml-0.5 text-xs font-normal text-gray-500 capitalize'>{getVarType()}</div>
</>
</div>
)}
</div>
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent style={{
zIndex: 100,
minWidth: 227,
}}>
<VarReferencePopup
vars={outputVars}
onChange={(value) => {
onChange(value)
setOpen(false)
}}
/>
{!isConstant && (
<VarReferencePopup
vars={outputVars}
onChange={(value) => {
onChange(value, varKindType)
setOpen(false)
}}
itemWidth={width}
/>
)}
</PortalToFollowElemContent>
</PortalToFollowElem>
</div>
</div >
)
}
export default React.memo(VarReferencePicker)

View File

@ -13,6 +13,7 @@ type ObjectChildrenProps = {
data: Var[]
objPath: string[]
onChange: (value: ValueSelector) => void
itemWidth?: number
}
type ItemProps = {
@ -21,6 +22,7 @@ type ItemProps = {
objPath: string[]
itemData: Var
onChange: (value: ValueSelector) => void
itemWidth?: number
}
const Item: FC<ItemProps> = ({
@ -29,6 +31,7 @@ const Item: FC<ItemProps> = ({
objPath,
itemData,
onChange,
itemWidth,
}) => {
const isObj = itemData.type === VarType.object && itemData.children && itemData.children.length > 0
const itemRef = useRef(null)
@ -40,7 +43,11 @@ const Item: FC<ItemProps> = ({
return (
<div
ref={itemRef}
className={cn(isObj ? 'hover:bg-primary-50 pr-1' : 'hover:bg-gray-50 pr-[18px]', 'relative flex items-center h-6 w-[252px] pl-3 rounded-md cursor-pointer')}
className={cn(
isObj ? 'hover:bg-primary-50 pr-1' : 'hover:bg-gray-50 pr-[18px]',
'relative w-full flex items-center h-6 pl-3 rounded-md cursor-pointer')
}
// style={{ width: itemWidth || 252 }}
onClick={handleChosen}
>
<div className='flex items-center w-0 grow'>
@ -59,6 +66,7 @@ const Item: FC<ItemProps> = ({
objPath={[...objPath, itemData.variable]}
data={itemData.children as Var[]}
onChange={onChange}
itemWidth={itemWidth}
/>
)}
</div>
@ -71,11 +79,15 @@ const ObjectChildren: FC<ObjectChildrenProps> = ({
objPath,
data,
onChange,
itemWidth,
}) => {
const currObjPath = objPath
return (
<div className='absolute right-[248px] top-[-2px] bg-white rounded-lg border border-gray-200 shadow-lg space-y-1'>
<div className='absolute top-[-2px] bg-white rounded-lg border border-gray-200 shadow-lg space-y-1' style={{
right: itemWidth ? itemWidth - 4 : 215,
minWidth: 252,
}}>
<div className='flex items-center h-[22px] px-3 text-xs font-normal text-gray-700'><span className='text-gray-500'>{title}.</span>{currObjPath.join('.')}</div>
{
data?.map((v, i) => (
@ -96,14 +108,17 @@ const ObjectChildren: FC<ObjectChildrenProps> = ({
type Props = {
vars: NodeOutPutVar[]
onChange: (value: ValueSelector) => void
itemWidth?: number
}
const VarReferencePopup: FC<Props> = ({
vars,
onChange,
itemWidth,
}) => {
return (
<div className='p-1 bg-white rounded-lg border border-gray-200 shadow-lg space-y-1'>
<div className='p-1 bg-white rounded-lg border border-gray-200 shadow-lg space-y-1' style={{
width: itemWidth || 228,
}}>
{vars.map((item, i) => (
<div key={i}>
<div className='flex items-center h-[22px] px-3 text-xs font-medium text-gray-500 uppercase'>{item.title}</div>
@ -115,6 +130,7 @@ const VarReferencePopup: FC<Props> = ({
objPath={[]}
itemData={v}
onChange={onChange}
itemWidth={itemWidth}
/>
))}
</div>

View File

@ -3,7 +3,8 @@ import type { FC } from 'react'
import React, { useCallback } from 'react'
import produce from 'immer'
import type { ToolVarInput } from '../types'
import { VarType } from '../types'
import { VarType as VarKindType } from '../types'
import { type ValueSelector } from '@/app/components/workflow/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'
@ -15,6 +16,7 @@ type Props = {
schema: CredentialFormSchema[]
value: ToolVarInput[]
onChange: (value: ToolVarInput[]) => void
isSupportConstantValue?: boolean
}
const InputVarList: FC<Props> = ({
@ -23,6 +25,7 @@ const InputVarList: FC<Props> = ({
schema,
value,
onChange,
isSupportConstantValue,
}) => {
const language = useLanguage()
@ -35,17 +38,26 @@ const InputVarList: FC<Props> = ({
})()
const handleChange = useCallback((variable: string) => {
return (varValue: any) => {
return (varValue: ValueSelector | string, varKindType: VarKindType) => {
const newValue = produce(value, (draft: ToolVarInput[]) => {
const target = draft.find(item => item.variable === variable)
if (target) {
target.value_selector = varValue // TODO: support constant value
if (!isSupportConstantValue || varKindType === VarKindType.selector) {
if (isSupportConstantValue)
target.variable_type = VarKindType.selector
target.value_selector = varValue as ValueSelector
}
else {
target.variable_type = VarKindType.static
target.value = varValue as string
}
}
else {
draft.push({
variable,
variable_type: VarType.selector, // TODO: support constant value
value_selector: varValue,
variable_type: VarKindType.static,
value: '',
})
}
})
@ -74,9 +86,12 @@ const InputVarList: FC<Props> = ({
<VarReferencePicker
readonly={readOnly}
isShowNodeName
width={372}
nodeId={nodeId}
value={varInput?.value_selector || []} // TODO: support constant value
value={varInput?.variable_type === VarKindType.static ? (varInput?.value || '') : (varInput?.value_selector || [])}
onChange={handleChange(variable)}
isSupportConstantValue={isSupportConstantValue}
defaultVarKindType={varInput?.variable_type}
/>
{tooltip && <div className='leading-[18px] text-xs font-normal text-gray-600'>{tooltip[language] || tooltip.en_US}</div>}
</div>

View File

@ -82,6 +82,7 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
schema={toolInputVarSchema as any}
value={inputs.tool_parameters}
onChange={setInputVar}
isSupportConstantValue
/>
</Field>
)}

View File

@ -3,6 +3,7 @@ import type {
Node as ReactFlowNode,
} from 'reactflow'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
import type { VarType as VarKindType } from '@/app/components/workflow/nodes/tool/types'
export enum BlockEnum {
Start = 'start',
@ -57,6 +58,8 @@ export type ValueSelector = string[] // [nodeId, key | obj key path]
export type Variable = {
variable: string
value_selector: ValueSelector
variable_type?: VarKindType
value?: string
}
export type VariableWithValue = {