mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Refa: Pdf 2 Slices page to new style (#8386)
### What problem does this PR solve? Refactor Pdf 2 Slices page to new style ### Type of change - [X] Refactoring
This commit is contained in:
@ -64,7 +64,8 @@ export const useNavigatePage = () => {
|
|||||||
const navigateToChunkParsedResult = useCallback(
|
const navigateToChunkParsedResult = useCallback(
|
||||||
(id: string, knowledgeId?: string) => () => {
|
(id: string, knowledgeId?: string) => () => {
|
||||||
navigate(
|
navigate(
|
||||||
`${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`,
|
// `${Routes.ParsedResult}/${id}?${QueryStringMap.KnowledgeId}=${knowledgeId}`,
|
||||||
|
`${Routes.ParsedResult}/chunks?id=${knowledgeId}&doc_id=${id}`,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[navigate],
|
[navigate],
|
||||||
|
|||||||
91
web/src/hooks/use-chunk-request.ts
Normal file
91
web/src/hooks/use-chunk-request.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { ResponseGetType } from '@/interfaces/database/base';
|
||||||
|
import { IChunk, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||||
|
import kbService from '@/services/knowledge-service';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useDebounce } from 'ahooks';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { IChunkListResult } from './chunk-hooks';
|
||||||
|
import {
|
||||||
|
useGetPaginationWithRouter,
|
||||||
|
useHandleSearchChange,
|
||||||
|
} from './logic-hooks';
|
||||||
|
import { useGetKnowledgeSearchParams } from './route-hook';
|
||||||
|
|
||||||
|
export const useFetchNextChunkList = (): ResponseGetType<{
|
||||||
|
data: IChunk[];
|
||||||
|
total: number;
|
||||||
|
documentInfo: IKnowledgeFile;
|
||||||
|
}> &
|
||||||
|
IChunkListResult => {
|
||||||
|
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||||
|
const { documentId } = useGetKnowledgeSearchParams();
|
||||||
|
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||||
|
const [available, setAvailable] = useState<number | undefined>();
|
||||||
|
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||||
|
|
||||||
|
const { data, isFetching: loading } = useQuery({
|
||||||
|
queryKey: [
|
||||||
|
'fetchChunkList',
|
||||||
|
documentId,
|
||||||
|
pagination.current,
|
||||||
|
pagination.pageSize,
|
||||||
|
debouncedSearchString,
|
||||||
|
available,
|
||||||
|
],
|
||||||
|
placeholderData: (previousData: any) =>
|
||||||
|
previousData ?? { data: [], total: 0, documentInfo: {} }, // https://github.com/TanStack/query/issues/8183
|
||||||
|
gcTime: 0,
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await kbService.chunk_list({
|
||||||
|
doc_id: documentId,
|
||||||
|
page: pagination.current,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
available_int: available,
|
||||||
|
keywords: searchString,
|
||||||
|
});
|
||||||
|
if (data.code === 0) {
|
||||||
|
const res = data.data;
|
||||||
|
return {
|
||||||
|
data: res.chunks,
|
||||||
|
total: res.total,
|
||||||
|
documentInfo: res.doc,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
data?.data ?? {
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
documentInfo: {},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
|
(e) => {
|
||||||
|
setPagination({ page: 1 });
|
||||||
|
handleInputChange(e);
|
||||||
|
},
|
||||||
|
[handleInputChange, setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSetAvailable = useCallback(
|
||||||
|
(a: number | undefined) => {
|
||||||
|
setPagination({ page: 1 });
|
||||||
|
setAvailable(a);
|
||||||
|
},
|
||||||
|
[setAvailable, setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
loading,
|
||||||
|
pagination,
|
||||||
|
setPagination,
|
||||||
|
searchString,
|
||||||
|
handleInputChange: onInputChange,
|
||||||
|
available,
|
||||||
|
handleSetAvailable,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
.image {
|
||||||
|
width: 100px !important;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.imagePreview {
|
||||||
|
max-width: 50vw;
|
||||||
|
max-height: 50vh;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
.chunkText;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentEllipsis {
|
||||||
|
.multipleLineEllipsis(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentText {
|
||||||
|
word-break: break-all !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chunkCard {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardSelected {
|
||||||
|
background-color: @selectedBackgroundColor;
|
||||||
|
}
|
||||||
|
.cardSelectedDark {
|
||||||
|
background-color: #ffffff2f;
|
||||||
|
}
|
||||||
@ -0,0 +1,101 @@
|
|||||||
|
import Image from '@/components/image';
|
||||||
|
import { IChunk } from '@/interfaces/database/knowledge';
|
||||||
|
import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useTheme } from '@/components/theme-provider';
|
||||||
|
import { ChunkTextMode } from '../../constant';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
item: IChunk;
|
||||||
|
checked: boolean;
|
||||||
|
switchChunk: (available?: number, chunkIds?: string[]) => void;
|
||||||
|
editChunk: (chunkId: string) => void;
|
||||||
|
handleCheckboxClick: (chunkId: string, checked: boolean) => void;
|
||||||
|
selected: boolean;
|
||||||
|
clickChunkCard: (chunkId: string) => void;
|
||||||
|
textMode: ChunkTextMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChunkCard = ({
|
||||||
|
item,
|
||||||
|
checked,
|
||||||
|
handleCheckboxClick,
|
||||||
|
editChunk,
|
||||||
|
switchChunk,
|
||||||
|
selected,
|
||||||
|
clickChunkCard,
|
||||||
|
textMode,
|
||||||
|
}: IProps) => {
|
||||||
|
const available = Number(item.available_int);
|
||||||
|
const [enabled, setEnabled] = useState(false);
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
const onChange = (checked: boolean) => {
|
||||||
|
setEnabled(checked);
|
||||||
|
switchChunk(available === 0 ? 1 : 0, [item.chunk_id]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheck: CheckboxProps['onChange'] = (e) => {
|
||||||
|
handleCheckboxClick(item.chunk_id, e.target.checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContentDoubleClick = () => {
|
||||||
|
editChunk(item.chunk_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContentClick = () => {
|
||||||
|
clickChunkCard(item.chunk_id);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setEnabled(available === 1);
|
||||||
|
}, [available]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className={classNames(styles.chunkCard, {
|
||||||
|
[`${theme === 'dark' ? styles.cardSelectedDark : styles.cardSelected}`]:
|
||||||
|
selected,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Flex gap={'middle'} justify={'space-between'}>
|
||||||
|
<Checkbox onChange={handleCheck} checked={checked}></Checkbox>
|
||||||
|
{item.image_id && (
|
||||||
|
<Popover
|
||||||
|
placement="right"
|
||||||
|
content={
|
||||||
|
<Image id={item.image_id} className={styles.imagePreview}></Image>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Image id={item.image_id} className={styles.image}></Image>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<section
|
||||||
|
onDoubleClick={handleContentDoubleClick}
|
||||||
|
onClick={handleContentClick}
|
||||||
|
className={styles.content}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: DOMPurify.sanitize(item.content_with_weight),
|
||||||
|
}}
|
||||||
|
className={classNames(styles.contentText, {
|
||||||
|
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Switch checked={enabled} onChange={onChange} />
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChunkCard;
|
||||||
@ -0,0 +1,140 @@
|
|||||||
|
import EditTag from '@/components/edit-tag';
|
||||||
|
import { useFetchChunk } from '@/hooks/chunk-hooks';
|
||||||
|
import { IModalProps } from '@/interfaces/common';
|
||||||
|
import { IChunk } from '@/interfaces/database/knowledge';
|
||||||
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import { Divider, Form, Input, Modal, Space, Switch } from 'antd';
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useDeleteChunkByIds } from '../../hooks';
|
||||||
|
import {
|
||||||
|
transformTagFeaturesArrayToObject,
|
||||||
|
transformTagFeaturesObjectToArray,
|
||||||
|
} from '../../utils';
|
||||||
|
import { TagFeatureItem } from './tag-feature-item';
|
||||||
|
|
||||||
|
type FieldType = Pick<
|
||||||
|
IChunk,
|
||||||
|
'content_with_weight' | 'tag_kwd' | 'question_kwd' | 'important_kwd'
|
||||||
|
>;
|
||||||
|
|
||||||
|
interface kFProps {
|
||||||
|
doc_id: string;
|
||||||
|
chunkId: string | undefined;
|
||||||
|
parserId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChunkCreatingModal: React.FC<IModalProps<any> & kFProps> = ({
|
||||||
|
doc_id,
|
||||||
|
chunkId,
|
||||||
|
hideModal,
|
||||||
|
onOk,
|
||||||
|
loading,
|
||||||
|
parserId,
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [checked, setChecked] = useState(false);
|
||||||
|
const { removeChunk } = useDeleteChunkByIds();
|
||||||
|
const { data } = useFetchChunk(chunkId);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const isTagParser = parserId === 'tag';
|
||||||
|
|
||||||
|
const handleOk = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
console.log('🚀 ~ handleOk ~ values:', values);
|
||||||
|
|
||||||
|
onOk?.({
|
||||||
|
...values,
|
||||||
|
tag_feas: transformTagFeaturesArrayToObject(values.tag_feas),
|
||||||
|
available_int: checked ? 1 : 0, // available_int
|
||||||
|
});
|
||||||
|
} catch (errorInfo) {
|
||||||
|
console.log('Failed:', errorInfo);
|
||||||
|
}
|
||||||
|
}, [checked, form, onOk]);
|
||||||
|
|
||||||
|
const handleRemove = useCallback(() => {
|
||||||
|
if (chunkId) {
|
||||||
|
return removeChunk([chunkId], doc_id);
|
||||||
|
}
|
||||||
|
}, [chunkId, doc_id, removeChunk]);
|
||||||
|
|
||||||
|
const handleCheck = useCallback(() => {
|
||||||
|
setChecked(!checked);
|
||||||
|
}, [checked]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.code === 0) {
|
||||||
|
const { available_int, tag_feas } = data.data;
|
||||||
|
form.setFieldsValue({
|
||||||
|
...(data.data || {}),
|
||||||
|
tag_feas: transformTagFeaturesObjectToArray(tag_feas),
|
||||||
|
});
|
||||||
|
|
||||||
|
setChecked(available_int !== 0);
|
||||||
|
}
|
||||||
|
}, [data, form, chunkId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={`${chunkId ? t('common.edit') : t('common.create')} ${t('chunk.chunk')}`}
|
||||||
|
open={true}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={hideModal}
|
||||||
|
okButtonProps={{ loading }}
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<Form form={form} autoComplete="off" layout={'vertical'}>
|
||||||
|
<Form.Item<FieldType>
|
||||||
|
label={t('chunk.chunk')}
|
||||||
|
name="content_with_weight"
|
||||||
|
rules={[{ required: true, message: t('chunk.chunkMessage') }]}
|
||||||
|
>
|
||||||
|
<Input.TextArea autoSize={{ minRows: 4, maxRows: 10 }} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item<FieldType> label={t('chunk.keyword')} name="important_kwd">
|
||||||
|
<EditTag></EditTag>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item<FieldType>
|
||||||
|
label={t('chunk.question')}
|
||||||
|
name="question_kwd"
|
||||||
|
tooltip={t('chunk.questionTip')}
|
||||||
|
>
|
||||||
|
<EditTag></EditTag>
|
||||||
|
</Form.Item>
|
||||||
|
{isTagParser && (
|
||||||
|
<Form.Item<FieldType>
|
||||||
|
label={t('knowledgeConfiguration.tagName')}
|
||||||
|
name="tag_kwd"
|
||||||
|
>
|
||||||
|
<EditTag></EditTag>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isTagParser && <TagFeatureItem></TagFeatureItem>}
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
{chunkId && (
|
||||||
|
<section>
|
||||||
|
<Divider></Divider>
|
||||||
|
<Space size={'large'}>
|
||||||
|
<Switch
|
||||||
|
checkedChildren={t('chunk.enabled')}
|
||||||
|
unCheckedChildren={t('chunk.disabled')}
|
||||||
|
onChange={handleCheck}
|
||||||
|
checked={checked}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span onClick={handleRemove}>
|
||||||
|
<DeleteOutlined /> {t('common.delete')}
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ChunkCreatingModal;
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
import {
|
||||||
|
useFetchKnowledgeBaseConfiguration,
|
||||||
|
useFetchTagListByKnowledgeIds,
|
||||||
|
} from '@/hooks/knowledge-hooks';
|
||||||
|
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Form, InputNumber, Select } from 'antd';
|
||||||
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FormListItem } from '../../utils';
|
||||||
|
|
||||||
|
const FieldKey = 'tag_feas';
|
||||||
|
|
||||||
|
export const TagFeatureItem = () => {
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: knowledgeConfiguration } = useFetchKnowledgeBaseConfiguration();
|
||||||
|
|
||||||
|
const { setKnowledgeIds, list } = useFetchTagListByKnowledgeIds();
|
||||||
|
|
||||||
|
const tagKnowledgeIds = useMemo(() => {
|
||||||
|
return knowledgeConfiguration?.parser_config?.tag_kb_ids ?? [];
|
||||||
|
}, [knowledgeConfiguration?.parser_config?.tag_kb_ids]);
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return list.map((x) => ({
|
||||||
|
value: x[0],
|
||||||
|
label: x[0],
|
||||||
|
}));
|
||||||
|
}, [list]);
|
||||||
|
|
||||||
|
const filterOptions = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
const tags: FormListItem[] = form.getFieldValue(FieldKey) ?? [];
|
||||||
|
|
||||||
|
// Exclude it's own current data
|
||||||
|
const list = tags
|
||||||
|
.filter((x, idx) => x && index !== idx)
|
||||||
|
.map((x) => x.tag);
|
||||||
|
|
||||||
|
// Exclude the selected data from other options from one's own options.
|
||||||
|
return options.filter((x) => !list.some((y) => x.value === y));
|
||||||
|
},
|
||||||
|
[form, options],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setKnowledgeIds(tagKnowledgeIds);
|
||||||
|
}, [setKnowledgeIds, tagKnowledgeIds]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item label={t('knowledgeConfiguration.tags')}>
|
||||||
|
<Form.List name={FieldKey} initialValue={[]}>
|
||||||
|
{(fields, { add, remove }) => (
|
||||||
|
<>
|
||||||
|
{fields.map(({ key, name, ...restField }) => (
|
||||||
|
<div key={key} className="flex gap-3 items-center">
|
||||||
|
<div className="flex flex-1 gap-8">
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'tag']}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseSelect') },
|
||||||
|
]}
|
||||||
|
className="w-2/3"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
placeholder={t('knowledgeConfiguration.tagName')}
|
||||||
|
options={filterOptions(name)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
{...restField}
|
||||||
|
name={[name, 'frequency']}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseInput') },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
placeholder={t('knowledgeConfiguration.frequency')}
|
||||||
|
max={10}
|
||||||
|
min={0}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
<MinusCircleOutlined
|
||||||
|
onClick={() => remove(name)}
|
||||||
|
className="mb-6"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => add()}
|
||||||
|
block
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
{t('knowledgeConfiguration.addTag')}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Ban, CircleCheck, Trash2 } from 'lucide-react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
export default ({ selectAllChunk, checked }) => {
|
||||||
|
const handleSelectAllCheck = useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
console.log('eee=', e);
|
||||||
|
selectAllChunk(e);
|
||||||
|
},
|
||||||
|
[selectAllChunk],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-[40px]">
|
||||||
|
<div className="flex items-center gap-3 cursor-pointer">
|
||||||
|
<Checkbox
|
||||||
|
id="all_chunks_checkbox"
|
||||||
|
onCheckedChange={handleSelectAllCheck}
|
||||||
|
checked={checked}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="all_chunks_checkbox">All Chunks</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center cursor-pointer">
|
||||||
|
<CircleCheck size={16} />
|
||||||
|
<span className="block ml-1">Enable</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center cursor-pointer">
|
||||||
|
<Ban size={16} />
|
||||||
|
<span className="block ml-1">Disable</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-red-500 cursor-pointer">
|
||||||
|
<Trash2 size={16} />
|
||||||
|
<span className="block ml-1">Delete</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
|
||||||
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
|
import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Popover,
|
||||||
|
Radio,
|
||||||
|
RadioChangeEvent,
|
||||||
|
Segmented,
|
||||||
|
SegmentedProps,
|
||||||
|
Space,
|
||||||
|
} from 'antd';
|
||||||
|
import { ChunkTextMode } from '../../constant';
|
||||||
|
|
||||||
|
export default ({
|
||||||
|
changeChunkTextMode,
|
||||||
|
available,
|
||||||
|
selectAllChunk,
|
||||||
|
handleSetAvailable,
|
||||||
|
createChunk,
|
||||||
|
handleInputChange,
|
||||||
|
searchString,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslate('chunk');
|
||||||
|
|
||||||
|
const handleFilterChange = (e: RadioChangeEvent) => {
|
||||||
|
selectAllChunk(false);
|
||||||
|
handleSetAvailable(e.target.value);
|
||||||
|
};
|
||||||
|
const filterContent = (
|
||||||
|
<Radio.Group onChange={handleFilterChange} value={available}>
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Radio value={undefined}>{t('all')}</Radio>
|
||||||
|
<Radio value={1}>{t('enabled')}</Radio>
|
||||||
|
<Radio value={0}>{t('disabled')}</Radio>
|
||||||
|
</Space>
|
||||||
|
</Radio.Group>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex pr-[25px]">
|
||||||
|
<Segmented
|
||||||
|
options={[
|
||||||
|
{ label: t(ChunkTextMode.Full), value: ChunkTextMode.Full },
|
||||||
|
{ label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse },
|
||||||
|
]}
|
||||||
|
onChange={changeChunkTextMode as SegmentedProps['onChange']}
|
||||||
|
/>
|
||||||
|
<div className="ml-auto"></div>
|
||||||
|
<Input
|
||||||
|
style={{ width: 200 }}
|
||||||
|
size="middle"
|
||||||
|
placeholder={t('search')}
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
allowClear
|
||||||
|
onChange={handleInputChange}
|
||||||
|
value={searchString}
|
||||||
|
/>
|
||||||
|
<div className="w-[20px]"></div>
|
||||||
|
<Popover content={filterContent} placement="bottom" arrow={false}>
|
||||||
|
<Button icon={<FilterIcon />} />
|
||||||
|
</Popover>
|
||||||
|
<div className="w-[20px]"></div>
|
||||||
|
<Button
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
type="primary"
|
||||||
|
onClick={() => createChunk()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,221 @@
|
|||||||
|
import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
|
||||||
|
import { KnowledgeRouteKey } from '@/constants/knowledge';
|
||||||
|
import { IChunkListResult, useSelectChunkList } from '@/hooks/chunk-hooks';
|
||||||
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
|
import { useKnowledgeBaseId } from '@/hooks/knowledge-hooks';
|
||||||
|
import {
|
||||||
|
ArrowLeftOutlined,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
CloseCircleOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
DownOutlined,
|
||||||
|
FilePdfOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
SearchOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
Menu,
|
||||||
|
MenuProps,
|
||||||
|
Popover,
|
||||||
|
Radio,
|
||||||
|
RadioChangeEvent,
|
||||||
|
Segmented,
|
||||||
|
SegmentedProps,
|
||||||
|
Space,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { Link } from 'umi';
|
||||||
|
import { ChunkTextMode } from '../../constant';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
interface IProps
|
||||||
|
extends Pick<
|
||||||
|
IChunkListResult,
|
||||||
|
'searchString' | 'handleInputChange' | 'available' | 'handleSetAvailable'
|
||||||
|
> {
|
||||||
|
checked: boolean;
|
||||||
|
selectAllChunk: (checked: boolean) => void;
|
||||||
|
createChunk: () => void;
|
||||||
|
removeChunk: () => void;
|
||||||
|
switchChunk: (available: number) => void;
|
||||||
|
changeChunkTextMode(mode: ChunkTextMode): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChunkToolBar = ({
|
||||||
|
selectAllChunk,
|
||||||
|
checked,
|
||||||
|
createChunk,
|
||||||
|
removeChunk,
|
||||||
|
switchChunk,
|
||||||
|
changeChunkTextMode,
|
||||||
|
available,
|
||||||
|
handleSetAvailable,
|
||||||
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
}: IProps) => {
|
||||||
|
const data = useSelectChunkList();
|
||||||
|
const documentInfo = data?.documentInfo;
|
||||||
|
const knowledgeBaseId = useKnowledgeBaseId();
|
||||||
|
const [isShowSearchBox, setIsShowSearchBox] = useState(false);
|
||||||
|
const { t } = useTranslate('chunk');
|
||||||
|
|
||||||
|
const handleSelectAllCheck = useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
selectAllChunk(e.target.checked);
|
||||||
|
},
|
||||||
|
[selectAllChunk],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSearchIconClick = () => {
|
||||||
|
setIsShowSearchBox(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchBlur = () => {
|
||||||
|
if (!searchString?.trim()) {
|
||||||
|
setIsShowSearchBox(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
removeChunk();
|
||||||
|
}, [removeChunk]);
|
||||||
|
|
||||||
|
const handleEnabledClick = useCallback(() => {
|
||||||
|
switchChunk(1);
|
||||||
|
}, [switchChunk]);
|
||||||
|
|
||||||
|
const handleDisabledClick = useCallback(() => {
|
||||||
|
switchChunk(0);
|
||||||
|
}, [switchChunk]);
|
||||||
|
|
||||||
|
const items: MenuProps['items'] = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: '1',
|
||||||
|
label: (
|
||||||
|
<>
|
||||||
|
<Checkbox onChange={handleSelectAllCheck} checked={checked}>
|
||||||
|
<b>{t('selectAll')}</b>
|
||||||
|
</Checkbox>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
label: (
|
||||||
|
<Space onClick={handleEnabledClick}>
|
||||||
|
<CheckCircleOutlined />
|
||||||
|
<b>{t('enabledSelected')}</b>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: '3',
|
||||||
|
label: (
|
||||||
|
<Space onClick={handleDisabledClick}>
|
||||||
|
<CloseCircleOutlined />
|
||||||
|
<b>{t('disabledSelected')}</b>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
label: (
|
||||||
|
<Space onClick={handleDelete}>
|
||||||
|
<DeleteOutlined />
|
||||||
|
<b>{t('deleteSelected')}</b>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [
|
||||||
|
checked,
|
||||||
|
handleSelectAllCheck,
|
||||||
|
handleDelete,
|
||||||
|
handleEnabledClick,
|
||||||
|
handleDisabledClick,
|
||||||
|
t,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<Menu style={{ width: 200 }} items={items} selectable={false} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFilterChange = (e: RadioChangeEvent) => {
|
||||||
|
selectAllChunk(false);
|
||||||
|
handleSetAvailable(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterContent = (
|
||||||
|
<Radio.Group onChange={handleFilterChange} value={available}>
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Radio value={undefined}>{t('all')}</Radio>
|
||||||
|
<Radio value={1}>{t('enabled')}</Radio>
|
||||||
|
<Radio value={0}>{t('disabled')}</Radio>
|
||||||
|
</Space>
|
||||||
|
</Radio.Group>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex justify="space-between" align="center">
|
||||||
|
<Space size={'middle'}>
|
||||||
|
<Link
|
||||||
|
to={`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`}
|
||||||
|
>
|
||||||
|
<ArrowLeftOutlined />
|
||||||
|
</Link>
|
||||||
|
<FilePdfOutlined />
|
||||||
|
<Text ellipsis={{ tooltip: documentInfo?.name }} style={{ width: 150 }}>
|
||||||
|
{documentInfo?.name}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Segmented
|
||||||
|
options={[
|
||||||
|
{ label: t(ChunkTextMode.Full), value: ChunkTextMode.Full },
|
||||||
|
{ label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse },
|
||||||
|
]}
|
||||||
|
onChange={changeChunkTextMode as SegmentedProps['onChange']}
|
||||||
|
/>
|
||||||
|
<Popover content={content} placement="bottom" arrow={false}>
|
||||||
|
<Button>
|
||||||
|
{t('bulk')}
|
||||||
|
<DownOutlined />
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
{isShowSearchBox ? (
|
||||||
|
<Input
|
||||||
|
size="middle"
|
||||||
|
placeholder={t('search')}
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
allowClear
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onBlur={handleSearchBlur}
|
||||||
|
value={searchString}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Button icon={<SearchOutlined />} onClick={handleSearchIconClick} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Popover content={filterContent} placement="bottom" arrow={false}>
|
||||||
|
<Button icon={<FilterIcon />} />
|
||||||
|
</Popover>
|
||||||
|
<Button
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
type="primary"
|
||||||
|
onClick={() => createChunk()}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChunkToolBar;
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { formatDate } from '@/utils/date';
|
||||||
|
import { formatBytes } from '@/utils/file-util';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
size: number;
|
||||||
|
name: string;
|
||||||
|
create_date: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ({ size, name, create_date }: Props) => {
|
||||||
|
const sizeName = formatBytes(size);
|
||||||
|
const dateStr = formatDate(create_date);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="text-[24px]">{name}</h2>
|
||||||
|
<div className="text-[#979AAB] pt-[5px]">
|
||||||
|
Size:{sizeName} Uploaded Time:{dateStr}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
||||||
|
import { api_host } from '@/utils/api';
|
||||||
|
import { useSize } from 'ahooks';
|
||||||
|
import { CustomTextRenderer } from 'node_modules/react-pdf/dist/esm/shared/types';
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
export const useDocumentResizeObserver = () => {
|
||||||
|
const [containerWidth, setContainerWidth] = useState<number>();
|
||||||
|
const [containerRef, setContainerRef] = useState<HTMLElement | null>(null);
|
||||||
|
const size = useSize(containerRef);
|
||||||
|
|
||||||
|
const onResize = useCallback((width?: number) => {
|
||||||
|
if (width) {
|
||||||
|
setContainerWidth(width);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onResize(size?.width);
|
||||||
|
}, [size?.width, onResize]);
|
||||||
|
|
||||||
|
return { containerWidth, setContainerRef };
|
||||||
|
};
|
||||||
|
|
||||||
|
function highlightPattern(text: string, pattern: string, pageNumber: number) {
|
||||||
|
if (pageNumber === 2) {
|
||||||
|
return `<mark>${text}</mark>`;
|
||||||
|
}
|
||||||
|
if (text.trim() !== '' && pattern.match(text)) {
|
||||||
|
// return pattern.replace(text, (value) => `<mark>${value}</mark>`);
|
||||||
|
return `<mark>${text}</mark>`;
|
||||||
|
}
|
||||||
|
return text.replace(pattern, (value) => `<mark>${value}</mark>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHighlightText = (searchText: string = '') => {
|
||||||
|
const textRenderer: CustomTextRenderer = useCallback(
|
||||||
|
(textItem) => {
|
||||||
|
return highlightPattern(textItem.str, searchText, textItem.pageNumber);
|
||||||
|
},
|
||||||
|
[searchText],
|
||||||
|
);
|
||||||
|
|
||||||
|
return textRenderer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetDocumentUrl = () => {
|
||||||
|
const { documentId } = useGetKnowledgeSearchParams();
|
||||||
|
|
||||||
|
const url = useMemo(() => {
|
||||||
|
return `${api_host}/document/get/${documentId}`;
|
||||||
|
}, [documentId]);
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
.documentContainer {
|
||||||
|
width: 100%;
|
||||||
|
// height: calc(100vh - 284px);
|
||||||
|
height: calc(100vh - 170px);
|
||||||
|
position: relative;
|
||||||
|
:global(.PdfHighlighter) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
:global(.Highlight--scrolledTo .Highlight__part) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
background-color: rgba(255, 226, 143, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,123 @@
|
|||||||
|
import { Skeleton } from 'antd';
|
||||||
|
import { memo, useEffect, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
AreaHighlight,
|
||||||
|
Highlight,
|
||||||
|
IHighlight,
|
||||||
|
PdfHighlighter,
|
||||||
|
PdfLoader,
|
||||||
|
Popup,
|
||||||
|
} from 'react-pdf-highlighter';
|
||||||
|
import { useGetDocumentUrl } from './hooks';
|
||||||
|
|
||||||
|
import { useCatchDocumentError } from '@/components/pdf-previewer/hooks';
|
||||||
|
import FileError from '@/pages/document-viewer/file-error';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
highlights: IHighlight[];
|
||||||
|
setWidthAndHeight: (width: number, height: number) => void;
|
||||||
|
}
|
||||||
|
const HighlightPopup = ({
|
||||||
|
comment,
|
||||||
|
}: {
|
||||||
|
comment: { text: string; emoji: string };
|
||||||
|
}) =>
|
||||||
|
comment.text ? (
|
||||||
|
<div className="Highlight__popup">
|
||||||
|
{comment.emoji} {comment.text}
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
// TODO: merge with DocumentPreviewer
|
||||||
|
const Preview = ({ highlights: state, setWidthAndHeight }: IProps) => {
|
||||||
|
const url = useGetDocumentUrl();
|
||||||
|
|
||||||
|
const ref = useRef<(highlight: IHighlight) => void>(() => {});
|
||||||
|
const error = useCatchDocumentError(url);
|
||||||
|
|
||||||
|
const resetHash = () => {};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state.length > 0) {
|
||||||
|
ref?.current(state[0]);
|
||||||
|
}
|
||||||
|
}, [state]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${styles.documentContainer} rounded-[10px] overflow-hidden `}
|
||||||
|
>
|
||||||
|
<PdfLoader
|
||||||
|
url={url}
|
||||||
|
beforeLoad={<Skeleton active />}
|
||||||
|
workerSrc="/pdfjs-dist/pdf.worker.min.js"
|
||||||
|
errorMessage={<FileError>{error}</FileError>}
|
||||||
|
>
|
||||||
|
{(pdfDocument) => {
|
||||||
|
pdfDocument.getPage(1).then((page) => {
|
||||||
|
const viewport = page.getViewport({ scale: 1 });
|
||||||
|
const width = viewport.width;
|
||||||
|
const height = viewport.height;
|
||||||
|
setWidthAndHeight(width, height);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PdfHighlighter
|
||||||
|
pdfDocument={pdfDocument}
|
||||||
|
enableAreaSelection={(event) => event.altKey}
|
||||||
|
onScrollChange={resetHash}
|
||||||
|
scrollRef={(scrollTo) => {
|
||||||
|
ref.current = scrollTo;
|
||||||
|
}}
|
||||||
|
onSelectionFinished={() => null}
|
||||||
|
highlightTransform={(
|
||||||
|
highlight,
|
||||||
|
index,
|
||||||
|
setTip,
|
||||||
|
hideTip,
|
||||||
|
viewportToScaled,
|
||||||
|
screenshot,
|
||||||
|
isScrolledTo,
|
||||||
|
) => {
|
||||||
|
const isTextHighlight = !Boolean(
|
||||||
|
highlight.content && highlight.content.image,
|
||||||
|
);
|
||||||
|
|
||||||
|
const component = isTextHighlight ? (
|
||||||
|
<Highlight
|
||||||
|
isScrolledTo={isScrolledTo}
|
||||||
|
position={highlight.position}
|
||||||
|
comment={highlight.comment}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<AreaHighlight
|
||||||
|
isScrolledTo={isScrolledTo}
|
||||||
|
highlight={highlight}
|
||||||
|
onChange={() => {}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popup
|
||||||
|
popupContent={<HighlightPopup {...highlight} />}
|
||||||
|
onMouseOver={(popupContent) =>
|
||||||
|
setTip(highlight, () => popupContent)
|
||||||
|
}
|
||||||
|
onMouseOut={hideTip}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{component}
|
||||||
|
</Popup>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
highlights={state}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</PdfLoader>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Preview);
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export enum ChunkTextMode {
|
||||||
|
Full = 'full',
|
||||||
|
Ellipse = 'ellipse',
|
||||||
|
}
|
||||||
@ -0,0 +1,129 @@
|
|||||||
|
import {
|
||||||
|
useCreateChunk,
|
||||||
|
useDeleteChunk,
|
||||||
|
useSelectChunkList,
|
||||||
|
} from '@/hooks/chunk-hooks';
|
||||||
|
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
||||||
|
import { useGetKnowledgeSearchParams } from '@/hooks/route-hook';
|
||||||
|
import { IChunk } from '@/interfaces/database/knowledge';
|
||||||
|
import { buildChunkHighlights } from '@/utils/document-util';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { IHighlight } from 'react-pdf-highlighter';
|
||||||
|
import { ChunkTextMode } from './constant';
|
||||||
|
|
||||||
|
export const useHandleChunkCardClick = () => {
|
||||||
|
const [selectedChunkId, setSelectedChunkId] = useState<string>('');
|
||||||
|
|
||||||
|
const handleChunkCardClick = useCallback((chunkId: string) => {
|
||||||
|
setSelectedChunkId(chunkId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { handleChunkCardClick, selectedChunkId };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSelectedChunk = (selectedChunkId: string) => {
|
||||||
|
const data = useSelectChunkList();
|
||||||
|
return (
|
||||||
|
data?.data?.find((x) => x.chunk_id === selectedChunkId) ?? ({} as IChunk)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetChunkHighlights = (selectedChunkId: string) => {
|
||||||
|
const [size, setSize] = useState({ width: 849, height: 1200 });
|
||||||
|
const selectedChunk: IChunk = useGetSelectedChunk(selectedChunkId);
|
||||||
|
|
||||||
|
const highlights: IHighlight[] = useMemo(() => {
|
||||||
|
return buildChunkHighlights(selectedChunk, size);
|
||||||
|
}, [selectedChunk, size]);
|
||||||
|
|
||||||
|
const setWidthAndHeight = useCallback((width: number, height: number) => {
|
||||||
|
setSize((pre) => {
|
||||||
|
if (pre.height !== height || pre.width !== width) {
|
||||||
|
return { height, width };
|
||||||
|
}
|
||||||
|
return pre;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { highlights, setWidthAndHeight };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Switch chunk text to be fully displayed or ellipse
|
||||||
|
export const useChangeChunkTextMode = () => {
|
||||||
|
const [textMode, setTextMode] = useState<ChunkTextMode>(ChunkTextMode.Full);
|
||||||
|
|
||||||
|
const changeChunkTextMode = useCallback((mode: ChunkTextMode) => {
|
||||||
|
setTextMode(mode);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { textMode, changeChunkTextMode };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteChunkByIds = (): {
|
||||||
|
removeChunk: (chunkIds: string[], documentId: string) => Promise<number>;
|
||||||
|
} => {
|
||||||
|
const { deleteChunk } = useDeleteChunk();
|
||||||
|
const showDeleteConfirm = useShowDeleteConfirm();
|
||||||
|
|
||||||
|
const removeChunk = useCallback(
|
||||||
|
(chunkIds: string[], documentId: string) => () => {
|
||||||
|
return deleteChunk({ chunkIds, doc_id: documentId });
|
||||||
|
},
|
||||||
|
[deleteChunk],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onRemoveChunk = useCallback(
|
||||||
|
(chunkIds: string[], documentId: string): Promise<number> => {
|
||||||
|
return showDeleteConfirm({ onOk: removeChunk(chunkIds, documentId) });
|
||||||
|
},
|
||||||
|
[removeChunk, showDeleteConfirm],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
removeChunk: onRemoveChunk,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateChunk = () => {
|
||||||
|
const [chunkId, setChunkId] = useState<string | undefined>('');
|
||||||
|
const {
|
||||||
|
visible: chunkUpdatingVisible,
|
||||||
|
hideModal: hideChunkUpdatingModal,
|
||||||
|
showModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { createChunk, loading } = useCreateChunk();
|
||||||
|
const { documentId } = useGetKnowledgeSearchParams();
|
||||||
|
|
||||||
|
const onChunkUpdatingOk = useCallback(
|
||||||
|
async (params: IChunk) => {
|
||||||
|
const code = await createChunk({
|
||||||
|
...params,
|
||||||
|
doc_id: documentId,
|
||||||
|
chunk_id: chunkId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
hideChunkUpdatingModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[createChunk, hideChunkUpdatingModal, chunkId, documentId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleShowChunkUpdatingModal = useCallback(
|
||||||
|
async (id?: string) => {
|
||||||
|
setChunkId(id);
|
||||||
|
showModal();
|
||||||
|
},
|
||||||
|
[showModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chunkUpdatingLoading: loading,
|
||||||
|
onChunkUpdatingOk,
|
||||||
|
chunkUpdatingVisible,
|
||||||
|
hideChunkUpdatingModal,
|
||||||
|
showChunkUpdatingModal: handleShowChunkUpdatingModal,
|
||||||
|
chunkId,
|
||||||
|
documentId,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,95 @@
|
|||||||
|
.chunkPage {
|
||||||
|
padding: 24px;
|
||||||
|
padding-top: 2px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
// height: calc(100vh - 112px);
|
||||||
|
height: 100vh;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
margin: 10px 0;
|
||||||
|
display: flex;
|
||||||
|
height: 32px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagePdfWrapper {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageWrapper {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageContent {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
padding-right: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.spin {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.documentPreview {
|
||||||
|
// width: 40%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chunkContainer {
|
||||||
|
display: flex;
|
||||||
|
// height: calc(100vh - 332px);
|
||||||
|
height: calc(100vh - 270px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chunkOtherContainer {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pageFooter {
|
||||||
|
padding-top: 10px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
height: 100px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.context {
|
||||||
|
flex: 1;
|
||||||
|
// width: 207px;
|
||||||
|
height: 88px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
:global {
|
||||||
|
.ant-card-body {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@ -0,0 +1,235 @@
|
|||||||
|
import { useFetchNextChunkList, useSwitchChunk } from '@/hooks/chunk-hooks';
|
||||||
|
import type { PaginationProps } from 'antd';
|
||||||
|
import { Flex, Pagination, Space, Spin, message } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import ChunkCard from './components/chunk-card';
|
||||||
|
import CreatingModal from './components/chunk-creating-modal';
|
||||||
|
import DocumentPreview from './components/document-preview/preview';
|
||||||
|
import {
|
||||||
|
useChangeChunkTextMode,
|
||||||
|
useDeleteChunkByIds,
|
||||||
|
useGetChunkHighlights,
|
||||||
|
useHandleChunkCardClick,
|
||||||
|
useUpdateChunk,
|
||||||
|
} from './hooks';
|
||||||
|
|
||||||
|
import ChunkResultBar from './components/chunk-result-bar';
|
||||||
|
import CheckboxSets from './components/chunk-result-bar/checkbox-sets';
|
||||||
|
import DocumentHeader from './components/document-preview/document-header';
|
||||||
|
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const Chunk = () => {
|
||||||
|
const [selectedChunkIds, setSelectedChunkIds] = useState<string[]>([]);
|
||||||
|
const { removeChunk } = useDeleteChunkByIds();
|
||||||
|
const {
|
||||||
|
data: { documentInfo, data = [], total },
|
||||||
|
pagination,
|
||||||
|
loading,
|
||||||
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
available,
|
||||||
|
handleSetAvailable,
|
||||||
|
} = useFetchNextChunkList();
|
||||||
|
const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
|
||||||
|
const isPdf = documentInfo?.type === 'pdf';
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
|
||||||
|
const { switchChunk } = useSwitchChunk();
|
||||||
|
const {
|
||||||
|
chunkUpdatingLoading,
|
||||||
|
onChunkUpdatingOk,
|
||||||
|
showChunkUpdatingModal,
|
||||||
|
hideChunkUpdatingModal,
|
||||||
|
chunkId,
|
||||||
|
chunkUpdatingVisible,
|
||||||
|
documentId,
|
||||||
|
} = useUpdateChunk();
|
||||||
|
|
||||||
|
const onPaginationChange: PaginationProps['onShowSizeChange'] = (
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
) => {
|
||||||
|
setSelectedChunkIds([]);
|
||||||
|
pagination.onChange?.(page, size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectAllChunk = useCallback(
|
||||||
|
(checked: boolean) => {
|
||||||
|
setSelectedChunkIds(checked ? data.map((x) => x.chunk_id) : []);
|
||||||
|
},
|
||||||
|
[data],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSingleCheckboxClick = useCallback(
|
||||||
|
(chunkId: string, checked: boolean) => {
|
||||||
|
setSelectedChunkIds((previousIds) => {
|
||||||
|
const idx = previousIds.findIndex((x) => x === chunkId);
|
||||||
|
const nextIds = [...previousIds];
|
||||||
|
if (checked && idx === -1) {
|
||||||
|
nextIds.push(chunkId);
|
||||||
|
} else if (!checked && idx !== -1) {
|
||||||
|
nextIds.splice(idx, 1);
|
||||||
|
}
|
||||||
|
return nextIds;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const showSelectedChunkWarning = useCallback(() => {
|
||||||
|
message.warning(t('message.pleaseSelectChunk'));
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
const handleRemoveChunk = useCallback(async () => {
|
||||||
|
if (selectedChunkIds.length > 0) {
|
||||||
|
const resCode: number = await removeChunk(selectedChunkIds, documentId);
|
||||||
|
if (resCode === 0) {
|
||||||
|
setSelectedChunkIds([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showSelectedChunkWarning();
|
||||||
|
}
|
||||||
|
}, [selectedChunkIds, documentId, removeChunk, showSelectedChunkWarning]);
|
||||||
|
|
||||||
|
const handleSwitchChunk = useCallback(
|
||||||
|
async (available?: number, chunkIds?: string[]) => {
|
||||||
|
let ids = chunkIds;
|
||||||
|
if (!chunkIds) {
|
||||||
|
ids = selectedChunkIds;
|
||||||
|
if (selectedChunkIds.length === 0) {
|
||||||
|
showSelectedChunkWarning();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resCode: number = await switchChunk({
|
||||||
|
chunk_ids: ids,
|
||||||
|
available_int: available,
|
||||||
|
doc_id: documentId,
|
||||||
|
});
|
||||||
|
if (!chunkIds && resCode === 0) {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[switchChunk, documentId, selectedChunkIds, showSelectedChunkWarning],
|
||||||
|
);
|
||||||
|
|
||||||
|
const { highlights, setWidthAndHeight } =
|
||||||
|
useGetChunkHighlights(selectedChunkId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.chunkPage}>
|
||||||
|
{/* <ChunkToolBar
|
||||||
|
selectAllChunk={selectAllChunk}
|
||||||
|
createChunk={showChunkUpdatingModal}
|
||||||
|
removeChunk={handleRemoveChunk}
|
||||||
|
checked={selectedChunkIds.length === data.length}
|
||||||
|
switchChunk={handleSwitchChunk}
|
||||||
|
changeChunkTextMode={changeChunkTextMode}
|
||||||
|
searchString={searchString}
|
||||||
|
handleInputChange={handleInputChange}
|
||||||
|
available={available}
|
||||||
|
handleSetAvailable={handleSetAvailable}
|
||||||
|
></ChunkToolBar> */}
|
||||||
|
{/* <Divider></Divider> */}
|
||||||
|
<Flex flex={1} gap={'middle'}>
|
||||||
|
<div className="w-[40%]">
|
||||||
|
<div className="h-[100px] flex flex-col justify-end pb-[5px]">
|
||||||
|
<DocumentHeader {...documentInfo} />
|
||||||
|
</div>
|
||||||
|
{isPdf && (
|
||||||
|
<section className={styles.documentPreview}>
|
||||||
|
<DocumentPreview
|
||||||
|
highlights={highlights}
|
||||||
|
setWidthAndHeight={setWidthAndHeight}
|
||||||
|
></DocumentPreview>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Flex
|
||||||
|
vertical
|
||||||
|
className={isPdf ? styles.pagePdfWrapper : styles.pageWrapper}
|
||||||
|
>
|
||||||
|
<Spin spinning={loading} className={styles.spin} size="large">
|
||||||
|
<div className="h-[100px] flex flex-col justify-end pb-[5px]">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-[24px]">Chunk Result</h2>
|
||||||
|
<div className="text-[14px] text-[#979AAB]">
|
||||||
|
View the chunked segments used for embedding and retrieval.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className=" rounded-[16px] bg-[#FFF]/10 pl-[20px] pb-[20px] pt-[20px] box-border ">
|
||||||
|
<ChunkResultBar
|
||||||
|
handleInputChange={handleInputChange}
|
||||||
|
searchString={searchString}
|
||||||
|
changeChunkTextMode={changeChunkTextMode}
|
||||||
|
createChunk={showChunkUpdatingModal}
|
||||||
|
available={available}
|
||||||
|
selectAllChunk={selectAllChunk}
|
||||||
|
handleSetAvailable={handleSetAvailable}
|
||||||
|
/>
|
||||||
|
<div className="pt-[5px] pb-[5px]">
|
||||||
|
<CheckboxSets
|
||||||
|
selectAllChunk={selectAllChunk}
|
||||||
|
checked={selectedChunkIds.length === data.length}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.pageContent}>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
size={'middle'}
|
||||||
|
className={classNames(styles.chunkContainer, {
|
||||||
|
[styles.chunkOtherContainer]: !isPdf,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{data.map((item) => (
|
||||||
|
<ChunkCard
|
||||||
|
item={item}
|
||||||
|
key={item.chunk_id}
|
||||||
|
editChunk={showChunkUpdatingModal}
|
||||||
|
checked={selectedChunkIds.some(
|
||||||
|
(x) => x === item.chunk_id,
|
||||||
|
)}
|
||||||
|
handleCheckboxClick={handleSingleCheckboxClick}
|
||||||
|
switchChunk={handleSwitchChunk}
|
||||||
|
clickChunkCard={handleChunkCardClick}
|
||||||
|
selected={item.chunk_id === selectedChunkId}
|
||||||
|
textMode={textMode}
|
||||||
|
></ChunkCard>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
<div className={styles.pageFooter}>
|
||||||
|
<Pagination
|
||||||
|
{...pagination}
|
||||||
|
total={total}
|
||||||
|
size={'small'}
|
||||||
|
onChange={onPaginationChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</div>
|
||||||
|
{chunkUpdatingVisible && (
|
||||||
|
<CreatingModal
|
||||||
|
doc_id={documentId}
|
||||||
|
chunkId={chunkId}
|
||||||
|
hideModal={hideChunkUpdatingModal}
|
||||||
|
visible={chunkUpdatingVisible}
|
||||||
|
loading={chunkUpdatingLoading}
|
||||||
|
onOk={onChunkUpdatingOk}
|
||||||
|
parserId={documentInfo.parser_id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chunk;
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
export type FormListItem = {
|
||||||
|
frequency: number;
|
||||||
|
tag: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function transformTagFeaturesArrayToObject(
|
||||||
|
list: Array<FormListItem> = [],
|
||||||
|
) {
|
||||||
|
return list.reduce<Record<string, number>>((pre, cur) => {
|
||||||
|
pre[cur.tag] = cur.frequency;
|
||||||
|
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transformTagFeaturesObjectToArray(
|
||||||
|
object: Record<string, number> = {},
|
||||||
|
) {
|
||||||
|
return Object.keys(object).reduce<Array<FormListItem>>((pre, key) => {
|
||||||
|
pre.push({ frequency: object[key], tag: key });
|
||||||
|
|
||||||
|
return pre;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
21
web/src/pages/chunk/parsed-result/add-knowledge/constant.ts
Normal file
21
web/src/pages/chunk/parsed-result/add-knowledge/constant.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { KnowledgeRouteKey } from '@/constants/knowledge';
|
||||||
|
|
||||||
|
export const routeMap = {
|
||||||
|
[KnowledgeRouteKey.Dataset]: 'Dataset',
|
||||||
|
[KnowledgeRouteKey.Testing]: 'Retrieval testing',
|
||||||
|
[KnowledgeRouteKey.Configuration]: 'Configuration',
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum KnowledgeDatasetRouteKey {
|
||||||
|
Chunk = 'chunk',
|
||||||
|
File = 'file',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const datasetRouteMap = {
|
||||||
|
[KnowledgeDatasetRouteKey.Chunk]: 'Chunk',
|
||||||
|
[KnowledgeDatasetRouteKey.File]: 'File Upload',
|
||||||
|
};
|
||||||
|
|
||||||
|
export * from '@/constants/knowledge';
|
||||||
|
|
||||||
|
export const TagRenameId = 'tagRename';
|
||||||
19
web/src/pages/chunk/parsed-result/add-knowledge/index.less
Normal file
19
web/src/pages/chunk/parsed-result/add-knowledge/index.less
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
.contentWrapper {
|
||||||
|
flex: 1;
|
||||||
|
overflow-x: auto;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 16px 20px 28px 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
margin-top: 16px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
web/src/pages/chunk/parsed-result/add-knowledge/index.tsx
Normal file
74
web/src/pages/chunk/parsed-result/add-knowledge/index.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { useKnowledgeBaseId } from '@/hooks/knowledge-hooks';
|
||||||
|
import {
|
||||||
|
useNavigateWithFromState,
|
||||||
|
useSecondPathName,
|
||||||
|
useThirdPathName,
|
||||||
|
} from '@/hooks/route-hook';
|
||||||
|
import { Breadcrumb } from 'antd';
|
||||||
|
import { ItemType } from 'antd/es/breadcrumb/Breadcrumb';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Link, Outlet } from 'umi';
|
||||||
|
import Siderbar from './components/knowledge-sidebar';
|
||||||
|
import { KnowledgeDatasetRouteKey, KnowledgeRouteKey } from './constant';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const KnowledgeAdding = () => {
|
||||||
|
const knowledgeBaseId = useKnowledgeBaseId();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const activeKey: KnowledgeRouteKey =
|
||||||
|
(useSecondPathName() as KnowledgeRouteKey) || KnowledgeRouteKey.Dataset;
|
||||||
|
|
||||||
|
const datasetActiveKey: KnowledgeDatasetRouteKey =
|
||||||
|
useThirdPathName() as KnowledgeDatasetRouteKey;
|
||||||
|
|
||||||
|
const gotoList = useNavigateWithFromState();
|
||||||
|
|
||||||
|
const breadcrumbItems: ItemType[] = useMemo(() => {
|
||||||
|
const items: ItemType[] = [
|
||||||
|
{
|
||||||
|
title: (
|
||||||
|
<a onClick={() => gotoList('/knowledge')}>
|
||||||
|
{t('header.knowledgeBase')}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: datasetActiveKey ? (
|
||||||
|
<Link
|
||||||
|
to={`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`}
|
||||||
|
>
|
||||||
|
{t(`knowledgeDetails.${activeKey}`)}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
t(`knowledgeDetails.${activeKey}`)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (datasetActiveKey) {
|
||||||
|
items.push({
|
||||||
|
title: t(`knowledgeDetails.${datasetActiveKey}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}, [activeKey, datasetActiveKey, gotoList, knowledgeBaseId, t]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Siderbar></Siderbar>
|
||||||
|
<div className={styles.contentWrapper}>
|
||||||
|
<Breadcrumb items={breadcrumbItems} />
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Outlet></Outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KnowledgeAdding;
|
||||||
@ -262,6 +262,11 @@ const routes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `${Routes.ParsedResult}/chunks`,
|
||||||
|
layout: false,
|
||||||
|
component: `@/pages${Routes.Chunk}/parsed-result/add-knowledge/components/knowledge-chunk`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: Routes.Chunk,
|
path: Routes.Chunk,
|
||||||
layout: false,
|
layout: false,
|
||||||
@ -270,10 +275,10 @@ const routes = [
|
|||||||
path: Routes.Chunk,
|
path: Routes.Chunk,
|
||||||
component: `@/pages${Routes.Chunk}`,
|
component: `@/pages${Routes.Chunk}`,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
// {
|
||||||
path: `${Routes.ParsedResult}/:id`,
|
// path: `${Routes.ParsedResult}/:id`,
|
||||||
component: `@/pages${Routes.Chunk}/parsed-result`,
|
// component: `@/pages${Routes.Chunk}/parsed-result`,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
path: `${Routes.ChunkResult}/:id`,
|
path: `${Routes.ChunkResult}/:id`,
|
||||||
component: `@/pages${Routes.Chunk}/chunk-result`,
|
component: `@/pages${Routes.Chunk}/chunk-result`,
|
||||||
|
|||||||
Reference in New Issue
Block a user