Feat: Use shadcn-ui to build GenerateForm. #3221 (#5449)

### What problem does this PR solve?

Feat: Use shadcn-ui to build GenerateForm. #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2025-02-27 18:13:41 +08:00 committed by GitHub
parent 651422127c
commit 244cf49ba4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 142 additions and 249 deletions

View File

@ -1,5 +1,14 @@
import { Form, InputNumber } from 'antd'; import { Form, InputNumber } from 'antd';
import { useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from './ui/form';
import { Input } from './ui/input';
const MessageHistoryWindowSizeItem = ({ const MessageHistoryWindowSizeItem = ({
initialValue, initialValue,
@ -21,3 +30,24 @@ const MessageHistoryWindowSizeItem = ({
}; };
export default MessageHistoryWindowSizeItem; export default MessageHistoryWindowSizeItem;
export function MessageHistoryWindowSizeFormField() {
const form = useFormContext();
const { t } = useTranslation();
return (
<FormField
control={form.control}
name={'message_history_window_size'}
render={({ field }) => (
<FormItem>
<FormLabel>{t('flow.messageHistoryWindowSize')}</FormLabel>
<FormControl>
<Input {...field} type={'number'}></Input>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
}

View File

@ -52,10 +52,12 @@ export default SimilaritySlider;
interface SimilaritySliderFormFieldProps { interface SimilaritySliderFormFieldProps {
vectorSimilarityWeightName?: string; vectorSimilarityWeightName?: string;
isTooltipShown?: boolean;
} }
export function SimilaritySliderFormField({ export function SimilaritySliderFormField({
vectorSimilarityWeightName = 'vector_similarity_weight', vectorSimilarityWeightName = 'vector_similarity_weight',
isTooltipShown,
}: SimilaritySliderFormFieldProps) { }: SimilaritySliderFormFieldProps) {
const form = useFormContext(); const form = useFormContext();
const { t } = useTranslate('knowledgeDetails'); const { t } = useTranslate('knowledgeDetails');
@ -67,7 +69,9 @@ export function SimilaritySliderFormField({
name={'similarity_threshold'} name={'similarity_threshold'}
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('similarityThreshold')}</FormLabel> <FormLabel tooltip={isTooltipShown && t('similarityThresholdTip')}>
{t('similarityThreshold')}
</FormLabel>
<FormControl> <FormControl>
<SingleFormSlider <SingleFormSlider
{...field} {...field}
@ -84,7 +88,11 @@ export function SimilaritySliderFormField({
name={vectorSimilarityWeightName} name={vectorSimilarityWeightName}
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('vectorSimilarityWeight')}</FormLabel> <FormLabel
tooltip={isTooltipShown && t('vectorSimilarityWeightTip')}
>
{t('vectorSimilarityWeight')}
</FormLabel>
<FormControl> <FormControl>
<SingleFormSlider <SingleFormSlider
{...field} {...field}

View File

@ -14,6 +14,8 @@ import {
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { Info } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip';
const Form = FormProvider; const Form = FormProvider;
@ -88,17 +90,31 @@ FormItem.displayName = 'FormItem';
const FormLabel = React.forwardRef< const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & {
>(({ className, ...props }, ref) => { tooltip?: React.ReactNode;
}
>(({ className, tooltip, ...props }, ref) => {
const { error, formItemId } = useFormField(); const { error, formItemId } = useFormField();
return ( return (
<Label <Label
ref={ref} ref={ref}
className={cn(error && 'text-destructive', className)} className={cn(error && 'text-destructive', className, 'flex')}
htmlFor={formItemId} htmlFor={formItemId}
{...props} {...props}
/> >
{props.children}
{tooltip && (
<Tooltip>
<TooltipTrigger>
<Info className="size-3 ml-2" />
</TooltipTrigger>
<TooltipContent>
<p>{tooltip}</p>
</TooltipContent>
</Tooltip>
)}
</Label>
); );
}); });
FormLabel.displayName = 'FormLabel'; FormLabel.displayName = 'FormLabel';

View File

@ -122,7 +122,7 @@ const FormSheet = ({
<span>{t(`${lowerFirst(operatorName)}Description`)}</span> <span>{t(`${lowerFirst(operatorName)}Description`)}</span>
</section> </section>
</SheetHeader> </SheetHeader>
<section> <section className="pt-4">
{visible && ( {visible && (
<FlowFormContext.Provider value={node}> <FlowFormContext.Provider value={node}>
<OperatorForm <OperatorForm

View File

@ -82,8 +82,15 @@ export function useFormConfigMap() {
}, },
[Operator.Generate]: { [Operator.Generate]: {
component: GenerateForm, component: GenerateForm,
defaultValues: {}, defaultValues: {
schema: z.object({}), cite: true,
prompt: t('flow.promptText'),
},
schema: z.object({
prompt: z.string().min(1, {
message: t('flow.promptMessage'),
}),
}),
}, },
[Operator.Answer]: { [Operator.Answer]: {
component: AnswerForm, component: AnswerForm,

View File

@ -121,7 +121,7 @@ export function DynamicInputVariable({ node }: IProps) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<Collapsible defaultOpen className="group/collapsible pt-4"> <Collapsible defaultOpen className="group/collapsible">
<CollapsibleTrigger className="flex justify-between w-full pb-2"> <CollapsibleTrigger className="flex justify-between w-full pb-2">
<span className="font-bold text-2xl text-colors-text-neutral-strong"> <span className="font-bold text-2xl text-colors-text-neutral-strong">
{t('flow.input')} {t('flow.input')}

View File

@ -1,101 +0,0 @@
import { EditableCell, EditableRow } from '@/components/editable-cell';
import { useTranslate } from '@/hooks/common-hooks';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { DeleteOutlined } from '@ant-design/icons';
import { Button, Flex, Select, Table, TableProps } from 'antd';
import { useBuildComponentIdSelectOptions } from '../../hooks/use-get-begin-query';
import { IGenerateParameter } from '../../interface';
import { useHandleOperateParameters } from './hooks';
import styles from './index.less';
interface IProps {
node?: RAGFlowNodeType;
}
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const DynamicParameters = ({ node }: IProps) => {
const nodeId = node?.id;
const { t } = useTranslate('flow');
const options = useBuildComponentIdSelectOptions(nodeId, node?.parentId);
const {
dataSource,
handleAdd,
handleRemove,
handleSave,
handleComponentIdChange,
} = useHandleOperateParameters(nodeId!);
const columns: TableProps<IGenerateParameter>['columns'] = [
{
title: t('key'),
dataIndex: 'key',
key: 'key',
width: '40%',
onCell: (record: IGenerateParameter) => ({
record,
editable: true,
dataIndex: 'key',
title: 'key',
handleSave,
}),
},
{
title: t('value'),
dataIndex: 'component_id',
key: 'component_id',
align: 'center',
width: '40%',
render(text, record) {
return (
<Select
style={{ width: '100%' }}
allowClear
options={options}
value={text}
onChange={handleComponentIdChange(record)}
/>
);
},
},
{
title: t('operation'),
dataIndex: 'operation',
width: 20,
key: 'operation',
align: 'center',
fixed: 'right',
render(_, record) {
return <DeleteOutlined onClick={handleRemove(record.id)} />;
},
},
];
return (
<section>
<Flex justify="end">
<Button size="small" onClick={handleAdd}>
{t('add')}
</Button>
</Flex>
<Table
dataSource={dataSource}
columns={columns}
rowKey={'id'}
className={styles.variableTable}
components={components}
rowClassName={() => styles.editableRow}
scroll={{ x: true }}
bordered
/>
</section>
);
};
export default DynamicParameters;

View File

@ -1,70 +0,0 @@
import get from 'lodash/get';
import { useCallback, useMemo } from 'react';
import { v4 as uuid } from 'uuid';
import { IGenerateParameter } from '../../interface';
import useGraphStore from '../../store';
export const useHandleOperateParameters = (nodeId: string) => {
const { getNode, updateNodeForm } = useGraphStore((state) => state);
const node = getNode(nodeId);
const dataSource: IGenerateParameter[] = useMemo(
() => get(node, 'data.form.parameters', []) as IGenerateParameter[],
[node],
);
const handleComponentIdChange = useCallback(
(row: IGenerateParameter) => (value: string) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
component_id: value,
});
updateNodeForm(nodeId, { parameters: newData });
},
[updateNodeForm, nodeId, dataSource],
);
const handleRemove = useCallback(
(id?: string) => () => {
const newData = dataSource.filter((item) => item.id !== id);
updateNodeForm(nodeId, { parameters: newData });
},
[updateNodeForm, nodeId, dataSource],
);
const handleAdd = useCallback(() => {
updateNodeForm(nodeId, {
parameters: [
...dataSource,
{
id: uuid(),
key: '',
component_id: undefined,
},
],
});
}, [dataSource, nodeId, updateNodeForm]);
const handleSave = (row: IGenerateParameter) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
updateNodeForm(nodeId, { parameters: newData });
};
return {
handleAdd,
handleRemove,
handleComponentIdChange,
handleSave,
dataSource,
};
};

View File

@ -1,21 +0,0 @@
.variableTable {
margin-top: 14px;
}
.editableRow {
:global(.editable-cell) {
position: relative;
}
:global(.editable-cell-value-wrap) {
padding: 5px 12px;
cursor: pointer;
height: 30px !important;
}
&:hover {
:global(.editable-cell-value-wrap) {
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 2px;
}
}
}

View File

@ -1,55 +1,76 @@
import LLMSelect from '@/components/llm-select'; import LLMSelect from '@/components/llm-select';
import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item'; import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
import { PromptEditor } from '@/components/prompt-editor'; import { PromptEditor } from '@/components/prompt-editor';
import { useTranslate } from '@/hooks/common-hooks'; import {
import { Form, Switch } from 'antd'; Form,
import { IOperatorForm } from '../../interface'; FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Switch } from '@/components/ui/switch';
import { useTranslation } from 'react-i18next';
import { INextOperatorForm } from '../../interface';
const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => { const GenerateForm = ({ form }: INextOperatorForm) => {
const { t } = useTranslate('flow'); const { t } = useTranslation();
return ( return (
<Form <Form {...form}>
name="basic" <form
autoComplete="off" className="space-y-6"
form={form} onSubmit={(e) => {
onValuesChange={onValuesChange} e.preventDefault();
layout={'vertical'} }}
>
<Form.Item
name={'llm_id'}
label={t('model', { keyPrefix: 'chat' })}
tooltip={t('modelTip', { keyPrefix: 'chat' })}
> >
<LLMSelect></LLMSelect> <FormField
</Form.Item> control={form.control}
<Form.Item name="llm_id"
name={['prompt']} render={({ field }) => (
label={t('systemPrompt')} <FormItem>
initialValue={t('promptText')} <FormLabel tooltip={t('chat.modelTip')}>
tooltip={t('promptTip', { keyPrefix: 'knowledgeConfiguration' })} {t('chat.model')}
rules={[ </FormLabel>
{ <FormControl>
required: true, <LLMSelect {...field} />
message: t('promptMessage'), </FormControl>
}, <FormMessage />
]} </FormItem>
> )}
{/* <Input.TextArea rows={8}></Input.TextArea> */} />
<PromptEditor></PromptEditor> <FormField
</Form.Item> control={form.control}
<Form.Item name="prompt"
name={['cite']} render={({ field }) => (
label={t('cite')} <FormItem>
initialValue={true} <FormLabel tooltip={t('knowledgeConfiguration.promptTip')}>
valuePropName="checked" {t('flow.systemPrompt')}
tooltip={t('citeTip')} </FormLabel>
> <FormControl>
<Switch /> <PromptEditor {...field} />
</Form.Item> </FormControl>
<MessageHistoryWindowSizeItem <FormMessage />
initialValue={12} </FormItem>
></MessageHistoryWindowSizeItem> )}
/>
<FormField
control={form.control}
name="cite"
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t('flow.citeTip')}>
{t('flow.cite')}
</FormLabel>
<FormControl>
<Switch {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField>
</form>
</Form> </Form>
); );
}; };

View File

@ -26,7 +26,10 @@ const RetrievalForm = ({ form, node }: INextOperatorForm) => {
}} }}
> >
<DynamicInputVariable node={node}></DynamicInputVariable> <DynamicInputVariable node={node}></DynamicInputVariable>
<SimilaritySliderFormField vectorSimilarityWeightName="keywords_similarity_weight"></SimilaritySliderFormField> <SimilaritySliderFormField
vectorSimilarityWeightName="keywords_similarity_weight"
isTooltipShown
></SimilaritySliderFormField>
<TopNFormField></TopNFormField> <TopNFormField></TopNFormField>
<RerankFormFields></RerankFormFields> <RerankFormFields></RerankFormFields>
<KnowledgeBaseFormField></KnowledgeBaseFormField> <KnowledgeBaseFormField></KnowledgeBaseFormField>