### What problem does this PR solve? Feat: Allows users to delete a condition of a conditional operator #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.0
| aria-expanded={open} | aria-expanded={open} | ||||
| ref={ref} | ref={ref} | ||||
| className={cn( | className={cn( | ||||
| 'bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px]', | |||||
| 'bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px]', | |||||
| triggerClassName, | triggerClassName, | ||||
| )} | )} | ||||
| > | > |
| {selectedValues.length > 0 ? ( | {selectedValues.length > 0 ? ( | ||||
| <div className="flex justify-between items-center w-full"> | <div className="flex justify-between items-center w-full"> | ||||
| <div className="flex flex-wrap items-center"> | <div className="flex flex-wrap items-center"> | ||||
| {selectedValues.slice(0, maxCount).map((value) => { | |||||
| {selectedValues?.slice(0, maxCount)?.map((value) => { | |||||
| const option = options.find((o) => o.value === value); | const option = options.find((o) => o.value === value); | ||||
| const IconComponent = option?.icon; | const IconComponent = option?.icon; | ||||
| return ( | return ( |
| {positions.map((position, idx) => { | {positions.map((position, idx) => { | ||||
| return ( | return ( | ||||
| <div key={idx}> | <div key={idx}> | ||||
| <section className="flex flex-col"> | |||||
| <div className="flex justify-between"> | |||||
| <span className="text-text-sub-title text-xs translate-y-2"> | |||||
| {idx < positions.length - 1 && | |||||
| position.condition?.logical_operator?.toUpperCase()} | |||||
| </span> | |||||
| <section className="flex flex-col text-xs"> | |||||
| <div className="text-right"> | |||||
| <span>{getConditionKey(idx, positions.length)}</span> | <span>{getConditionKey(idx, positions.length)}</span> | ||||
| <div className="text-text-sub-title"> | |||||
| {idx < positions.length - 1 && position.text} | |||||
| </div> | |||||
| </div> | </div> | ||||
| <span className="text-background-checked"> | |||||
| {idx < positions.length - 1 && | |||||
| position.condition?.logical_operator?.toUpperCase()} | |||||
| </span> | |||||
| {position.condition && ( | {position.condition && ( | ||||
| <ConditionBlock | <ConditionBlock | ||||
| condition={position.condition} | condition={position.condition} |
| return get(data, 'form.conditions', []); | return get(data, 'form.conditions', []); | ||||
| }, [data]); | }, [data]); | ||||
| useEffect(() => { | |||||
| console.info('xxx0000'); | |||||
| }, [conditions]); | |||||
| const positions = useMemo(() => { | const positions = useMemo(() => { | ||||
| const list: Array<{ | const list: Array<{ | ||||
| text: string; | text: string; | ||||
| }> = []; | }> = []; | ||||
| [...conditions, ''].forEach((x, idx) => { | [...conditions, ''].forEach((x, idx) => { | ||||
| let top = idx === 0 ? 53 : list[idx - 1].top + 10 + 14; // case number (Case 1) height + flex gap | |||||
| let top = idx === 0 ? 53 : list[idx - 1].top + 10 + 14 + 16 + 16; // case number (Case 1) height + flex gap | |||||
| if (idx >= 1) { | if (idx >= 1) { | ||||
| const previousItems = conditions[idx - 1]?.items ?? []; | const previousItems = conditions[idx - 1]?.items ?? []; | ||||
| if (previousItems.length > 0) { | if (previousItems.length > 0) { |
| import RelevantForm from '../form/relevant-form'; | import RelevantForm from '../form/relevant-form'; | ||||
| import RetrievalForm from '../form/retrieval-form/next'; | import RetrievalForm from '../form/retrieval-form/next'; | ||||
| import RewriteQuestionForm from '../form/rewrite-question-form'; | import RewriteQuestionForm from '../form/rewrite-question-form'; | ||||
| import { StringTransformForm } from '../form/string-transform-form'; | |||||
| import StringTransformForm from '../form/string-transform-form'; | |||||
| import SwitchForm from '../form/switch-form'; | import SwitchForm from '../form/switch-form'; | ||||
| import TavilyExtractForm from '../form/tavily-extract-form'; | import TavilyExtractForm from '../form/tavily-extract-form'; | ||||
| import TavilyForm from '../form/tavily-form'; | import TavilyForm from '../form/tavily-form'; |
| import { RAGFlowSelect } from '@/components/ui/select'; | import { RAGFlowSelect } from '@/components/ui/select'; | ||||
| import { buildOptions } from '@/utils/form'; | import { buildOptions } from '@/utils/form'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { useCallback, useMemo } from 'react'; | |||||
| import { memo, useCallback, useMemo } from 'react'; | |||||
| import { useForm, useWatch } from 'react-hook-form'; | import { useForm, useWatch } from 'react-hook-form'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { | import { | ||||
| ([key, val]) => ({ label: key, value: val }), | ([key, val]) => ({ label: key, value: val }), | ||||
| ); | ); | ||||
| export const StringTransformForm = ({ node }: INextOperatorForm) => { | |||||
| function StringTransformForm({ node }: INextOperatorForm) { | |||||
| const values = useValues(node); | const values = useValues(node); | ||||
| const FormSchema = z.object({ | const FormSchema = z.object({ | ||||
| method: z.string(), | method: z.string(), | ||||
| split_ref: z.string().optional(), | split_ref: z.string().optional(), | ||||
| script: z.string().optional(), | script: z.string().optional(), | ||||
| delimiters: z.array(z.string()), | |||||
| delimiters: z.array(z.string()).or(z.string()), | |||||
| outputs: z.object({ result: z.object({ type: z.string() }) }).optional(), | outputs: z.object({ result: z.object({ type: z.string() }) }).optional(), | ||||
| }); | }); | ||||
| const handleMethodChange = useCallback( | const handleMethodChange = useCallback( | ||||
| (value: StringTransformMethod) => { | (value: StringTransformMethod) => { | ||||
| const isMerge = value === StringTransformMethod.Merge; | |||||
| const outputs = { | const outputs = { | ||||
| ...initialStringTransformValues.outputs, | ...initialStringTransformValues.outputs, | ||||
| result: { | result: { | ||||
| type: | |||||
| value === StringTransformMethod.Merge ? 'string' : 'Array<string>', | |||||
| type: isMerge ? 'string' : 'Array<string>', | |||||
| }, | }, | ||||
| }; | }; | ||||
| form.setValue('outputs', outputs); | form.setValue('outputs', outputs); | ||||
| form.setValue( | |||||
| 'delimiters', | |||||
| isMerge ? StringTransformDelimiter.Comma : [], | |||||
| ); | |||||
| }, | }, | ||||
| [form], | [form], | ||||
| ); | ); | ||||
| <MultiSelect | <MultiSelect | ||||
| options={DelimiterOptions} | options={DelimiterOptions} | ||||
| onValueChange={field.onChange} | onValueChange={field.onChange} | ||||
| defaultValue={field.value as string[]} | |||||
| variant="inverted" | variant="inverted" | ||||
| {...field} | |||||
| // {...field} | |||||
| /> | /> | ||||
| ) : ( | ) : ( | ||||
| <RAGFlowSelect | <RAGFlowSelect | ||||
| </div> | </div> | ||||
| </Form> | </Form> | ||||
| ); | ); | ||||
| }; | |||||
| } | |||||
| export default memo(StringTransformForm); |
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { toLower } from 'lodash'; | import { toLower } from 'lodash'; | ||||
| import { X } from 'lucide-react'; | import { X } from 'lucide-react'; | ||||
| import { useCallback, useMemo } from 'react'; | |||||
| import { memo, useCallback, useMemo } from 'react'; | |||||
| import { useFieldArray, useForm, useFormContext } from 'react-hook-form'; | import { useFieldArray, useForm, useFormContext } from 'react-hook-form'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| ); | ); | ||||
| return ( | return ( | ||||
| <section className="flex-1 space-y-2.5"> | |||||
| <section className="flex-1 space-y-2.5 min-w-0"> | |||||
| {fields.map((field, index) => { | {fields.map((field, index) => { | ||||
| return ( | return ( | ||||
| <div key={field.id} className="flex"> | <div key={field.id} className="flex"> | ||||
| <Card | <Card | ||||
| className={cn( | className={cn( | ||||
| 'relative bg-transparent border-input-border border flex-1 ', | |||||
| 'relative bg-transparent border-input-border border flex-1 min-w-0', | |||||
| { | { | ||||
| 'before:w-10 before:absolute before:h-[1px] before:bg-input-border before:top-1/2 before:-left-10': | 'before:w-10 before:absolute before:h-[1px] before:bg-input-border before:top-1/2 before:-left-10': | ||||
| index === 0 || index === fields.length - 1, | index === 0 || index === fields.length - 1, | ||||
| control={form.control} | control={form.control} | ||||
| name={`${name}.${index}.cpn_id`} | name={`${name}.${index}.cpn_id`} | ||||
| render={({ field }) => ( | render={({ field }) => ( | ||||
| <FormItem> | |||||
| <FormItem className="flex-1 min-w-0"> | |||||
| <FormControl> | <FormControl> | ||||
| <SelectWithSearch | <SelectWithSearch | ||||
| {...field} | {...field} | ||||
| options={finalOptions} | options={finalOptions} | ||||
| triggerClassName="w-30 text-background-checked bg-transparent border-none text-ellipsis" | |||||
| triggerClassName="text-background-checked bg-transparent border-none truncate" | |||||
| ></SelectWithSearch> | ></SelectWithSearch> | ||||
| </FormControl> | </FormControl> | ||||
| <FormMessage /> | <FormMessage /> | ||||
| ); | ); | ||||
| } | } | ||||
| const SwitchForm = ({ node }: IOperatorForm) => { | |||||
| function SwitchForm({ node }: IOperatorForm) { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const values = useValues(node); | const values = useValues(node); | ||||
| const switchOperatorOptions = useBuildSwitchOperatorOptions(); | const switchOperatorOptions = useBuildSwitchOperatorOptions(); | ||||
| {fields.map((field, index) => { | {fields.map((field, index) => { | ||||
| return ( | return ( | ||||
| <FormContainer key={field.id} className=""> | <FormContainer key={field.id} className=""> | ||||
| <div>{index === 0 ? 'IF' : 'ELSEIF'}</div> | |||||
| <div className="flex justify-between items-center"> | |||||
| <section> | |||||
| <span>{index === 0 ? 'IF' : 'ELSEIF'}</span> | |||||
| <div className="text-text-sub-title">Case {index + 1}</div> | |||||
| </section> | |||||
| {index !== 0 && ( | |||||
| <Button | |||||
| variant={'secondary'} | |||||
| className="-translate-y-1" | |||||
| onClick={() => remove(index)} | |||||
| > | |||||
| Remove <X /> | |||||
| </Button> | |||||
| )} | |||||
| </div> | |||||
| <section className="flex gap-2 !mt-2 relative"> | <section className="flex gap-2 !mt-2 relative"> | ||||
| <section className="flex flex-col"> | |||||
| <section className="flex flex-col w-[72px]"> | |||||
| <div className="relative w-1 flex-1 before:absolute before:w-[1px] before:bg-input-border before:top-20 before:bottom-0 before:left-10"></div> | <div className="relative w-1 flex-1 before:absolute before:w-[1px] before:bg-input-border before:top-20 before:bottom-0 before:left-10"></div> | ||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| <RAGFlowSelect | <RAGFlowSelect | ||||
| {...field} | {...field} | ||||
| options={switchLogicOperatorOptions} | options={switchLogicOperatorOptions} | ||||
| triggerClassName="w-18" | |||||
| /> | /> | ||||
| </FormControl> | </FormControl> | ||||
| <FormMessage /> | <FormMessage /> | ||||
| </form> | </form> | ||||
| </Form> | </Form> | ||||
| ); | ); | ||||
| }; | |||||
| } | |||||
| export default SwitchForm; | |||||
| export default memo(SwitchForm); |
| import { MoreButton } from '@/components/more-button'; | import { MoreButton } from '@/components/more-button'; | ||||
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||||
| import { RAGFlowAvatar } from '@/components/ragflow-avatar'; | |||||
| import { Card, CardContent } from '@/components/ui/card'; | import { Card, CardContent } from '@/components/ui/card'; | ||||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | ||||
| import { IFlow } from '@/interfaces/database/flow'; | import { IFlow } from '@/interfaces/database/flow'; | ||||
| <CardContent className="p-2.5 pt-2 group"> | <CardContent className="p-2.5 pt-2 group"> | ||||
| <section className="flex justify-between mb-2"> | <section className="flex justify-between mb-2"> | ||||
| <div className="flex gap-2 items-center"> | <div className="flex gap-2 items-center"> | ||||
| <Avatar className="size-6 rounded-lg"> | |||||
| <AvatarImage src={data.avatar} /> | |||||
| <AvatarFallback className="rounded-lg ">CN</AvatarFallback> | |||||
| </Avatar> | |||||
| <RAGFlowAvatar | |||||
| className="size-6 rounded-lg" | |||||
| avatar={data.avatar} | |||||
| name={data.title || 'CN'} | |||||
| ></RAGFlowAvatar> | |||||
| </div> | </div> | ||||
| <AgentDropdown | <AgentDropdown | ||||
| showAgentRenameModal={showAgentRenameModal} | showAgentRenameModal={showAgentRenameModal} |