### What problem does this PR solve? Feat: Display the document configuration dialog with shadcn #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.0
| @@ -0,0 +1,30 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { SliderInputFormField } from './slider-input-form-field'; | |||
| export function AutoKeywordsFormField() { | |||
| const { t } = useTranslate('knowledgeDetails'); | |||
| return ( | |||
| <SliderInputFormField | |||
| name={'parser_config.auto_keywords'} | |||
| label={t('autoKeywords')} | |||
| max={30} | |||
| min={0} | |||
| tooltip={t('autoKeywordsTip')} | |||
| ></SliderInputFormField> | |||
| ); | |||
| } | |||
| export function AutoQuestionsFormField() { | |||
| const { t } = useTranslate('knowledgeDetails'); | |||
| return ( | |||
| <SliderInputFormField | |||
| name={'parser_config.auto_questions'} | |||
| label={t('autoQuestions')} | |||
| max={10} | |||
| min={0} | |||
| tooltip={t('autoQuestionsTip')} | |||
| ></SliderInputFormField> | |||
| ); | |||
| } | |||
| @@ -14,21 +14,42 @@ import { | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '@/components/ui/form'; | |||
| import { DocumentParserType } from '@/constants/knowledge'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { IParserConfig } from '@/interfaces/database/document'; | |||
| import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; | |||
| import { zodResolver } from '@hookform/resolvers/zod'; | |||
| import { useForm } from 'react-hook-form'; | |||
| import {} from 'module'; | |||
| import { useMemo } from 'react'; | |||
| import { useForm, useWatch } from 'react-hook-form'; | |||
| import { z } from 'zod'; | |||
| import { | |||
| Select, | |||
| SelectContent, | |||
| SelectItem, | |||
| SelectTrigger, | |||
| SelectValue, | |||
| } from '../ui/select'; | |||
| import { useFetchParserListOnMount } from './hooks'; | |||
| AutoKeywordsFormField, | |||
| AutoQuestionsFormField, | |||
| } from '../auto-keywords-form-field'; | |||
| import { DatasetConfigurationContainer } from '../dataset-configuration-container'; | |||
| import { DelimiterFormField } from '../delimiter-form-field'; | |||
| import { EntityTypesFormField } from '../entity-types-form-field'; | |||
| import { ExcelToHtmlFormField } from '../excel-to-html-form-field'; | |||
| import { | |||
| DocumentType, | |||
| LayoutRecognizeFormField, | |||
| } from '../layout-recognize-form-field'; | |||
| import { MaxTokenNumberFormField } from '../max-token-number-from-field'; | |||
| import { | |||
| UseGraphRagFormField, | |||
| showGraphRagItems, | |||
| } from '../parse-configuration/graph-rag-form-fields'; | |||
| import RaptorFormFields, { | |||
| showRaptorParseConfiguration, | |||
| } from '../parse-configuration/raptor-form-fields'; | |||
| import { Input } from '../ui/input'; | |||
| import { RAGFlowSelect } from '../ui/select'; | |||
| import { useFetchParserListOnMount, useShowAutoKeywords } from './hooks'; | |||
| const FormId = 'ChunkMethodDialogForm'; | |||
| interface IProps | |||
| extends IModalProps<{ | |||
| @@ -42,6 +63,15 @@ interface IProps | |||
| documentId: string; | |||
| } | |||
| const hidePagesChunkMethods = [ | |||
| DocumentParserType.Qa, | |||
| DocumentParserType.Table, | |||
| DocumentParserType.Picture, | |||
| DocumentParserType.Resume, | |||
| DocumentParserType.One, | |||
| DocumentParserType.KnowledgeGraph, | |||
| ]; | |||
| export function ChunkMethodDialog({ | |||
| hideModal, | |||
| onOk, | |||
| @@ -58,68 +88,171 @@ export function ChunkMethodDialog({ | |||
| // form, | |||
| ); | |||
| const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration(); | |||
| const useGraphRag = useMemo(() => { | |||
| return knowledgeDetails.parser_config?.graphrag?.use_graphrag; | |||
| }, [knowledgeDetails.parser_config?.graphrag?.use_graphrag]); | |||
| const FormSchema = z.object({ | |||
| name: z | |||
| parser_id: z | |||
| .string() | |||
| .min(1, { | |||
| message: 'namePlaceholder', | |||
| }) | |||
| .trim(), | |||
| parser_config: z.object({ | |||
| task_page_size: z.coerce.number(), | |||
| layout_recognize: z.string(), | |||
| }), | |||
| }); | |||
| const form = useForm<z.infer<typeof FormSchema>>({ | |||
| resolver: zodResolver(FormSchema), | |||
| defaultValues: { name: '' }, | |||
| defaultValues: { | |||
| parser_id: parserId, | |||
| parser_config: { | |||
| task_page_size: 12, | |||
| layout_recognize: DocumentType.DeepDOC, | |||
| }, | |||
| }, | |||
| }); | |||
| const layoutRecognize = useWatch({ | |||
| name: 'parser_config.layout_recognize', | |||
| control: form.control, | |||
| }); | |||
| const selectedTag = useWatch({ | |||
| name: 'parser_id', | |||
| control: form.control, | |||
| }); | |||
| const isPdf = documentExtension === 'pdf'; | |||
| const showPages = useMemo(() => { | |||
| return isPdf && hidePagesChunkMethods.every((x) => x !== selectedTag); | |||
| }, [selectedTag, isPdf]); | |||
| const showOne = useMemo(() => { | |||
| return ( | |||
| isPdf && | |||
| hidePagesChunkMethods | |||
| .filter((x) => x !== DocumentParserType.One) | |||
| .every((x) => x !== selectedTag) | |||
| ); | |||
| }, [selectedTag, isPdf]); | |||
| const showMaxTokenNumber = | |||
| selectedTag === DocumentParserType.Naive || | |||
| selectedTag === DocumentParserType.KnowledgeGraph; | |||
| const showEntityTypes = selectedTag === DocumentParserType.KnowledgeGraph; | |||
| const showExcelToHtml = | |||
| selectedTag === DocumentParserType.Naive && documentExtension === 'xlsx'; | |||
| const showAutoKeywords = useShowAutoKeywords(); | |||
| async function onSubmit(data: z.infer<typeof FormSchema>) { | |||
| const ret = await onOk?.(); | |||
| if (ret) { | |||
| hideModal?.(); | |||
| } | |||
| console.log('🚀 ~ onSubmit ~ data:', data); | |||
| // const ret = await onOk?.(); | |||
| // if (ret) { | |||
| // hideModal?.(); | |||
| // } | |||
| } | |||
| return ( | |||
| <Dialog open onOpenChange={hideModal}> | |||
| <DialogContent> | |||
| <DialogContent className="max-w-[50vw]"> | |||
| <DialogHeader> | |||
| <DialogTitle>{t('chunkMethod')}</DialogTitle> | |||
| </DialogHeader> | |||
| <Form {...form}> | |||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> | |||
| <form | |||
| onSubmit={form.handleSubmit(onSubmit)} | |||
| className="space-y-6" | |||
| id={FormId} | |||
| > | |||
| <FormField | |||
| control={form.control} | |||
| name="name" | |||
| name="parser_id" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('name')}</FormLabel> | |||
| <FormControl> | |||
| <Select | |||
| <RAGFlowSelect | |||
| {...field} | |||
| autoComplete="off" | |||
| onValueChange={field.onChange} | |||
| > | |||
| <FormControl> | |||
| <SelectTrigger> | |||
| <SelectValue placeholder="Select a verified email to display" /> | |||
| </SelectTrigger> | |||
| </FormControl> | |||
| <SelectContent> | |||
| {parserList.map((x) => ( | |||
| <SelectItem value={x.value} key={x.value}> | |||
| {x.label} | |||
| </SelectItem> | |||
| ))} | |||
| </SelectContent> | |||
| </Select> | |||
| options={parserList} | |||
| ></RAGFlowSelect> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| {showPages && layoutRecognize && ( | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_config.task_page_size" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('taskPageSizeTip')}> | |||
| {t('taskPageSize')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <Input | |||
| {...field} | |||
| type={'number'} | |||
| min={1} | |||
| max={128} | |||
| ></Input> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| )} | |||
| <DatasetConfigurationContainer show={showOne || showMaxTokenNumber}> | |||
| {showOne && <LayoutRecognizeFormField></LayoutRecognizeFormField>} | |||
| {showMaxTokenNumber && ( | |||
| <> | |||
| <MaxTokenNumberFormField | |||
| max={ | |||
| selectedTag === DocumentParserType.KnowledgeGraph | |||
| ? 8192 * 2 | |||
| : 2048 | |||
| } | |||
| ></MaxTokenNumberFormField> | |||
| <DelimiterFormField></DelimiterFormField> | |||
| </> | |||
| )} | |||
| </DatasetConfigurationContainer> | |||
| <DatasetConfigurationContainer | |||
| show={showAutoKeywords(selectedTag) || showExcelToHtml} | |||
| > | |||
| {showAutoKeywords(selectedTag) && ( | |||
| <> | |||
| <AutoKeywordsFormField></AutoKeywordsFormField> | |||
| <AutoQuestionsFormField></AutoQuestionsFormField> | |||
| </> | |||
| )} | |||
| {showExcelToHtml && <ExcelToHtmlFormField></ExcelToHtmlFormField>} | |||
| </DatasetConfigurationContainer> | |||
| {showRaptorParseConfiguration( | |||
| selectedTag as DocumentParserType, | |||
| ) && ( | |||
| <DatasetConfigurationContainer> | |||
| <RaptorFormFields></RaptorFormFields> | |||
| </DatasetConfigurationContainer> | |||
| )} | |||
| {showGraphRagItems(selectedTag as DocumentParserType) && | |||
| useGraphRag && <UseGraphRagFormField></UseGraphRagFormField>} | |||
| {showEntityTypes && <EntityTypesFormField></EntityTypesFormField>} | |||
| </form> | |||
| </Form> | |||
| <DialogFooter> | |||
| <Button type="submit">Save changes</Button> | |||
| <Button type="submit" form={FormId}> | |||
| Save changes | |||
| </Button> | |||
| </DialogFooter> | |||
| </DialogContent> | |||
| </Dialog> | |||
| @@ -0,0 +1,59 @@ | |||
| import { forwardRef } from 'react'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from './ui/form'; | |||
| import { Input, InputProps } from './ui/input'; | |||
| interface IProps { | |||
| value?: string | undefined; | |||
| onChange?: (val: string | undefined) => void; | |||
| } | |||
| export const DelimiterInput = forwardRef<HTMLInputElement, InputProps & IProps>( | |||
| ({ value, onChange, maxLength, defaultValue }, ref) => { | |||
| const nextValue = value?.replaceAll('\n', '\\n'); | |||
| const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||
| const val = e.target.value; | |||
| const nextValue = val.replaceAll('\\n', '\n'); | |||
| onChange?.(nextValue); | |||
| }; | |||
| return ( | |||
| <Input | |||
| value={nextValue} | |||
| onChange={handleInputChange} | |||
| maxLength={maxLength} | |||
| defaultValue={defaultValue} | |||
| ref={ref} | |||
| ></Input> | |||
| ); | |||
| }, | |||
| ); | |||
| export function DelimiterFormField() { | |||
| const { t } = useTranslation(); | |||
| const form = useFormContext(); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name={'parser_config.delimiter'} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('knowledgeDetails.delimiterTip')}> | |||
| {t('knowledgeDetails.delimiter')} | |||
| </FormLabel> | |||
| <FormControl defaultValue={`\n`}> | |||
| <DelimiterInput {...field}></DelimiterInput> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,37 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import EditTag from './edit-tag'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from './ui/form'; | |||
| type EntityTypesFormFieldProps = { | |||
| name?: string; | |||
| }; | |||
| export function EntityTypesFormField({ | |||
| name = 'parser_config.entity_types', | |||
| }: EntityTypesFormFieldProps) { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const form = useFormContext(); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name={name} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('entityTypes')}</FormLabel> | |||
| <FormControl> | |||
| <EditTag {...field}></EditTag> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,34 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from './ui/form'; | |||
| import { Switch } from './ui/switch'; | |||
| export function ExcelToHtmlFormField() { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslate('knowledgeDetails'); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_config.html4excel" | |||
| render={({ field }) => ( | |||
| <FormItem defaultChecked={false}> | |||
| <FormLabel tooltip={t('html4excelTip')}>{t('html4excel')}</FormLabel> | |||
| <FormControl> | |||
| <Switch | |||
| checked={field.value} | |||
| onCheckedChange={field.onChange} | |||
| ></Switch> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,70 @@ | |||
| import { LlmModelType } from '@/constants/knowledge'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks'; | |||
| import { camelCase } from 'lodash'; | |||
| import { useMemo } from 'react'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from './ui/form'; | |||
| import { RAGFlowSelect } from './ui/select'; | |||
| export const enum DocumentType { | |||
| DeepDOC = 'DeepDOC', | |||
| PlainText = 'Plain Text', | |||
| } | |||
| export function LayoutRecognizeFormField() { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslate('knowledgeDetails'); | |||
| const allOptions = useSelectLlmOptionsByModelType(); | |||
| const options = useMemo(() => { | |||
| const list = [DocumentType.DeepDOC, DocumentType.PlainText].map((x) => ({ | |||
| label: x === DocumentType.PlainText ? t(camelCase(x)) : 'DeepDoc', | |||
| value: x, | |||
| })); | |||
| const image2TextList = allOptions[LlmModelType.Image2text].map((x) => { | |||
| return { | |||
| ...x, | |||
| options: x.options.map((y) => { | |||
| return { | |||
| ...y, | |||
| label: ( | |||
| <div className="flex justify-between items-center gap-2"> | |||
| {y.label} | |||
| <span className="text-red-500 text-sm">Experimental</span> | |||
| </div> | |||
| ), | |||
| }; | |||
| }), | |||
| }; | |||
| }); | |||
| return [...list, ...image2TextList]; | |||
| }, [allOptions, t]); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_config.layout_recognize" | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('layoutRecognizeTip')}> | |||
| {t('layoutRecognize')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <RAGFlowSelect {...field} options={options}></RAGFlowSelect> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { SliderInputFormField } from './slider-input-form-field'; | |||
| interface IProps { | |||
| initialValue?: number; | |||
| max?: number; | |||
| } | |||
| export function MaxTokenNumberFormField({ max = 2048 }: IProps) { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| return ( | |||
| <SliderInputFormField | |||
| name={'parser_config.chunk_token_num'} | |||
| label={t('chunkTokenNumber')} | |||
| max={max} | |||
| ></SliderInputFormField> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,173 @@ | |||
| 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 { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '../ui/form'; | |||
| import { Switch } from '../ui/switch'; | |||
| const excludedTagParseMethods = [ | |||
| DocumentParserType.Table, | |||
| DocumentParserType.KnowledgeGraph, | |||
| DocumentParserType.Tag, | |||
| ]; | |||
| export const showTagItems = (parserId: DocumentParserType) => { | |||
| return !excludedTagParseMethods.includes(parserId); | |||
| }; | |||
| const enum MethodValue { | |||
| General = 'general', | |||
| Light = 'light', | |||
| } | |||
| export const excludedParseMethods = [ | |||
| DocumentParserType.Table, | |||
| DocumentParserType.Resume, | |||
| DocumentParserType.Picture, | |||
| DocumentParserType.KnowledgeGraph, | |||
| DocumentParserType.Qa, | |||
| DocumentParserType.Tag, | |||
| ]; | |||
| export const showGraphRagItems = (parserId: DocumentParserType | undefined) => { | |||
| return !excludedParseMethods.some((x) => x === parserId); | |||
| }; | |||
| 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'); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name="parser_config.graphrag.use_graphrag" | |||
| render={({ field }) => ( | |||
| <FormItem defaultChecked={false}> | |||
| <FormLabel tooltip={t('useGraphRagTip')}> | |||
| {t('useGraphRag')} | |||
| </FormLabel> | |||
| <FormControl> | |||
| <Switch | |||
| checked={field.value} | |||
| onCheckedChange={field.onChange} | |||
| ></Switch> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| // The three types "table", "resume" and "one" do not display this configuration. | |||
| const GraphRagItems = ({ marginBottom = false }: GraphRagItemsProps) => { | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const methodOptions = useMemo(() => { | |||
| return [MethodValue.Light, MethodValue.General].map((x) => ({ | |||
| value: x, | |||
| label: upperFirst(x), | |||
| })); | |||
| }, []); | |||
| const renderWideTooltip = useCallback( | |||
| (title: React.ReactNode | string) => { | |||
| return { | |||
| title: typeof title === 'string' ? t(title) : title, | |||
| overlayInnerStyle: { width: '32vw' }, | |||
| }; | |||
| }, | |||
| [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')} | |||
| tooltip={renderWideTooltip( | |||
| <div | |||
| dangerouslySetInnerHTML={{ | |||
| __html: t('graphRagMethodTip'), | |||
| }} | |||
| ></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> | |||
| ); | |||
| }; | |||
| export default GraphRagItems; | |||
| @@ -0,0 +1,142 @@ | |||
| import { DocumentParserType } from '@/constants/knowledge'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import random from 'lodash/random'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { useCallback } from 'react'; | |||
| import { useFormContext, useWatch } from 'react-hook-form'; | |||
| import { SliderInputFormField } from '../slider-input-form-field'; | |||
| import { Button } from '../ui/button'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from '../ui/form'; | |||
| import { Input } from '../ui/input'; | |||
| import { Switch } from '../ui/switch'; | |||
| import { Textarea } from '../ui/textarea'; | |||
| export const excludedParseMethods = [ | |||
| DocumentParserType.Table, | |||
| DocumentParserType.Resume, | |||
| DocumentParserType.One, | |||
| DocumentParserType.Picture, | |||
| DocumentParserType.KnowledgeGraph, | |||
| DocumentParserType.Qa, | |||
| DocumentParserType.Tag, | |||
| ]; | |||
| export const showRaptorParseConfiguration = ( | |||
| parserId: DocumentParserType | undefined, | |||
| ) => { | |||
| return !excludedParseMethods.some((x) => x === parserId); | |||
| }; | |||
| export const excludedTagParseMethods = [ | |||
| DocumentParserType.Table, | |||
| DocumentParserType.KnowledgeGraph, | |||
| DocumentParserType.Tag, | |||
| ]; | |||
| export const showTagItems = (parserId: DocumentParserType) => { | |||
| return !excludedTagParseMethods.includes(parserId); | |||
| }; | |||
| const UseRaptorField = 'parser_config.raptor.use_raptor'; | |||
| const RandomSeedField = 'parser_config.raptor.random_seed'; | |||
| // The three types "table", "resume" and "one" do not display this configuration. | |||
| const RaptorFormFields = () => { | |||
| const form = useFormContext(); | |||
| const { t } = useTranslate('knowledgeConfiguration'); | |||
| const useRaptor = useWatch({ name: UseRaptorField }); | |||
| const handleGenerate = useCallback(() => { | |||
| form.setValue(RandomSeedField, random(10000)); | |||
| }, [form]); | |||
| return ( | |||
| <> | |||
| <FormField | |||
| control={form.control} | |||
| name={UseRaptorField} | |||
| render={({ field }) => ( | |||
| <FormItem defaultChecked={false}> | |||
| <FormLabel tooltip={t('useRaptorTip')}>{t('useRaptor')}</FormLabel> | |||
| <FormControl> | |||
| <Switch | |||
| checked={field.value} | |||
| onCheckedChange={field.onChange} | |||
| ></Switch> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| {useRaptor && ( | |||
| <> | |||
| <FormField | |||
| control={form.control} | |||
| name={'parser_config.raptor.prompt'} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel tooltip={t('promptTip')}>{t('prompt')}</FormLabel> | |||
| <FormControl defaultValue={t('promptText')}> | |||
| <Textarea {...field} rows={8} /> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| <SliderInputFormField | |||
| name={'parser_config.raptor.max_token'} | |||
| label={t('maxToken')} | |||
| tooltip={t('maxTokenTip')} | |||
| defaultValue={256} | |||
| max={2048} | |||
| min={0} | |||
| ></SliderInputFormField> | |||
| <SliderInputFormField | |||
| name={'parser_config.raptor.threshold'} | |||
| label={t('threshold')} | |||
| tooltip={t('thresholdTip')} | |||
| defaultValue={0.1} | |||
| step={0.01} | |||
| max={1} | |||
| min={0} | |||
| ></SliderInputFormField> | |||
| <SliderInputFormField | |||
| name={'parser_config.raptor.max_cluster'} | |||
| label={t('maxCluster')} | |||
| tooltip={t('maxClusterTip')} | |||
| defaultValue={64} | |||
| max={1024} | |||
| min={1} | |||
| ></SliderInputFormField> | |||
| <FormField | |||
| control={form.control} | |||
| name={'parser_config.raptor.random_seed'} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <FormLabel>{t('randomSeed')}</FormLabel> | |||
| <FormControl defaultValue={0}> | |||
| <div className="flex gap-4"> | |||
| <Input {...field} /> | |||
| <Button size={'sm'} onClick={handleGenerate}> | |||
| <Plus /> | |||
| </Button> | |||
| </div> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| </> | |||
| )} | |||
| </> | |||
| ); | |||
| }; | |||
| export default RaptorFormFields; | |||
| @@ -0,0 +1,71 @@ | |||
| import { ReactNode } from 'react'; | |||
| import { useFormContext } from 'react-hook-form'; | |||
| import { SingleFormSlider } from './ui/dual-range-slider'; | |||
| import { | |||
| FormControl, | |||
| FormField, | |||
| FormItem, | |||
| FormLabel, | |||
| FormMessage, | |||
| } from './ui/form'; | |||
| import { Input } from './ui/input'; | |||
| type SliderInputFormFieldProps = { | |||
| max?: number; | |||
| min?: number; | |||
| step?: number; | |||
| name: string; | |||
| label: string; | |||
| tooltip?: ReactNode; | |||
| defaultValue?: number; | |||
| }; | |||
| export function SliderInputFormField({ | |||
| max, | |||
| min, | |||
| step, | |||
| label, | |||
| name, | |||
| tooltip, | |||
| defaultValue, | |||
| }: SliderInputFormFieldProps) { | |||
| const form = useFormContext(); | |||
| return ( | |||
| <FormField | |||
| control={form.control} | |||
| name={name} | |||
| defaultValue={defaultValue} | |||
| render={({ field }) => ( | |||
| <FormItem> | |||
| <div className="flex items-center justify-between"> | |||
| <FormLabel tooltip={tooltip}>{label}</FormLabel> | |||
| <FormControl> | |||
| <Input | |||
| type={'number'} | |||
| className="h-7 w-20" | |||
| max={max} | |||
| min={min} | |||
| step={step} | |||
| {...field} | |||
| // defaultValue={defaultValue} | |||
| ></Input> | |||
| </FormControl> | |||
| </div> | |||
| <FormControl> | |||
| <SingleFormSlider | |||
| {...field} | |||
| max={max} | |||
| min={min} | |||
| step={step} | |||
| // defaultValue={ | |||
| // typeof defaultValue === 'number' ? [defaultValue] : undefined | |||
| // } | |||
| ></SingleFormSlider> | |||
| </FormControl> | |||
| <FormMessage /> | |||
| </FormItem> | |||
| )} | |||
| /> | |||
| ); | |||
| } | |||
| @@ -222,11 +222,12 @@ export const RAGFlowSelect = forwardRef< | |||
| allowClear, | |||
| placeholder, | |||
| contentProps = {}, | |||
| defaultValue, | |||
| }, | |||
| ref, | |||
| ) { | |||
| const [key, setKey] = React.useState(+new Date()); | |||
| const [value, setValue] = React.useState<string | undefined>(undefined); | |||
| const [value, setValue] = React.useState<string | undefined>(defaultValue); | |||
| const FormControlWidget = FormControlComponent | |||
| ? FormControlComponent | |||
| @@ -1,4 +1,5 @@ | |||
| import { IDocumentInfo } from '@/interfaces/database/document'; | |||
| import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; | |||
| import i18n from '@/locales/config'; | |||
| import kbService from '@/services/knowledge-service'; | |||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||
| @@ -20,6 +21,7 @@ export const enum DocumentApiAction { | |||
| RunDocumentByIds = 'runDocumentByIds', | |||
| RemoveDocument = 'removeDocument', | |||
| SaveDocumentName = 'saveDocumentName', | |||
| SetDocumentParser = 'setDocumentParser', | |||
| } | |||
| export const useUploadNextDocument = () => { | |||
| @@ -247,3 +249,40 @@ export const useSaveDocumentName = () => { | |||
| return { loading, saveName: mutateAsync, data }; | |||
| }; | |||
| export const useSetDocumentParser = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [DocumentApiAction.SetDocumentParser], | |||
| mutationFn: async ({ | |||
| parserId, | |||
| documentId, | |||
| parserConfig, | |||
| }: { | |||
| parserId: string; | |||
| documentId: string; | |||
| parserConfig: IChangeParserConfigRequestBody; | |||
| }) => { | |||
| const { data } = await kbService.document_change_parser({ | |||
| parser_id: parserId, | |||
| doc_id: documentId, | |||
| parser_config: parserConfig, | |||
| }); | |||
| if (data.code === 0) { | |||
| queryClient.invalidateQueries({ | |||
| queryKey: [DocumentApiAction.FetchDocumentList], | |||
| }); | |||
| message.success(i18n.t('message.modified')); | |||
| } | |||
| return data.code; | |||
| }, | |||
| }); | |||
| return { setDocumentParser: mutateAsync, data, loading }; | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| import { | |||
| IKnowledge, | |||
| IKnowledgeResult, | |||
| INextTestingResult, | |||
| } from '@/interfaces/database/knowledge'; | |||
| @@ -22,6 +23,7 @@ export const enum KnowledgeApiAction { | |||
| CreateKnowledge = 'createKnowledge', | |||
| DeleteKnowledge = 'deleteKnowledge', | |||
| SaveKnowledge = 'saveKnowledge', | |||
| FetchKnowledgeDetail = 'fetchKnowledgeDetail', | |||
| } | |||
| export const useKnowledgeBaseId = () => { | |||
| @@ -204,3 +206,21 @@ export const useUpdateKnowledge = (shouldFetchList = false) => { | |||
| return { data, loading, saveKnowledgeConfiguration: mutateAsync }; | |||
| }; | |||
| export const useFetchKnowledgeBaseConfiguration = () => { | |||
| const { id } = useParams(); | |||
| const { data, isFetching: loading } = useQuery<IKnowledge>({ | |||
| queryKey: [KnowledgeApiAction.FetchKnowledgeDetail], | |||
| initialData: {} as IKnowledge, | |||
| gcTime: 0, | |||
| queryFn: async () => { | |||
| const { data } = await kbService.get_kb_detail({ | |||
| kb_id: id, | |||
| }); | |||
| return data?.data ?? {}; | |||
| }, | |||
| }); | |||
| return { data, loading }; | |||
| }; | |||
| @@ -29,7 +29,7 @@ import { useFetchDocumentList } from '@/hooks/use-document-request'; | |||
| import { IDocumentInfo } from '@/interfaces/database/document'; | |||
| import { getExtension } from '@/utils/document-util'; | |||
| import { useMemo } from 'react'; | |||
| import { useChangeDocumentParser } from './hooks'; | |||
| import { useChangeDocumentParser } from './use-change-document-parser'; | |||
| import { useDatasetTableColumns } from './use-dataset-table-columns'; | |||
| import { useRenameDocument } from './use-rename-document'; | |||
| @@ -57,7 +57,8 @@ export function DatasetTable() { | |||
| changeParserVisible, | |||
| hideChangeParserModal, | |||
| showChangeParserModal, | |||
| } = useChangeDocumentParser(currentRecord.id); | |||
| changeParserRecord, | |||
| } = useChangeDocumentParser(); | |||
| const { | |||
| renameLoading, | |||
| @@ -198,10 +199,10 @@ export function DatasetTable() { | |||
| </div> | |||
| {changeParserVisible && ( | |||
| <ChunkMethodDialog | |||
| documentId={currentRecord.id} | |||
| parserId={currentRecord.parser_id} | |||
| parserConfig={currentRecord.parser_config} | |||
| documentExtension={getExtension(currentRecord.name)} | |||
| documentId={changeParserRecord.id} | |||
| parserId={changeParserRecord.parser_id} | |||
| parserConfig={changeParserRecord.parser_config} | |||
| documentExtension={getExtension(changeParserRecord.name)} | |||
| onOk={onChangeParserOk} | |||
| visible={changeParserVisible} | |||
| hideModal={hideChangeParserModal} | |||
| @@ -1,11 +1,6 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { | |||
| useCreateNextDocument, | |||
| useNextWebCrawl, | |||
| useSetNextDocumentParser, | |||
| } from '@/hooks/document-hooks'; | |||
| import { useCreateNextDocument, useNextWebCrawl } from '@/hooks/document-hooks'; | |||
| import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; | |||
| import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; | |||
| import { useCallback, useState } from 'react'; | |||
| import { useNavigate } from 'umi'; | |||
| @@ -50,44 +45,6 @@ export const useCreateEmptyDocument = () => { | |||
| }; | |||
| }; | |||
| export const useChangeDocumentParser = (documentId: string) => { | |||
| const { setDocumentParser, loading } = useSetNextDocumentParser(); | |||
| const { | |||
| visible: changeParserVisible, | |||
| hideModal: hideChangeParserModal, | |||
| showModal: showChangeParserModal, | |||
| } = useSetModalState(); | |||
| const onChangeParserOk = useCallback( | |||
| async ({ | |||
| parserId, | |||
| parserConfig, | |||
| }: { | |||
| parserId: string; | |||
| parserConfig: IChangeParserConfigRequestBody; | |||
| }) => { | |||
| const ret = await setDocumentParser({ | |||
| parserId, | |||
| documentId, | |||
| parserConfig, | |||
| }); | |||
| if (ret === 0) { | |||
| hideChangeParserModal(); | |||
| } | |||
| }, | |||
| [hideChangeParserModal, setDocumentParser, documentId], | |||
| ); | |||
| return { | |||
| changeParserLoading: loading, | |||
| onChangeParserOk, | |||
| changeParserVisible, | |||
| hideChangeParserModal, | |||
| showChangeParserModal, | |||
| }; | |||
| }; | |||
| export const useGetRowSelection = () => { | |||
| const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | |||
| @@ -1,12 +1,20 @@ | |||
| import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { | |||
| HoverCard, | |||
| HoverCardContent, | |||
| HoverCardTrigger, | |||
| } from '@/components/ui/hover-card'; | |||
| import { Progress } from '@/components/ui/progress'; | |||
| import { Separator } from '@/components/ui/separator'; | |||
| import { IDocumentInfo } from '@/interfaces/database/document'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { CircleX, Play, RefreshCw } from 'lucide-react'; | |||
| import { PropsWithChildren, useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { RunningStatus } from './constant'; | |||
| import { ParsingCard } from './parsing-card'; | |||
| import { UseChangeDocumentParserShowType } from './use-change-document-parser'; | |||
| import { useHandleRunDocumentByIds } from './use-run-document'; | |||
| import { isParserRunning } from './utils'; | |||
| @@ -18,7 +26,26 @@ const IconMap = { | |||
| [RunningStatus.FAIL]: <RefreshCw />, | |||
| }; | |||
| export function ParsingStatusCell({ record }: { record: IDocumentInfo }) { | |||
| function MenuItem({ | |||
| children, | |||
| onClick, | |||
| }: PropsWithChildren & { onClick?(): void }) { | |||
| return ( | |||
| <div | |||
| onClick={onClick} | |||
| className={cn( | |||
| 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', | |||
| )} | |||
| > | |||
| {children} | |||
| </div> | |||
| ); | |||
| } | |||
| export function ParsingStatusCell({ | |||
| record, | |||
| showChangeParserModal, | |||
| }: { record: IDocumentInfo } & UseChangeDocumentParserShowType) { | |||
| const { t } = useTranslation(); | |||
| const { run, parser_id, progress, chunk_num, id } = record; | |||
| const operationIcon = IconMap[run]; | |||
| @@ -33,12 +60,27 @@ export function ParsingStatusCell({ record }: { record: IDocumentInfo }) { | |||
| handleRunDocumentByIds(record.id, isRunning, shouldDelete); | |||
| }; | |||
| const handleShowChangeParserModal = useCallback(() => { | |||
| showChangeParserModal(record); | |||
| }, [record, showChangeParserModal]); | |||
| return ( | |||
| <section className="flex gap-2 items-center "> | |||
| <div> | |||
| <Button variant={'ghost'} size={'sm'}> | |||
| {parser_id} | |||
| </Button> | |||
| <HoverCard> | |||
| <HoverCardTrigger> | |||
| <Button variant={'ghost'} size={'sm'}> | |||
| {parser_id} | |||
| </Button> | |||
| </HoverCardTrigger> | |||
| <HoverCardContent> | |||
| <MenuItem onClick={handleShowChangeParserModal}> | |||
| {t('knowledgeDetails.chunkMethod')} | |||
| </MenuItem> | |||
| <MenuItem>{t('knowledgeDetails.setMetaData')}</MenuItem> | |||
| </HoverCardContent> | |||
| </HoverCard> | |||
| <Separator orientation="vertical" /> | |||
| </div> | |||
| <ConfirmDeleteDialog | |||
| @@ -0,0 +1,54 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useSetDocumentParser } from '@/hooks/use-document-request'; | |||
| import { IDocumentInfo } from '@/interfaces/database/document'; | |||
| import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; | |||
| import { useCallback, useState } from 'react'; | |||
| export const useChangeDocumentParser = () => { | |||
| const { setDocumentParser, loading } = useSetDocumentParser(); | |||
| const [record, setRecord] = useState<IDocumentInfo>({} as IDocumentInfo); | |||
| const { | |||
| visible: changeParserVisible, | |||
| hideModal: hideChangeParserModal, | |||
| showModal: showChangeParserModal, | |||
| } = useSetModalState(); | |||
| const onChangeParserOk = useCallback( | |||
| async (parserId: string, parserConfig: IChangeParserConfigRequestBody) => { | |||
| if (record?.id) { | |||
| const ret = await setDocumentParser({ | |||
| parserId, | |||
| documentId: record?.id, | |||
| parserConfig, | |||
| }); | |||
| if (ret === 0) { | |||
| hideChangeParserModal(); | |||
| } | |||
| } | |||
| }, | |||
| [record?.id, setDocumentParser, hideChangeParserModal], | |||
| ); | |||
| const handleShowChangeParserModal = useCallback( | |||
| (row: IDocumentInfo) => { | |||
| setRecord(row); | |||
| showChangeParserModal(); | |||
| }, | |||
| [showChangeParserModal], | |||
| ); | |||
| return { | |||
| changeParserLoading: loading, | |||
| onChangeParserOk, | |||
| changeParserVisible, | |||
| hideChangeParserModal, | |||
| showChangeParserModal: handleShowChangeParserModal, | |||
| changeParserRecord: record, | |||
| }; | |||
| }; | |||
| export type UseChangeDocumentParserShowType = Pick< | |||
| ReturnType<typeof useChangeDocumentParser>, | |||
| 'showChangeParserModal' | |||
| >; | |||
| @@ -15,17 +15,13 @@ import { formatDate } from '@/utils/date'; | |||
| import { getExtension } from '@/utils/document-util'; | |||
| import { ColumnDef } from '@tanstack/table-core'; | |||
| import { ArrowUpDown } from 'lucide-react'; | |||
| import { useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { DatasetActionCell } from './dataset-action-cell'; | |||
| import { useChangeDocumentParser } from './hooks'; | |||
| import { ParsingStatusCell } from './parsing-status-cell'; | |||
| import { UseChangeDocumentParserShowType } from './use-change-document-parser'; | |||
| import { UseRenameDocumentShowType } from './use-rename-document'; | |||
| type UseDatasetTableColumnsType = Pick< | |||
| ReturnType<typeof useChangeDocumentParser>, | |||
| 'showChangeParserModal' | |||
| > & { | |||
| type UseDatasetTableColumnsType = UseChangeDocumentParserShowType & { | |||
| setCurrentRecord: (record: IDocumentInfo) => void; | |||
| } & UseRenameDocumentShowType; | |||
| @@ -42,13 +38,6 @@ export function useDatasetTableColumns({ | |||
| // setCurrentRecord(record); | |||
| // showRenameModal(); | |||
| // }; | |||
| const onShowChangeParserModal = useCallback( | |||
| (record: IDocumentInfo) => () => { | |||
| setCurrentRecord(record); | |||
| showChangeParserModal(); | |||
| }, | |||
| [setCurrentRecord, showChangeParserModal], | |||
| ); | |||
| // const onShowSetMetaModal = useCallback(() => { | |||
| // setRecord(); | |||
| @@ -168,7 +157,12 @@ export function useDatasetTableColumns({ | |||
| header: t('parsingStatus'), | |||
| // meta: { cellClassName: 'min-w-[20vw]' }, | |||
| cell: ({ row }) => { | |||
| return <ParsingStatusCell record={row.original}></ParsingStatusCell>; | |||
| return ( | |||
| <ParsingStatusCell | |||
| record={row.original} | |||
| showChangeParserModal={showChangeParserModal} | |||
| ></ParsingStatusCell> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||