mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-14 04:05:58 +08:00
Feat: Add InnerBlurInput component to avoid frequent updates of zustand causing the input box to lose focus #3221 (#7955)
### What problem does this PR solve? Feat: Add InnerBlurInput component to avoid frequent updates of zustand causing the input box to lose focus #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
49ff1ca934
commit
e97fd2b5e6
@ -1,4 +1,5 @@
|
|||||||
import { Form, InputNumber } from 'antd';
|
import { Form, InputNumber } from 'antd';
|
||||||
|
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 {
|
import {
|
||||||
@ -8,7 +9,7 @@ import {
|
|||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from './ui/form';
|
} from './ui/form';
|
||||||
import { Input } from './ui/input';
|
import { BlurInput, Input } from './ui/input';
|
||||||
|
|
||||||
const MessageHistoryWindowSizeItem = ({
|
const MessageHistoryWindowSizeItem = ({
|
||||||
initialValue,
|
initialValue,
|
||||||
@ -31,10 +32,20 @@ const MessageHistoryWindowSizeItem = ({
|
|||||||
|
|
||||||
export default MessageHistoryWindowSizeItem;
|
export default MessageHistoryWindowSizeItem;
|
||||||
|
|
||||||
export function MessageHistoryWindowSizeFormField() {
|
type MessageHistoryWindowSizeFormFieldProps = {
|
||||||
|
useBlurInput?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MessageHistoryWindowSizeFormField({
|
||||||
|
useBlurInput = false,
|
||||||
|
}: MessageHistoryWindowSizeFormFieldProps) {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const NextInput = useMemo(() => {
|
||||||
|
return useBlurInput ? BlurInput : Input;
|
||||||
|
}, [useBlurInput]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@ -45,7 +56,7 @@ export function MessageHistoryWindowSizeFormField() {
|
|||||||
{t('flow.messageHistoryWindowSize')}
|
{t('flow.messageHistoryWindowSize')}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} type={'number'}></Input>
|
<NextInput {...field} type={'number'}></NextInput>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { CheckIcon, ChevronDownIcon } from 'lucide-react';
|
import { CheckIcon, ChevronDownIcon } from 'lucide-react';
|
||||||
import { Fragment, useCallback, useEffect, useId, useState } from 'react';
|
import {
|
||||||
|
Fragment,
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useId,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
@ -72,11 +79,10 @@ export type SelectWithSearchFlagProps = {
|
|||||||
onChange?(value: string): void;
|
onChange?(value: string): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SelectWithSearch({
|
export const SelectWithSearch = forwardRef<
|
||||||
value: val = '',
|
React.ElementRef<typeof Button>,
|
||||||
onChange,
|
SelectWithSearchFlagProps
|
||||||
options = countries,
|
>(({ value: val = '', onChange, options = countries }, ref) => {
|
||||||
}: SelectWithSearchFlagProps) {
|
|
||||||
const id = useId();
|
const id = useId();
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const [value, setValue] = useState<string>('');
|
const [value, setValue] = useState<string>('');
|
||||||
@ -102,6 +108,7 @@ export function SelectWithSearch({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
|
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]"
|
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 ? (
|
||||||
@ -160,4 +167,4 @@ export function SelectWithSearch({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
@ -67,4 +67,42 @@ const SearchInput = (props: InputProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Value = string | readonly string[] | number | undefined;
|
||||||
|
|
||||||
|
export const InnerBlurInput = React.forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
InputProps & { value: Value; onChange(value: Value): void }
|
||||||
|
>(({ value, onChange, ...props }, ref) => {
|
||||||
|
const [val, setVal] = React.useState<Value>();
|
||||||
|
|
||||||
|
const handleChange: React.ChangeEventHandler<HTMLInputElement> =
|
||||||
|
React.useCallback((e) => {
|
||||||
|
setVal(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleBlur: React.FocusEventHandler<HTMLInputElement> =
|
||||||
|
React.useCallback(
|
||||||
|
(e) => {
|
||||||
|
onChange?.(e.target.value);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setVal(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
{...props}
|
||||||
|
value={val}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
ref={ref}
|
||||||
|
></Input>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BlurInput = React.memo(InnerBlurInput);
|
||||||
|
|
||||||
export { ExpandedInput, Input, SearchInput };
|
export { ExpandedInput, Input, SearchInput };
|
||||||
|
@ -20,3 +20,42 @@ const Textarea = React.forwardRef<
|
|||||||
Textarea.displayName = 'Textarea';
|
Textarea.displayName = 'Textarea';
|
||||||
|
|
||||||
export { Textarea };
|
export { Textarea };
|
||||||
|
|
||||||
|
type Value = string | readonly string[] | number | undefined;
|
||||||
|
|
||||||
|
export const BlurTextarea = React.forwardRef<
|
||||||
|
HTMLTextAreaElement,
|
||||||
|
React.ComponentProps<'textarea'> & {
|
||||||
|
value: Value;
|
||||||
|
onChange(value: Value): void;
|
||||||
|
}
|
||||||
|
>(({ value, onChange, ...props }, ref) => {
|
||||||
|
const [val, setVal] = React.useState<Value>();
|
||||||
|
|
||||||
|
const handleChange: React.ChangeEventHandler<HTMLTextAreaElement> =
|
||||||
|
React.useCallback((e) => {
|
||||||
|
setVal(e.target.value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleBlur: React.FocusEventHandler<HTMLTextAreaElement> =
|
||||||
|
React.useCallback(
|
||||||
|
(e) => {
|
||||||
|
onChange?.(e.target.value);
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
setVal(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Textarea
|
||||||
|
{...props}
|
||||||
|
value={val}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
ref={ref}
|
||||||
|
></Textarea>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -11,6 +11,7 @@ import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { get, isPlainObject, lowerFirst } from 'lodash';
|
import { get, isPlainObject, lowerFirst } from 'lodash';
|
||||||
|
import omit from 'lodash/omit';
|
||||||
import { Play, X } from 'lucide-react';
|
import { Play, X } from 'lucide-react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
@ -54,7 +55,7 @@ const FormSheet = ({
|
|||||||
const OperatorForm = currentFormMap.component ?? EmptyContent;
|
const OperatorForm = currentFormMap.component ?? EmptyContent;
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
defaultValues: currentFormMap.defaultValues,
|
values: currentFormMap.defaultValues,
|
||||||
resolver: zodResolver(currentFormMap.schema),
|
resolver: zodResolver(currentFormMap.schema),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,10 +90,16 @@ const FormSheet = ({
|
|||||||
if (isPlainObject(formData)) {
|
if (isPlainObject(formData)) {
|
||||||
// form.setFieldsValue({ ...formData, items });
|
// form.setFieldsValue({ ...formData, items });
|
||||||
console.info('xxx');
|
console.info('xxx');
|
||||||
form.reset({ ...formData, items });
|
const nextValues = {
|
||||||
|
...omit(formData, 'category_description'),
|
||||||
|
items,
|
||||||
|
};
|
||||||
|
// Object.entries(nextValues).forEach(([key, value]) => {
|
||||||
|
// form.setValue(key, value, { shouldDirty: false });
|
||||||
|
// });
|
||||||
|
form.reset(nextValues);
|
||||||
}
|
}
|
||||||
}
|
} else if (operatorName === Operator.Message) {
|
||||||
if (operatorName === Operator.Message) {
|
|
||||||
form.reset({
|
form.reset({
|
||||||
...formData,
|
...formData,
|
||||||
content: convertToObjectArray(formData.content),
|
content: convertToObjectArray(formData.content),
|
||||||
|
@ -124,15 +124,26 @@ export function useFormConfigMap() {
|
|||||||
presencePenaltyEnabled: true,
|
presencePenaltyEnabled: true,
|
||||||
frequencyPenaltyEnabled: true,
|
frequencyPenaltyEnabled: true,
|
||||||
maxTokensEnabled: true,
|
maxTokensEnabled: true,
|
||||||
|
items: [],
|
||||||
},
|
},
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
parameter: z.string().optional(),
|
parameter: z.string().optional(),
|
||||||
...LlmSettingSchema,
|
...LlmSettingSchema,
|
||||||
message_history_window_size: z.number(),
|
message_history_window_size: z.coerce.number(),
|
||||||
items: z.array(
|
items: z.array(
|
||||||
z.object({
|
z
|
||||||
|
.object({
|
||||||
name: z.string().min(1, t('flow.nameMessage')).trim(),
|
name: z.string().min(1, t('flow.nameMessage')).trim(),
|
||||||
}),
|
description: z.string().optional(),
|
||||||
|
// examples: z
|
||||||
|
// .array(
|
||||||
|
// z.object({
|
||||||
|
// value: z.string(),
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
// .optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@ -180,6 +191,12 @@ export function useFormConfigMap() {
|
|||||||
arguments: z.array(
|
arguments: z.array(
|
||||||
z.object({ name: z.string(), component_id: z.string() }),
|
z.object({ name: z.string(), component_id: z.string() }),
|
||||||
),
|
),
|
||||||
|
return: z.union([
|
||||||
|
z
|
||||||
|
.array(z.object({ name: z.string(), component_id: z.string() }))
|
||||||
|
.optional(),
|
||||||
|
z.object({ name: z.string(), component_id: z.string() }),
|
||||||
|
]),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
[Operator.WaitingDialogue]: {
|
[Operator.WaitingDialogue]: {
|
||||||
|
@ -12,8 +12,7 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { RAGFlowSelect } from '@/components/ui/select';
|
import { BlurTextarea } from '@/components/ui/textarea';
|
||||||
import { Textarea } from '@/components/ui/textarea';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { useUpdateNodeInternals } from '@xyflow/react';
|
import { useUpdateNodeInternals } from '@xyflow/react';
|
||||||
@ -23,6 +22,7 @@ import { ChevronsUpDown, X } from 'lucide-react';
|
|||||||
import {
|
import {
|
||||||
ChangeEventHandler,
|
ChangeEventHandler,
|
||||||
FocusEventHandler,
|
FocusEventHandler,
|
||||||
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
@ -104,7 +104,7 @@ const NameInput = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormSet = ({ nodeId, index }: IProps & { index: number }) => {
|
const InnerFormSet = ({ nodeId, index }: IProps & { index: number }) => {
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
const { t } = useTranslate('flow');
|
const { t } = useTranslate('flow');
|
||||||
const buildCategorizeToOptions = useBuildFormSelectOptions(
|
const buildCategorizeToOptions = useBuildFormSelectOptions(
|
||||||
@ -152,13 +152,13 @@ const FormSet = ({ nodeId, index }: IProps & { index: number }) => {
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('description')}</FormLabel>
|
<FormLabel>{t('description')}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea {...field} rows={3} />
|
<BlurTextarea {...field} rows={3} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
{/* <FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={buildFieldName('examples')}
|
name={buildFieldName('examples')}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
@ -170,8 +170,8 @@ const FormSet = ({ nodeId, index }: IProps & { index: number }) => {
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
<FormField
|
{/* <FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={buildFieldName('to')}
|
name={buildFieldName('to')}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
@ -202,11 +202,13 @@ const FormSet = ({ nodeId, index }: IProps & { index: number }) => {
|
|||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/> */}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FormSet = memo(InnerFormSet);
|
||||||
|
|
||||||
const DynamicCategorize = ({ nodeId }: IProps) => {
|
const DynamicCategorize = ({ nodeId }: IProps) => {
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
const form = useFormContext();
|
const form = useFormContext();
|
||||||
@ -219,6 +221,8 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
|
|||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
append({
|
append({
|
||||||
name: humanId(),
|
name: humanId(),
|
||||||
|
description: '',
|
||||||
|
// examples: [],
|
||||||
});
|
});
|
||||||
if (nodeId) updateNodeInternals(nodeId);
|
if (nodeId) updateNodeInternals(nodeId);
|
||||||
};
|
};
|
||||||
@ -226,7 +230,7 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 ">
|
<div className="flex flex-col gap-4 ">
|
||||||
{fields.map((field, index) => (
|
{fields.map((field, index) => (
|
||||||
<Collapsible key={field.id}>
|
<Collapsible key={field.id} defaultOpen>
|
||||||
<div className="flex items-center justify-between space-x-4">
|
<div className="flex items-center justify-between space-x-4">
|
||||||
<h4 className="font-bold">
|
<h4 className="font-bold">
|
||||||
{form.getValues(`items.${index}.name`)}
|
{form.getValues(`items.${index}.name`)}
|
||||||
@ -262,4 +266,4 @@ const DynamicCategorize = ({ nodeId }: IProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DynamicCategorize;
|
export default memo(DynamicCategorize);
|
||||||
|
@ -41,7 +41,9 @@ const CategorizeForm = ({ form, node }: INextOperatorForm) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<LargeModelFormField></LargeModelFormField>
|
<LargeModelFormField></LargeModelFormField>
|
||||||
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
|
<MessageHistoryWindowSizeFormField
|
||||||
|
useBlurInput
|
||||||
|
></MessageHistoryWindowSizeFormField>
|
||||||
<DynamicCategorize nodeId={node?.id}></DynamicCategorize>
|
<DynamicCategorize nodeId={node?.id}></DynamicCategorize>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
FormItem,
|
FormItem,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { BlurInput } from '@/components/ui/input';
|
||||||
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 { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
@ -58,10 +58,10 @@ export function DynamicVariableForm({ node, name = 'arguments' }: IProps) {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="w-2/5">
|
<FormItem className="w-2/5">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<BlurInput
|
||||||
{...field}
|
{...field}
|
||||||
placeholder={t('common.pleaseInput')}
|
placeholder={t('common.pleaseInput')}
|
||||||
></Input>
|
></BlurInput>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
@ -37,18 +37,49 @@ export const useHandleFormValuesChange = (
|
|||||||
[updateNodeForm, id],
|
[updateNodeForm, id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = useWatch({ control: form?.control });
|
let values = useWatch({ control: form?.control });
|
||||||
|
|
||||||
console.log('🚀 ~ x:', value);
|
// console.log('🚀 ~ x:', values);
|
||||||
|
|
||||||
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) {
|
if (id && form?.formState.isDirty) {
|
||||||
console.log('🚀 ~ useEffect ~ value:', value, operatorName);
|
values = form?.getValues();
|
||||||
|
let nextValues: any = values;
|
||||||
// run(id, nextValues);
|
// run(id, nextValues);
|
||||||
updateNodeForm(id, value);
|
|
||||||
|
const categoryDescriptionRegex = /items\.\d+\.name/g;
|
||||||
|
|
||||||
|
if (operatorName === Operator.Categorize) {
|
||||||
|
console.log('🚀 ~ useEffect ~ values:', values);
|
||||||
|
const categoryDescription = Array.isArray(values.items)
|
||||||
|
? buildCategorizeObjectFromList(values.items)
|
||||||
|
: {};
|
||||||
|
if (categoryDescription) {
|
||||||
|
nextValues = {
|
||||||
|
...omit(values, 'items'),
|
||||||
|
category_description: categoryDescription,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}, [form?.formState.isDirty, id, operatorName, updateNodeForm, value]);
|
} else if (operatorName === Operator.Message) {
|
||||||
|
nextValues = {
|
||||||
|
...values,
|
||||||
|
content: convertToStringArray(values.content),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNodeForm(id, nextValues);
|
||||||
|
}
|
||||||
|
}, [form?.formState.isDirty, id, operatorName, updateNodeForm, values]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// form?.subscribe({
|
||||||
|
// formState: { values: true },
|
||||||
|
// callback: ({ values }) => {
|
||||||
|
// // console.info('subscribe', values);
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// }, [form]);
|
||||||
|
|
||||||
return { handleValuesChange };
|
return { handleValuesChange };
|
||||||
|
|
||||||
|
56
web/src/pages/demo.tsx
Normal file
56
web/src/pages/demo.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { Form, useForm } from 'react-hook-form';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import DynamicCategorize from './agent/form/categorize-form/dynamic-categorize';
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
items: z
|
||||||
|
.array(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
name: z.string().min(1, 'xxx').trim(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
// examples: z
|
||||||
|
// .array(
|
||||||
|
// z.object({
|
||||||
|
// value: z.string(),
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
// .optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function Demo() {
|
||||||
|
const [flag, setFlag] = useState(false);
|
||||||
|
|
||||||
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
form?.reset();
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
|
const handleSwitch = useCallback(() => {
|
||||||
|
setFlag(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form {...form}>
|
||||||
|
<DynamicCategorize></DynamicCategorize>
|
||||||
|
</Form>
|
||||||
|
<Button onClick={handleReset}>reset</Button>
|
||||||
|
<Button onClick={handleSwitch}>switch</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user