mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Add MultiSelect #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -23,9 +23,10 @@ import {
|
||||
} from '@/components/ui/select';
|
||||
import { FormSlider } from '@/components/ui/slider';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import ChunkMethodCard from './chunk-method-card';
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.number().min(2, {
|
||||
parser_id: z.string().min(1, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
a: z.number().min(2, {
|
||||
@ -46,7 +47,7 @@ export default function AdvancedSettingForm() {
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
username: 0,
|
||||
parser_id: '',
|
||||
},
|
||||
});
|
||||
|
||||
@ -59,9 +60,9 @@ export default function AdvancedSettingForm() {
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
name="a"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormItem className="w-2/5">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<FormSlider {...field}></FormSlider>
|
||||
@ -73,11 +74,12 @@ export default function AdvancedSettingForm() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<ChunkMethodCard></ChunkMethodCard>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="a"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormItem className="w-2/5">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<FormSlider {...field}></FormSlider>
|
||||
@ -93,7 +95,7 @@ export default function AdvancedSettingForm() {
|
||||
control={form.control}
|
||||
name="b"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormItem className="w-2/5">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
@ -118,7 +120,7 @@ export default function AdvancedSettingForm() {
|
||||
control={form.control}
|
||||
name="c"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormItem className="w-2/5">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<FormSlider {...field}></FormSlider>
|
||||
@ -134,7 +136,7 @@ export default function AdvancedSettingForm() {
|
||||
control={form.control}
|
||||
name="d"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormItem className="w-2/5">
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
@ -153,7 +155,7 @@ export default function AdvancedSettingForm() {
|
||||
variant={'tertiary'}
|
||||
size={'sm'}
|
||||
type="submit"
|
||||
className="w-full"
|
||||
className="w-2/5"
|
||||
>
|
||||
Test
|
||||
</Button>
|
||||
|
||||
@ -4,16 +4,16 @@ import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
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 { MultiSelect } from '@/components/ui/multi-select';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@ -21,34 +21,48 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { FormSlider } from '@/components/ui/slider';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { Cat, Dog, Fish, Rabbit, Turtle } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
const formSchema = z.object({
|
||||
username: z.number().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
a: z.number().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
b: z.string().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
c: z.number().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
d: z.string().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
});
|
||||
const frameworksList = [
|
||||
{ value: 'react', label: 'React', icon: Turtle },
|
||||
{ value: 'angular', label: 'Angular', icon: Cat },
|
||||
{ value: 'vue', label: 'Vue', icon: Dog },
|
||||
{ value: 'svelte', label: 'Svelte', icon: Rabbit },
|
||||
{ value: 'ember', label: 'Ember', icon: Fish },
|
||||
];
|
||||
|
||||
export default function BasicSettingForm() {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
|
||||
const formSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
a: z.number().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
language: z.string().min(1, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
c: z.number().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
d: z.string().min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
}),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
username: 0,
|
||||
name: '',
|
||||
language: 'English',
|
||||
},
|
||||
});
|
||||
const [selectedFrameworks, setSelectedFrameworks] = useState<string[]>([
|
||||
'react',
|
||||
'angular',
|
||||
]);
|
||||
|
||||
function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
console.log(values);
|
||||
@ -59,42 +73,42 @@ export default function BasicSettingForm() {
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="username"
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormControl>
|
||||
<FormSlider {...field}></FormSlider>
|
||||
<Input
|
||||
{...field}
|
||||
className="bg-colors-background-inverse-weak"
|
||||
></Input>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="a"
|
||||
name="d"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<FormSlider {...field}></FormSlider>
|
||||
<Input
|
||||
{...field}
|
||||
className="bg-colors-background-inverse-weak"
|
||||
></Input>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="b"
|
||||
name="language"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormLabel>{t('language')}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-colors-background-inverse-weak">
|
||||
@ -107,9 +121,6 @@ export default function BasicSettingForm() {
|
||||
<SelectItem value="m@support.com">m@support.com</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@ -121,42 +132,20 @@ export default function BasicSettingForm() {
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<FormSlider {...field}></FormSlider>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="d"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Username</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
<MultiSelect
|
||||
options={frameworksList}
|
||||
onValueChange={setSelectedFrameworks}
|
||||
defaultValue={selectedFrameworks}
|
||||
placeholder="Select frameworks"
|
||||
variant="inverted"
|
||||
maxCount={100}
|
||||
{...field}
|
||||
className="bg-colors-background-inverse-weak"
|
||||
></Textarea>
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is your public display name.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
variant={'tertiary'}
|
||||
size={'sm'}
|
||||
type="submit"
|
||||
className="w-full"
|
||||
>
|
||||
Test
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
|
||||
124
web/src/pages/dataset/setting/chunk-method-card.tsx
Normal file
124
web/src/pages/dataset/setting/chunk-method-card.tsx
Normal file
@ -0,0 +1,124 @@
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useSelectParserList } from '@/hooks/user-setting-hooks';
|
||||
import { Col, Divider, Empty, Row, Typography } from 'antd';
|
||||
import DOMPurify from 'dompurify';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
import { useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import styles from './index.less';
|
||||
import { ImageMap } from './utils';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
|
||||
const parserList = useSelectParserList();
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
|
||||
const item = useMemo(() => {
|
||||
const item = parserList.find((x) => x.value === chunkMethod);
|
||||
if (item) {
|
||||
return {
|
||||
title: item.label,
|
||||
description: t(camelCase(item.value)),
|
||||
};
|
||||
}
|
||||
return { title: '', description: '' };
|
||||
}, [parserList, chunkMethod, t]);
|
||||
|
||||
const imageList = useMemo(() => {
|
||||
if (chunkMethod in ImageMap) {
|
||||
return ImageMap[chunkMethod as keyof typeof ImageMap];
|
||||
}
|
||||
return [];
|
||||
}, [chunkMethod]);
|
||||
|
||||
return (
|
||||
<section className={styles.categoryPanelWrapper}>
|
||||
{imageList.length > 0 ? (
|
||||
<>
|
||||
<Title level={5} className={styles.topTitle}>
|
||||
{`"${item.title}" ${t('methodTitle')}`}
|
||||
</Title>
|
||||
<p
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(item.description),
|
||||
}}
|
||||
></p>
|
||||
<Title level={5}>{`"${item.title}" ${t('methodExamples')}`}</Title>
|
||||
<Text>{t('methodExamplesDescription')}</Text>
|
||||
<Row gutter={[10, 10]} className={styles.imageRow}>
|
||||
{imageList.map((x) => (
|
||||
<Col span={12} key={x}>
|
||||
<SvgIcon
|
||||
name={x}
|
||||
width={'100%'}
|
||||
className={styles.image}
|
||||
></SvgIcon>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<Title level={5}>
|
||||
{item.title} {t('dialogueExamplesTitle')}
|
||||
</Title>
|
||||
<Divider></Divider>
|
||||
</>
|
||||
) : (
|
||||
<Empty description={''} image={null}>
|
||||
<p>{t('methodEmpty')}</p>
|
||||
<SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon>
|
||||
</Empty>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default function ChunkMethodCard() {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const form = useFormContext();
|
||||
|
||||
return (
|
||||
<Card className="border-0 p-6 mb-8 bg-colors-background-inverse-weak flex">
|
||||
<div className="w-2/5">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="parser_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('chunkMethod')}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-colors-background-inverse-weak">
|
||||
<SelectValue placeholder="Select a verified email to display" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="m@example.com">m@example.com</SelectItem>
|
||||
<SelectItem value="m@google.com">m@google.com</SelectItem>
|
||||
<SelectItem value="m@support.com">m@support.com</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<CategoryPanel chunkMethod=""></CategoryPanel>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
45
web/src/pages/dataset/setting/index.less
Normal file
45
web/src/pages/dataset/setting/index.less
Normal file
@ -0,0 +1,45 @@
|
||||
.tags {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.preset {
|
||||
display: flex;
|
||||
height: 80px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 100px;
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.4);
|
||||
margin: 10px 0px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.configurationWrapper {
|
||||
padding: 0 52px;
|
||||
.buttonWrapper {
|
||||
text-align: right;
|
||||
}
|
||||
.variableSlider {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.categoryPanelWrapper {
|
||||
.topTitle {
|
||||
margin-top: 0;
|
||||
}
|
||||
.imageRow {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@ -14,9 +14,7 @@ export default function DatasetSettings() {
|
||||
|
||||
<div className="text-3xl font-bold mb-6">Advanced settings</div>
|
||||
<Card className="border-0 p-6 mb-8 bg-colors-background-inverse-weak">
|
||||
<div className="w-2/5">
|
||||
<AdvancedSettingForm></AdvancedSettingForm>
|
||||
</div>
|
||||
<AdvancedSettingForm></AdvancedSettingForm>
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
|
||||
19
web/src/pages/dataset/setting/utils.ts
Normal file
19
web/src/pages/dataset/setting/utils.ts
Normal file
@ -0,0 +1,19 @@
|
||||
const getImageName = (prefix: string, length: number) =>
|
||||
new Array(length)
|
||||
.fill(0)
|
||||
.map((x, idx) => `chunk-method/${prefix}-0${idx + 1}`);
|
||||
|
||||
export const ImageMap = {
|
||||
book: getImageName('book', 4),
|
||||
laws: getImageName('law', 2),
|
||||
manual: getImageName('manual', 4),
|
||||
picture: getImageName('media', 2),
|
||||
naive: getImageName('naive', 2),
|
||||
paper: getImageName('paper', 2),
|
||||
presentation: getImageName('presentation', 2),
|
||||
qa: getImageName('qa', 2),
|
||||
resume: getImageName('resume', 2),
|
||||
table: getImageName('table', 2),
|
||||
one: getImageName('one', 2),
|
||||
knowledge_graph: getImageName('knowledge-graph', 2),
|
||||
};
|
||||
@ -1,4 +1,4 @@
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { useIsDarkTheme, useTheme } from '@/components/theme-provider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
@ -52,6 +52,7 @@ export function SideBar() {
|
||||
const pathName = useSecondPathName();
|
||||
const { handleMenuClick } = useHandleMenuClick();
|
||||
const { setTheme } = useTheme();
|
||||
const isDarkTheme = useIsDarkTheme();
|
||||
|
||||
const handleThemeChange = useCallback(
|
||||
(checked: boolean) => {
|
||||
@ -89,7 +90,11 @@ export function SideBar() {
|
||||
|
||||
<div className="p-6 mt-auto border-t">
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<Switch id="dark-mode" onCheckedChange={handleThemeChange} />
|
||||
<Switch
|
||||
id="dark-mode"
|
||||
onCheckedChange={handleThemeChange}
|
||||
checked={isDarkTheme}
|
||||
/>
|
||||
<Label htmlFor="dark-mode" className="text-sm">
|
||||
Dark
|
||||
</Label>
|
||||
|
||||
Reference in New Issue
Block a user