### What problem does this PR solve? Feat: Add ChatBasicSetting component #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.17.0
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; | import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; | ||||
| import { UserOutlined } from '@ant-design/icons'; | import { UserOutlined } from '@ant-design/icons'; | ||||
| import { Avatar, Form, Select, Space } from 'antd'; | |||||
| import { Avatar as AntAvatar, Form, Select, Space } from 'antd'; | |||||
| import { Book } from 'lucide-react'; | |||||
| import { useFormContext } from 'react-hook-form'; | |||||
| import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar'; | |||||
| import { FormControl, FormField, FormItem, FormLabel } from './ui/form'; | |||||
| import { MultiSelect } from './ui/multi-select'; | |||||
| const KnowledgeBaseItem = () => { | const KnowledgeBaseItem = () => { | ||||
| const { t } = useTranslate('chat'); | const { t } = useTranslate('chat'); | ||||
| const knowledgeOptions = filteredKnowledgeList.map((x) => ({ | const knowledgeOptions = filteredKnowledgeList.map((x) => ({ | ||||
| label: ( | label: ( | ||||
| <Space> | <Space> | ||||
| <Avatar size={20} icon={<UserOutlined />} src={x.avatar} /> | |||||
| <AntAvatar size={20} icon={<UserOutlined />} src={x.avatar} /> | |||||
| {x.name} | {x.name} | ||||
| </Space> | </Space> | ||||
| ), | ), | ||||
| }; | }; | ||||
| export default KnowledgeBaseItem; | export default KnowledgeBaseItem; | ||||
| export function KnowledgeBaseFormField() { | |||||
| const form = useFormContext(); | |||||
| const { t } = useTranslate('chat'); | |||||
| const { list: knowledgeList } = useFetchKnowledgeList(true); | |||||
| const filteredKnowledgeList = knowledgeList.filter( | |||||
| (x) => x.parser_id !== DocumentParserType.Tag, | |||||
| ); | |||||
| const knowledgeOptions = filteredKnowledgeList.map((x) => ({ | |||||
| label: x.name, | |||||
| value: x.id, | |||||
| icon: () => ( | |||||
| <Avatar className="size-4 mr-2"> | |||||
| <AvatarImage src={x.avatar} /> | |||||
| <AvatarFallback> | |||||
| <Book /> | |||||
| </AvatarFallback> | |||||
| </Avatar> | |||||
| ), | |||||
| })); | |||||
| return ( | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="kb_ids" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('knowledgeBases')}</FormLabel> | |||||
| <FormControl> | |||||
| <MultiSelect | |||||
| options={knowledgeOptions} | |||||
| onValueChange={field.onChange} | |||||
| placeholder={t('knowledgeBasesMessage')} | |||||
| variant="inverted" | |||||
| maxCount={100} | |||||
| {...field} | |||||
| /> | |||||
| </FormControl> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| ); | |||||
| } |
| export function AppSettings() { | |||||
| return <section className="p-6 w-[400px] max-w-[20%]">app-settings</section>; | |||||
| } |
| 'use client'; | |||||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||||
| import { useForm } from 'react-hook-form'; | |||||
| import { z } from 'zod'; | |||||
| import { FileUploader } from '@/components/file-uploader'; | |||||
| import { KnowledgeBaseFormField } from '@/components/knowledge-base-item'; | |||||
| import { | |||||
| Form, | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| FormMessage, | |||||
| } from '@/components/ui/form'; | |||||
| import { Input } from '@/components/ui/input'; | |||||
| import { useTranslate } from '@/hooks/common-hooks'; | |||||
| import { Subhead } from './subhead'; | |||||
| import { SwitchFormField } from './switch-fom-field'; | |||||
| export default function ChatBasicSetting() { | |||||
| const { t } = useTranslate('chat'); | |||||
| const promptConfigSchema = z.object({ | |||||
| quote: z.boolean(), | |||||
| keyword: z.boolean(), | |||||
| tts: z.boolean(), | |||||
| empty_response: z.string().min(1, { | |||||
| message: t('emptyResponse'), | |||||
| }), | |||||
| prologue: z.string().min(2, {}), | |||||
| }); | |||||
| const formSchema = z.object({ | |||||
| name: z.string().min(1, { message: t('assistantNameMessage') }), | |||||
| icon: z.array(z.instanceof(File)), | |||||
| language: z.string().min(1, { | |||||
| message: 'Username must be at least 2 characters.', | |||||
| }), | |||||
| description: z.string(), | |||||
| kb_ids: z.array(z.string()).min(0, { | |||||
| message: 'Username must be at least 1 characters.', | |||||
| }), | |||||
| prompt_config: promptConfigSchema, | |||||
| }); | |||||
| const form = useForm<z.infer<typeof formSchema>>({ | |||||
| resolver: zodResolver(formSchema), | |||||
| defaultValues: { | |||||
| name: '', | |||||
| language: 'English', | |||||
| prompt_config: { | |||||
| quote: true, | |||||
| keyword: false, | |||||
| tts: false, | |||||
| }, | |||||
| }, | |||||
| }); | |||||
| function onSubmit(values: z.infer<typeof formSchema>) { | |||||
| console.log(values); | |||||
| } | |||||
| return ( | |||||
| <section> | |||||
| <Subhead>Basic settings</Subhead> | |||||
| <Form {...form}> | |||||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="icon" | |||||
| render={({ field }) => ( | |||||
| <div className="space-y-6"> | |||||
| <FormItem className="w-full"> | |||||
| <FormLabel>{t('assistantAvatar')}</FormLabel> | |||||
| <FormControl> | |||||
| <FileUploader | |||||
| value={field.value} | |||||
| onValueChange={field.onChange} | |||||
| maxFileCount={1} | |||||
| maxSize={4 * 1024 * 1024} | |||||
| // progresses={progresses} | |||||
| // pass the onUpload function here for direct upload | |||||
| // onUpload={uploadFiles} | |||||
| // disabled={isUploading} | |||||
| /> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| </div> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="name" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('assistantName')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input {...field}></Input> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name="description" | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('description')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input {...field}></Input> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={'prompt_config.empty_response'} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('emptyResponse')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input {...field}></Input> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={'prompt_config.prologue'} | |||||
| render={({ field }) => ( | |||||
| <FormItem> | |||||
| <FormLabel>{t('setAnOpener')}</FormLabel> | |||||
| <FormControl> | |||||
| <Input {...field}></Input> | |||||
| </FormControl> | |||||
| <FormMessage /> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <SwitchFormField | |||||
| name={'prompt_config.quote'} | |||||
| label={t('quote')} | |||||
| ></SwitchFormField> | |||||
| <SwitchFormField | |||||
| name={'prompt_config.keyword'} | |||||
| label={t('keyword')} | |||||
| ></SwitchFormField> | |||||
| <SwitchFormField | |||||
| name={'prompt_config.tts'} | |||||
| label={t('tts')} | |||||
| ></SwitchFormField> | |||||
| <KnowledgeBaseFormField></KnowledgeBaseFormField> | |||||
| </form> | |||||
| </Form> | |||||
| </section> | |||||
| ); | |||||
| } |
| import { Subhead } from './subhead'; | |||||
| export function ChatModelSettings() { | |||||
| return ( | |||||
| <section> | |||||
| <Subhead>Model Setting</Subhead> | |||||
| </section> | |||||
| ); | |||||
| } |
| import { Subhead } from './subhead'; | |||||
| export function ChatPromptEngine() { | |||||
| return ( | |||||
| <section> | |||||
| <Subhead>Prompt Engine</Subhead> | |||||
| </section> | |||||
| ); | |||||
| } |
| import ChatBasicSetting from './chat-basic-settings'; | |||||
| import { ChatModelSettings } from './chat-model-settings'; | |||||
| import { ChatPromptEngine } from './chat-prompt-engine'; | |||||
| export function AppSettings() { | |||||
| return ( | |||||
| <section className="p-6 w-[500px] max-w-[25%]"> | |||||
| <div className="text-2xl font-bold mb-4 text-colors-text-neutral-strong"> | |||||
| App settings | |||||
| </div> | |||||
| <ChatBasicSetting></ChatBasicSetting> | |||||
| <ChatPromptEngine></ChatPromptEngine> | |||||
| <ChatModelSettings></ChatModelSettings> | |||||
| </section> | |||||
| ); | |||||
| } |
| import { PropsWithChildren } from 'react'; | |||||
| export function Subhead({ children }: PropsWithChildren) { | |||||
| return ( | |||||
| <div className="text-xl font-bold mb-4 text-colors-text-neutral-strong"> | |||||
| {children} | |||||
| </div> | |||||
| ); | |||||
| } |
| import { | |||||
| FormControl, | |||||
| FormField, | |||||
| FormItem, | |||||
| FormLabel, | |||||
| } from '@/components/ui/form'; | |||||
| import { Switch } from '@/components/ui/switch'; | |||||
| import { ReactNode } from 'react'; | |||||
| import { useFormContext } from 'react-hook-form'; | |||||
| interface SwitchFormItemProps { | |||||
| name: string; | |||||
| label: ReactNode; | |||||
| } | |||||
| export function SwitchFormField({ label, name }: SwitchFormItemProps) { | |||||
| const form = useFormContext(); | |||||
| return ( | |||||
| <FormField | |||||
| control={form.control} | |||||
| name={name} | |||||
| render={({ field }) => ( | |||||
| <FormItem className="flex justify-between"> | |||||
| <FormLabel className="text-base">{label}</FormLabel> | |||||
| <FormControl> | |||||
| <Switch | |||||
| checked={field.value} | |||||
| onCheckedChange={field.onChange} | |||||
| aria-readonly | |||||
| className="!m-0" | |||||
| /> | |||||
| </FormControl> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| ); | |||||
| } |