mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-23 04:46:07 +00:00
feat: use dynamic form to render pipeline config
This commit is contained in:
@@ -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[];
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user