feat: meta field for bot form

This commit is contained in:
Junyan Qin
2025-05-07 21:38:04 +08:00
parent 124e1215e8
commit 0d21faa9d3
5 changed files with 210 additions and 164 deletions
@@ -1,14 +1,3 @@
.configPageContainer {
width: 100%;
height: 100%;
}
.cardContainer {
width: 420px;
height: 220px;
border: 1px solid black;
}
.botListContainer { .botListContainer {
align-self: flex-start; align-self: flex-start;
justify-self: flex-start; justify-self: flex-start;
@@ -1,8 +1,3 @@
import {
BotFormEntity,
IBotFormEntity,
} from '@/app/home/bots/components/bot-form/BotFormEntity';
import { Button, Form, Input, notification, Select, Space } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { IChooseAdapterEntity } from '@/app/home/bots/components/bot-form/ChooseAdapterEntity'; import { IChooseAdapterEntity } from '@/app/home/bots/components/bot-form/ChooseAdapterEntity';
import { import {
@@ -15,20 +10,70 @@ import DynamicFormComponent from '@/app/home/components/dynamic-form/DynamicForm
import { httpClient } from '@/app/infra/http/HttpClient'; import { httpClient } from '@/app/infra/http/HttpClient';
import { Bot } from '@/app/infra/api/api-types'; import { Bot } from '@/app/infra/api/api-types';
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { z } from "zod"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogFooter,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Checkbox } from "@/components/ui/checkbox"
const formSchema = z.object({
name: z.string().min(1, { message: '机器人名称不能为空' }),
description: z.string().min(1, { message: '机器人描述不能为空' }),
adapter: z.string().min(1, { message: '适配器不能为空' }),
adapter_config: z.record(z.string(), z.any()),
});
export default function BotForm({ export default function BotForm({
initBotId, initBotId,
onFormSubmit, onFormSubmit,
onFormCancel, onFormCancel,
onBotDeleted,
}: { }: {
initBotId?: string; initBotId?: string;
onFormSubmit: (value: IBotFormEntity) => void; onFormSubmit: (value: z.infer<typeof formSchema>) => void;
onFormCancel: (value: IBotFormEntity) => void; onFormCancel: () => void;
onBotDeleted: () => void;
}) { }) {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: '',
description: '一个机器人',
adapter: '',
adapter_config: {},
},
});
const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false);
const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] = const [adapterNameToDynamicConfigMap, setAdapterNameToDynamicConfigMap] =
useState(new Map<string, IDynamicFormItemConfig[]>()); useState(new Map<string, IDynamicFormItemConfig[]>());
const [form] = Form.useForm<IBotFormEntity>(); // const [form] = Form.useForm<IBotFormEntity>();
const [showDynamicForm, setShowDynamicForm] = useState<boolean>(false); const [showDynamicForm, setShowDynamicForm] = useState<boolean>(false);
const [dynamicForm] = Form.useForm(); // const [dynamicForm] = Form.useForm();
const [adapterNameList, setAdapterNameList] = useState< const [adapterNameList, setAdapterNameList] = useState<
IChooseAdapterEntity[] IChooseAdapterEntity[]
>([]); >([]);
@@ -78,29 +123,32 @@ export default function BotForm({
}); });
// 拉取初始化表单信息 // 拉取初始化表单信息
if (initBotId) { if (initBotId) {
getBotFieldById(initBotId).then((val) => { getBotConfig(initBotId).then((val) => {
form.setFieldsValue(val); // form.setFieldsValue(val);
handleAdapterSelect(val.adapter); handleAdapterSelect(val.adapter);
dynamicForm.setFieldsValue(val.adapter_config); // dynamicForm.setFieldsValue(val.adapter_config);
}); });
} else { } else {
form.resetFields(); // form.resetFields();
} }
setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap); setAdapterNameToDynamicConfigMap(adapterNameToDynamicConfigMap);
} }
async function onCreateMode() {} async function onCreateMode() { }
function onEditMode() {} function onEditMode() {
console.log('onEditMode', form.getValues());
async function getBotFieldById(botId: string): Promise<IBotFormEntity> { }
async function getBotConfig(botId: string): Promise<z.infer<typeof formSchema>> {
const bot = (await httpClient.getBot(botId)).bot; const bot = (await httpClient.getBot(botId)).bot;
return new BotFormEntity({ return {
adapter: bot.adapter, adapter: bot.adapter,
description: bot.description, description: bot.description,
name: bot.name, name: bot.name,
adapter_config: bot.adapter_config, adapter_config: bot.adapter_config,
}); };
} }
function handleAdapterSelect(adapterName: string) { function handleAdapterSelect(adapterName: string) {
@@ -119,11 +167,11 @@ export default function BotForm({
} }
function handleSubmitButton() { function handleSubmitButton() {
form.submit(); // form.submit();
} }
function handleFormFinish() { function handleFormFinish() {
dynamicForm.submit(); // dynamicForm.submit();
} }
// 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里 // 只有通过外层固定表单验证才会走到这里,真正的提交逻辑在这里
@@ -132,12 +180,12 @@ export default function BotForm({
console.log('set loading', true); console.log('set loading', true);
if (initBotId) { if (initBotId) {
// 编辑提交 // 编辑提交
console.log('submit edit', form.getFieldsValue(), value); // console.log('submit edit', form.getFieldsValue(), value);
const updateBot: Bot = { const updateBot: Bot = {
uuid: initBotId, uuid: initBotId,
name: form.getFieldsValue().name, name: form.getValues().name,
description: form.getFieldsValue().description, description: form.getValues().description,
adapter: form.getFieldsValue().adapter, adapter: form.getValues().adapter,
adapter_config: value, adapter_config: value,
}; };
httpClient httpClient
@@ -145,55 +193,55 @@ export default function BotForm({
.then((res) => { .then((res) => {
// TODO success toast // TODO success toast
console.log('update bot success', res); console.log('update bot success', res);
onFormSubmit(form.getFieldsValue()); onFormSubmit(form.getValues());
notification.success({ // notification.success({
message: '更新成功', // message: '更新成功',
description: '机器人更新成功', // description: '机器人更新成功',
}); // });
}) })
.catch(() => { .catch(() => {
// TODO error toast // TODO error toast
notification.error({ // notification.error({
message: '更新失败', // message: '更新失败',
description: '机器人更新失败', // description: '机器人更新失败',
}); // });
}) })
.finally(() => { .finally(() => {
setIsLoading(false); setIsLoading(false);
form.resetFields(); form.reset();
dynamicForm.resetFields(); // dynamicForm.resetFields();
}); });
} else { } else {
// 创建提交 // 创建提交
console.log('submit create', form.getFieldsValue(), value); console.log('submit create', form.getValues(), value);
const newBot: Bot = { const newBot: Bot = {
name: form.getFieldsValue().name, name: form.getValues().name,
description: form.getFieldsValue().description, description: form.getValues().description,
adapter: form.getFieldsValue().adapter, adapter: form.getValues().adapter,
adapter_config: value, adapter_config: value,
}; };
httpClient httpClient
.createBot(newBot) .createBot(newBot)
.then((res) => { .then((res) => {
// TODO success toast // TODO success toast
notification.success({ // notification.success({
message: '创建成功', // message: '创建成功',
description: '机器人创建成功', // description: '机器人创建成功',
}); // });
console.log(res); console.log(res);
onFormSubmit(form.getFieldsValue()); onFormSubmit(form.getValues());
}) })
.catch(() => { .catch(() => {
// TODO error toast // TODO error toast
notification.error({ // notification.error({
message: '创建失败', // message: '创建失败',
description: '机器人创建失败', // description: '机器人创建失败',
}); // });
}) })
.finally(() => { .finally(() => {
setIsLoading(false); setIsLoading(false);
form.resetFields(); form.reset();
dynamicForm.resetFields(); // dynamicForm.resetFields();
}); });
} }
setShowDynamicForm(false); setShowDynamicForm(false);
@@ -203,90 +251,122 @@ export default function BotForm({
} }
function handleSaveButton() { function handleSaveButton() {
form.submit(); form.handleSubmit(onDynamicFormSubmit)();
}
function deleteBot() {
if (initBotId) {
httpClient.deleteBot(initBotId).then(() => {
onBotDeleted();
});
}
} }
return ( return (
<div> <div>
<Form <Dialog open={showDeleteConfirmModal} onOpenChange={setShowDeleteConfirmModal}>
form={form} <DialogContent>
labelCol={{ span: 5 }} <DialogHeader>
wrapperCol={{ span: 18 }} <DialogTitle></DialogTitle>
layout="vertical" </DialogHeader>
onFinish={handleFormFinish} <DialogDescription>
disabled={isLoading}
> </DialogDescription>
<Form.Item<IBotFormEntity> <DialogFooter>
label={'机器人名称'} <Button variant="outline" onClick={() => setShowDeleteConfirmModal(false)}>
name={'name'}
rules={[{ required: true, message: '该项为必填项哦~' }]} </Button>
> <Button variant="destructive" onClick={() => {
<Input deleteBot();
placeholder="为机器人取个好听的名字吧~" setShowDeleteConfirmModal(false);
style={{ width: 260 }} }}>
></Input>
</Form.Item> </Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Form.Item<IBotFormEntity> <Form {...form}>
label={'描述'} <form onSubmit={form.handleSubmit(onDynamicFormSubmit)} className="space-y-8">
name={'description'} <div className="space-y-4">
rules={[{ required: true, message: '该项为必填项哦~' }]} <FormField
> control={form.control}
<Input placeholder="简单描述一下这个机器人"></Input> name="name"
</Form.Item> render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel><span className="text-red-500">*</span></FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Form.Item<IBotFormEntity> <FormField
label={'平台/适配器选择'} control={form.control}
name={'adapter'} name="adapter"
rules={[{ required: true, message: '该项为必填项哦~' }]} render={({ field }) => (
> <FormItem>
<Select <FormLabel>/<span className="text-red-500">*</span></FormLabel>
style={{ width: 220 }} <FormControl>
onChange={(value) => { <div className="relative">
handleAdapterSelect(value); <Select
}} onValueChange={(value) => {
options={adapterNameList} field.onChange(value);
/> handleAdapterSelect(value);
</Form.Item> }}
value={field.value}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="选择适配器" />
</SelectTrigger>
<SelectContent className="fixed z-[1000]">
<SelectGroup>
{adapterNameList.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<DialogFooter>
{!initBotId && (
<Button type="submit"></Button>
)}
{initBotId && (
<Button type="button" variant="destructive" onClick={() => setShowDeleteConfirmModal(true)}>
</Button>
)}
<Button type="button" onClick={() => onFormCancel()}>
</Button>
</DialogFooter>
</form>
</Form> </Form>
{showDynamicForm && (
<DynamicFormComponent
form={dynamicForm}
itemConfigList={dynamicFormConfigList}
onSubmit={onDynamicFormSubmit}
/>
)}
<Space>
{!initBotId && (
<Button
type="primary"
htmlType="button"
onClick={handleSubmitButton}
loading={isLoading}
>
</Button>
)}
{initBotId && (
<Button
type="primary"
htmlType="submit"
onClick={handleSaveButton}
loading={isLoading}
>
</Button>
)}
<Button
htmlType="button"
onClick={() => {
onFormCancel(form.getFieldsValue());
}}
disabled={isLoading}
>
</Button>
</Space>
</div> </div>
); );
} }
@@ -1,20 +0,0 @@
export interface IBotFormEntity {
name: string;
description: string;
adapter: string;
adapter_config: object;
}
export class BotFormEntity implements IBotFormEntity {
adapter: string;
description: string;
name: string;
adapter_config: object;
constructor(props: IBotFormEntity) {
this.adapter = props.adapter;
this.description = props.description;
this.name = props.name;
this.adapter_config = props.adapter_config;
}
}
@@ -1,7 +1,3 @@
.configPageContainer {
width: 100%;
height: 100%;
}
.modelListContainer { .modelListContainer {
align-self: flex-start; align-self: flex-start;
@@ -1,4 +1,3 @@
import styles from '@/app/home/models/LLMConfig.module.css';
import { SelectProps } from 'antd'; import { SelectProps } from 'antd';
import { ICreateLLMField } from '@/app/home/models/ICreateLLMField'; import { ICreateLLMField } from '@/app/home/models/ICreateLLMField';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -258,7 +257,7 @@ export default function LLMForm({
} }
return ( return (
<div className={styles.modalContainer}> <div>
<Dialog open={showDeleteConfirmModal} onOpenChange={setShowDeleteConfirmModal}> <Dialog open={showDeleteConfirmModal} onOpenChange={setShowDeleteConfirmModal}>
<DialogContent> <DialogContent>
@@ -301,6 +300,7 @@ export default function LLMForm({
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="model_provider" name="model_provider"
@@ -333,6 +333,7 @@ export default function LLMForm({
</FormItem> </FormItem>
)} )}
/> />
<FormField <FormField
control={form.control} control={form.control}
name="url" name="url"