Feat: Add data set configuration form #3221 (#7646)

### 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)
This commit is contained in:
balibabu 2025-05-14 19:09:01 +08:00 committed by GitHub
parent 5d5dbb3bcb
commit 5218ff775c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 1762 additions and 90 deletions

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
)}
/>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -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>
);
}

View File

@ -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,
};
};

View File

@ -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)),
});
<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>
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,
},
});
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>
);
}

View File

@ -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>}
</>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>;
}