mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-06-01 16:20:59 +08:00
fix: filter knowledge list by keywords and clear the selected file list after the file is uploaded successfully and add ellipsis pattern to chunk list (#628)
### What problem does this PR solve? #627 fix: filter knowledge list by keywords fix: clear the selected file list after the file is uploaded successfully feat: add ellipsis pattern to chunk list ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
parent
7d3b68bb1e
commit
9703633a57
@ -48,6 +48,7 @@ const ChunkMethodModal: React.FC<IProps> = ({
|
|||||||
visible,
|
visible,
|
||||||
documentExtension,
|
documentExtension,
|
||||||
parserConfig,
|
parserConfig,
|
||||||
|
loading,
|
||||||
}) => {
|
}) => {
|
||||||
const { parserList, handleChange, selectedTag } = useFetchParserListOnMount(
|
const { parserList, handleChange, selectedTag } = useFetchParserListOnMount(
|
||||||
documentId,
|
documentId,
|
||||||
@ -109,6 +110,7 @@ const ChunkMethodModal: React.FC<IProps> = ({
|
|||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
afterClose={afterClose}
|
afterClose={afterClose}
|
||||||
|
confirmLoading={loading}
|
||||||
>
|
>
|
||||||
<Space size={[0, 8]} wrap>
|
<Space size={[0, 8]} wrap>
|
||||||
<Form.Item label={t('chunkMethod')} className={styles.chunkMethod}>
|
<Form.Item label={t('chunkMethod')} className={styles.chunkMethod}>
|
||||||
|
@ -68,15 +68,20 @@ const FileUploadModal = ({
|
|||||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||||
const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
|
const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
|
||||||
|
|
||||||
|
const clearFileList = () => {
|
||||||
|
setFileList([]);
|
||||||
|
setDirectoryFileList([]);
|
||||||
|
};
|
||||||
|
|
||||||
const onOk = async () => {
|
const onOk = async () => {
|
||||||
const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]);
|
const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]);
|
||||||
if (ret !== undefined && ret === 0) {
|
|
||||||
setFileList([]);
|
|
||||||
setDirectoryFileList([]);
|
|
||||||
}
|
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const afterClose = () => {
|
||||||
|
clearFileList();
|
||||||
|
};
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
const items: TabsProps['items'] = [
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
@ -110,6 +115,7 @@ const FileUploadModal = ({
|
|||||||
onOk={onOk}
|
onOk={onOk}
|
||||||
onCancel={hideModal}
|
onCancel={hideModal}
|
||||||
confirmLoading={loading}
|
confirmLoading={loading}
|
||||||
|
afterClose={afterClose}
|
||||||
>
|
>
|
||||||
<Flex gap={'large'} vertical>
|
<Flex gap={'large'} vertical>
|
||||||
<Segmented
|
<Segmented
|
||||||
|
@ -125,13 +125,19 @@ export const useFetchKnowledgeBaseConfiguration = () => {
|
|||||||
}, [fetchKnowledgeBaseConfiguration]);
|
}, [fetchKnowledgeBaseConfiguration]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useSelectKnowledgeList = () => {
|
||||||
|
const knowledgeModel = useSelector((state) => state.knowledgeModel);
|
||||||
|
const { data = [] } = knowledgeModel;
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const useFetchKnowledgeList = (
|
export const useFetchKnowledgeList = (
|
||||||
shouldFilterListWithoutDocument: boolean = false,
|
shouldFilterListWithoutDocument: boolean = false,
|
||||||
) => {
|
) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const loading = useOneNamespaceEffectsLoading('knowledgeModel', ['getList']);
|
const loading = useOneNamespaceEffectsLoading('knowledgeModel', ['getList']);
|
||||||
|
|
||||||
const knowledgeModel = useSelector((state: any) => state.knowledgeModel);
|
const knowledgeModel = useSelector((state) => state.knowledgeModel);
|
||||||
const { data = [] } = knowledgeModel;
|
const { data = [] } = knowledgeModel;
|
||||||
const list: IKnowledge[] = useMemo(() => {
|
const list: IKnowledge[] = useMemo(() => {
|
||||||
return shouldFilterListWithoutDocument
|
return shouldFilterListWithoutDocument
|
||||||
|
@ -42,3 +42,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.textEllipsis() {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multipleLineEllipsis(@line) {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: @line;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
@ -64,6 +64,7 @@ export default {
|
|||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Please input name!',
|
namePlaceholder: 'Please input name!',
|
||||||
doc: 'Docs',
|
doc: 'Docs',
|
||||||
|
searchKnowledgePlaceholder: 'Search',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
dataset: 'Dataset',
|
dataset: 'Dataset',
|
||||||
@ -278,6 +279,8 @@ export default {
|
|||||||
keyword: 'Keyword',
|
keyword: 'Keyword',
|
||||||
function: 'Function',
|
function: 'Function',
|
||||||
chunkMessage: 'Please input value!',
|
chunkMessage: 'Please input value!',
|
||||||
|
full: 'Full text',
|
||||||
|
ellipse: 'Ellipse',
|
||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
createAssistant: 'Create an Assistant',
|
createAssistant: 'Create an Assistant',
|
||||||
|
@ -64,6 +64,7 @@ export default {
|
|||||||
name: '名稱',
|
name: '名稱',
|
||||||
namePlaceholder: '請輸入名稱',
|
namePlaceholder: '請輸入名稱',
|
||||||
doc: '文件',
|
doc: '文件',
|
||||||
|
searchKnowledgePlaceholder: '搜索',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
dataset: '數據集',
|
dataset: '數據集',
|
||||||
@ -251,6 +252,8 @@ export default {
|
|||||||
keyword: '關鍵詞',
|
keyword: '關鍵詞',
|
||||||
function: '函數',
|
function: '函數',
|
||||||
chunkMessage: '請輸入值!',
|
chunkMessage: '請輸入值!',
|
||||||
|
full: '全文',
|
||||||
|
ellipse: '省略',
|
||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
createAssistant: '新建助理',
|
createAssistant: '新建助理',
|
||||||
|
@ -64,6 +64,7 @@ export default {
|
|||||||
name: '名称',
|
name: '名称',
|
||||||
namePlaceholder: '请输入名称',
|
namePlaceholder: '请输入名称',
|
||||||
doc: '文档',
|
doc: '文档',
|
||||||
|
searchKnowledgePlaceholder: '搜索',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
dataset: '数据集',
|
dataset: '数据集',
|
||||||
@ -268,6 +269,8 @@ export default {
|
|||||||
keyword: '关键词',
|
keyword: '关键词',
|
||||||
function: '函数',
|
function: '函数',
|
||||||
chunkMessage: '请输入值!',
|
chunkMessage: '请输入值!',
|
||||||
|
full: '全文',
|
||||||
|
ellipse: '省略',
|
||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
createAssistant: '新建助理',
|
createAssistant: '新建助理',
|
||||||
|
@ -14,6 +14,10 @@
|
|||||||
.chunkText;
|
.chunkText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contentEllipsis {
|
||||||
|
.multipleLineEllipsis(3);
|
||||||
|
}
|
||||||
|
|
||||||
.chunkCard {
|
.chunkCard {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { Card, Checkbox, CheckboxProps, Flex, Popover, Switch } from 'antd';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { ChunkTextMode } from '../../constant';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -14,6 +15,7 @@ interface IProps {
|
|||||||
handleCheckboxClick: (chunkId: string, checked: boolean) => void;
|
handleCheckboxClick: (chunkId: string, checked: boolean) => void;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
clickChunkCard: (chunkId: string) => void;
|
clickChunkCard: (chunkId: string) => void;
|
||||||
|
textMode: ChunkTextMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChunkCard = ({
|
const ChunkCard = ({
|
||||||
@ -24,6 +26,7 @@ const ChunkCard = ({
|
|||||||
switchChunk,
|
switchChunk,
|
||||||
selected,
|
selected,
|
||||||
clickChunkCard,
|
clickChunkCard,
|
||||||
|
textMode,
|
||||||
}: IProps) => {
|
}: IProps) => {
|
||||||
const available = Number(item.available_int);
|
const available = Number(item.available_int);
|
||||||
const [enabled, setEnabled] = useState(available === 1);
|
const [enabled, setEnabled] = useState(available === 1);
|
||||||
@ -68,8 +71,15 @@ const ChunkCard = ({
|
|||||||
onDoubleClick={handleContentDoubleClick}
|
onDoubleClick={handleContentDoubleClick}
|
||||||
onClick={handleContentClick}
|
onClick={handleContentClick}
|
||||||
className={styles.content}
|
className={styles.content}
|
||||||
dangerouslySetInnerHTML={{ __html: item.content_with_weight }}
|
>
|
||||||
></section>
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: item.content_with_weight }}
|
||||||
|
className={classNames({
|
||||||
|
[styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse,
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Switch checked={enabled} onChange={onChange} />
|
<Switch checked={enabled} onChange={onChange} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,12 +22,18 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
Radio,
|
Radio,
|
||||||
RadioChangeEvent,
|
RadioChangeEvent,
|
||||||
|
Segmented,
|
||||||
|
SegmentedProps,
|
||||||
Space,
|
Space,
|
||||||
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
||||||
import { Link, useDispatch, useSelector } from 'umi';
|
import { Link, useDispatch, useSelector } from 'umi';
|
||||||
|
import { ChunkTextMode } from '../../constant';
|
||||||
import { ChunkModelState } from '../../model';
|
import { ChunkModelState } from '../../model';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
getChunkList: () => void;
|
getChunkList: () => void;
|
||||||
@ -35,6 +41,7 @@ interface IProps {
|
|||||||
createChunk: () => void;
|
createChunk: () => void;
|
||||||
removeChunk: () => void;
|
removeChunk: () => void;
|
||||||
switchChunk: (available: number) => void;
|
switchChunk: (available: number) => void;
|
||||||
|
changeChunkTextMode(mode: ChunkTextMode): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChunkToolBar = ({
|
const ChunkToolBar = ({
|
||||||
@ -44,6 +51,7 @@ const ChunkToolBar = ({
|
|||||||
createChunk,
|
createChunk,
|
||||||
removeChunk,
|
removeChunk,
|
||||||
switchChunk,
|
switchChunk,
|
||||||
|
changeChunkTextMode,
|
||||||
}: IProps) => {
|
}: IProps) => {
|
||||||
const { documentInfo, available, searchString }: ChunkModelState =
|
const { documentInfo, available, searchString }: ChunkModelState =
|
||||||
useSelector((state: any) => state.chunkModel);
|
useSelector((state: any) => state.chunkModel);
|
||||||
@ -170,9 +178,18 @@ const ChunkToolBar = ({
|
|||||||
<ArrowLeftOutlined />
|
<ArrowLeftOutlined />
|
||||||
</Link>
|
</Link>
|
||||||
<FilePdfOutlined />
|
<FilePdfOutlined />
|
||||||
{documentInfo.name}
|
<Text ellipsis={{ tooltip: documentInfo.name }} style={{ width: 150 }}>
|
||||||
|
{documentInfo.name}
|
||||||
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
<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}>
|
<Popover content={content} placement="bottom" arrow={false}>
|
||||||
<Button>
|
<Button>
|
||||||
{t('bulk')}
|
{t('bulk')}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
export enum ChunkTextMode {
|
||||||
|
Full = 'full',
|
||||||
|
Ellipse = 'ellipse',
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { buildChunkHighlights } from '@/utils/documentUtils';
|
|||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { IHighlight } from 'react-pdf-highlighter';
|
import { IHighlight } from 'react-pdf-highlighter';
|
||||||
import { useSelector } from 'umi';
|
import { useSelector } from 'umi';
|
||||||
|
import { ChunkTextMode } from './constant';
|
||||||
|
|
||||||
export const useSelectDocumentInfo = () => {
|
export const useSelectDocumentInfo = () => {
|
||||||
const documentInfo: IKnowledgeFile = useSelector(
|
const documentInfo: IKnowledgeFile = useSelector(
|
||||||
@ -63,3 +64,14 @@ export const useSelectChunkListLoading = () => {
|
|||||||
'switch_chunk',
|
'switch_chunk',
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 };
|
||||||
|
};
|
||||||
|
@ -10,6 +10,7 @@ import CreatingModal from './components/chunk-creating-modal';
|
|||||||
import ChunkToolBar from './components/chunk-toolbar';
|
import ChunkToolBar from './components/chunk-toolbar';
|
||||||
import DocumentPreview from './components/document-preview/preview';
|
import DocumentPreview from './components/document-preview/preview';
|
||||||
import {
|
import {
|
||||||
|
useChangeChunkTextMode,
|
||||||
useHandleChunkCardClick,
|
useHandleChunkCardClick,
|
||||||
useSelectChunkListLoading,
|
useSelectChunkListLoading,
|
||||||
useSelectDocumentInfo,
|
useSelectDocumentInfo,
|
||||||
@ -35,6 +36,7 @@ const Chunk = () => {
|
|||||||
const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
|
const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick();
|
||||||
const isPdf = documentInfo.type === 'pdf';
|
const isPdf = documentInfo.type === 'pdf';
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { changeChunkTextMode, textMode } = useChangeChunkTextMode();
|
||||||
|
|
||||||
const getChunkList = useFetchChunkList();
|
const getChunkList = useFetchChunkList();
|
||||||
|
|
||||||
@ -87,9 +89,10 @@ const Chunk = () => {
|
|||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const showSelectedChunkWarning = () => {
|
|
||||||
|
const showSelectedChunkWarning = useCallback(() => {
|
||||||
message.warning(t('message.pleaseSelectChunk'));
|
message.warning(t('message.pleaseSelectChunk'));
|
||||||
};
|
}, [t]);
|
||||||
|
|
||||||
const handleRemoveChunk = useCallback(async () => {
|
const handleRemoveChunk = useCallback(async () => {
|
||||||
if (selectedChunkIds.length > 0) {
|
if (selectedChunkIds.length > 0) {
|
||||||
@ -100,7 +103,7 @@ const Chunk = () => {
|
|||||||
} else {
|
} else {
|
||||||
showSelectedChunkWarning();
|
showSelectedChunkWarning();
|
||||||
}
|
}
|
||||||
}, [selectedChunkIds, documentId, removeChunk]);
|
}, [selectedChunkIds, documentId, removeChunk, showSelectedChunkWarning]);
|
||||||
|
|
||||||
const switchChunk = useCallback(
|
const switchChunk = useCallback(
|
||||||
async (available?: number, chunkIds?: string[]) => {
|
async (available?: number, chunkIds?: string[]) => {
|
||||||
@ -125,7 +128,13 @@ const Chunk = () => {
|
|||||||
getChunkList();
|
getChunkList();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, documentId, getChunkList, selectedChunkIds],
|
[
|
||||||
|
dispatch,
|
||||||
|
documentId,
|
||||||
|
getChunkList,
|
||||||
|
selectedChunkIds,
|
||||||
|
showSelectedChunkWarning,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -147,6 +156,7 @@ const Chunk = () => {
|
|||||||
removeChunk={handleRemoveChunk}
|
removeChunk={handleRemoveChunk}
|
||||||
checked={selectedChunkIds.length === data.length}
|
checked={selectedChunkIds.length === data.length}
|
||||||
switchChunk={switchChunk}
|
switchChunk={switchChunk}
|
||||||
|
changeChunkTextMode={changeChunkTextMode}
|
||||||
></ChunkToolBar>
|
></ChunkToolBar>
|
||||||
<Divider></Divider>
|
<Divider></Divider>
|
||||||
<Flex flex={1} gap={'middle'}>
|
<Flex flex={1} gap={'middle'}>
|
||||||
@ -175,6 +185,7 @@ const Chunk = () => {
|
|||||||
switchChunk={switchChunk}
|
switchChunk={switchChunk}
|
||||||
clickChunkCard={handleChunkCardClick}
|
clickChunkCard={handleChunkCardClick}
|
||||||
selected={item.chunk_id === selectedChunkId}
|
selected={item.chunk_id === selectedChunkId}
|
||||||
|
textMode={textMode}
|
||||||
></ChunkCard>
|
></ChunkCard>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -23,7 +23,6 @@ import {
|
|||||||
useFetchDocumentListOnMount,
|
useFetchDocumentListOnMount,
|
||||||
useGetPagination,
|
useGetPagination,
|
||||||
useHandleSearchChange,
|
useHandleSearchChange,
|
||||||
useNavigateToOtherPage,
|
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
@ -44,7 +43,6 @@ const DocumentToolbar = ({
|
|||||||
const { handleInputChange } = useHandleSearchChange(setPagination);
|
const { handleInputChange } = useHandleSearchChange(setPagination);
|
||||||
const removeDocument = useRemoveDocument();
|
const removeDocument = useRemoveDocument();
|
||||||
const showDeleteConfirm = useShowDeleteConfirm();
|
const showDeleteConfirm = useShowDeleteConfirm();
|
||||||
const { linkToUploadPage } = useNavigateToOtherPage();
|
|
||||||
const runDocumentByIds = useRunDocument();
|
const runDocumentByIds = useRunDocument();
|
||||||
const { knowledgeId } = useGetKnowledgeSearchParams();
|
const { knowledgeId } = useGetKnowledgeSearchParams();
|
||||||
const changeStatus = useSetDocumentStatus();
|
const changeStatus = useSetDocumentStatus();
|
||||||
@ -77,7 +75,6 @@ const DocumentToolbar = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
// disabled: true,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [showDocumentUploadModal, showCreateModal, t]);
|
}, [showDocumentUploadModal, showCreateModal, t]);
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
.datasetWrapper {
|
.datasetWrapper {
|
||||||
padding: 30px;
|
padding: 30px 30px 0;
|
||||||
flex: 1;
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.documentTable {
|
||||||
|
tbody {
|
||||||
|
// height: calc(100vh - 508px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.filter {
|
||||||
|
@ -179,7 +179,8 @@ const KnowledgeFile = () => {
|
|||||||
// loading={loading}
|
// loading={loading}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
rowSelection={rowSelection}
|
rowSelection={rowSelection}
|
||||||
scroll={{ scrollToFirstRowOnChange: true, x: 1300, y: 'fill' }}
|
className={styles.documentTable}
|
||||||
|
scroll={{ scrollToFirstRowOnChange: true, x: 1300 }}
|
||||||
/>
|
/>
|
||||||
<CreateFileModal
|
<CreateFileModal
|
||||||
visible={createVisible}
|
visible={createVisible}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
.uploader {
|
|
||||||
:global {
|
|
||||||
.ant-upload-list {
|
|
||||||
max-height: 40vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
import { useTranslate } from '@/hooks/commonHooks';
|
|
||||||
import { IModalProps } from '@/interfaces/common';
|
|
||||||
import { InboxOutlined } from '@ant-design/icons';
|
|
||||||
import {
|
|
||||||
Flex,
|
|
||||||
Modal,
|
|
||||||
Segmented,
|
|
||||||
Tabs,
|
|
||||||
TabsProps,
|
|
||||||
Upload,
|
|
||||||
UploadFile,
|
|
||||||
UploadProps,
|
|
||||||
} from 'antd';
|
|
||||||
import { Dispatch, SetStateAction, useState } from 'react';
|
|
||||||
|
|
||||||
import styles from './index.less';
|
|
||||||
|
|
||||||
const { Dragger } = Upload;
|
|
||||||
|
|
||||||
const FileUpload = ({
|
|
||||||
directory,
|
|
||||||
fileList,
|
|
||||||
setFileList,
|
|
||||||
}: {
|
|
||||||
directory: boolean;
|
|
||||||
fileList: UploadFile[];
|
|
||||||
setFileList: Dispatch<SetStateAction<UploadFile[]>>;
|
|
||||||
}) => {
|
|
||||||
const { t } = useTranslate('fileManager');
|
|
||||||
const props: UploadProps = {
|
|
||||||
multiple: true,
|
|
||||||
onRemove: (file) => {
|
|
||||||
const index = fileList.indexOf(file);
|
|
||||||
const newFileList = fileList.slice();
|
|
||||||
newFileList.splice(index, 1);
|
|
||||||
setFileList(newFileList);
|
|
||||||
},
|
|
||||||
beforeUpload: (file) => {
|
|
||||||
setFileList((pre) => {
|
|
||||||
return [...pre, file];
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
directory,
|
|
||||||
fileList,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dragger {...props} className={styles.uploader}>
|
|
||||||
<p className="ant-upload-drag-icon">
|
|
||||||
<InboxOutlined />
|
|
||||||
</p>
|
|
||||||
<p className="ant-upload-text">{t('uploadTitle')}</p>
|
|
||||||
<p className="ant-upload-hint">{t('uploadDescription')}</p>
|
|
||||||
</Dragger>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const FileUploadModal = ({
|
|
||||||
visible,
|
|
||||||
hideModal,
|
|
||||||
loading,
|
|
||||||
onOk: onFileUploadOk,
|
|
||||||
}: IModalProps<UploadFile[]>) => {
|
|
||||||
const { t } = useTranslate('fileManager');
|
|
||||||
const [value, setValue] = useState<string | number>('local');
|
|
||||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
|
||||||
const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]);
|
|
||||||
|
|
||||||
const onOk = async () => {
|
|
||||||
const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]);
|
|
||||||
console.info(ret);
|
|
||||||
if (ret !== undefined && ret === 0) {
|
|
||||||
setFileList([]);
|
|
||||||
setDirectoryFileList([]);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
|
||||||
{
|
|
||||||
key: '1',
|
|
||||||
label: t('file'),
|
|
||||||
children: (
|
|
||||||
<FileUpload
|
|
||||||
directory={false}
|
|
||||||
fileList={fileList}
|
|
||||||
setFileList={setFileList}
|
|
||||||
></FileUpload>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '2',
|
|
||||||
label: t('directory'),
|
|
||||||
children: (
|
|
||||||
<FileUpload
|
|
||||||
directory
|
|
||||||
fileList={directoryFileList}
|
|
||||||
setFileList={setDirectoryFileList}
|
|
||||||
></FileUpload>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Modal
|
|
||||||
title={t('uploadFile')}
|
|
||||||
open={visible}
|
|
||||||
onOk={onOk}
|
|
||||||
onCancel={hideModal}
|
|
||||||
confirmLoading={loading}
|
|
||||||
>
|
|
||||||
<Flex gap={'large'} vertical>
|
|
||||||
<Segmented
|
|
||||||
options={[
|
|
||||||
{ label: t('local'), value: 'local' },
|
|
||||||
{ label: t('s3'), value: 's3' },
|
|
||||||
]}
|
|
||||||
block
|
|
||||||
value={value}
|
|
||||||
onChange={setValue}
|
|
||||||
/>
|
|
||||||
{value === 'local' ? (
|
|
||||||
<Tabs defaultActiveKey="1" items={items} />
|
|
||||||
) : (
|
|
||||||
t('comingSoon', { keyPrefix: 'common' })
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FileUploadModal;
|
|
@ -16,13 +16,13 @@ import {
|
|||||||
useSelectFileListLoading,
|
useSelectFileListLoading,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
|
import FileUploadModal from '@/components/file-upload-modal';
|
||||||
import RenameModal from '@/components/rename-modal';
|
import RenameModal from '@/components/rename-modal';
|
||||||
import SvgIcon from '@/components/svg-icon';
|
import SvgIcon from '@/components/svg-icon';
|
||||||
import { useTranslate } from '@/hooks/commonHooks';
|
import { useTranslate } from '@/hooks/commonHooks';
|
||||||
import { formatNumberWithThousandsSeparator } from '@/utils/commonUtil';
|
import { formatNumberWithThousandsSeparator } from '@/utils/commonUtil';
|
||||||
import { getExtension } from '@/utils/documentUtils';
|
import { getExtension } from '@/utils/documentUtils';
|
||||||
import ConnectToKnowledgeModal from './connect-to-knowledge-modal';
|
import ConnectToKnowledgeModal from './connect-to-knowledge-modal';
|
||||||
import FileUploadModal from './file-upload-modal';
|
|
||||||
import FolderCreateModal from './folder-create-modal';
|
import FolderCreateModal from './folder-create-modal';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
|
19
web/src/pages/knowledge/hooks.ts
Normal file
19
web/src/pages/knowledge/hooks.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useSelectKnowledgeList } from '@/hooks/knowledgeHook';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
export const useSearchKnowledge = () => {
|
||||||
|
const [searchString, setSearchString] = useState<string>('');
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setSearchString(e.target.value);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSelectKnowledgeListByKeywords = (keywords: string) => {
|
||||||
|
const list = useSelectKnowledgeList();
|
||||||
|
return list.filter((x) => x.name.includes(keywords));
|
||||||
|
};
|
@ -1,16 +1,19 @@
|
|||||||
import ModalManager from '@/components/modal-manager';
|
import ModalManager from '@/components/modal-manager';
|
||||||
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
|
import { useFetchKnowledgeList } from '@/hooks/knowledgeHook';
|
||||||
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
import { useSelectUserInfo } from '@/hooks/userSettingHook';
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import { Button, Empty, Flex, Space, Spin } from 'antd';
|
import { Button, Empty, Flex, Input, Space, Spin } from 'antd';
|
||||||
import KnowledgeCard from './knowledge-card';
|
import KnowledgeCard from './knowledge-card';
|
||||||
import KnowledgeCreatingModal from './knowledge-creating-modal';
|
import KnowledgeCreatingModal from './knowledge-creating-modal';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useSearchKnowledge, useSelectKnowledgeListByKeywords } from './hooks';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
const Knowledge = () => {
|
const KnowledgeList = () => {
|
||||||
const { list, loading } = useFetchKnowledgeList();
|
const { searchString, handleInputChange } = useSearchKnowledge();
|
||||||
|
const { loading } = useFetchKnowledgeList();
|
||||||
|
const list = useSelectKnowledgeListByKeywords(searchString);
|
||||||
const userInfo = useSelectUserInfo();
|
const userInfo = useSelectUserInfo();
|
||||||
const { t } = useTranslation('translation', { keyPrefix: 'knowledgeList' });
|
const { t } = useTranslation('translation', { keyPrefix: 'knowledgeList' });
|
||||||
|
|
||||||
@ -24,9 +27,15 @@ const Knowledge = () => {
|
|||||||
<p className={styles.description}>{t('description')}</p>
|
<p className={styles.description}>{t('description')}</p>
|
||||||
</div>
|
</div>
|
||||||
<Space size={'large'}>
|
<Space size={'large'}>
|
||||||
{/* <Button icon={<FilterIcon />} className={styles.filterButton}>
|
<Input
|
||||||
Filters
|
placeholder={t('searchKnowledgePlaceholder')}
|
||||||
</Button> */}
|
value={searchString}
|
||||||
|
style={{ width: 220 }}
|
||||||
|
allowClear
|
||||||
|
onChange={handleInputChange}
|
||||||
|
prefix={<SearchOutlined />}
|
||||||
|
/>
|
||||||
|
|
||||||
<ModalManager>
|
<ModalManager>
|
||||||
{({ visible, hideModal, showModal }) => (
|
{({ visible, hideModal, showModal }) => (
|
||||||
<>
|
<>
|
||||||
@ -70,4 +79,4 @@ const Knowledge = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Knowledge;
|
export default KnowledgeList;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user