mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-15 06:50:58 +00:00
feat: meta field for bot form
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user