### What problem does this PR solve? Feat: Filter the query variable drop-down box options by type #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| </Popover> | </Popover> | ||||
| ); | ); | ||||
| }); | }); | ||||
| SelectWithSearch.displayName = 'SelectWithSearch'; |
| import { IAgentNode } from '@/interfaces/database/flow'; | import { IAgentNode } from '@/interfaces/database/flow'; | ||||
| import { Handle, NodeProps, Position } from '@xyflow/react'; | import { Handle, NodeProps, Position } from '@xyflow/react'; | ||||
| import { memo, useMemo } from 'react'; | import { memo, useMemo } from 'react'; | ||||
| import { NodeHandleId, Operator } from '../../constant'; | |||||
| import { NodeHandleId } from '../../constant'; | |||||
| import useGraphStore from '../../store'; | import useGraphStore from '../../store'; | ||||
| import { isBottomSubAgent } from '../../utils'; | |||||
| import { CommonHandle } from './handle'; | import { CommonHandle } from './handle'; | ||||
| import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; | import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| isConnectable = true, | isConnectable = true, | ||||
| selected, | selected, | ||||
| }: NodeProps<IAgentNode>) { | }: NodeProps<IAgentNode>) { | ||||
| const getNode = useGraphStore((state) => state.getNode); | |||||
| const edges = useGraphStore((state) => state.edges); | const edges = useGraphStore((state) => state.edges); | ||||
| const isNotParentAgent = useMemo(() => { | |||||
| const edge = edges.find((x) => x.target === id); | |||||
| const label = getNode(edge?.source)?.data.label; | |||||
| return label !== Operator.Agent; | |||||
| }, [edges, getNode, id]); | |||||
| const isHeadAgent = useMemo(() => { | |||||
| return !isBottomSubAgent(edges, id); | |||||
| }, [edges, id]); | |||||
| return ( | return ( | ||||
| <ToolBar selected={selected} id={id} label={data.label}> | <ToolBar selected={selected} id={id} label={data.label}> | ||||
| <NodeWrapper> | <NodeWrapper> | ||||
| {isNotParentAgent && ( | |||||
| {isHeadAgent && ( | |||||
| <> | <> | ||||
| <CommonHandle | <CommonHandle | ||||
| type="target" | type="target" |
| AgentTop = 'agentTop', | AgentTop = 'agentTop', | ||||
| AgentBottom = 'agentBottom', | AgentBottom = 'agentBottom', | ||||
| } | } | ||||
| export enum VariableType { | |||||
| String = 'string', | |||||
| Array = 'array', | |||||
| } |
| import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | import { LlmSettingSchema } from '@/components/llm-setting-items/next'; | ||||
| import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | import { CodeTemplateStrMap, ProgrammingLanguage } from '@/constants/agent'; | ||||
| import IterationStartForm from '@/pages/flow/form/iteration-start-from'; | |||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { Operator } from '../constant'; | import { Operator } from '../constant'; | ||||
| schema: z.object({}), | schema: z.object({}), | ||||
| }, | }, | ||||
| [Operator.IterationStart]: { | [Operator.IterationStart]: { | ||||
| component: () => <></>, | |||||
| component: IterationStartForm, | |||||
| defaultValues: {}, | defaultValues: {}, | ||||
| schema: z.object({}), | schema: z.object({}), | ||||
| }, | }, |
| import { useSetModalState } from '@/hooks/common-hooks'; | import { useSetModalState } from '@/hooks/common-hooks'; | ||||
| import { useSetSelectedRecord } from '@/hooks/logic-hooks'; | import { useSetSelectedRecord } from '@/hooks/logic-hooks'; | ||||
| import { useCallback, useMemo, useState } from 'react'; | import { useCallback, useMemo, useState } from 'react'; | ||||
| import { UseFormReturn } from 'react-hook-form'; | |||||
| import { BeginQuery, INextOperatorForm } from '../../interface'; | import { BeginQuery, INextOperatorForm } from '../../interface'; | ||||
| export const useEditQueryRecord = ({ form, node }: INextOperatorForm) => { | |||||
| export const useEditQueryRecord = ({ | |||||
| form, | |||||
| }: INextOperatorForm & { form: UseFormReturn }) => { | |||||
| const { setRecord, currentRecord } = useSetSelectedRecord<BeginQuery>(); | const { setRecord, currentRecord } = useSetSelectedRecord<BeginQuery>(); | ||||
| const { visible, hideModal, showModal } = useSetModalState(); | const { visible, hideModal, showModal } = useSetModalState(); | ||||
| const [index, setIndex] = useState(-1); | const [index, setIndex] = useState(-1); | ||||
| const handleEditRecord = useCallback( | const handleEditRecord = useCallback( | ||||
| (record: BeginQuery) => { | (record: BeginQuery) => { | ||||
| const inputs: BeginQuery[] = form?.getValues('inputs') || []; | const inputs: BeginQuery[] = form?.getValues('inputs') || []; | ||||
| console.log('🚀 ~ useEditQueryRecord ~ inputs:', inputs); | |||||
| const nextQuery: BeginQuery[] = | const nextQuery: BeginQuery[] = | ||||
| index > -1 ? inputs.toSpliced(index, 1, record) : [...inputs, record]; | index > -1 ? inputs.toSpliced(index, 1, record) : [...inputs, record]; |
| FormLabel, | FormLabel, | ||||
| FormMessage, | FormMessage, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { useMemo } from 'react'; | |||||
| import { useFormContext } from 'react-hook-form'; | import { useFormContext } from 'react-hook-form'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { VariableType } from '../../constant'; | |||||
| import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query'; | import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query'; | ||||
| type QueryVariableProps = { name?: string }; | |||||
| type QueryVariableProps = { name?: string; type?: VariableType }; | |||||
| export function QueryVariable({ name = 'query' }: QueryVariableProps) { | |||||
| export function QueryVariable({ | |||||
| name = 'query', | |||||
| type = VariableType.String, | |||||
| }: QueryVariableProps) { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const form = useFormContext(); | const form = useFormContext(); | ||||
| const nextOptions = useBuildQueryVariableOptions(); | const nextOptions = useBuildQueryVariableOptions(); | ||||
| const finalOptions = useMemo(() => { | |||||
| return nextOptions.map((x) => { | |||||
| return { ...x, options: x.options.filter((y) => y.type === type) }; | |||||
| }); | |||||
| }, [nextOptions, type]); | |||||
| return ( | return ( | ||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| <FormLabel tooltip={t('chat.modelTip')}>{t('flow.query')}</FormLabel> | <FormLabel tooltip={t('chat.modelTip')}>{t('flow.query')}</FormLabel> | ||||
| <FormControl> | <FormControl> | ||||
| <SelectWithSearch | <SelectWithSearch | ||||
| options={nextOptions} | |||||
| options={finalOptions} | |||||
| {...field} | {...field} | ||||
| ></SelectWithSearch> | ></SelectWithSearch> | ||||
| </FormControl> | </FormControl> |
| import { useMemo } from 'react'; | import { useMemo } from 'react'; | ||||
| import { useForm } from 'react-hook-form'; | import { useForm } from 'react-hook-form'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { initialRetrievalValues } from '../../constant'; | |||||
| import { initialRetrievalValues, VariableType } from '../../constant'; | |||||
| import { useWatchFormChange } from '../../hooks/use-watch-form-change'; | import { useWatchFormChange } from '../../hooks/use-watch-form-change'; | ||||
| import { INextOperatorForm } from '../../interface'; | import { INextOperatorForm } from '../../interface'; | ||||
| import { Output } from '../components/output'; | import { Output } from '../components/output'; | ||||
| }} | }} | ||||
| > | > | ||||
| <FormContainer> | <FormContainer> | ||||
| <QueryVariable name="items_ref"></QueryVariable> | |||||
| <QueryVariable | |||||
| name="items_ref" | |||||
| type={VariableType.Array} | |||||
| ></QueryVariable> | |||||
| </FormContainer> | </FormContainer> | ||||
| <Output list={outputList}></Output> | <Output list={outputList}></Output> | ||||
| </form> | </form> |
| return { addToolNode }; | return { addToolNode }; | ||||
| } | } | ||||
| function isBottomSubAgent(type: string, position: Position) { | |||||
| return ( | |||||
| (type === Operator.Agent && position === Position.Bottom) || | |||||
| type === Operator.Tool | |||||
| ); | |||||
| } | |||||
| export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) { | export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) { | ||||
| const { edges, nodes, addEdge, addNode, getNode } = useGraphStore( | |||||
| const { edges, nodes, addEdge, addNode, getNode, updateNode } = useGraphStore( | |||||
| (state) => state, | (state) => state, | ||||
| ); | ); | ||||
| const getNodeName = useGetNodeName(); | const getNodeName = useGetNodeName(); | ||||
| if (node && node.parentId) { | if (node && node.parentId) { | ||||
| newNode.parentId = node.parentId; | newNode.parentId = node.parentId; | ||||
| newNode.extent = 'parent'; | newNode.extent = 'parent'; | ||||
| const parentNode = getNode(node.parentId); | |||||
| if (parentNode && !isBottomSubAgent(type, params.position)) { | |||||
| const MoveRightDistance = 310; | |||||
| updateNode({ | |||||
| ...parentNode, | |||||
| width: (parentNode.width || 0) + MoveRightDistance, | |||||
| position: { | |||||
| x: parentNode.position.x + MoveRightDistance / 2, | |||||
| y: parentNode.position.y, | |||||
| }, | |||||
| }); | |||||
| } | |||||
| } | } | ||||
| if (type === Operator.Iteration) { | if (type === Operator.Iteration) { | ||||
| initializeOperatorParams, | initializeOperatorParams, | ||||
| nodes, | nodes, | ||||
| reactFlowInstance, | reactFlowInstance, | ||||
| updateNode, | |||||
| ], | ], | ||||
| ); | ); | ||||
| import { isEmpty } from 'lodash'; | import { isEmpty } from 'lodash'; | ||||
| import get from 'lodash/get'; | import get from 'lodash/get'; | ||||
| import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; | import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; | ||||
| import { BeginId, Operator } from '../constant'; | |||||
| import { BeginId, BeginQueryType, Operator, VariableType } from '../constant'; | |||||
| import { AgentFormContext } from '../context'; | import { AgentFormContext } from '../context'; | ||||
| import { buildBeginInputListFromObject } from '../form/begin-form/utils'; | import { buildBeginInputListFromObject } from '../form/begin-form/utils'; | ||||
| import { BeginQuery } from '../interface'; | import { BeginQuery } from '../interface'; | ||||
| return Object.keys(outputs).map((x) => ({ | return Object.keys(outputs).map((x) => ({ | ||||
| label: x, | label: x, | ||||
| value: `${nodeId}@${x}`, | value: `${nodeId}@${x}`, | ||||
| type: outputs[x]?.type, | |||||
| })); | })); | ||||
| } | } | ||||
| Operator.Note, | Operator.Note, | ||||
| ]; | ]; | ||||
| const StringList = [ | |||||
| BeginQueryType.Line, | |||||
| BeginQueryType.Paragraph, | |||||
| BeginQueryType.Options, | |||||
| ]; | |||||
| function transferToVariableType(type: string) { | |||||
| if (StringList.some((x) => x === type)) { | |||||
| return VariableType.String; | |||||
| } | |||||
| return type; | |||||
| } | |||||
| export function useBuildBeginVariableOptions() { | export function useBuildBeginVariableOptions() { | ||||
| const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); | const getBeginNodeDataQuery = useGetBeginNodeDataQuery(); | ||||
| options: query.map((x) => ({ | options: query.map((x) => ({ | ||||
| label: x.name, | label: x.name, | ||||
| value: `begin@${x.key}`, | value: `begin@${x.key}`, | ||||
| type: transferToVariableType(x.type), | |||||
| })), | })), | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| const options = useBuildVariableOptions(node?.id); | const options = useBuildVariableOptions(node?.id); | ||||
| const nextOptions = useMemo(() => { | const nextOptions = useMemo(() => { | ||||
| const globalOptions = Object.keys(data?.dsl?.globals ?? {}).map((x) => ({ | |||||
| label: x, | |||||
| value: x, | |||||
| const globals = data?.dsl?.globals ?? {}; | |||||
| const globalOptions = Object.entries(globals).map(([key, value]) => ({ | |||||
| label: key, | |||||
| value: key, | |||||
| type: Array.isArray(value) ? VariableType.Array : typeof value, | |||||
| })); | })); | ||||
| return [ | return [ | ||||
| { ...options[0], options: [...options[0]?.options, ...globalOptions] }, | { ...options[0], options: [...options[0]?.options, ...globalOptions] }, |
| }; | }; | ||||
| }; | }; | ||||
| const ExcludedNodes = [Operator.IterationStart, Operator.Note]; | |||||
| const ExcludedNodes = [Operator.Note]; | |||||
| export function useShowDrawer({ | export function useShowDrawer({ | ||||
| drawerVisible, | drawerVisible, |
| onSelectionChange: OnSelectionChangeFunc; | onSelectionChange: OnSelectionChangeFunc; | ||||
| addNode: (nodes: RAGFlowNodeType) => void; | addNode: (nodes: RAGFlowNodeType) => void; | ||||
| getNode: (id?: string | null) => RAGFlowNodeType | undefined; | getNode: (id?: string | null) => RAGFlowNodeType | undefined; | ||||
| updateNode: (node: RAGFlowNodeType) => void; | |||||
| addEdge: (connection: Connection) => void; | addEdge: (connection: Connection) => void; | ||||
| getEdge: (id: string) => Edge | undefined; | getEdge: (id: string) => Edge | undefined; | ||||
| updateFormDataOnConnect: (connection: Connection) => void; | updateFormDataOnConnect: (connection: Connection) => void; | ||||
| addNode: (node: RAGFlowNodeType) => { | addNode: (node: RAGFlowNodeType) => { | ||||
| set({ nodes: get().nodes.concat(node) }); | set({ nodes: get().nodes.concat(node) }); | ||||
| }, | }, | ||||
| updateNode: (node) => { | |||||
| const { nodes } = get(); | |||||
| const nextNodes = nodes.map((x) => { | |||||
| if (x.id === node.id) { | |||||
| return node; | |||||
| } | |||||
| return x; | |||||
| }); | |||||
| set({ nodes: nextNodes }); | |||||
| }, | |||||
| getNode: (id?: string | null) => { | getNode: (id?: string | null) => { | ||||
| return get().nodes.find((x) => x.id === id); | return get().nodes.find((x) => x.id === id); | ||||
| }, | }, |
| export const initialIterationValues = { | export const initialIterationValues = { | ||||
| delimiter: ',', | delimiter: ',', | ||||
| }; | }; | ||||
| export const initialIterationStartValues = {}; | |||||
| export const initialIterationStartValues = { | |||||
| outputs: { | |||||
| item: { | |||||
| type: 'unkown', | |||||
| }, | |||||
| index: { | |||||
| type: 'integer', | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| export const initialCodeValues = { | export const initialCodeValues = { | ||||
| lang: 'python', | lang: 'python', |
| import { Output, OutputType } from '@/pages/agent/form/components/output'; | |||||
| import { initialIterationStartValues } from '../../constant'; | |||||
| const outputs = initialIterationStartValues.outputs; | |||||
| const outputList = Object.entries(outputs).reduce<OutputType[]>( | |||||
| (pre, [key, value]) => { | |||||
| pre.push({ title: key, type: value.type }); | |||||
| return pre; | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| const IterationStartForm = () => { | |||||
| return ( | |||||
| <section className="space-y-6 p-4"> | |||||
| <Output list={outputList}></Output> | |||||
| </section> | |||||
| ); | |||||
| }; | |||||
| export default IterationStartForm; |