### 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
| 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; |
| import { DocumentParserType } from '@/constants/knowledge'; | import { DocumentParserType } from '@/constants/knowledge'; | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { cn } from '@/lib/utils'; | import { cn } from '@/lib/utils'; | ||||
| import { Switch as AntSwitch, Form, Select } from 'antd'; | |||||
| import { upperFirst } from 'lodash'; | import { upperFirst } from 'lodash'; | ||||
| import { useCallback, useMemo } from 'react'; | 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 { | import { | ||||
| FormControl, | FormControl, | ||||
| FormField, | FormField, | ||||
| FormLabel, | FormLabel, | ||||
| FormMessage, | FormMessage, | ||||
| } from '../ui/form'; | } from '../ui/form'; | ||||
| import { RAGFlowSelect } from '../ui/select'; | |||||
| import { Switch } from '../ui/switch'; | import { Switch } from '../ui/switch'; | ||||
| const excludedTagParseMethods = [ | const excludedTagParseMethods = [ | ||||
| marginBottom?: boolean; | 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() { | export function UseGraphRagFormField() { | ||||
| const form = useFormContext(); | const form = useFormContext(); | ||||
| const { t } = useTranslate('knowledgeConfiguration'); | const { t } = useTranslate('knowledgeConfiguration'); | ||||
| // The three types "table", "resume" and "one" do not display this configuration. | // The three types "table", "resume" and "one" do not display this configuration. | ||||
| const GraphRagItems = ({ marginBottom = false }: GraphRagItemsProps) => { | const GraphRagItems = ({ marginBottom = false }: GraphRagItemsProps) => { | ||||
| const { t } = useTranslate('knowledgeConfiguration'); | const { t } = useTranslate('knowledgeConfiguration'); | ||||
| const form = useFormContext(); | |||||
| const useRaptor = useWatch({ | |||||
| control: form.control, | |||||
| name: 'parser_config.graphrag.use_graphrag', | |||||
| }); | |||||
| const methodOptions = useMemo(() => { | const methodOptions = useMemo(() => { | ||||
| return [MethodValue.Light, MethodValue.General].map((x) => ({ | return [MethodValue.Light, MethodValue.General].map((x) => ({ | ||||
| const renderWideTooltip = useCallback( | const renderWideTooltip = useCallback( | ||||
| (title: React.ReactNode | string) => { | (title: React.ReactNode | string) => { | ||||
| return { | |||||
| title: typeof title === 'string' ? t(title) : title, | |||||
| overlayInnerStyle: { width: '32vw' }, | |||||
| }; | |||||
| return typeof title === 'string' ? t(title) : title; | |||||
| }, | }, | ||||
| [t], | [t], | ||||
| ); | ); | ||||
| return ( | 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( | tooltip={renderWideTooltip( | ||||
| <div | <div | ||||
| dangerouslySetInnerHTML={{ | dangerouslySetInnerHTML={{ | ||||
| }} | }} | ||||
| ></div>, | ></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> | |||||
| ); | ); | ||||
| }; | }; | ||||
| 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; |
| 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> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| )} | |||||
| /> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| </> | |||||
| ); | |||||
| } |
| import PageRankFormField from '@/components/page-rank-form-field'; | |||||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||||
| export function TableConfiguration() { | |||||
| return ( | |||||
| <> | |||||
| <EmbeddingModelItem></EmbeddingModelItem> | |||||
| <ChunkMethodItem></ChunkMethodItem> | |||||
| <PageRankFormField></PageRankFormField> | |||||
| </> | |||||
| ); | |||||
| } |
| import PageRankFormField from '@/components/page-rank-form-field'; | |||||
| import { ChunkMethodItem, EmbeddingModelItem } from './common-item'; | |||||
| export function TagConfiguration() { | |||||
| return ( | |||||
| <> | |||||
| <EmbeddingModelItem></EmbeddingModelItem> | |||||
| <ChunkMethodItem></ChunkMethodItem> | |||||
| <PageRankFormField></PageRankFormField> | |||||
| </> | |||||
| ); | |||||
| } |
| 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> | |||||
| ); | |||||
| } |
| 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, | |||||
| }; | |||||
| }; |
| 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() { | 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> | </section> | ||||
| ); | ); | ||||
| } | } |
| 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>} | |||||
| </> | |||||
| ); | |||||
| } |
| '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> | |||||
| ); | |||||
| } |
| 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> | |||||
| ); | |||||
| } |
| '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> | |||||
| ); | |||||
| } |
| 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> | |||||
| ); | |||||
| } |
| 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>; | |||||
| } |