### What problem does this PR solve? Feat: Add data set configuration form #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.0
| @@ -0,0 +1,19 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { SliderInputFormField } from './slider-input-form-field'; | |||
| export function PageRankFormField() { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| return ( | |||
| <SliderInputFormField | |||
| name={'pagerank'} | |||
| label={t('pageRank')} | |||
| tooltip={t('pageRankTip')} | |||
| defaultValue={0} | |||
| max={100} | |||
| min={1} | |||
| ></SliderInputFormField> | |||
| ); | |||
| } | |||
| export default PageRankFormField; | |||
| @@ -1,12 +1,11 @@ | |||
| import { DocumentParserType } from '@/constants/knowledge'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { Switch as AntSwitch, Form, Select } from 'antd'; | |||
| import { upperFirst } from 'lodash'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { DatasetConfigurationContainer } from '../dataset-configuration-container'; | |||
| import EntityTypesItem from '../entity-types-item'; | |||
| import { useFormContext, useWatch } from 'react-hook-form'; | |||
| import { EntityTypesFormField } from '../entity-types-form-field'; | |||
| import { FormContainer } from '../form-container'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| @@ -14,6 +13,7 @@ import { | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '../ui/form'; | |||
| import { RAGFlowSelect } from '../ui/select'; | |||
| import { Switch } from '../ui/switch'; | |||
| const excludedTagParseMethods = [ | |||
| @@ -48,22 +48,6 @@ type GraphRagItemsProps = { | |||
| marginBottom?: boolean; | |||
| }; | |||
| export function UseGraphRagItem() { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| return ( | |||
| <Form.Item | |||
| name={['parser_config', 'graphrag', 'use_graphrag']} | |||
| label={t('useGraphRag')} | |||
| initialValue={false} | |||
| valuePropName="checked" | |||
| tooltip={t('useGraphRagTip')} | |||
| > | |||
| <AntSwitch /> | |||
| </Form.Item> | |||
| ); | |||
| } | |||
| export function UseGraphRagFormField() { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| @@ -93,6 +77,12 @@ export function UseGraphRagFormField() { | |||
| // The three types "table", "resume" and "one" do not display this configuration. | |||
| const GraphRagItems = ({ marginBottom = false }: GraphRagItemsProps) => { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const form = useFormContext(); | |||
| const useRaptor = useWatch({ | |||
| control: form.control, | |||
| name: 'parser_config.graphrag.use_graphrag', | |||
| }); | |||
| const methodOptions = useMemo(() => { | |||
| return [MethodValue.Light, MethodValue.General].map((x) => ({ | |||
| @@ -103,39 +93,23 @@ const GraphRagItems = ({ marginBottom = false }: GraphRagItemsProps) => { | |||
| const renderWideTooltip = useCallback( | |||
| (title: React.ReactNode | string) => { | |||
| return { | |||
| title: typeof title === 'string' ? t(title) : title, | |||
| overlayInnerStyle: { width: '32vw' }, | |||
| }; | |||
| return typeof title === 'string' ? t(title) : title; | |||
| }, | |||
| [t], | |||
| ); | |||
| return ( | |||
| <DatasetConfigurationContainer className={cn({ 'mb-4': marginBottom })}> | |||
| <UseGraphRagItem></UseGraphRagItem> | |||
| <Form.Item | |||
| shouldUpdate={(prevValues, curValues) => | |||
| prevValues.parser_config.graphrag.use_graphrag !== | |||
| curValues.parser_config.graphrag.use_graphrag | |||
| } | |||
| > | |||
| {({ getFieldValue }) => { | |||
| const useRaptor = getFieldValue([ | |||
| 'parser_config', | |||
| 'graphrag', | |||
| 'use_graphrag', | |||
| ]); | |||
| return ( | |||
| useRaptor && ( | |||
| <> | |||
| <EntityTypesItem | |||
| field={['parser_config', 'graphrag', 'entity_types']} | |||
| ></EntityTypesItem> | |||
| <Form.Item | |||
| name={['parser_config', 'graphrag', 'method']} | |||
| label={t('graphRagMethod')} | |||
| <FormContainer className={cn({ 'mb-4': marginBottom })}> | |||
| <UseGraphRagFormField></UseGraphRagFormField> | |||
| {useRaptor && ( | |||
| <> | |||
| <EntityTypesFormField name="parser_config.graphrag.entity_types"></EntityTypesFormField> | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_config.graphrag.method" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel | |||
| tooltip={renderWideTooltip( | |||
| <div | |||
| dangerouslySetInnerHTML={{ | |||
| @@ -143,30 +117,60 @@ const GraphRagItems = ({ marginBottom = false }: GraphRagItemsProps) => { | |||
| }} | |||
| ></div>, | |||
| )} | |||
| initialValue={MethodValue.Light} | |||
| > | |||
| <Select options={methodOptions} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name={['parser_config', 'graphrag', 'resolution']} | |||
| label={t('resolution')} | |||
| tooltip={renderWideTooltip('resolutionTip')} | |||
| > | |||
| <AntSwitch /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name={['parser_config', 'graphrag', 'community']} | |||
| label={t('community')} | |||
| tooltip={renderWideTooltip('communityTip')} | |||
| > | |||
| <AntSwitch /> | |||
| </Form.Item> | |||
| </> | |||
| ) | |||
| ); | |||
| }} | |||
| </Form.Item> | |||
| </DatasetConfigurationContainer> | |||
| {t('graphRagMethod')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <RAGFlowSelect | |||
| {...field} | |||
| options={methodOptions} | |||
| ></RAGFlowSelect> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_config.graphrag.resolution" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={renderWideTooltip('resolutionTip')}> | |||
| {t('resolution')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <Switch | |||
| checked={field.value} | |||
| onCheckedChange={field.onChange} | |||
| ></Switch> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_config.graphrag.community" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={renderWideTooltip('communityTip')}> | |||
| {t('community')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <Switch | |||
| checked={field.value} | |||
| onCheckedChange={field.onChange} | |||
| ></Switch> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </> | |||
| )} | |||
| </FormContainer> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,77 @@ | |||
| import SvgIcon from '@/components/svg-icon'; | |||
| 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 styles from './index.less'; | |||
| import { TagTabs } from './tag-tabs'; | |||
| import { ImageMap } from './utils'; | |||
| const { 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 ? ( | |||
| <> | |||
| <h5 className="font-semibold text-base mt-0 mb-1"> | |||
| {`"${item.title}" ${t('methodTitle')}`} | |||
| </h5> | |||
| <p | |||
| dangerouslySetInnerHTML={{ | |||
| __html: DOMPurify.sanitize(item.description), | |||
| }} | |||
| ></p> | |||
| <h5 className="font-semibold text-base mt-4 mb-1">{`"${item.title}" ${t('methodExamples')}`}</h5> | |||
| <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> | |||
| <h5 className="font-semibold text-base mt-4 mb-1"> | |||
| {item.title} {t('dialogueExamplesTitle')} | |||
| </h5> | |||
| <Divider></Divider> | |||
| </> | |||
| ) : ( | |||
| <Empty description={''} image={null}> | |||
| <p>{t('methodEmpty')}</p> | |||
| <SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon> | |||
| </Empty> | |||
| )} | |||
| {chunkMethod === 'tag' && <TagTabs></TagTabs>} | |||
| </section> | |||
| ); | |||
| }; | |||
| export default CategoryPanel; | |||
| @@ -0,0 +1,78 @@ | |||
| import { useFormContext, useWatch } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { DocumentParserType } from '@/constants/knowledge'; | |||
| import { useMemo, useState } from 'react'; | |||
| import { AudioConfiguration } from './configuration/audio'; | |||
| import { BookConfiguration } from './configuration/book'; | |||
| import { EmailConfiguration } from './configuration/email'; | |||
| import { KnowledgeGraphConfiguration } from './configuration/knowledge-graph'; | |||
| import { LawsConfiguration } from './configuration/laws'; | |||
| import { ManualConfiguration } from './configuration/manual'; | |||
| import { NaiveConfiguration } from './configuration/naive'; | |||
| import { OneConfiguration } from './configuration/one'; | |||
| import { PaperConfiguration } from './configuration/paper'; | |||
| import { PictureConfiguration } from './configuration/picture'; | |||
| import { PresentationConfiguration } from './configuration/presentation'; | |||
| import { QAConfiguration } from './configuration/qa'; | |||
| import { ResumeConfiguration } from './configuration/resume'; | |||
| import { TableConfiguration } from './configuration/table'; | |||
| import { TagConfiguration } from './configuration/tag'; | |||
| import { useFetchKnowledgeConfigurationOnMount } from './hooks'; | |||
| const ConfigurationComponentMap = { | |||
| [DocumentParserType.Naive]: NaiveConfiguration, | |||
| [DocumentParserType.Qa]: QAConfiguration, | |||
| [DocumentParserType.Resume]: ResumeConfiguration, | |||
| [DocumentParserType.Manual]: ManualConfiguration, | |||
| [DocumentParserType.Table]: TableConfiguration, | |||
| [DocumentParserType.Paper]: PaperConfiguration, | |||
| [DocumentParserType.Book]: BookConfiguration, | |||
| [DocumentParserType.Laws]: LawsConfiguration, | |||
| [DocumentParserType.Presentation]: PresentationConfiguration, | |||
| [DocumentParserType.Picture]: PictureConfiguration, | |||
| [DocumentParserType.One]: OneConfiguration, | |||
| [DocumentParserType.Audio]: AudioConfiguration, | |||
| [DocumentParserType.Email]: EmailConfiguration, | |||
| [DocumentParserType.Tag]: TagConfiguration, | |||
| [DocumentParserType.KnowledgeGraph]: KnowledgeGraphConfiguration, | |||
| }; | |||
| function EmptyComponent() { | |||
| return <div></div>; | |||
| } | |||
| export function ChunkMethodForm() { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslation(); | |||
| const [finalParserId, setFinalParserId] = useState<DocumentParserType>( | |||
| DocumentParserType.Naive, | |||
| ); | |||
| const knowledgeDetails = useFetchKnowledgeConfigurationOnMount(form); | |||
| const parserId: DocumentParserType = useWatch({ | |||
| control: form.control, | |||
| name: 'parser_id', | |||
| }); | |||
| const ConfigurationComponent = useMemo(() => { | |||
| return finalParserId | |||
| ? ConfigurationComponentMap[finalParserId] | |||
| : EmptyComponent; | |||
| }, [finalParserId]); | |||
| // useEffect(() => { | |||
| // setFinalParserId(parserId); | |||
| // }, [parserId]); | |||
| // useEffect(() => { | |||
| // setFinalParserId(knowledgeDetails.parser_id as DocumentParserType); | |||
| // }, [knowledgeDetails.parser_id]); | |||
| return ( | |||
| <section> | |||
| <ConfigurationComponent></ConfigurationComponent> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function AudioConfiguration() { | |||
| return ( | |||
| <> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| <GraphRagItems marginBottom></GraphRagItems> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function BookConfiguration() { | |||
| return ( | |||
| <> | |||
| <LayoutRecognizeFormField></LayoutRecognizeFormField> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| <GraphRagItems marginBottom></GraphRagItems> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,75 @@ | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { RAGFlowSelect } from '@/components/ui/select'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { | |||
| useHasParsedDocument, | |||
| useSelectChunkMethodList, | |||
| useSelectEmbeddingModelOptions, | |||
| } from '../hooks'; | |||
| export function ChunkMethodItem() { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const form = useFormContext(); | |||
| // const handleChunkMethodSelectChange = useHandleChunkMethodSelectChange(form); | |||
| const parserList = useSelectChunkMethodList(); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name={'parser_id'} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('chunkMethodTip')}> | |||
| {t('chunkMethod')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <RAGFlowSelect | |||
| {...field} | |||
| options={parserList} | |||
| placeholder={t('chunkMethodPlaceholder')} | |||
| // onChange={handleChunkMethodSelectChange} | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| export function EmbeddingModelItem() { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const form = useFormContext(); | |||
| const embeddingModelOptions = useSelectEmbeddingModelOptions(); | |||
| const disabled = useHasParsedDocument(); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name={'embd_id'} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('embeddingModelTip')}> | |||
| {t('embeddingModel')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <RAGFlowSelect | |||
| {...field} | |||
| options={embeddingModelOptions} | |||
| disabled={disabled} | |||
| placeholder={t('embeddingModelPlaceholder')} | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function EmailConfiguration() { | |||
| return ( | |||
| <> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| <GraphRagItems marginBottom></GraphRagItems> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| import { DelimiterFormField } from '@/components/delimiter-form-field'; | |||
| import { EntityTypesFormField } from '@/components/entity-types-form-field'; | |||
| import { MaxTokenNumberFormField } from '@/components/max-token-number-from-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function KnowledgeGraphConfiguration() { | |||
| return ( | |||
| <> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <EntityTypesFormField></EntityTypesFormField> | |||
| <MaxTokenNumberFormField max={8192 * 2}></MaxTokenNumberFormField> | |||
| <DelimiterFormField></DelimiterFormField> | |||
| </> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function LawsConfiguration() { | |||
| return ( | |||
| <> | |||
| <LayoutRecognizeFormField></LayoutRecognizeFormField> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| <GraphRagItems marginBottom></GraphRagItems> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function ManualConfiguration() { | |||
| return ( | |||
| <> | |||
| <LayoutRecognizeFormField></LayoutRecognizeFormField> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| <GraphRagItems marginBottom></GraphRagItems> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import { DelimiterFormField } from '@/components/delimiter-form-field'; | |||
| import { ExcelToHtmlFormField } from '@/components/excel-to-html-form-field'; | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; | |||
| import { MaxTokenNumberFormField } from '@/components/max-token-number-from-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function NaiveConfiguration() { | |||
| return ( | |||
| <section className="space-y-5 mb-4 overflow-auto"> | |||
| <FormContainer> | |||
| <LayoutRecognizeFormField></LayoutRecognizeFormField> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <MaxTokenNumberFormField></MaxTokenNumberFormField> | |||
| <DelimiterFormField></DelimiterFormField> | |||
| </FormContainer> | |||
| <FormContainer> | |||
| <PageRankFormField></PageRankFormField> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| <ExcelToHtmlFormField></ExcelToHtmlFormField> | |||
| <TagItems></TagItems> | |||
| </FormContainer> | |||
| <FormContainer> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| </FormContainer> | |||
| <GraphRagItems></GraphRagItems> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,30 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function OneConfiguration() { | |||
| return ( | |||
| <> | |||
| <LayoutRecognizeFormField></LayoutRecognizeFormField> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <GraphRagItems marginBottom></GraphRagItems> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function PaperConfiguration() { | |||
| return ( | |||
| <> | |||
| <LayoutRecognizeFormField></LayoutRecognizeFormField> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| <GraphRagItems marginBottom></GraphRagItems> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function PictureConfiguration() { | |||
| return ( | |||
| <> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| import { | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '@/components/auto-keywords-form-field'; | |||
| import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import GraphRagItems from '@/components/parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields from '@/components/parse-configuration/raptor-form-fields'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function PresentationConfiguration() { | |||
| return ( | |||
| <> | |||
| <LayoutRecognizeFormField></LayoutRecognizeFormField> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| <GraphRagItems marginBottom></GraphRagItems> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function QAConfiguration() { | |||
| return ( | |||
| <> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import { TagItems } from '../tag-item'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function ResumeConfiguration() { | |||
| return ( | |||
| <> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| <TagItems></TagItems> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function TableConfiguration() { | |||
| return ( | |||
| <> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| import PageRankFormField from '@/components/page-rank-form-field'; | |||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||
| export function TagConfiguration() { | |||
| return ( | |||
| <> | |||
| <EmbeddingModelItem></EmbeddingModelItem> | |||
| <ChunkMethodItem></ChunkMethodItem> | |||
| <PageRankFormField></PageRankFormField> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,108 @@ | |||
| import { FileUploader } from '@/components/file-uploader'; | |||
| import { FormContainer } from '@/components/form-container'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| export function GeneralForm() { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <FormContainer className="space-y-2"> | |||
| <FormField | |||
| control={form.control} | |||
| name="name" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('knowledgeConfiguration.name')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field}></Input> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="description" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('knowledgeConfiguration.description')}</FormLabel> | |||
| <FormControl> | |||
| <Input {...field}></Input> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="avatar" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('knowledgeConfiguration.photo')}</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> | |||
| )} | |||
| /> | |||
| <FormField | |||
| control={form.control} | |||
| name="type" | |||
| render={({ field }) => ( | |||
| <FormItem className="space-y-3"> | |||
| <FormLabel tooltip={t('knowledgeConfiguration.permissionsTip')}> | |||
| {t('knowledgeConfiguration.permissions')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <RadioGroup | |||
| onValueChange={field.onChange} | |||
| defaultValue={field.value} | |||
| className="flex flex-col space-y-1" | |||
| > | |||
| <FormItem className="flex items-center space-x-3 space-y-0"> | |||
| <FormControl> | |||
| <RadioGroupItem value="me" /> | |||
| </FormControl> | |||
| <FormLabel className="font-normal"> | |||
| {t('knowledgeConfiguration.me')} | |||
| </FormLabel> | |||
| </FormItem> | |||
| <FormItem className="flex items-center space-x-3 space-y-0"> | |||
| <FormControl> | |||
| <RadioGroupItem value="team" /> | |||
| </FormControl> | |||
| <FormLabel className="font-normal"> | |||
| {t('knowledgeConfiguration.team')} | |||
| </FormLabel> | |||
| </FormItem> | |||
| </RadioGroup> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </FormContainer> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,123 @@ | |||
| import { LlmModelType } from '@/constants/knowledge'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks'; | |||
| import { useNavigateToDataset } from '@/hooks/route-hook'; | |||
| import { | |||
| useFetchKnowledgeBaseConfiguration, | |||
| useUpdateKnowledge, | |||
| } from '@/hooks/use-knowledge-request'; | |||
| import { useSelectParserList } from '@/hooks/user-setting-hooks'; | |||
| import { | |||
| getBase64FromUploadFileList, | |||
| getUploadFileListFromBase64, | |||
| } from '@/utils/file-util'; | |||
| import { useIsFetching } from '@tanstack/react-query'; | |||
| import { Form, UploadFile } from 'antd'; | |||
| import { FormInstance } from 'antd/lib'; | |||
| import pick from 'lodash/pick'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import { UseFormReturn } from 'react-hook-form'; | |||
| export const useSubmitKnowledgeConfiguration = (form: FormInstance) => { | |||
| const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge(); | |||
| const navigateToDataset = useNavigateToDataset(); | |||
| const submitKnowledgeConfiguration = useCallback(async () => { | |||
| const values = await form.validateFields(); | |||
| const avatar = await getBase64FromUploadFileList(values.avatar); | |||
| saveKnowledgeConfiguration({ | |||
| ...values, | |||
| avatar, | |||
| }); | |||
| navigateToDataset(); | |||
| }, [saveKnowledgeConfiguration, form, navigateToDataset]); | |||
| return { | |||
| submitKnowledgeConfiguration, | |||
| submitLoading: loading, | |||
| navigateToDataset, | |||
| }; | |||
| }; | |||
| // The value that does not need to be displayed in the analysis method Select | |||
| const HiddenFields = ['email', 'picture', 'audio']; | |||
| export function useSelectChunkMethodList() { | |||
| const parserList = useSelectParserList(); | |||
| return parserList.filter((x) => !HiddenFields.some((y) => y === x.value)); | |||
| } | |||
| export function useSelectEmbeddingModelOptions() { | |||
| const allOptions = useSelectLlmOptionsByModelType(); | |||
| return allOptions[LlmModelType.Embedding]; | |||
| } | |||
| export function useHasParsedDocument() { | |||
| const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration(); | |||
| return knowledgeDetails.chunk_num > 0; | |||
| } | |||
| export const useFetchKnowledgeConfigurationOnMount = (form: UseFormReturn) => { | |||
| const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration(); | |||
| useEffect(() => { | |||
| const fileList: UploadFile[] = getUploadFileListFromBase64( | |||
| knowledgeDetails.avatar, | |||
| ); | |||
| form.reset({ | |||
| ...pick(knowledgeDetails, [ | |||
| 'description', | |||
| 'name', | |||
| 'permission', | |||
| 'embd_id', | |||
| 'parser_id', | |||
| 'language', | |||
| 'parser_config', | |||
| 'pagerank', | |||
| ]), | |||
| avatar: fileList, | |||
| }); | |||
| }, [form, knowledgeDetails]); | |||
| return knowledgeDetails; | |||
| }; | |||
| export const useSelectKnowledgeDetailsLoading = () => | |||
| useIsFetching({ queryKey: ['fetchKnowledgeDetail'] }) > 0; | |||
| export const useHandleChunkMethodChange = () => { | |||
| const [form] = Form.useForm(); | |||
| const chunkMethod = Form.useWatch('parser_id', form); | |||
| useEffect(() => { | |||
| console.log('🚀 ~ useHandleChunkMethodChange ~ chunkMethod:', chunkMethod); | |||
| }, [chunkMethod]); | |||
| return { form, chunkMethod }; | |||
| }; | |||
| export const useRenameKnowledgeTag = () => { | |||
| const [tag, setTag] = useState<string>(''); | |||
| const { | |||
| visible: tagRenameVisible, | |||
| hideModal: hideTagRenameModal, | |||
| showModal: showFileRenameModal, | |||
| } = useSetModalState(); | |||
| const handleShowTagRenameModal = useCallback( | |||
| (record: string) => { | |||
| setTag(record); | |||
| showFileRenameModal(); | |||
| }, | |||
| [showFileRenameModal], | |||
| ); | |||
| return { | |||
| initialName: tag, | |||
| tagRenameVisible, | |||
| hideTagRenameModal, | |||
| showTagRenameModal: handleShowTagRenameModal, | |||
| }; | |||
| }; | |||
| @@ -1,25 +1,137 @@ | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| import AdvancedSettingForm from './advanced-setting-form'; | |||
| import BasicSettingForm from './basic-setting-form'; | |||
| import { Form } from '@/components/ui/form'; | |||
| import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; | |||
| import { DocumentParserType } from '@/constants/knowledge'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import CategoryPanel from './category-panel'; | |||
| import { ChunkMethodForm } from './chunk-method-form'; | |||
| import { GeneralForm } from './general-form'; | |||
| const enum DocumentType { | |||
| DeepDOC = 'DeepDOC', | |||
| PlainText = 'Plain Text', | |||
| } | |||
| const initialEntityTypes = [ | |||
| 'organization', | |||
| 'person', | |||
| 'geo', | |||
| 'event', | |||
| 'category', | |||
| ]; | |||
| const enum MethodValue { | |||
| General = 'general', | |||
| Light = 'light', | |||
| } | |||
| export default function DatasetSettings() { | |||
| return ( | |||
| <section className="p-8 overflow-y-scroll max-h-[90vh]"> | |||
| <div className="text-3xl font-bold pb-6">Basic settings</div> | |||
| <Card className="border-0 p-6 bg-colors-background-inverse-weak"> | |||
| <CardContent> | |||
| <div className="w-2/5"> | |||
| <BasicSettingForm></BasicSettingForm> | |||
| </div> | |||
| </CardContent> | |||
| </Card> | |||
| const formSchema = z.object({ | |||
| name: z.string().min(1, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| description: z.string().min(2, { | |||
| message: 'Username must be at least 2 characters.', | |||
| }), | |||
| avatar: z.instanceof(File), | |||
| permission: z.string(), | |||
| parser_id: z.string(), | |||
| parser_config: z.object({ | |||
| layout_recognize: z.string(), | |||
| chunk_token_num: z.number(), | |||
| delimiter: z.string(), | |||
| auto_keywords: z.number(), | |||
| auto_questions: z.number(), | |||
| html4excel: z.boolean(), | |||
| tag_kb_ids: z.array(z.string()), | |||
| topn_tags: z.number(), | |||
| raptor: z.object({ | |||
| use_raptor: z.boolean(), | |||
| prompt: z.string(), | |||
| max_token: z.number(), | |||
| threshold: z.number(), | |||
| max_cluster: z.number(), | |||
| random_seed: z.number(), | |||
| }), | |||
| graphrag: z.object({ | |||
| use_graphrag: z.boolean(), | |||
| entity_types: z.array(z.string()), | |||
| method: z.string(), | |||
| resolution: z.boolean(), | |||
| community: z.boolean(), | |||
| }), | |||
| }), | |||
| pagerank: z.number(), | |||
| // icon: z.array(z.instanceof(File)), | |||
| }); | |||
| const form = useForm<z.infer<typeof formSchema>>({ | |||
| resolver: zodResolver(formSchema), | |||
| defaultValues: { | |||
| name: '', | |||
| parser_id: DocumentParserType.Naive, | |||
| permission: 'me', | |||
| parser_config: { | |||
| layout_recognize: DocumentType.DeepDOC, | |||
| chunk_token_num: 512, | |||
| delimiter: `\n`, | |||
| auto_keywords: 0, | |||
| auto_questions: 0, | |||
| html4excel: false, | |||
| topn_tags: 3, | |||
| raptor: { | |||
| use_raptor: false, | |||
| max_token: 256, | |||
| threshold: 0.1, | |||
| max_cluster: 64, | |||
| random_seed: 0, | |||
| }, | |||
| graphrag: { | |||
| use_graphrag: false, | |||
| entity_types: initialEntityTypes, | |||
| method: MethodValue.Light, | |||
| }, | |||
| }, | |||
| pagerank: 0, | |||
| }, | |||
| }); | |||
| <div className="text-3xl font-bold pb-6 pt-8">Advanced settings</div> | |||
| <Card className="border-0 p-6 bg-colors-background-inverse-weak"> | |||
| <CardContent> | |||
| <AdvancedSettingForm></AdvancedSettingForm> | |||
| </CardContent> | |||
| </Card> | |||
| async function onSubmit(data: z.infer<typeof formSchema>) { | |||
| console.log('🚀 ~ DatasetSettings ~ data:', data); | |||
| } | |||
| return ( | |||
| <section className="p-5 "> | |||
| <div className="pb-5"> | |||
| <div className="text-2xl font-semibold">Configuration</div> | |||
| <p className="text-text-sub-title pt-2"> | |||
| Update your knowledge base configuration here, particularly the chunk | |||
| method. | |||
| </p> | |||
| </div> | |||
| <div className="flex gap-14"> | |||
| <Form {...form}> | |||
| <form | |||
| onSubmit={form.handleSubmit(onSubmit)} | |||
| className="space-y-6 basis-full" | |||
| > | |||
| <Tabs defaultValue="account"> | |||
| <TabsList className="grid w-full grid-cols-2"> | |||
| <TabsTrigger value="account">Account</TabsTrigger> | |||
| <TabsTrigger value="password">Password</TabsTrigger> | |||
| </TabsList> | |||
| <TabsContent value="account"> | |||
| <GeneralForm></GeneralForm> | |||
| </TabsContent> | |||
| <TabsContent value="password"> | |||
| <ChunkMethodForm></ChunkMethodForm> | |||
| </TabsContent> | |||
| </Tabs> | |||
| </form> | |||
| </Form> | |||
| <CategoryPanel chunkMethod={DocumentParserType.Naive}></CategoryPanel> | |||
| </div> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,144 @@ | |||
| import { SliderInputFormField } from '@/components/slider-input-form-field'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { MultiSelect } from '@/components/ui/multi-select'; | |||
| import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks'; | |||
| import { UserOutlined } from '@ant-design/icons'; | |||
| import { Avatar, Flex, Form, InputNumber, Select, Slider, Space } from 'antd'; | |||
| import DOMPurify from 'dompurify'; | |||
| import { useFormContext, useWatch } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| export const TagSetItem = () => { | |||
| const { t } = useTranslation(); | |||
| const form = useFormContext(); | |||
| const { list: knowledgeList } = useFetchKnowledgeList(true); | |||
| const knowledgeOptions = knowledgeList | |||
| .filter((x) => x.parser_id === 'tag') | |||
| .map((x) => ({ | |||
| label: x.name, | |||
| value: x.id, | |||
| icon: () => ( | |||
| <Space> | |||
| <Avatar size={20} icon={<UserOutlined />} src={x.avatar} /> | |||
| {x.name} | |||
| </Space> | |||
| ), | |||
| })); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_config.tag_kb_ids" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel | |||
| tooltip={ | |||
| <div | |||
| dangerouslySetInnerHTML={{ | |||
| __html: DOMPurify.sanitize( | |||
| t('knowledgeConfiguration.tagSetTip'), | |||
| ), | |||
| }} | |||
| ></div> | |||
| } | |||
| > | |||
| {t('knowledgeConfiguration.tagSet')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <MultiSelect | |||
| options={knowledgeOptions} | |||
| onValueChange={field.onChange} | |||
| placeholder={t('chat.knowledgeBasesMessage')} | |||
| variant="inverted" | |||
| maxCount={0} | |||
| {...field} | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| return ( | |||
| <Form.Item | |||
| label={t('knowledgeConfiguration.tagSet')} | |||
| name={['parser_config', 'tag_kb_ids']} | |||
| tooltip={ | |||
| <div | |||
| dangerouslySetInnerHTML={{ | |||
| __html: DOMPurify.sanitize(t('knowledgeConfiguration.tagSetTip')), | |||
| }} | |||
| ></div> | |||
| } | |||
| rules={[ | |||
| { | |||
| message: t('chat.knowledgeBasesMessage'), | |||
| type: 'array', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| mode="multiple" | |||
| options={knowledgeOptions} | |||
| placeholder={t('chat.knowledgeBasesMessage')} | |||
| ></Select> | |||
| </Form.Item> | |||
| ); | |||
| }; | |||
| export const TopNTagsItem = () => { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| <SliderInputFormField | |||
| name={'parser_config.topn_tags'} | |||
| label={t('knowledgeConfiguration.topnTags')} | |||
| max={10} | |||
| min={1} | |||
| defaultValue={3} | |||
| ></SliderInputFormField> | |||
| ); | |||
| return ( | |||
| <Form.Item label={t('knowledgeConfiguration.topnTags')}> | |||
| <Flex gap={20} align="center"> | |||
| <Flex flex={1}> | |||
| <Form.Item | |||
| name={['parser_config', 'topn_tags']} | |||
| noStyle | |||
| initialValue={3} | |||
| > | |||
| <Slider max={10} min={1} style={{ width: '100%' }} /> | |||
| </Form.Item> | |||
| </Flex> | |||
| <Form.Item name={['parser_config', 'topn_tags']} noStyle> | |||
| <InputNumber max={10} min={1} /> | |||
| </Form.Item> | |||
| </Flex> | |||
| </Form.Item> | |||
| ); | |||
| }; | |||
| export function TagItems() { | |||
| const form = useFormContext(); | |||
| const ids: string[] = useWatch({ | |||
| control: form.control, | |||
| name: 'parser_config.tag_kb_ids', | |||
| }); | |||
| return ( | |||
| <> | |||
| <TagSetItem></TagSetItem> | |||
| {Array.isArray(ids) && ids.length > 0 && <TopNTagsItem></TopNTagsItem>} | |||
| </> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,307 @@ | |||
| 'use client'; | |||
| import { | |||
| ColumnDef, | |||
| ColumnFiltersState, | |||
| SortingState, | |||
| VisibilityState, | |||
| flexRender, | |||
| getCoreRowModel, | |||
| getFilteredRowModel, | |||
| getPaginationRowModel, | |||
| getSortedRowModel, | |||
| useReactTable, | |||
| } from '@tanstack/react-table'; | |||
| import { ArrowUpDown, Pencil, Trash2 } from 'lucide-react'; | |||
| import * as React from 'react'; | |||
| import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Checkbox } from '@/components/ui/checkbox'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { | |||
| Table, | |||
| TableBody, | |||
| TableCell, | |||
| TableHead, | |||
| TableHeader, | |||
| TableRow, | |||
| } from '@/components/ui/table'; | |||
| import { | |||
| Tooltip, | |||
| TooltipContent, | |||
| TooltipProvider, | |||
| TooltipTrigger, | |||
| } from '@/components/ui/tooltip'; | |||
| import { useDeleteTag, useFetchTagList } from '@/hooks/knowledge-hooks'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useRenameKnowledgeTag } from '../hooks'; | |||
| import { RenameDialog } from './rename-dialog'; | |||
| export type ITag = { | |||
| tag: string; | |||
| frequency: number; | |||
| }; | |||
| export function TagTable() { | |||
| const { t } = useTranslation(); | |||
| const { list } = useFetchTagList(); | |||
| const [tagList, setTagList] = useState<ITag[]>([]); | |||
| const [sorting, setSorting] = React.useState<SortingState>([]); | |||
| const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>( | |||
| [], | |||
| ); | |||
| const [columnVisibility, setColumnVisibility] = | |||
| React.useState<VisibilityState>({}); | |||
| const [rowSelection, setRowSelection] = useState({}); | |||
| const { deleteTag } = useDeleteTag(); | |||
| useEffect(() => { | |||
| setTagList(list.map((x) => ({ tag: x[0], frequency: x[1] }))); | |||
| }, [list]); | |||
| const handleDeleteTag = useCallback( | |||
| (tags: string[]) => () => { | |||
| deleteTag(tags); | |||
| }, | |||
| [deleteTag], | |||
| ); | |||
| const { | |||
| showTagRenameModal, | |||
| hideTagRenameModal, | |||
| tagRenameVisible, | |||
| initialName, | |||
| } = useRenameKnowledgeTag(); | |||
| const columns: ColumnDef<ITag>[] = [ | |||
| { | |||
| id: 'select', | |||
| header: ({ table }) => ( | |||
| <Checkbox | |||
| checked={ | |||
| table.getIsAllPageRowsSelected() || | |||
| (table.getIsSomePageRowsSelected() && 'indeterminate') | |||
| } | |||
| onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} | |||
| aria-label="Select all" | |||
| /> | |||
| ), | |||
| cell: ({ row }) => ( | |||
| <Checkbox | |||
| checked={row.getIsSelected()} | |||
| onCheckedChange={(value) => row.toggleSelected(!!value)} | |||
| aria-label="Select row" | |||
| /> | |||
| ), | |||
| enableSorting: false, | |||
| enableHiding: false, | |||
| }, | |||
| { | |||
| accessorKey: 'tag', | |||
| header: ({ column }) => { | |||
| return ( | |||
| <Button | |||
| variant="ghost" | |||
| onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} | |||
| > | |||
| {t('knowledgeConfiguration.tagName')} | |||
| <ArrowUpDown /> | |||
| </Button> | |||
| ); | |||
| }, | |||
| cell: ({ row }) => { | |||
| const value: string = row.getValue('tag'); | |||
| return <div>{value}</div>; | |||
| }, | |||
| }, | |||
| { | |||
| accessorKey: 'frequency', | |||
| header: ({ column }) => { | |||
| return ( | |||
| <Button | |||
| variant="ghost" | |||
| onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')} | |||
| > | |||
| {t('knowledgeConfiguration.frequency')} | |||
| <ArrowUpDown /> | |||
| </Button> | |||
| ); | |||
| }, | |||
| cell: ({ row }) => ( | |||
| <div className="capitalize ">{row.getValue('frequency')}</div> | |||
| ), | |||
| }, | |||
| { | |||
| id: 'actions', | |||
| enableHiding: false, | |||
| header: t('common.action'), | |||
| cell: ({ row }) => { | |||
| return ( | |||
| <div className="flex gap-1"> | |||
| <Tooltip> | |||
| <ConfirmDeleteDialog onOk={handleDeleteTag([row.original.tag])}> | |||
| <TooltipTrigger asChild> | |||
| <Button variant="ghost" size="icon"> | |||
| <Trash2 /> | |||
| </Button> | |||
| </TooltipTrigger> | |||
| </ConfirmDeleteDialog> | |||
| <TooltipContent> | |||
| <p>{t('common.delete')}</p> | |||
| </TooltipContent> | |||
| </Tooltip> | |||
| <Tooltip> | |||
| <TooltipTrigger asChild> | |||
| <Button | |||
| variant="ghost" | |||
| size="icon" | |||
| onClick={() => showTagRenameModal(row.original.tag)} | |||
| > | |||
| <Pencil /> | |||
| </Button> | |||
| </TooltipTrigger> | |||
| <TooltipContent> | |||
| <p>{t('common.rename')}</p> | |||
| </TooltipContent> | |||
| </Tooltip> | |||
| </div> | |||
| ); | |||
| }, | |||
| }, | |||
| ]; | |||
| const table = useReactTable({ | |||
| data: tagList, | |||
| columns, | |||
| onSortingChange: setSorting, | |||
| onColumnFiltersChange: setColumnFilters, | |||
| getCoreRowModel: getCoreRowModel(), | |||
| getPaginationRowModel: getPaginationRowModel(), | |||
| getSortedRowModel: getSortedRowModel(), | |||
| getFilteredRowModel: getFilteredRowModel(), | |||
| onColumnVisibilityChange: setColumnVisibility, | |||
| onRowSelectionChange: setRowSelection, | |||
| state: { | |||
| sorting, | |||
| columnFilters, | |||
| columnVisibility, | |||
| rowSelection, | |||
| }, | |||
| }); | |||
| const selectedRowLength = table.getFilteredSelectedRowModel().rows.length; | |||
| return ( | |||
| <TooltipProvider> | |||
| <div className="w-full"> | |||
| <div className="flex items-center justify-between py-4 "> | |||
| <Input | |||
| placeholder={t('knowledgeConfiguration.searchTags')} | |||
| value={(table.getColumn('tag')?.getFilterValue() as string) ?? ''} | |||
| onChange={(event) => | |||
| table.getColumn('tag')?.setFilterValue(event.target.value) | |||
| } | |||
| className="w-1/2" | |||
| /> | |||
| {selectedRowLength > 0 && ( | |||
| <ConfirmDeleteDialog | |||
| onOk={handleDeleteTag( | |||
| table | |||
| .getFilteredSelectedRowModel() | |||
| .rows.map((x) => x.original.tag), | |||
| )} | |||
| > | |||
| <Button variant="outline" size="icon"> | |||
| <Trash2 /> | |||
| </Button> | |||
| </ConfirmDeleteDialog> | |||
| )} | |||
| </div> | |||
| <div className="rounded-md border"> | |||
| <Table> | |||
| <TableHeader> | |||
| {table.getHeaderGroups().map((headerGroup) => ( | |||
| <TableRow key={headerGroup.id}> | |||
| {headerGroup.headers.map((header) => { | |||
| return ( | |||
| <TableHead key={header.id}> | |||
| {header.isPlaceholder | |||
| ? null | |||
| : flexRender( | |||
| header.column.columnDef.header, | |||
| header.getContext(), | |||
| )} | |||
| </TableHead> | |||
| ); | |||
| })} | |||
| </TableRow> | |||
| ))} | |||
| </TableHeader> | |||
| <TableBody> | |||
| {table.getRowModel().rows?.length ? ( | |||
| table.getRowModel().rows.map((row) => ( | |||
| <TableRow | |||
| key={row.id} | |||
| data-state={row.getIsSelected() && 'selected'} | |||
| > | |||
| {row.getVisibleCells().map((cell) => ( | |||
| <TableCell key={cell.id}> | |||
| {flexRender( | |||
| cell.column.columnDef.cell, | |||
| cell.getContext(), | |||
| )} | |||
| </TableCell> | |||
| ))} | |||
| </TableRow> | |||
| )) | |||
| ) : ( | |||
| <TableRow> | |||
| <TableCell | |||
| colSpan={columns.length} | |||
| className="h-24 text-center" | |||
| > | |||
| No results. | |||
| </TableCell> | |||
| </TableRow> | |||
| )} | |||
| </TableBody> | |||
| </Table> | |||
| </div> | |||
| <div className="flex items-center justify-end space-x-2 py-4"> | |||
| <div className="flex-1 text-sm text-muted-foreground"> | |||
| {selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '} | |||
| row(s) selected. | |||
| </div> | |||
| <div className="space-x-2"> | |||
| <Button | |||
| variant="outline" | |||
| size="sm" | |||
| onClick={() => table.previousPage()} | |||
| disabled={!table.getCanPreviousPage()} | |||
| > | |||
| {t('common.previousPage')} | |||
| </Button> | |||
| <Button | |||
| variant="outline" | |||
| size="sm" | |||
| onClick={() => table.nextPage()} | |||
| disabled={!table.getCanNextPage()} | |||
| > | |||
| {t('common.nextPage')} | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {tagRenameVisible && ( | |||
| <RenameDialog | |||
| hideModal={hideTagRenameModal} | |||
| initialName={initialName} | |||
| ></RenameDialog> | |||
| )} | |||
| </TooltipProvider> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| import { | |||
| Dialog, | |||
| DialogContent, | |||
| DialogFooter, | |||
| DialogHeader, | |||
| DialogTitle, | |||
| } from '@/components/ui/dialog'; | |||
| import { LoadingButton } from '@/components/ui/loading-button'; | |||
| import { useTagIsRenaming } from '@/hooks/knowledge-hooks'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { TagRenameId } from '@/pages/add-knowledge/constant'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { RenameForm } from './rename-form'; | |||
| export function RenameDialog({ | |||
| hideModal, | |||
| initialName, | |||
| }: IModalProps<any> & { initialName: string }) { | |||
| const { t } = useTranslation(); | |||
| const loading = useTagIsRenaming(); | |||
| return ( | |||
| <Dialog open onOpenChange={hideModal}> | |||
| <DialogContent className="sm:max-w-[425px]"> | |||
| <DialogHeader> | |||
| <DialogTitle>{t('common.rename')}</DialogTitle> | |||
| </DialogHeader> | |||
| <RenameForm | |||
| initialName={initialName} | |||
| hideModal={hideModal} | |||
| ></RenameForm> | |||
| <DialogFooter> | |||
| <LoadingButton type="submit" form={TagRenameId} loading={loading}> | |||
| {t('common.save')} | |||
| </LoadingButton> | |||
| </DialogFooter> | |||
| </DialogContent> | |||
| </Dialog> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,83 @@ | |||
| 'use client'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { | |||
| Form, | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { Input } from '@/components/ui/input'; | |||
| import { useRenameTag } from '@/hooks/knowledge-hooks'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { TagRenameId } from '@/pages/add-knowledge/constant'; | |||
| import { useEffect } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| export function RenameForm({ | |||
| initialName, | |||
| hideModal, | |||
| }: IModalProps<any> & { initialName: string }) { | |||
| const { t } = useTranslation(); | |||
| const FormSchema = z.object({ | |||
| name: z | |||
| .string() | |||
| .min(1, { | |||
| message: t('common.namePlaceholder'), | |||
| }) | |||
| .trim(), | |||
| }); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| resolver: zodResolver(FormSchema), | |||
| defaultValues: { | |||
| name: '', | |||
| }, | |||
| }); | |||
| const { renameTag } = useRenameTag(); | |||
| async function onSubmit(data: z.infer<typeof FormSchema>) { | |||
| const ret = await renameTag({ fromTag: initialName, toTag: data.name }); | |||
| if (ret) { | |||
| hideModal?.(); | |||
| } | |||
| } | |||
| useEffect(() => { | |||
| form.setValue('name', initialName); | |||
| }, [form, initialName]); | |||
| return ( | |||
| <Form {...form}> | |||
| <form | |||
| onSubmit={form.handleSubmit(onSubmit)} | |||
| className="space-y-6" | |||
| id={TagRenameId} | |||
| > | |||
| <FormField | |||
| control={form.control} | |||
| name="name" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('common.name')}</FormLabel> | |||
| <FormControl> | |||
| <Input | |||
| placeholder={t('common.namePlaceholder')} | |||
| {...field} | |||
| autoComplete="off" | |||
| /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </form> | |||
| </Form> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| import { Segmented } from 'antd'; | |||
| import { SegmentedLabeledOption } from 'antd/es/segmented'; | |||
| import { upperFirst } from 'lodash'; | |||
| import { useState } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { TagTable } from './tag-table'; | |||
| import { TagWordCloud } from './tag-word-cloud'; | |||
| enum TagType { | |||
| Cloud = 'cloud', | |||
| Table = 'table', | |||
| } | |||
| const TagContentMap = { | |||
| [TagType.Cloud]: <TagWordCloud></TagWordCloud>, | |||
| [TagType.Table]: <TagTable></TagTable>, | |||
| }; | |||
| export function TagTabs() { | |||
| const [value, setValue] = useState<TagType>(TagType.Cloud); | |||
| const { t } = useTranslation(); | |||
| const options: SegmentedLabeledOption[] = [TagType.Cloud, TagType.Table].map( | |||
| (x) => ({ | |||
| label: t(`knowledgeConfiguration.tag${upperFirst(x)}`), | |||
| value: x, | |||
| }), | |||
| ); | |||
| return ( | |||
| <section className="mt-4"> | |||
| <Segmented | |||
| value={value} | |||
| options={options} | |||
| onChange={(val) => setValue(val as TagType)} | |||
| /> | |||
| {TagContentMap[value]} | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,62 @@ | |||
| import { useFetchTagList } from '@/hooks/knowledge-hooks'; | |||
| import { Chart } from '@antv/g2'; | |||
| import { sumBy } from 'lodash'; | |||
| import { useCallback, useEffect, useMemo, useRef } from 'react'; | |||
| export function TagWordCloud() { | |||
| const domRef = useRef<HTMLDivElement>(null); | |||
| let chartRef = useRef<Chart>(); | |||
| const { list } = useFetchTagList(); | |||
| const { list: tagList } = useMemo(() => { | |||
| const nextList = list.sort((a, b) => b[1] - a[1]).slice(0, 256); | |||
| return { | |||
| list: nextList.map((x) => ({ text: x[0], value: x[1], name: x[0] })), | |||
| sumValue: sumBy(nextList, (x: [string, number]) => x[1]), | |||
| length: nextList.length, | |||
| }; | |||
| }, [list]); | |||
| const renderWordCloud = useCallback(() => { | |||
| if (domRef.current) { | |||
| chartRef.current = new Chart({ container: domRef.current }); | |||
| chartRef.current.options({ | |||
| type: 'wordCloud', | |||
| autoFit: true, | |||
| layout: { | |||
| fontSize: [10, 50], | |||
| // fontSize: (d: any) => { | |||
| // if (d.value) { | |||
| // return (d.value / sumValue) * 100 * (length / 10); | |||
| // } | |||
| // return 0; | |||
| // }, | |||
| }, | |||
| data: { | |||
| type: 'inline', | |||
| value: tagList, | |||
| }, | |||
| encode: { color: 'text' }, | |||
| legend: false, | |||
| tooltip: { | |||
| title: 'name', // title | |||
| items: ['value'], // data item | |||
| }, | |||
| }); | |||
| chartRef.current.render(); | |||
| } | |||
| }, [tagList]); | |||
| useEffect(() => { | |||
| renderWordCloud(); | |||
| return () => { | |||
| chartRef.current?.destroy(); | |||
| }; | |||
| }, [renderWordCloud]); | |||
| return <div ref={domRef} className="w-full h-[38vh]"></div>; | |||
| } | |||