Feat: If a query variable in a data manipulation operator is deleted, a warning message should be displayed to the user. #10427

This commit is contained in:
bill 2025-11-19 18:19:31 +08:00
parent db5ec89dc5
commit 652e650663
8 changed files with 37 additions and 9 deletions

View file

@ -48,7 +48,7 @@ const JsonSchemaVisualizer: FC<JsonSchemaVisualizerProps> = ({
try { try {
const parsedJson = JSON.parse(value); const parsedJson = JSON.parse(value);
if (onChange) { if (onChange && typeof parsedJson !== 'number') {
onChange(parsedJson); onChange(parsedJson);
} }
} catch (_error) { } catch (_error) {

View file

@ -1646,6 +1646,7 @@ The variable aggregation node (originally the variable assignment node) is a cru
beginInputTip: beginInputTip:
'By defining input parameters, this content can be accessed by other components in subsequent processes.', 'By defining input parameters, this content can be accessed by other components in subsequent processes.',
query: 'Query variables', query: 'Query variables',
queryRequired: 'Query is required',
queryTip: 'Select the variable you want to use', queryTip: 'Select the variable you want to use',
agent: 'Agent', agent: 'Agent',
addAgent: 'Add Agent', addAgent: 'Add Agent',

View file

@ -1549,6 +1549,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
task: '任务', task: '任务',
beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。', beginInputTip: '通过定义输入参数,此内容可以被后续流程中的其他组件访问。',
query: '查询变量', query: '查询变量',
queryRequired: '查询变量是必填项',
queryTip: '选择您想要使用的变量', queryTip: '选择您想要使用的变量',
agent: '智能体', agent: '智能体',
addAgent: '添加智能体', addAgent: '添加智能体',

View file

@ -11,11 +11,12 @@ export function DataOperationsNode({
}: NodeProps<BaseNode<DataOperationsFormSchemaType>>) { }: NodeProps<BaseNode<DataOperationsFormSchemaType>>) {
const { data } = props; const { data } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const operations = data.form?.operations;
return ( return (
<RagNode {...props}> <RagNode {...props}>
<LabelCard> <LabelCard>
{t(`flow.operationsOptions.${camelCase(data.form?.operations)}`)} {operations && t(`flow.operationsOptions.${camelCase(operations)}`)}
</LabelCard> </LabelCard>
</RagNode> </RagNode>
); );

View file

@ -7,17 +7,24 @@ export type FormListHeaderProps = {
label: ReactNode; label: ReactNode;
tooltip?: string; tooltip?: string;
onClick?: () => void; onClick?: () => void;
disabled?: boolean;
}; };
export function DynamicFormHeader({ export function DynamicFormHeader({
label, label,
tooltip, tooltip,
onClick, onClick,
disabled = false,
}: FormListHeaderProps) { }: FormListHeaderProps) {
return ( return (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<FormLabel tooltip={tooltip}>{label}</FormLabel> <FormLabel tooltip={tooltip}>{label}</FormLabel>
<Button variant={'ghost'} type="button" onClick={onClick}> <Button
variant={'ghost'}
type="button"
onClick={onClick}
disabled={disabled}
>
<Plus /> <Plus />
</Button> </Button>
</div> </div>

View file

@ -2,6 +2,10 @@ import { Button } from '@/components/ui/button';
import { X } from 'lucide-react'; import { X } from 'lucide-react';
import { useFieldArray, useFormContext } from 'react-hook-form'; import { useFieldArray, useFormContext } from 'react-hook-form';
import { JsonSchemaDataType } from '../../constant'; import { JsonSchemaDataType } from '../../constant';
import {
flatOptions,
useFilterQueryVariableOptionsByTypes,
} from '../../hooks/use-get-begin-query';
import { DynamicFormHeader, FormListHeaderProps } from './dynamic-fom-header'; import { DynamicFormHeader, FormListHeaderProps } from './dynamic-fom-header';
import { QueryVariable } from './query-variable'; import { QueryVariable } from './query-variable';
@ -16,6 +20,10 @@ export function QueryVariableList({
const form = useFormContext(); const form = useFormContext();
const name = 'query'; const name = 'query';
let options = useFilterQueryVariableOptionsByTypes(types);
const secondOptions = flatOptions(options);
const { fields, remove, append } = useFieldArray({ const { fields, remove, append } = useFieldArray({
name: name, name: name,
control: form.control, control: form.control,
@ -26,14 +34,15 @@ export function QueryVariableList({
<DynamicFormHeader <DynamicFormHeader
label={label} label={label}
tooltip={tooltip} tooltip={tooltip}
onClick={() => append({ input: '' })} onClick={() => append({ input: secondOptions.at(0)?.value })}
disabled={!secondOptions.length}
></DynamicFormHeader> ></DynamicFormHeader>
<div className="space-y-5"> <div className="space-y-5">
{fields.map((field, index) => { {fields.map((field, index) => {
const nameField = `${name}.${index}.input`; const nameField = `${name}.${index}.input`;
return ( return (
<div key={field.id} className="flex items-center gap-2"> <div key={field.id} className="flex gap-2">
<QueryVariable <QueryVariable
name={nameField} name={nameField}
hideLabel hideLabel

View file

@ -4,6 +4,7 @@ import { Form } from '@/components/ui/form';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { buildOptions } from '@/utils/form'; import { buildOptions } from '@/utils/form';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { memo } from 'react'; import { memo } from 'react';
import { useForm, useWatch } from 'react-hook-form'; import { useForm, useWatch } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -25,7 +26,11 @@ import { SelectKeys } from './select-keys';
import { Updates } from './updates'; import { Updates } from './updates';
export const RetrievalPartialSchema = { export const RetrievalPartialSchema = {
query: z.array(z.object({ input: z.string().optional() })), query: z.array(
z.object({
input: z.string().min(1, { message: t('flow.queryRequired') }),
}),
),
operations: z.string(), operations: z.string(),
select_keys: z.array(z.object({ name: z.string().optional() })).optional(), select_keys: z.array(z.object({ name: z.string().optional() })).optional(),
remove_keys: z.array(z.object({ name: z.string().optional() })).optional(), remove_keys: z.array(z.object({ name: z.string().optional() })).optional(),

View file

@ -317,14 +317,18 @@ export const useGetComponentLabelByValue = (nodeId: string) => {
return getLabel; return getLabel;
}; };
export function flatOptions(options: DefaultOptionType[]) {
return options.reduce<DefaultOptionType[]>((pre, cur) => {
return [...pre, ...cur.options];
}, []);
}
export function useFlattenQueryVariableOptions(nodeId?: string) { export function useFlattenQueryVariableOptions(nodeId?: string) {
const { getNode } = useGraphStore((state) => state); const { getNode } = useGraphStore((state) => state);
const nextOptions = useBuildQueryVariableOptions(getNode(nodeId)); const nextOptions = useBuildQueryVariableOptions(getNode(nodeId));
const flattenOptions = useMemo(() => { const flattenOptions = useMemo(() => {
return nextOptions.reduce<DefaultOptionType[]>((pre, cur) => { return flatOptions(nextOptions);
return [...pre, ...cur.options];
}, []);
}, [nextOptions]); }, [nextOptions]);
return flattenOptions; return flattenOptions;