feat: use dynamic form to render pipeline config

This commit is contained in:
Junyan Qin
2025-05-08 18:17:42 +08:00
parent e74de068ea
commit f6d3619bbe
2 changed files with 175 additions and 108 deletions
@@ -22,6 +22,32 @@ import {
import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic'; import { IDynamicFormItemSchema } from '@/app/infra/entities/form/dynamic';
import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent'; import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicFormComponent';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import { z } from "zod"
import { Input } from "@/components/ui/input"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
const formSchema = z.object({
basic: z.object({
name: z.string().min(1, { message: '名称不能为空' }),
description: z.string().min(1, { message: '描述不能为空' }),
}),
ai: z.record(z.string(), z.any()),
trigger: z.record(z.string(), z.any()),
safety: z.record(z.string(), z.any()),
output: z.record(z.string(), z.any()),
});
type FormValues = z.infer<typeof formSchema>;
export default function PipelineFormComponent({ export default function PipelineFormComponent({
initValues, initValues,
@@ -58,6 +84,17 @@ export default function PipelineFormComponent({
const [safetyConfigTabSchema, setSafetyConfigTabSchema] = useState<PipelineConfigTab>(); const [safetyConfigTabSchema, setSafetyConfigTabSchema] = useState<PipelineConfigTab>();
const [outputConfigTabSchema, setOutputConfigTabSchema] = useState<PipelineConfigTab>(); const [outputConfigTabSchema, setOutputConfigTabSchema] = useState<PipelineConfigTab>();
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
basic: {},
ai: {},
trigger: {},
safety: {},
output: {},
},
});
useEffect(() => { useEffect(() => {
getLLMModelList(); getLLMModelList();
@@ -77,16 +114,11 @@ export default function PipelineFormComponent({
}); });
}, []); }, []);
// useEffect(() => { useEffect(() => {
// console.log('initValues change: ', initValues); if (initValues) {
// if (initValues) { form.reset(initValues);
// // basicForm.setFieldsValue(initValues.basic); }
// // aiForm.setFieldsValue(initValues.ai); }, [initValues, form]);
// // triggerForm.setFieldsValue(initValues.trigger);
// // safetyForm.setFieldsValue(initValues.safety);
// // outputForm.setFieldsValue(initValues.output);
// }
// }, [aiForm, basicForm, initValues, outputForm, safetyForm, triggerForm]);
function getLLMModelList() { function getLLMModelList() {
httpClient httpClient
@@ -138,120 +170,154 @@ export default function PipelineFormComponent({
} }
} }
function handleCommit() { function handleFormSubmit(values: FormValues) {
if (isEditMode) { if (isEditMode) {
handleModify(); handleModify(values);
} else { } else {
handleCreate(); handleCreate(values);
} }
} }
function handleCreate() { function handleCreate(values: FormValues) {
// Promise.all([ const pipeline: Pipeline = {
// // basicForm.validateFields(), config: values,
// // aiForm.validateFields(), created_at: '',
// // triggerForm.validateFields(), description: '',
// // safetyForm.validateFields(), for_version: '',
// // outputForm.validateFields(), name: '',
// ]) stages: [],
// .then(() => { updated_at: '',
// const pipeline = assembleForm(); uuid: UUID.generate(),
// httpClient.createPipeline(pipeline).then(() => onFinish()); is_default: false,
// }) };
// .catch((e) => { httpClient.createPipeline(pipeline).then(() => onFinish());
// console.error(e);
// });
} }
function handleModify() { function handleModify(values: FormValues) {
// Promise.all([ const pipeline: Pipeline = {
// // basicForm.validateFields(), config: values,
// // aiForm.validateFields(), created_at: '',
// // triggerForm.validateFields(), description: '',
// // safetyForm.validateFields(), for_version: '',
// // outputForm.validateFields(), name: '',
// ]) stages: [],
// .then(() => { updated_at: '',
// const pipeline = assembleForm(); uuid: pipelineId || '',
// httpClient is_default: false,
// .updatePipeline(pipelineId || '', pipeline) };
// .then(() => onFinish()); httpClient.updatePipeline(pipelineId || '', pipeline).then(() => onFinish());
// })
// .catch((e) => {
// console.error(e);
// });
} }
// TODO 类型混乱,需要优化 function renderDynamicForms(stage: PipelineConfigStage, formName: keyof FormValues) {
// function assembleForm(): Pipeline { return (
// console.log('basicForm:', basicForm.getFieldsValue()); <div key={stage.name} className="space-y-4 mb-6">
// console.log('aiForm:', aiForm.getFieldsValue()); <div className="text-lg font-medium">{stage.label.zh_CN}</div>
// console.log('triggerForm:', triggerForm.getFieldsValue()); {stage.description && (
// console.log('safetyForm:', safetyForm.getFieldsValue()); <div className="text-sm text-gray-500">{stage.description.zh_CN}</div>
// console.log('outputForm:', outputForm.getFieldsValue()); )}
// const config: object = { <DynamicFormComponent
// ai: aiForm.getFieldsValue(), itemConfigList={stage.config}
// trigger: triggerForm.getFieldsValue(), initialValues={(form.watch(formName) as Record<string, any>)?.[stage.name] || {}}
// safety: safetyForm.getFieldsValue(), onSubmit={(values) => {
// output: outputForm.getFieldsValue(), const currentValues = form.getValues(formName) as Record<string, any> || {};
// }; form.setValue(formName, {
...currentValues,
// return { [stage.name]: values,
// config, });
// created_at: '', }}
// description: basicForm.getFieldsValue().description, />
// for_version: '', </div>
// name: basicForm.getFieldsValue().name, );
// stages: [], }
// updated_at: '',
// uuid: UUID.generate(),
// };
// }
return ( return (
<div style={{ maxHeight: '70vh', overflowY: 'auto' }}> <div style={{ maxHeight: '70vh', overflowY: 'auto' }}>
<Form {...form}>
<Tabs defaultValue={getNowFormLabel().name}> <form onSubmit={form.handleSubmit(handleFormSubmit)}>
<TabsList> <Tabs defaultValue={getNowFormLabel().name}>
{formLabelList.map((formLabel) => ( <TabsList>
<TabsTrigger key={formLabel.name} value={formLabel.name}> {formLabelList.map((formLabel) => (
{formLabel.label} <TabsTrigger key={formLabel.name} value={formLabel.name}>
</TabsTrigger> {formLabel.label}
))} </TabsTrigger>
</TabsList> ))}
{formLabelList.map((formLabel) => ( </TabsList>
<TabsContent key={formLabel.name} value={formLabel.name}>
<h1>{formLabel.label}</h1>
<div>name: {formLabel.name}</div>
</TabsContent> {formLabelList.map((formLabel) => (
))} <TabsContent key={formLabel.name} value={formLabel.name}>
</Tabs> <h1 className="text-xl font-bold mb-4">{formLabel.label}</h1>
{formLabel.name === 'basic' && (
<div className="space-y-6">
<FormField
control={form.control}
name="basic.name"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="basic.description"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
)}
{/* <div className={`${styles.changeFormButtonGroupContainer}`}> {formLabel.name === 'ai' && aiConfigTabSchema && (
<Button <div className="space-y-6">
type="primary" {aiConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'ai'))}
icon={<CaretLeftOutlined />} </div>
onClick={reduceFormLabelIndex} )}
disabled={!getPreFormLabel()}
>
{getPreFormLabel()?.label || '暂无更多'}
</Button>
<Button
type="primary"
icon={<CaretRightOutlined />}
onClick={addFormLabelIndex}
disabled={!getNextFormLabel()}
iconPosition={'end'}
>
{getNextFormLabel()?.label || '暂无更多'}
</Button>
<Button type="primary" onClick={handleCommit}> {formLabel.name === 'trigger' && triggerConfigTabSchema && (
提交 <div className="space-y-6">
</Button> {triggerConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'trigger'))}
</div> */} </div>
)}
{formLabel.name === 'safety' && safetyConfigTabSchema && (
<div className="space-y-6">
{safetyConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'safety'))}
</div>
)}
{formLabel.name === 'output' && outputConfigTabSchema && (
<div className="space-y-6">
{outputConfigTabSchema.stages.map((stage) => renderDynamicForms(stage, 'output'))}
</div>
)}
</TabsContent>
))}
</Tabs>
<div className="sticky bottom-0 left-0 right-0 bg-background border-t p-4 mt-4">
<div className="flex justify-end gap-2">
<Button type="submit">
{isEditMode ? '保存' : '提交'}
</Button>
<Button type="button" variant="outline" onClick={onFinish}>
</Button>
</div>
</div>
</form>
</Form>
</div> </div>
); );
} }
@@ -18,5 +18,6 @@ export interface PipelineConfigTab {
export interface PipelineConfigStage { export interface PipelineConfigStage {
name: string; name: string;
label: I18nLabel; label: I18nLabel;
description?: I18nLabel;
config: IDynamicFormItemSchema[]; config: IDynamicFormItemSchema[];
} }