diff --git a/web/src/assets/svg/refresh.svg b/web/src/assets/svg/refresh.svg new file mode 100644 index 0000000000000000000000000000000000000000..0b575716113a0eecbb79c32eda00ea856b41f204 --- /dev/null +++ b/web/src/assets/svg/refresh.svg @@ -0,0 +1,5 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path + d="M14.1667 4.27108C15.9345 5.55903 17.0834 7.6453 17.0834 9.99992C17.0834 13.9119 13.9121 17.0833 10.0001 17.0833H9.58342M5.83341 15.7288C4.06564 14.4408 2.91675 12.3545 2.91675 9.99992C2.91675 6.0879 6.08806 2.91659 10.0001 2.91659H10.4167M10.8334 18.6666L9.16675 16.9999L10.8334 15.3333M9.16675 4.66659L10.8334 2.99992L9.16675 1.33325" + stroke="#17B26A" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" /> +</svg> \ No newline at end of file diff --git a/web/src/assets/svg/run.svg b/web/src/assets/svg/run.svg new file mode 100644 index 0000000000000000000000000000000000000000..e33c897cc7b86ddf2dee734da3b2bbeff4caac57 --- /dev/null +++ b/web/src/assets/svg/run.svg @@ -0,0 +1,15 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g clip-path="url(#clip0_490_5128)"> + <path + d="M10.0001 18.3334C14.6025 18.3334 18.3334 14.6025 18.3334 10.0001C18.3334 5.39771 14.6025 1.66675 10.0001 1.66675C5.39771 1.66675 1.66675 5.39771 1.66675 10.0001C1.66675 14.6025 5.39771 18.3334 10.0001 18.3334Z" + stroke="#17B26A" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" /> + <path + d="M7.91675 7.47119C7.91675 7.07345 7.91675 6.87459 7.99987 6.76356C8.0723 6.66681 8.18318 6.60628 8.30373 6.59767C8.44207 6.58779 8.60935 6.69533 8.94392 6.91041L12.8777 9.4393C13.1681 9.62593 13.3132 9.71925 13.3634 9.83791C13.4072 9.94159 13.4072 10.0586 13.3634 10.1623C13.3132 10.2809 13.1681 10.3742 12.8777 10.5609L8.94392 13.0898C8.60935 13.3048 8.44207 13.4124 8.30373 13.4025C8.18318 13.3939 8.0723 13.3334 7.99987 13.2366C7.91675 13.1256 7.91675 12.9267 7.91675 12.529V7.47119Z" + stroke="#17B26A" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round" /> + </g> + <defs> + <clipPath id="clip0_490_5128"> + <rect width="20" height="20" fill="white" /> + </clipPath> + </defs> +</svg> \ No newline at end of file diff --git a/web/src/constants/knowledge.ts b/web/src/constants/knowledge.ts index 043c445e2b0c824436b4fbc38b4c22c5dc1d229f..06870f31aa363d8af32767ee07b07f1a400f3748 100644 --- a/web/src/constants/knowledge.ts +++ b/web/src/constants/knowledge.ts @@ -2,13 +2,12 @@ export enum KnowledgeRouteKey { Dataset = 'dataset', Testing = 'testing', Configuration = 'configuration', - TempTesting = 'tempTesting', } export enum RunningStatus { - UNSTART = '0', - RUNNING = '1', - CANCEL = '2', - DONE = '3', - FAIL = '4', + UNSTART = '0', // need to run + RUNNING = '1', // need to cancel + CANCEL = '2', // need to refresh + DONE = '3', // need to refresh + FAIL = '4', // need to refresh } diff --git a/web/src/hooks/knowledgeHook.ts b/web/src/hooks/knowledgeHook.ts index 9e023a9cf024d4edc44c33eabc36a85016c6ca94..c9a4b7316216961cc3973869826cd161274d11df 100644 --- a/web/src/hooks/knowledgeHook.ts +++ b/web/src/hooks/knowledgeHook.ts @@ -1,6 +1,6 @@ import showDeleteConfirm from '@/components/deleting-confirm'; -import { IKnowledge } from '@/interfaces/database/knowledge'; -import { useCallback } from 'react'; +import { IKnowledge, ITenantInfo } from '@/interfaces/database/knowledge'; +import { useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSearchParams, useSelector } from 'umi'; export const useKnowledgeBaseId = (): string => { @@ -77,3 +77,50 @@ export const useDeleteChunkByIds = (): { removeChunk: onRemoveChunk, }; }; + +export const useSelectParserList = (): Array<{ + value: string; + label: string; +}> => { + const tenantIfo: Nullable<ITenantInfo> = useSelector( + (state: any) => state.settingModel.tenantIfo, + ); + + const parserList = useMemo(() => { + const parserArray: Array<string> = tenantIfo?.parser_ids.split(',') ?? []; + return parserArray.map((x) => { + const arr = x.split(':'); + return { value: arr[0], label: arr[1] }; + }); + }, [tenantIfo]); + + return parserList; +}; + +export const useFetchParserList = () => { + const dispatch = useDispatch(); + + useEffect(() => { + dispatch({ + type: 'settingModel/getTenantInfo', + }); + }, [dispatch]); +}; + +export const useFetchKnowledgeBaseConfiguration = () => { + const dispatch = useDispatch(); + const knowledgeBaseId = useKnowledgeBaseId(); + + const fetchKnowledgeBaseConfiguration = useCallback(() => { + dispatch({ + type: 'kSModel/getKbDetail', + payload: { + kb_id: knowledgeBaseId, + }, + }); + }, [dispatch, knowledgeBaseId]); + + useEffect(() => { + fetchKnowledgeBaseConfiguration(); + }, [fetchKnowledgeBaseConfiguration]); +}; diff --git a/web/src/interfaces/database/llm.ts b/web/src/interfaces/database/llm.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e8c7a919d07956fd825b8d4625938452fc1d328 --- /dev/null +++ b/web/src/interfaces/database/llm.ts @@ -0,0 +1,16 @@ +export interface IThirdOAIModel { + available: boolean; + create_date: string; + create_time: number; + fid: string; + id: number; + llm_name: string; + max_tokens: number; + model_type: string; + status: string; + tags: string; + update_date: string; + update_time: number; +} + +export type IThirdOAIModelCollection = Record<string, IThirdOAIModel[]>; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/index.less b/web/src/pages/add-knowledge/components/knowledge-chunk/index.less index a798eb9e2714dde665e0f5b854b38c6a47a60173..4eb426b08c7d439cddb08e650ec1ed273566b8f8 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/index.less +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/index.less @@ -26,6 +26,7 @@ .chunkContainer { height: calc(100vh - 320px); overflow: auto; + width: 100%; } .pageFooter { diff --git a/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx index db475e22d418fa22c5725df0a6d896795da9b95d..b836ece32aff1abe96504d980c9dbc17fcb6b241 100644 --- a/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx @@ -2,10 +2,11 @@ import { ReactComponent as SelectFilesEndIcon } from '@/assets/svg/select-files- import { ReactComponent as SelectFilesStartIcon } from '@/assets/svg/select-files-start.svg'; import { useDeleteDocumentById, + useFetchParserList, useGetDocumentDefaultParser, useKnowledgeBaseId, + useSelectParserList, } from '@/hooks/knowledgeHook'; -import { ITenantInfo } from '@/interfaces/database/knowledge'; import uploadService from '@/services/uploadService'; import { ArrowLeftOutlined, @@ -28,9 +29,8 @@ import { UploadProps, } from 'antd'; import classNames from 'classnames'; -import { ReactElement, useEffect, useMemo, useRef, useState } from 'react'; -import { Nullable } from 'typings'; -import { Link, useDispatch, useNavigate, useSelector } from 'umi'; +import { ReactElement, useEffect, useRef, useState } from 'react'; +import { Link, useDispatch, useNavigate } from 'umi'; import { KnowledgeRouteKey } from '@/constants/knowledge'; import styles from './index.less'; @@ -45,13 +45,11 @@ const UploaderItem = ({ file, actions, isUpload, - parserArray, }: { isUpload: boolean; originNode: ReactElement; file: UploadFile; fileList: object[]; - parserArray: string[]; actions: { download: Function; preview: Function; remove: any }; }) => { const { parserConfig, defaultParserId } = useGetDocumentDefaultParser( @@ -63,12 +61,7 @@ const UploaderItem = ({ const documentId = file?.response?.id; - const parserList = useMemo(() => { - return parserArray.map((x) => { - const arr = x.split(':'); - return { value: arr[0], label: arr[1] }; - }); - }, [parserArray]); + const parserList = useSelectParserList(); const saveParser = (parserId: string) => { dispatch({ @@ -154,14 +147,10 @@ const KnowledgeUploadFile = () => { const knowledgeBaseId = useKnowledgeBaseId(); const [isUpload, setIsUpload] = useState(true); const dispatch = useDispatch(); - const tenantIfo: Nullable<ITenantInfo> = useSelector( - (state: any) => state.settingModel.tenantIfo, - ); + const navigate = useNavigate(); const fileListRef = useRef<UploadFile[]>([]); - const parserArray = tenantIfo?.parser_ids.split(',') ?? []; - const createRequest: (props: UploadRequestOption) => void = async function ({ file, onSuccess, @@ -170,9 +159,13 @@ const KnowledgeUploadFile = () => { }) { const { data } = await uploadService.uploadFile(file, knowledgeBaseId); if (data.retcode === 0) { - onSuccess && onSuccess(data.data); + if (onSuccess) { + onSuccess(data.data); + } } else { - onError && onError(data.data); + if (onError) { + onError(data.data); + } } }; @@ -188,7 +181,6 @@ const KnowledgeUploadFile = () => { fileList={fileList} originNode={originNode} actions={actions} - parserArray={parserArray} ></UploaderItem> ); }, @@ -215,11 +207,7 @@ const KnowledgeUploadFile = () => { } }; - useEffect(() => { - dispatch({ - type: 'settingModel/getTenantInfo', - }); - }, []); + useFetchParserList(); return ( <div className={styles.uploadWrapper}> diff --git a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx index 6d8c7845232de6466eee01f60bce103f54b056f1..a61d8fc5487fe0cedb4fd48e9244ed0120a8ffb9 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx @@ -1,8 +1,5 @@ import { KnowledgeRouteKey } from '@/constants/knowledge'; -import { - useDeleteDocumentById, - useKnowledgeBaseId, -} from '@/hooks/knowledgeHook'; +import { useKnowledgeBaseId } from '@/hooks/knowledgeHook'; import { Pagination } from '@/interfaces/common'; import { IKnowledgeFile } from '@/interfaces/database/knowledge'; import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil'; @@ -40,7 +37,6 @@ const KnowledgeFile = () => { const effects = useSelector((state: any) => state.loading.effects); const { data, total } = kFModel; const knowledgeBaseId = useKnowledgeBaseId(); - const { removeDocument } = useDeleteDocumentById(); const loading = getOneNamespaceEffectsLoading('kFModel', effects, [ 'getKfList', @@ -132,9 +128,7 @@ const KnowledgeFile = () => { }, }); }; - const onRmDocument = () => { - removeDocument(doc_id); - }; + const showCEFModal = () => { dispatch({ type: 'kFModel/updateState', @@ -144,15 +138,6 @@ const KnowledgeFile = () => { }); }; - const showSegmentSetModal = () => { - dispatch({ - type: 'kFModel/updateState', - payload: { - isShowSegmentSetModal: true, - }, - }); - }; - const actionItems: MenuProps['items'] = useMemo(() => { return [ { @@ -185,31 +170,6 @@ const KnowledgeFile = () => { }, ]; }, []); - const chunkItems: MenuProps['items'] = [ - { - key: '1', - label: ( - <div> - <Button type="link" onClick={showSegmentSetModal}> - {' '} - 分段设置 - </Button> - </div> - ), - }, - { - key: '2', - label: ( - <div> - <Button type="link" onClick={onRmDocument}> - {' '} - Delete - </Button> - </div> - ), - // disabled: true, - }, - ]; const toChunk = (id: string) => { navigate( diff --git a/web/src/pages/add-knowledge/components/knowledge-file/model.ts b/web/src/pages/add-knowledge/components/knowledge-file/model.ts index 1b673355efd35f1c031ea7ad6bf6ba941c19b28d..ac7f0e38c0f14f06978f7b248457c01e202736c6 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-file/model.ts @@ -55,33 +55,23 @@ const model: DvaModel<KFModelState> = { return { ...state, pagination: { ...state.pagination, ...payload } }; }, }, - subscriptions: { - setup({ dispatch, history }) { - history.listen((location) => {}); - }, - }, effects: { - *createKf({ payload = {} }, { call, put }) { - const { data, response } = yield call(kbService.createKb, payload); - const { retcode, data: res, retmsg } = data; + *createKf({ payload = {} }, { call }) { + const { data } = yield call(kbService.createKb, payload); + const { retcode } = data; if (retcode === 0) { message.success('创建æˆåŠŸï¼'); } }, - *updateKf({ payload = {} }, { call, put }) { - const { data, response } = yield call(kbService.updateKb, payload); - const { retcode, data: res, retmsg } = data; + *updateKf({ payload = {} }, { call }) { + const { data } = yield call(kbService.updateKb, payload); + const { retcode } = data; if (retcode === 0) { message.success('修改æˆåŠŸï¼'); } }, - *getKfDetail({ payload = {}, callback }, { call, put }) { - const { data, response } = yield call(kbService.get_kb_detail, payload); - const { retcode, data: res, retmsg } = data; - if (retcode === 0) { - // localStorage.setItem('userInfo',res.) - callback && callback(res); - } + *getKfDetail({ payload = {} }, { call }) { + const { data } = yield call(kbService.get_kb_detail, payload); }, *getKfList({ payload = {} }, { call, put, select }) { const state: KFModelState = yield select((state: any) => state.kFModel); @@ -119,11 +109,11 @@ const model: DvaModel<KFModelState> = { { type: 'poll', delay: 5000 }, // TODO: Provide type support for this effect ], *updateDocumentStatus({ payload = {} }, { call, put }) { - const { data, response } = yield call( + const { data } = yield call( kbService.document_change_status, pick(payload, ['doc_id', 'status']), ); - const { retcode, data: res, retmsg } = data; + const { retcode } = data; if (retcode === 0) { message.success('修改æˆåŠŸï¼'); put({ @@ -133,10 +123,10 @@ const model: DvaModel<KFModelState> = { } }, *document_rm({ payload = {} }, { call, put }) { - const { data, response } = yield call(kbService.document_rm, { + const { data } = yield call(kbService.document_rm, { doc_id: payload.doc_id, }); - const { retcode, data: res, retmsg } = data; + const { retcode } = data; if (retcode === 0) { message.success('åˆ é™¤æˆåŠŸï¼'); yield put({ @@ -151,7 +141,7 @@ const model: DvaModel<KFModelState> = { kbService.document_rename, omit(payload, ['kb_id']), ); - const { retcode, data: res, retmsg } = data; + const { retcode } = data; if (retcode === 0) { message.success('rename successï¼'); yield put({ @@ -168,7 +158,7 @@ const model: DvaModel<KFModelState> = { }, *document_create({ payload = {} }, { call, put }) { const { data } = yield call(kbService.document_create, payload); - const { retcode, data: res } = data; + const { retcode } = data; if (retcode === 0) { put({ type: 'kFModel/updateState', @@ -181,19 +171,25 @@ const model: DvaModel<KFModelState> = { return retcode; }, *document_run({ payload = {} }, { call, put }) { - const { data } = yield call(kbService.document_run, payload); + const { data } = yield call( + kbService.document_run, + omit(payload, ['knowledgeBaseId']), + ); const { retcode } = data; if (retcode === 0) { - message.success('Run successfully ï¼'); + if (payload.knowledgeBaseId) { + yield put({ + type: 'getKfList', + payload: { kb_id: payload.knowledgeBaseId }, + }); + } + message.success('Operation successfully ï¼'); } return retcode; }, *document_change_parser({ payload = {} }, { call, put }) { - const { data, response } = yield call( - kbService.document_change_parser, - payload, - ); - const { retcode, data: res, retmsg } = data; + const { data } = yield call(kbService.document_change_parser, payload); + const { retcode } = data; if (retcode === 0) { put({ type: 'updateState', diff --git a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less index b595d95b15cc73e91d94865d77c31c6ab4f2dfe5..ada0866af9f68a3f36595a766c5d55444b184522 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less +++ b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.less @@ -1,3 +1,12 @@ .popover-content { width: 300px; } + +.operationIcon { + text-align: center; + margin-right: 20%; + width: 20px; + &:hover { + cursor: pointer; + } +} diff --git a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx index 7c2784ca24cfea41f36e44ab82cc4d5d07db8ce6..5c0711a451d1518cc4318d602f8625cfcae40cc7 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx @@ -1,9 +1,21 @@ +import { ReactComponent as RefreshIcon } from '@/assets/svg/refresh.svg'; +import { ReactComponent as RunIcon } from '@/assets/svg/run.svg'; import { IKnowledgeFile } from '@/interfaces/database/knowledge'; import { Badge, DescriptionsProps, Flex, Popover, Space, Tag } from 'antd'; import { RunningStatus, RunningStatusMap } from '../constant'; +import { CloseCircleOutlined } from '@ant-design/icons'; +import { useDispatch } from 'umi'; import styles from './index.less'; +const iconMap = { + [RunningStatus.UNSTART]: RunIcon, + [RunningStatus.RUNNING]: CloseCircleOutlined, + [RunningStatus.CANCEL]: RefreshIcon, + [RunningStatus.DONE]: RefreshIcon, + [RunningStatus.FAIL]: RefreshIcon, +}; + interface IProps { record: IKnowledgeFile; } @@ -31,7 +43,7 @@ const PopoverContent = ({ record }: IProps) => { <Flex vertical className={styles['popover-content']}> {items.map((x) => { return ( - <div> + <div key={x.key}> <b>{x.label}:</b> <p>{x.children}</p> </div> @@ -42,27 +54,46 @@ const PopoverContent = ({ record }: IProps) => { }; export const ParsingStatusCell = ({ record }: IProps) => { + const dispatch = useDispatch(); const text = record.run; const runningStatus = RunningStatusMap[text]; const isRunning = text === RunningStatus.RUNNING; + const OperationIcon = iconMap[text]; + + const handleOperationIconClick = () => { + dispatch({ + type: 'kFModel/document_run', + payload: { + doc_ids: [record.id], + run: isRunning ? 2 : 1, + knowledgeBaseId: record.kb_id, + }, + }); + }; + return ( - <Popover - content={isRunning && <PopoverContent record={record}></PopoverContent>} - > - <Tag color={runningStatus.color}> - {isRunning ? ( - <Space> - <Badge color={runningStatus.color} /> - {runningStatus.label} - <span>{record.progress * 100}%</span> - </Space> - ) : ( - runningStatus.label - )} - </Tag> - </Popover> + <Flex justify={'space-between'}> + <Popover + content={isRunning && <PopoverContent record={record}></PopoverContent>} + > + <Tag color={runningStatus.color}> + {isRunning ? ( + <Space> + <Badge color={runningStatus.color} /> + {runningStatus.label} + <span>{(record.progress * 100).toFixed(2)}%</span> + </Space> + ) : ( + runningStatus.label + )} + </Tag> + </Popover> + <div onClick={handleOperationIconClick} className={styles.operationIcon}> + <OperationIcon /> + </div> + </Flex> ); }; diff --git a/web/src/pages/add-knowledge/components/knowledge-search/index.less b/web/src/pages/add-knowledge/components/knowledge-search/index.less deleted file mode 100644 index 4f51079ac30540b776343bb3bb5715333ef0e8cc..0000000000000000000000000000000000000000 --- a/web/src/pages/add-knowledge/components/knowledge-search/index.less +++ /dev/null @@ -1,79 +0,0 @@ -.chunkPage { - padding: 24px; - display: flex; - height: calc(100vh - 112px); - // flex-direction: column; - - .filter { - margin-right: 20px; - display: flex; - height: 32px; - width: 300px; - flex-wrap: wrap; - justify-content: space-between; - } - - .pageContainer { - flex: 1; - display: flex; - flex-direction: column; - - .pageContent { - flex: 1; - width: 100%; - padding-right: 12px; - overflow-y: auto; - - .spin { - min-height: 400px; - } - } - - .pageFooter { - height: 32px; - float: right; - } - } - -} - -.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; - -} \ No newline at end of file diff --git a/web/src/pages/add-knowledge/components/knowledge-search/index.tsx b/web/src/pages/add-knowledge/components/knowledge-search/index.tsx deleted file mode 100644 index 6c17135ce60f37b0f2c9f8d8b584dea0a04b4d9c..0000000000000000000000000000000000000000 --- a/web/src/pages/add-knowledge/components/knowledge-search/index.tsx +++ /dev/null @@ -1,276 +0,0 @@ -import { useKnowledgeBaseId } from '@/hooks/knowledgeHook'; -import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; -import { api_host } from '@/utils/api'; -import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons'; -import type { PaginationProps } from 'antd'; -import { - Card, - Col, - Input, - Pagination, - Popconfirm, - Row, - Select, - Spin, - Switch, -} from 'antd'; -import { debounce } from 'lodash'; -import React, { useCallback, useEffect } from 'react'; -import { useDispatch, useSelector } from 'umi'; -import CreateModal from '../knowledge-chunk/components/chunk-creating-modal'; - -import styles from './index.less'; - -const KnowledgeSearching = () => { - const dispatch = useDispatch(); - const kSearchModel = useSelector((state: any) => state.kSearchModel); - const chunkModel = useSelector((state: any) => state.chunkModel); - const loading = useOneNamespaceEffectsLoading('kSearchModel', [ - 'chunk_list', - 'switch_chunk', - ]); - const knowledgeBaseId = useKnowledgeBaseId(); - - const { - data = [], - total, - d_list = [], - question, - doc_ids, - pagination, - } = kSearchModel; - const { chunk_id, doc_id, isShowCreateModal } = chunkModel; - - const getChunkList = () => { - dispatch({ - type: 'kSearchModel/chunk_list', - payload: { - kb_id: knowledgeBaseId, - }, - }); - }; - const confirm = (id: string) => { - dispatch({ - type: 'kSearchModel/rm_chunk', - payload: { - chunk_ids: [id], - kb_id: knowledgeBaseId, - }, - }); - }; - const handleEditchunk = (item: any) => { - const { chunk_id, doc_id } = item; - dispatch({ - type: 'chunkModel/updateState', - payload: { - isShowCreateModal: true, - chunk_id, - doc_id, - }, - }); - getChunkList(); - }; - const onShowSizeChange: PaginationProps['onShowSizeChange'] = ( - page, - size, - ) => { - dispatch({ - type: 'kSearchModel/updateState', - payload: { - pagination: { page, size }, - }, - }); - }; - useEffect(() => { - dispatch({ - type: 'kSearchModel/updateState', - payload: { - doc_ids: [], - question: '', - }, - }); - dispatch({ - type: 'kSearchModel/getKfList', - payload: { - kb_id: knowledgeBaseId, - }, - }); - }, []); - const switchChunk = (item: any, available_int: boolean) => { - const { chunk_id, doc_id } = item; - - dispatch({ - type: 'kSearchModel/switch_chunk', - payload: { - chunk_ids: [chunk_id], - doc_id, - available_int, - kb_id: knowledgeBaseId, - }, - }); - }; - - useEffect(() => { - getChunkList(); - }, [doc_ids, pagination, question]); - const debounceChange = debounce((value) => { - dispatch({ - type: 'kSearchModel/updateState', - payload: { - question: value, - }, - }); - }, 300); - - const debounceCallback = useCallback( - (value: string) => debounceChange(value), - [], - ); - const handleInputChange = ( - e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, - ) => { - const value = e.target.value; - debounceCallback(value); - }; - const handleSelectChange = (value: any[]) => { - dispatch({ - type: 'kSearchModel/updateState', - payload: { - doc_ids: value, - }, - }); - }; - - return ( - <> - <div className={styles.chunkPage}> - <div className={styles.filter}> - <Select - showSearch - placeholder="文件列表" - optionFilterProp="children" - onChange={handleSelectChange} - style={{ width: 300, marginBottom: 20 }} - options={d_list} - fieldNames={{ label: 'name', value: 'id' }} - mode="multiple" - /> - - <Input.TextArea - autoSize={{ minRows: 6, maxRows: 6 }} - placeholder="æœç´¢" - style={{ width: 300 }} - allowClear - onChange={handleInputChange} - /> - </div> - <div className={styles.pageContainer}> - <div className={styles.pageContent}> - <Spin spinning={loading} className={styles.spin} size="large"> - <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24 }}> - {data.map((item: any) => { - return ( - <Col - className="gutter-row" - key={item.chunk_id} - xs={24} - sm={12} - md={12} - lg={8} - > - <Card - className={styles.card} - onClick={() => { - handleEditchunk(item); - }} - > - <img - style={{ width: '50px' }} - src={`${api_host}/document/image/${item.img_id}`} - alt="" - /> - <div className={styles.container}> - <div className={styles.content}> - <span className={styles.context}> - {item.content_ltks} - </span> - <span className={styles.delete}> - <Switch - size="small" - defaultValue={item.doc_ids == '1'} - onChange={(checked: boolean, e: any) => { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - switchChunk(item, checked); - }} - /> - </span> - </div> - <div className={styles.footer}> - <span className={styles.text}> - <MinusSquareOutlined /> - {item.doc_num}文档 - </span> - <span className={styles.text}> - <MinusSquareOutlined /> - {item.chunk_num}个 - </span> - <span className={styles.text}> - <MinusSquareOutlined /> - {item.token_num}åƒå—符 - </span> - <span style={{ float: 'right' }}> - <Popconfirm - title="Delete the task" - description="Are you sure to delete this task?" - onConfirm={(e: any) => { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - console.log(confirm); - confirm(item.chunk_id); - }} - okText="Yes" - cancelText="No" - > - <DeleteOutlined - onClick={(e) => { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - }} - /> - </Popconfirm> - </span> - </div> - </div> - </Card> - </Col> - ); - })} - </Row> - </Spin> - </div> - <div className={styles.pageFooter}> - <Pagination - responsive - showLessItems - showQuickJumper - showSizeChanger - onChange={onShowSizeChange} - defaultPageSize={30} - pageSizeOptions={[30, 60, 90]} - defaultCurrent={pagination.page} - total={total} - /> - </div> - </div> - </div> - <CreateModal - isShowCreateModal={isShowCreateModal} - chunk_id={chunk_id} - doc_id={doc_id} - /> - </> - ); -}; - -export default KnowledgeSearching; diff --git a/web/src/pages/add-knowledge/components/knowledge-search/model.ts b/web/src/pages/add-knowledge/components/knowledge-search/model.ts deleted file mode 100644 index 592417223c6c96c100845e97015cc2b506917892..0000000000000000000000000000000000000000 --- a/web/src/pages/add-knowledge/components/knowledge-search/model.ts +++ /dev/null @@ -1,160 +0,0 @@ -import kbService from '@/services/kbService'; -import omit from 'lodash/omit'; -import { DvaModel } from 'umi'; - -export interface KSearchModelState { - loading: boolean; - data: any[]; - total: number; - isShowCreateModal: boolean; - chunk_id: string; - chunkInfo: any; - d_list: any[]; - question: string; - doc_ids: any[]; - pagination: any; - doc_id: string; -} - -const model: DvaModel<KSearchModelState> = { - namespace: 'kSearchModel', - state: { - loading: false, - data: [], - total: 0, - isShowCreateModal: false, - chunk_id: '', - chunkInfo: {}, - d_list: [], - question: '', - doc_ids: [], - pagination: { page: 1, size: 30 }, - doc_id: '', - }, - reducers: { - updateState(state, { payload }) { - return { - ...state, - ...payload, - }; - }, - }, - subscriptions: { - setup({ dispatch, history }) { - history.listen((location) => { - console.log(location); - }); - }, - }, - effects: { - *getKfList({ payload = {} }, { call, put }) { - const { data, response } = yield call( - kbService.get_document_list, - payload, - ); - - const { retcode, data: res, retmsg } = data; - if (retcode === 0) { - yield put({ - type: 'updateState', - payload: { - d_list: res, - }, - }); - } - }, - *chunk_list({ payload = {} }, { call, put, select }) { - const { question, doc_ids, pagination }: KSearchModelState = yield select( - (state: any) => state.kSearchModel, - ); - const { data } = yield call(kbService.retrieval_test, { - ...payload, - ...pagination, - question, - doc_ids, - similarity_threshold: 0.1, - }); - const { retcode, data: res, retmsg } = data; - if (retcode === 0) { - yield put({ - type: 'updateState', - payload: { - data: res.chunks, - total: res.total, - }, - }); - } - }, - *switch_chunk({ payload = {} }, { call, put }) { - const { data } = yield call( - kbService.switch_chunk, - omit(payload, ['kb_id']), - ); - const { retcode } = data; - if (retcode === 0) { - yield put({ - type: 'chunk_list', - payload: { - kb_id: payload.kb_id, - }, - }); - } - }, - *rm_chunk({ payload = {} }, { call, put }) { - const { data } = yield call(kbService.rm_chunk, { - chunk_ids: payload.chunk_ids, - }); - const { retcode, data: res, retmsg } = data; - if (retcode === 0) { - // TODO: Can be extracted - yield put({ - type: 'chunk_list', - payload: { - kb_id: payload.kb_id, - }, - }); - } - }, - *get_chunk({ payload = {} }, { call, put }) { - const { data, response } = yield call(kbService.get_chunk, payload); - const { retcode, data: res, retmsg } = data; - if (retcode === 0) { - yield put({ - type: 'updateState', - payload: { - chunkInfo: res, - }, - }); - } - }, - *create_hunk({ payload = {} }, { call, put }) { - yield put({ - type: 'updateState', - payload: { - loading: true, - }, - }); - let service = kbService.create_chunk; - if (payload.chunk_id) { - service = kbService.set_chunk; - } - const { data } = yield call(service, payload); - const { retcode } = data; - yield put({ - type: 'updateState', - payload: { - loading: false, - }, - }); - if (retcode === 0) { - yield put({ - type: 'updateState', - payload: { - isShowCreateModal: false, - }, - }); - } - }, - }, -}; -export default model; diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6e71f8cab411160b5a8e30e6484db12678060d49 --- /dev/null +++ b/web/src/pages/add-knowledge/components/knowledge-setting/configuration.tsx @@ -0,0 +1,217 @@ +import { + useFetchKnowledgeBaseConfiguration, + useFetchParserList, + useKnowledgeBaseId, + useSelectParserList, +} from '@/hooks/knowledgeHook'; +import { + Button, + Divider, + Form, + Input, + Radio, + Select, + Space, + Typography, + Upload, + UploadFile, +} from 'antd'; +import pick from 'lodash/pick'; +import { useCallback, useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'umi'; + +import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; +import { IKnowledge } from '@/interfaces/database/knowledge'; +import { IThirdOAIModelCollection } from '@/interfaces/database/llm'; +import { PlusOutlined } from '@ant-design/icons'; +import styles from './index.less'; + +const { Title } = Typography; +const { Option } = Select; + +const Configuration = () => { + const [form] = Form.useForm(); + const dispatch = useDispatch(); + const knowledgeBaseId = useKnowledgeBaseId(); + const loading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']); + + const llmInfo: IThirdOAIModelCollection = useSelector( + (state: any) => state.settingModel.llmInfo, + ); + const knowledgeDetails: IKnowledge = useSelector( + (state: any) => state.kSModel.knowledgeDetails, + ); + + const normFile = (e: any) => { + if (Array.isArray(e)) { + return e; + } + return e?.fileList; + }; + + const parserList = useSelectParserList(); + + const embeddingModelOptions = useMemo(() => { + return Object.entries(llmInfo).map(([key, value]) => { + return { + label: key, + options: value.map((x) => ({ + label: x.llm_name, + value: x.llm_name, + })), + }; + }); + }, [llmInfo]); + + const onFinish = async (values: any) => { + console.info(values); + const fileList = values.avatar; + let avatar; + + if (Array.isArray(fileList)) { + avatar = fileList[0].thumbUrl; + } + + dispatch({ + type: 'kSModel/updateKb', + payload: { + ...values, + avatar, + kb_id: knowledgeBaseId, + }, + }); + }; + + const onFinishFailed = (errorInfo: any) => { + console.log('Failed:', errorInfo); + }; + + const fetchLlmList = useCallback(() => { + dispatch({ + type: 'settingModel/llm_list', + payload: { model_type: 'embedding' }, + }); + }, [dispatch]); + + useEffect(() => { + const avatar = knowledgeDetails.avatar; + let fileList: UploadFile[] = []; + + if (avatar) { + fileList = [{ uid: '1', name: 'file', thumbUrl: avatar, status: 'done' }]; + } + form.setFieldsValue({ + ...pick(knowledgeDetails, [ + 'description', + 'name', + 'permission', + 'embd_id', + 'parser_id', + ]), + avatar: fileList, + }); + }, [form, knowledgeDetails]); + + useFetchParserList(); + useFetchKnowledgeBaseConfiguration(); + + useEffect(() => { + fetchLlmList(); + }, [fetchLlmList]); + + return ( + <div className={styles.configurationWrapper}> + <Title level={5}>Configuration</Title> + <p>Update your knowledge base details especially parsing method here.</p> + <Divider></Divider> + <Form + form={form} + name="validateOnly" + layout="vertical" + autoComplete="off" + onFinish={onFinish} + onFinishFailed={onFinishFailed} + > + <Form.Item + name="name" + label="Knowledge base name" + rules={[{ required: true }]} + > + <Input /> + </Form.Item> + <Form.Item + name="avatar" + label="Knowledge base photo" + valuePropName="fileList" + getValueFromEvent={normFile} + > + <Upload + listType="picture-card" + maxCount={1} + showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }} + > + <button style={{ border: 0, background: 'none' }} type="button"> + <PlusOutlined /> + <div style={{ marginTop: 8 }}>Upload</div> + </button> + </Upload> + </Form.Item> + <Form.Item name="description" label="Knowledge base bio"> + <Input /> + </Form.Item> + <Form.Item + name="permission" + label="Permissions" + rules={[{ required: true }]} + > + <Radio.Group> + <Radio value="me">Only me</Radio> + <Radio value="team">Team</Radio> + </Radio.Group> + </Form.Item> + <Form.Item + name="embd_id" + label="Embedding Model" + rules={[{ required: true }]} + > + <Select + placeholder="Please select a country" + options={embeddingModelOptions} + ></Select> + </Form.Item> + <Form.Item + name="parser_id" + label="Knowledge base category" + rules={[{ required: true }]} + > + <Select placeholder="Please select a country"> + {parserList.map((x) => ( + <Option value={x.value} key={x.value}> + {x.label} + </Option> + ))} + </Select> + </Form.Item> + <Form.Item> + <div className={styles.buttonWrapper}> + <Space> + <Button htmlType="reset" size={'middle'}> + Cancel + </Button> + <Button + htmlType="submit" + type="primary" + size={'middle'} + loading={loading} + > + Save + </Button> + </Space> + </div> + </Form.Item> + </Form> + </div> + ); +}; + +export default Configuration; diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/index.less b/web/src/pages/add-knowledge/components/knowledge-setting/index.less index 1c06588d52f330b3c649ee6a99129ffbb85ff204..0691fd904031769b6e4d1d919fcb137d5ad7853e 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/index.less +++ b/web/src/pages/add-knowledge/components/knowledge-setting/index.less @@ -1,24 +1,30 @@ .tags { - margin-bottom: 24px; + margin-bottom: 24px; } .preset { - display: flex; - height: 80px; - background-color: rgba(0, 0, 0, 0.1); - border-radius: 5px; - padding: 5px; - margin-bottom: 24px; + display: flex; + height: 80px; + background-color: rgba(0, 0, 0, 0.1); + border-radius: 5px; + padding: 5px; + margin-bottom: 24px; - .left { - flex: 1; + .left { + flex: 1; + } - } + .right { + width: 100px; + border-left: 1px solid rgba(0, 0, 0, 0.4); + margin: 10px 0px; + padding: 5px; + } +} - .right { - width: 100px; - border-left: 1px solid rgba(0, 0, 0, 0.4); - margin: 10px 0px; - padding: 5px; - } -} \ No newline at end of file +.configurationWrapper { + padding: 0 52px; + .buttonWrapper { + text-align: right; + } +} diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/index.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/index.tsx index 1926acb2aa802378d92527cc1a464c700a1cc356..74a0712d8d2a51efbbf5cb7ded5d04db245a5dd1 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/index.tsx @@ -3,6 +3,8 @@ import { useKnowledgeBaseId } from '@/hooks/knowledgeHook'; import { Button, Form, Input, Radio, Select, Space, Tag } from 'antd'; import { useCallback, useEffect, useState } from 'react'; import { useDispatch, useNavigate, useSelector } from 'umi'; +import Configuration from './configuration'; + import styles from './index.less'; const { CheckableTag } = Tag; @@ -12,7 +14,6 @@ const layout = { labelAlign: 'left' as const, }; const { Option } = Select; -/* eslint-disable no-template-curly-in-string */ const KnowledgeSetting = () => { const dispatch = useDispatch(); @@ -44,7 +45,7 @@ const KnowledgeSetting = () => { setSelectedTag(data.data.parser_id); } } - }, [knowledgeBaseId]); + }, [knowledgeBaseId, dispatch, form]); const onFinish = async () => { try { @@ -68,10 +69,11 @@ const KnowledgeSetting = () => { parser_id: selectedTag, }, }); - retcode === 0 && + if (retcode === 0) { navigate( `/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`, ); + } } } catch (error) { console.warn(error); @@ -158,4 +160,6 @@ const KnowledgeSetting = () => { ); }; -export default KnowledgeSetting; +// export default KnowledgeSetting; + +export default Configuration; diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts index 0cd54882727ea7b3eabbb70ae3d5c61c14977d4e..1f4499631eeb813a28df578f524316af06d93617 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts @@ -1,3 +1,4 @@ +import { IKnowledge } from '@/interfaces/database/knowledge'; import kbService from '@/services/kbService'; import { message } from 'antd'; import { DvaModel } from 'umi'; @@ -6,6 +7,7 @@ export interface KSModelState { isShowPSwModal: boolean; isShowTntModal: boolean; tenantIfo: any; + knowledgeDetails: IKnowledge; } const model: DvaModel<KSModelState> = { @@ -14,6 +16,7 @@ const model: DvaModel<KSModelState> = { isShowPSwModal: false, isShowTntModal: false, tenantIfo: {}, + knowledgeDetails: {} as any, }, reducers: { updateState(state, { payload }) { @@ -22,31 +25,32 @@ const model: DvaModel<KSModelState> = { ...payload, }; }, - }, - subscriptions: { - setup({ dispatch, history }) { - history.listen((location) => {}); + setKnowledgeDetails(state, { payload }) { + return { ...state, knowledgeDetails: payload }; }, }, effects: { - *createKb({ payload = {} }, { call, put }) { + *createKb({ payload = {} }, { call }) { const { data } = yield call(kbService.createKb, payload); const { retcode } = data; if (retcode === 0) { - message.success('创建知识库æˆåŠŸï¼'); + message.success('Created successfully!'); } return data; }, *updateKb({ payload = {} }, { call, put }) { const { data } = yield call(kbService.updateKb, payload); - const { retcode, data: res, retmsg } = data; + const { retcode } = data; if (retcode === 0) { - message.success('更新知识库æˆåŠŸï¼'); + yield put({ type: 'getKbDetail', payload: { kb_id: payload.kb_id } }); + message.success('Updated successfully!'); } }, *getKbDetail({ payload = {} }, { call, put }) { const { data } = yield call(kbService.get_kb_detail, payload); - + if (data.retcode === 0) { + yield put({ type: 'setKnowledgeDetails', payload: data.data }); + } return data; }, }, diff --git a/web/src/pages/add-knowledge/components/knowledge-sidebar/index.tsx b/web/src/pages/add-knowledge/components/knowledge-sidebar/index.tsx index e24e0bf320b1912ae61f9cba16eb245af1a2bcd1..44805fd52abde55bedbaf43659bf468a9ea7d814 100644 --- a/web/src/pages/add-knowledge/components/knowledge-sidebar/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-sidebar/index.tsx @@ -1,9 +1,10 @@ -import { ReactComponent as ConfigrationIcon } from '@/assets/svg/knowledge-configration.svg'; +import { ReactComponent as ConfigurationIcon } from '@/assets/svg/knowledge-configration.svg'; import { ReactComponent as DatasetIcon } from '@/assets/svg/knowledge-dataset.svg'; import { ReactComponent as TestingIcon } from '@/assets/svg/knowledge-testing.svg'; +import { useFetchKnowledgeBaseConfiguration } from '@/hooks/knowledgeHook'; import { useSecondPathName } from '@/hooks/routeHook'; +import { IKnowledge } from '@/interfaces/database/knowledge'; import { getWidth } from '@/utils'; -import { AntDesignOutlined } from '@ant-design/icons'; import { Avatar, Menu, MenuProps, Space } from 'antd'; import classNames from 'classnames'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -16,6 +17,9 @@ const KnowledgeSidebar = () => { const { id } = kAModel; let navigate = useNavigate(); const activeKey = useSecondPathName(); + const knowledgeDetails: IKnowledge = useSelector( + (state: any) => state.kSModel.knowledgeDetails, + ); const [windowWidth, setWindowWidth] = useState(getWidth()); const [collapsed, setCollapsed] = useState(false); @@ -62,12 +66,7 @@ const KnowledgeSidebar = () => { getItem( routeMap[KnowledgeRouteKey.Configuration], KnowledgeRouteKey.Configuration, - <ConfigrationIcon />, - ), - getItem( - routeMap[KnowledgeRouteKey.TempTesting], - KnowledgeRouteKey.TempTesting, - <TestingIcon />, + <ConfigurationIcon />, ), ]; }, [getItem]); @@ -93,16 +92,17 @@ const KnowledgeSidebar = () => { }; }, []); + useFetchKnowledgeBaseConfiguration(); + return ( <div className={styles.sidebarWrapper}> <div className={styles.sidebarTop}> <Space size={8} direction="vertical"> - <Avatar size={64} icon={<AntDesignOutlined />} /> - <div className={styles.knowledgeTitle}>Cloud Computing</div> + <Avatar size={64} src={knowledgeDetails.avatar} /> + <div className={styles.knowledgeTitle}>{knowledgeDetails.name}</div> </Space> <p className={styles.knowledgeDescription}> - A scalable, secure cloud-based database optimized for high-performance - computing and data storage. + {knowledgeDetails.description} </p> </div> <div className={styles.divider}></div> diff --git a/web/src/pages/setting/model.ts b/web/src/pages/setting/model.ts index 4a843e9dff05809112f14a6799d2d7c1073222fb..643ed5e717e26339b193bed395614b1c2dad3be1 100644 --- a/web/src/pages/setting/model.ts +++ b/web/src/pages/setting/model.ts @@ -1,4 +1,5 @@ import { ITenantInfo } from '@/interfaces/database/knowledge'; +import { IThirdOAIModelCollection as IThirdAiModelCollection } from '@/interfaces/database/llm'; import userService from '@/services/userService'; import authorizationUtil from '@/utils/authorizationUtil'; import { message } from 'antd'; @@ -12,7 +13,7 @@ export interface SettingModelState { isShowSSModal: boolean; llm_factory: string; tenantIfo: Nullable<ITenantInfo>; - llmInfo: any; + llmInfo: IThirdAiModelCollection; myLlm: any[]; factoriesList: any[]; } @@ -126,8 +127,8 @@ const model: DvaModel<SettingModelState> = { } }, *llm_list({ payload = {} }, { call, put }) { - const { data, response } = yield call(userService.llm_list, payload); - const { retcode, data: res, retmsg } = data; + const { data } = yield call(userService.llm_list, payload); + const { retcode, data: res } = data; if (retcode === 0) { yield put({ type: 'updateState', diff --git a/web/src/routes.ts b/web/src/routes.ts index c2f8aebcf024b1ec1b8b9767d4bfde84fff100a4..aff5178c4846461fe7175a77ac0c0eeb27515cbe 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -44,10 +44,6 @@ const routes = [ }, { path: '/knowledge/testing', - component: '@/pages/add-knowledge/components/knowledge-search', - }, - { - path: '/knowledge/tempTesting', component: '@/pages/add-knowledge/components/knowledge-testing', }, ], diff --git a/web/src/utils/fileUtil.ts b/web/src/utils/fileUtil.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab9cced01982380e61ce636f67d4013ca3922b11 --- /dev/null +++ b/web/src/utils/fileUtil.ts @@ -0,0 +1,28 @@ +export const transformFile2Base64 = (val: any): Promise<any> => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(val); + reader.onload = (): void => { + resolve(reader.result); + }; + reader.onerror = reject; + }); +}; + +export const transformBase64ToFile = ( + dataUrl: string, + filename: string = 'file', +) => { + let arr = dataUrl.split(','), + bstr = atob(arr[1]), + n = bstr.length, + u8arr = new Uint8Array(n); + + const mime = arr[0].match(/:(.*?);/); + const mimeType = mime ? mime[1] : 'image/png'; + + while (n--) { + u8arr[n] = bstr.charCodeAt(n); + } + return new File([u8arr], filename, { type: mimeType }); +};