Fixed the issue where variables were not displayed in the switch operator #3221 (#8601)

### What problem does this PR solve?

Feat: Fixed the issue where variables were not displayed in the switch
operator #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-07-01 15:52:14 +08:00
committed by GitHub
parent 1c77b4ed9b
commit 6b04b07eb4
7 changed files with 154 additions and 122 deletions

View File

@ -24,6 +24,7 @@ import {
PopoverContent, PopoverContent,
PopoverTrigger, PopoverTrigger,
} from '@/components/ui/popover'; } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
import { RAGFlowSelectOptionType } from '../ui/select'; import { RAGFlowSelectOptionType } from '../ui/select';
const countries = [ const countries = [
@ -77,96 +78,105 @@ export type SelectWithSearchFlagProps = {
options?: SelectWithSearchFlagOptionType[]; options?: SelectWithSearchFlagOptionType[];
value?: string; value?: string;
onChange?(value: string): void; onChange?(value: string): void;
triggerClassName?: string;
}; };
export const SelectWithSearch = forwardRef< export const SelectWithSearch = forwardRef<
React.ElementRef<typeof Button>, React.ElementRef<typeof Button>,
SelectWithSearchFlagProps SelectWithSearchFlagProps
>(({ value: val = '', onChange, options = countries }, ref) => { >(
const id = useId(); (
const [open, setOpen] = useState<boolean>(false); { value: val = '', onChange, options = countries, triggerClassName },
const [value, setValue] = useState<string>(''); ref,
) => {
const id = useId();
const [open, setOpen] = useState<boolean>(false);
const [value, setValue] = useState<string>('');
const handleSelect = useCallback( const handleSelect = useCallback(
(val: string) => { (val: string) => {
setValue(val);
setOpen(false);
onChange?.(val);
},
[onChange],
);
useEffect(() => {
setValue(val); setValue(val);
setOpen(false); }, [val]);
onChange?.(val);
},
[onChange],
);
useEffect(() => { return (
setValue(val); <Popover open={open} onOpenChange={setOpen}>
}, [val]); <PopoverTrigger asChild>
<Button
return ( id={id}
<Popover open={open} onOpenChange={setOpen}> variant="outline"
<PopoverTrigger asChild> role="combobox"
<Button aria-expanded={open}
id={id} ref={ref}
variant="outline" className={cn(
role="combobox" 'bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px]',
aria-expanded={open} triggerClassName,
ref={ref} )}
className="bg-background hover:bg-background border-input w-full justify-between px-3 font-normal outline-offset-0 outline-none focus-visible:outline-[3px]" >
> {value ? (
{value ? ( <span className="flex min-w-0 options-center gap-2">
<span className="flex min-w-0 options-center gap-2"> <span className="text-lg leading-none truncate">
<span className="text-lg leading-none truncate"> {
{ options
options .map((group) =>
.map((group) => group.options.find((item) => item.value === value),
group.options.find((item) => item.value === value), )
) .filter(Boolean)[0]?.label
.filter(Boolean)[0]?.label }
} </span>
</span> </span>
</span> ) : (
) : ( <span className="text-muted-foreground">Select value</span>
<span className="text-muted-foreground">Select value</span> )}
)} <ChevronDownIcon
<ChevronDownIcon size={16}
size={16} className="text-muted-foreground/80 shrink-0"
className="text-muted-foreground/80 shrink-0" aria-hidden="true"
aria-hidden="true" />
/> </Button>
</Button> </PopoverTrigger>
</PopoverTrigger> <PopoverContent
<PopoverContent className="border-input w-full min-w-[var(--radix-popper-anchor-width)] p-0"
className="border-input w-full min-w-[var(--radix-popper-anchor-width)] p-0" align="start"
align="start" >
> <Command>
<Command> <CommandInput placeholder="Search ..." />
<CommandInput placeholder="Search ..." /> <CommandList>
<CommandList> <CommandEmpty>No data found.</CommandEmpty>
<CommandEmpty>No data found.</CommandEmpty> {options.map((group) => (
{options.map((group) => ( <Fragment key={group.label}>
<Fragment key={group.label}> <CommandGroup heading={group.label}>
<CommandGroup heading={group.label}> {group.options.map((option) => (
{group.options.map((option) => ( <CommandItem
<CommandItem key={option.value}
key={option.value} value={option.value}
value={option.value} onSelect={handleSelect}
onSelect={handleSelect} >
> <span className="text-lg leading-none">
<span className="text-lg leading-none"> {option.label}
{option.label} </span>
</span>
{value === option.value && ( {value === option.value && (
<CheckIcon size={16} className="ml-auto" /> <CheckIcon size={16} className="ml-auto" />
)} )}
</CommandItem> </CommandItem>
))} ))}
</CommandGroup> </CommandGroup>
</Fragment> </Fragment>
))} ))}
</CommandList> </CommandList>
</Command> </Command>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
); );
}); },
);
SelectWithSearch.displayName = 'SelectWithSearch'; SelectWithSearch.displayName = 'SelectWithSearch';

View File

@ -4,7 +4,7 @@ import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow';
import { NodeProps, Position } from '@xyflow/react'; import { NodeProps, Position } from '@xyflow/react';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { NodeHandleId, SwitchOperatorOptions } from '../../constant'; import { NodeHandleId, SwitchOperatorOptions } from '../../constant';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query'; import { useGetVariableLabelByValue } from '../../hooks/use-get-begin-query';
import { CommonHandle } from './handle'; import { CommonHandle } from './handle';
import { RightHandleStyle } from './handle-icon'; import { RightHandleStyle } from './handle-icon';
import NodeHeader from './node-header'; import NodeHeader from './node-header';
@ -25,12 +25,9 @@ const getConditionKey = (idx: number, length: number) => {
const ConditionBlock = ({ const ConditionBlock = ({
condition, condition,
nodeId, nodeId,
}: { }: { condition: ISwitchCondition } & { nodeId: string }) => {
condition: ISwitchCondition;
nodeId: string;
}) => {
const items = condition?.items ?? []; const items = condition?.items ?? [];
const getLabel = useGetComponentLabelByValue(nodeId); const getLabel = useGetVariableLabelByValue(nodeId);
const renderOperatorIcon = useCallback((operator?: string) => { const renderOperatorIcon = useCallback((operator?: string) => {
const name = SwitchOperatorOptions.find((x) => x.value === operator)?.icon; const name = SwitchOperatorOptions.find((x) => x.value === operator)?.icon;
@ -83,8 +80,8 @@ function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
</div> </div>
{position.condition && ( {position.condition && (
<ConditionBlock <ConditionBlock
nodeId={id}
condition={position.condition} condition={position.condition}
nodeId={id}
></ConditionBlock> ></ConditionBlock>
)} )}
</section> </section>

View File

@ -81,7 +81,7 @@ export enum Operator {
Email = 'Email', Email = 'Email',
Iteration = 'Iteration', Iteration = 'Iteration',
IterationStart = 'IterationItem', IterationStart = 'IterationItem',
Code = 'Code', Code = 'CodeExec',
WaitingDialogue = 'WaitingDialogue', WaitingDialogue = 'WaitingDialogue',
Agent = 'Agent', Agent = 'Agent',
Tool = 'Tool', Tool = 'Tool',

View File

@ -1,5 +1,6 @@
import { FormContainer } from '@/components/form-container'; import { FormContainer } from '@/components/form-container';
import { IconFont } from '@/components/icon-font'; import { IconFont } from '@/components/icon-font';
import { SelectWithSearch } from '@/components/originui/select-with-search';
import { BlockButton, Button } from '@/components/ui/button'; import { BlockButton, Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { import {
@ -12,21 +13,20 @@ import {
import { RAGFlowSelect } from '@/components/ui/select'; import { RAGFlowSelect } from '@/components/ui/select';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { Textarea } from '@/components/ui/textarea'; import { Textarea } from '@/components/ui/textarea';
import { ISwitchForm } from '@/interfaces/database/agent';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { toLower } from 'lodash';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { useCallback, useMemo } from 'react'; import { 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';
import { import {
Operator,
SwitchLogicOperatorOptions, SwitchLogicOperatorOptions,
SwitchOperatorOptions, SwitchOperatorOptions,
VariableType,
} from '../../constant'; } from '../../constant';
import { useBuildFormSelectOptions } from '../../form-hooks'; import { useBuildQueryVariableOptions } from '../../hooks/use-get-begin-query';
import { useBuildComponentIdAndBeginOptions } from '../../hooks/use-get-begin-query';
import { IOperatorForm } from '../../interface'; import { IOperatorForm } from '../../interface';
import { useValues } from './use-values'; import { useValues } from './use-values';
import { useWatchFormChange } from './use-watch-change'; import { useWatchFormChange } from './use-watch-change';
@ -71,18 +71,24 @@ function useBuildSwitchOperatorOptions() {
function ConditionCards({ function ConditionCards({
name: parentName, name: parentName,
node,
parentIndex, parentIndex,
removeParent, removeParent,
parentLength, parentLength,
}: ConditionCardsProps) { }: ConditionCardsProps) {
const form = useFormContext(); const form = useFormContext();
const { t } = useTranslation();
const componentIdOptions = useBuildComponentIdAndBeginOptions( const nextOptions = useBuildQueryVariableOptions();
node?.id,
node?.parentId, const finalOptions = useMemo(() => {
); return nextOptions.map((x) => {
return {
...x,
options: x.options.filter(
(y) => !toLower(y.type).includes(VariableType.Array),
),
};
});
}, [nextOptions]);
const switchOperatorOptions = useBuildSwitchOperatorOptions(); const switchOperatorOptions = useBuildSwitchOperatorOptions();
@ -124,12 +130,11 @@ function ConditionCards({
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormControl> <FormControl>
<RAGFlowSelect <SelectWithSearch
{...field} {...field}
options={componentIdOptions} options={finalOptions}
placeholder={t('common.pleaseSelect')} triggerClassName="w-30 text-background-checked bg-transparent border-none text-ellipsis"
triggerClassName="w-30 text-background-checked bg-transparent border-none" ></SelectWithSearch>
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -224,17 +229,6 @@ const SwitchForm = ({ node }: IOperatorForm) => {
control: form.control, control: form.control,
}); });
const buildCategorizeToOptions = useBuildFormSelectOptions(
Operator.Switch,
node?.id,
);
const getSelectedConditionTos = () => {
const conditions: ISwitchForm['conditions'] = form?.getValues('conditions');
return conditions?.filter((x) => !!x).map((x) => x?.to) ?? [];
};
const switchLogicOperatorOptions = useMemo(() => { const switchLogicOperatorOptions = useMemo(() => {
return SwitchLogicOperatorOptions.map((x) => ({ return SwitchLogicOperatorOptions.map((x) => ({
value: x, value: x,

View File

@ -9,8 +9,9 @@ export function useWatchFormChange(id?: string, form?: UseFormReturn) {
useEffect(() => { useEffect(() => {
// Manually triggered form updates are synchronized to the canvas // Manually triggered form updates are synchronized to the canvas
if (id && form?.formState.isDirty) { console.log('🚀 ~ useWatchFormChange ~ values:', form?.formState.isDirty);
values = form?.getValues(); if (id) {
values = form?.getValues() || {};
let nextValues: any = { let nextValues: any = {
...values, ...values,
conditions: conditions:

View File

@ -2,7 +2,6 @@ import { Operator } from '../../constant';
import AkShareForm from '../akshare-form'; import AkShareForm from '../akshare-form';
import ArXivForm from '../arxiv-form'; import ArXivForm from '../arxiv-form';
import BingForm from '../bing-form'; import BingForm from '../bing-form';
import CodeForm from '../code-form';
import CrawlerForm from '../crawler-form'; import CrawlerForm from '../crawler-form';
import DeepLForm from '../deepl-form'; import DeepLForm from '../deepl-form';
import DuckDuckGoForm from '../duckduckgo-form'; import DuckDuckGoForm from '../duckduckgo-form';
@ -19,7 +18,7 @@ import TavilyForm from './tavily-form';
export const ToolFormConfigMap = { export const ToolFormConfigMap = {
[Operator.Retrieval]: RetrievalForm, [Operator.Retrieval]: RetrievalForm,
[Operator.Code]: CodeForm, [Operator.Code]: () => <div></div>,
[Operator.DuckDuckGo]: DuckDuckGoForm, [Operator.DuckDuckGo]: DuckDuckGoForm,
[Operator.Wikipedia]: WikipediaForm, [Operator.Wikipedia]: WikipediaForm,
[Operator.PubMed]: PubMedForm, [Operator.PubMed]: PubMedForm,

View File

@ -24,6 +24,18 @@ export const useGetBeginNodeDataQuery = () => {
return getBeginNodeDataQuery; return getBeginNodeDataQuery;
}; };
export const useGetBeginNodeDataInputs = () => {
const getNode = useGraphStore((state) => state.getNode);
const inputs = get(getNode(BeginId), 'data.form.inputs', {});
const beginNodeDataInputs = useMemo(() => {
return buildBeginInputListFromObject(inputs);
}, [inputs]);
return beginNodeDataInputs;
};
export const useGetBeginNodeDataQueryIsSafe = () => { export const useGetBeginNodeDataQueryIsSafe = () => {
const [isBeginNodeDataQuerySafe, setIsBeginNodeDataQuerySafe] = const [isBeginNodeDataQuerySafe, setIsBeginNodeDataQuerySafe] =
useState(false); useState(false);
@ -152,9 +164,9 @@ export const useBuildVariableOptions = (nodeId?: string, parentId?: string) => {
return options; return options;
}; };
export function useBuildQueryVariableOptions() { export function useBuildQueryVariableOptions(n?: RAGFlowNodeType) {
const { data } = useFetchAgent(); const { data } = useFetchAgent();
const node = useContext(AgentFormContext); const node = useContext(AgentFormContext) || n;
const options = useBuildVariableOptions(node?.id, node?.parentId); const options = useBuildVariableOptions(node?.id, node?.parentId);
const nextOptions = useMemo(() => { const nextOptions = useMemo(() => {
@ -170,7 +182,7 @@ export function useBuildQueryVariableOptions() {
{ ...options[0], options: [...options[0]?.options, ...globalOptions] }, { ...options[0], options: [...options[0]?.options, ...globalOptions] },
...options.slice(1), ...options.slice(1),
]; ];
}, [data.dsl.globals, options]); }, [data.dsl?.globals, options]);
return nextOptions; return nextOptions;
} }
@ -241,3 +253,22 @@ export const useGetComponentLabelByValue = (nodeId: string) => {
); );
return getLabel; return getLabel;
}; };
export function useGetVariableLabelByValue(nodeId: string) {
const { getNode } = useGraphStore((state) => state);
const nextOptions = useBuildQueryVariableOptions(getNode(nodeId));
const flattenOptions = useMemo(() => {
return nextOptions.reduce<DefaultOptionType[]>((pre, cur) => {
return [...pre, ...cur.options];
}, []);
}, [nextOptions]);
const getLabel = useCallback(
(val?: string) => {
return flattenOptions.find((x) => x.value === val)?.label;
},
[flattenOptions],
);
return getLabel;
}