feat: playground add submenu

This commit is contained in:
jialin
2024-11-22 10:00:34 +08:00
parent 22f90a505c
commit e05ec3d2d5
22 changed files with 1141 additions and 207 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
export default {
'menu.dashboard': 'Dashboard',
'menu.playground': 'Playground',
'menu.chat': 'Chat',
'menu.playground.rerank': 'Rerank',
'menu.playground.chat': 'Chat',
'menu.compare': 'Compare',
'menu.models': 'Models',
'menu.resources': 'Resources',
+2 -1
View File
@@ -1,7 +1,8 @@
export default {
'menu.dashboard': '概览',
'menu.playground': '试验场',
'menu.chat': '聊天',
'menu.playground.rerank': 'Rerank',
'menu.playground.chat': '对话',
'menu.compare': '多模型对比',
'menu.models': '模型',
'menu.resources': '资源',
@@ -24,7 +24,7 @@ import ReferenceParams from './reference-params';
import RerankMessage from './rerank-message';
import RerankerParams from './reranker-params';
import UploadFile from './upload-file';
import ViewCodeModal from './view-code-modal';
import ViewRerankCode from './view-rerank-code';
interface MessageProps {
modelList: Global.BaseOption<string>[];
@@ -34,6 +34,8 @@ interface MessageProps {
const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
const { modelList } = props;
const acceptType =
'.txt, .doc, .docx, .xls, .xlsx, .csv, .md, .pdf, .eml, .msg, .ppt, .pptx, .xml, .epub, .html';
const messageId = useRef<number>(0);
const [messageList, setMessageList] = useState<MessageItem[]>([]);
@@ -42,7 +44,6 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
const [searchParams] = useSearchParams();
const selectModel = searchParams.get('model') || '';
const [parameters, setParams] = useState<any>({});
const [systemMessage, setSystemMessage] = useState('');
const [show, setShow] = useState(false);
const [loading, setLoading] = useState(false);
const [tokenResult, setTokenResult] = useState<any>(null);
@@ -63,7 +64,10 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
>([]);
const { initialize, updateScrollerPosition } = useOverlayScroller();
const { initialize: innitializeParams } = useOverlayScroller();
const {
initialize: innitializeParams,
updateScrollerPosition: updateDocumentScrollerPosition
} = useOverlayScroller();
useImperativeHandle(ref, () => {
return {
@@ -95,32 +99,15 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
requestToken.current?.cancel?.();
requestToken.current = requestSource();
controllerRef.current?.abort?.();
controllerRef.current = new AbortController();
const signal = controllerRef.current.signal;
currentMessageRef.current = current
? [
{
content: current.content,
title: 'Query',
uid: messageId.current
}
]
: [];
contentRef.current = '';
setMessageList((pre) => {
return [...currentMessageRef.current];
});
contentRef.current = current?.content || '';
const documentList: any[] = [...textList, ...fileList];
console.log('documentList:', documentList);
const result: any = await rerankerQuery(
{
model: parameters.model,
top_n: parameters.top_n,
query: current?.content || '',
query: contentRef.current,
documents: [
...textList.map((item) => item.text),
...fileList.map((item) => item.text)
@@ -135,9 +122,9 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
setMessageId();
setTokenResult(result.usage);
setMessageList([
...currentMessageRef.current,
{
title: 'Results',
role: '',
content: result.results?.map((item: any) => {
return {
uid: item.index,
@@ -169,9 +156,7 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
};
const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => {
const currentMessage =
message.content || message.imgs?.length ? message : undefined;
submitMessage(currentMessage);
submitMessage(message);
};
const handleCloseViewCode = () => {
@@ -223,17 +208,15 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
}, [paramsRef.current, innitializeParams]);
useEffect(() => {
if (loading) {
updateScrollerPosition();
}
}, [messageList, loading]);
updateScrollerPosition();
}, [messageList]);
useEffect(() => {
if (messageList.length > messageListLengthCache.current) {
updateScrollerPosition();
if (textList.length + fileList.length > messageListLengthCache.current) {
updateDocumentScrollerPosition();
}
messageListLengthCache.current = messageList.length;
}, [messageList.length]);
messageListLengthCache.current = textList.length + fileList.length;
}, [textList.length, fileList.length]);
return (
<div className="ground-left-wrapper">
@@ -241,7 +224,18 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
<div className="message-list-wrap" ref={scroller}>
<>
<div className="content">
<RerankMessage dataList={messageList} />
<RerankMessage
dataList={messageList}
header={
<div className="result-header">
<span className="title">Results</span>
<ReferenceParams
usage={tokenResult}
showOutput={false}
></ReferenceParams>
</div>
}
/>
{loading && (
<Spin size="small">
<div style={{ height: '46px' }}></div>
@@ -250,24 +244,36 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
</div>
</>
</div>
{tokenResult && (
<div style={{ height: 40 }}>
<ReferenceParams
usage={tokenResult}
showOutput={false}
></ReferenceParams>
</div>
)}
<div
style={{
height: 70,
paddingLeft: 1
}}
>
<UploadFile
handleUpdateFileList={handleUpdateFileList}
accept={acceptType}
>
<div style={{ backgroundColor: 'var(--color-fill-sider)' }}>
<InboxOutlined className="font-size-16" />
<span className="m-l-10">
Click or drag file to this area to upload
</span>
</div>
<span className="text-tertiary">support {acceptType}</span>
</UploadFile>
</div>
<div className="ground-left-footer">
<MessageInput
scope="reranker"
loading={loading}
disabled={!parameters.model}
isEmpty={true}
shouldResetMessage={false}
handleSubmit={handleSendMessage}
handleAbortFetch={handleStopConversation}
clearAll={handleClear}
modelList={modelList}
modelList={[]}
placeholer={intl.formatMessage({
id: 'playground.input.keyword.holder'
})}
@@ -279,75 +285,62 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
collapsed: collapse
})}
style={{
paddingBottom: 80
overflow: 'hidden',
paddingBottom: 16
}}
ref={paramsRef}
>
<div className="box" style={{ padding: collapse ? 0 : '0 16px' }}>
<RerankerParams
setParams={setParams}
params={parameters}
selectedModel={selectModel}
modelList={modelList}
/>
<h3 className="m-b-20 m-l-10 flex-between flex-center font-size-14">
<span>Documents</span>
<Button
type="text"
icon={<ClearOutlined />}
size="middle"
onClick={handleClearDocuments}
></Button>
</h3>
<InputList
textList={textList}
onChange={handleTextListChange}
></InputList>
<div style={{ marginTop: 8 }}>
<FileList
fileList={fileList}
textListCount={textList.length || 0}
onDelete={handleDeleteFile}
></FileList>
<div className="box" style={{ paddingInline: 0, height: '100%' }}>
<div style={{ padding: collapse ? 0 : '0 16px' }}>
<RerankerParams
setParams={setParams}
params={parameters}
selectedModel={selectModel}
modelList={modelList}
/>
<h3 className="m-b-20 m-l-10 flex-between flex-center font-size-14">
<span>Documents</span>
<Button
type="text"
icon={<ClearOutlined />}
size="middle"
onClick={handleClearDocuments}
></Button>
</h3>
</div>
</div>
<div
style={{
height: collapse ? 0 : 60,
padding: '0 10px',
position: 'absolute',
overflow: 'hidden',
bottom: 20,
left: 0,
right: 0
}}
>
<UploadFile
handleUpdateFileList={handleUpdateFileList}
accept=".txt, .doc, .docx, .xls, .xlsx"
<div
className="docs-wrapper"
ref={paramsRef}
style={{
height: 'calc(100% - 210px)',
padding: '0 16px',
overflowY: 'auto'
}}
>
<div style={{ backgroundColor: 'var(--color-fill-sider)' }}>
<InboxOutlined className="font-size-16" />
<span className="m-l-10">
Click or drag file to this area to upload
</span>
<InputList
textList={textList}
onChange={handleTextListChange}
></InputList>
<div style={{ marginTop: 8 }}>
<FileList
fileList={fileList}
textListCount={textList.length || 0}
onDelete={handleDeleteFile}
></FileList>
</div>
<span className="text-tertiary">
support .txt, .doc, .docx, .xls, .xlsx
</span>
</UploadFile>
</div>
</div>
</div>
<ViewCodeModal
<ViewRerankCode
open={show}
systemMessage={systemMessage}
messageList={messageList}
parameters={parameters}
documentList={[...textList, ...fileList].map((item) => item.text)}
parameters={{
...parameters,
query: contentRef.current
}}
onCancel={handleCloseViewCode}
title={intl.formatMessage({ id: 'playground.viewcode' })}
></ViewCodeModal>
></ViewRerankCode>
</div>
);
});
@@ -73,6 +73,7 @@ interface MessageInputProps {
isEmpty?: boolean;
scope: string;
placeholer?: string;
shouldResetMessage?: boolean;
}
const MessageInput: React.FC<MessageInputProps> = ({
@@ -89,7 +90,8 @@ const MessageInput: React.FC<MessageInputProps> = ({
disabled,
isEmpty,
scope,
placeholer
placeholer,
shouldResetMessage = true
}) => {
const { TextArea } = Input;
const intl = useIntl();
@@ -126,7 +128,9 @@ const MessageInput: React.FC<MessageInputProps> = ({
};
const handleSendMessage = () => {
handleSubmit({ ...message });
resetMessage();
if (shouldResetMessage) {
resetMessage();
}
};
const onStop = () => {
handleAbortFetch();
@@ -360,17 +364,18 @@ const MessageInput: React.FC<MessageInputProps> = ({
)}
</>
)}
<Tooltip
title={intl.formatMessage({ id: 'playground.toolbar.clearmsg' })}
>
<Button
type="text"
icon={<ClearOutlined />}
size="middle"
disabled={loading}
onClick={handleClearAll}
></Button>
</Tooltip>
{scope !== 'reranker' && (
<Tooltip
title={intl.formatMessage({ id: 'playground.toolbar.clearmsg' })}
>
<Button
type="text"
icon={<ClearOutlined />}
size="middle"
onClick={handleClearAll}
></Button>
</Tooltip>
)}
{updateLayout && (
<>
<Divider type="vertical" style={{ margin: 0 }} />
@@ -1,4 +1,3 @@
import { StarFilled } from '@ant-design/icons';
import { Tooltip } from 'antd';
import _ from 'lodash';
import React from 'react';
@@ -6,17 +5,22 @@ import '../style/content-item.less';
import '../style/rerank-message.less';
interface RerankMessageProps {
header?: React.ReactNode;
dataList: { title?: string; content: any; uid: number | string }[];
}
const RerankMessage: React.FC<RerankMessageProps> = ({ dataList }) => {
const RerankMessage: React.FC<RerankMessageProps> = ({ header, dataList }) => {
if (!dataList || dataList.length === 0) {
return null;
}
return (
<div className="rerank-message">
{header}
{dataList.map((item) => {
return (
<div className="content-item" key={item.uid}>
<div className="content-item-role">
{/* <div className="content-item-role">
<span className="role">{item.title}</span>
</div>
</div> */}
<div className="content-item-content">
{Array.isArray(item.content) ? (
<div className="result">
@@ -24,20 +28,15 @@ const RerankMessage: React.FC<RerankMessageProps> = ({ dataList }) => {
return (
<dl className="content-item-text" key={sItem.uid}>
<dt className="rank">
<span>[{sItem.docIndex + 1}]</span>
<span className="score">
<Tooltip
title={
<span>Score: {_.round(sItem.score, 2)}</span>
}
>
<StarFilled className="m-r-5" />
{_.round(sItem.score, 2)}
</Tooltip>
<span className="doc-index">
{sItem.docIndex + 1}
</span>
</dt>
<dd className="text">{sItem.text}</dd>
{/* <dd className="doc-name">《{sItem.title}》</dd> */}
<Tooltip
title={<span>Score: {_.round(sItem.score, 2)}</span>}
>
<dd className="text">{sItem.text}</dd>
</Tooltip>
</dl>
);
})}
+34 -12
View File
@@ -1,4 +1,10 @@
import { readBlob, readExcelContent, readWordContent } from '@/utils';
import { readBlob } from '@/utils';
import readEpubContent from '@/utils/epub-reader';
import readExcelContent from '@/utils/excel-reader';
import readPDFContent from '@/utils/pdf-reader';
import readPptxContent from '@/utils/pptx-reader';
import readHtmlContent from '@/utils/read-html';
import readWordContent from '@/utils/word-reader';
import { PaperClipOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Tooltip, Upload } from 'antd';
@@ -26,16 +32,11 @@ const UploadImg: React.FC<UploadImgProps> = ({
const uploadRef = useRef<any>(null);
const wordReg = /\.(doc|docx)$/;
const pptReg = /\.(ppt|pptx)$/;
const pdfReg = /\.(pdf)$/;
const epubReg = /\.(epub)$/;
const excelReg = /\.(xls|xlsx)$/;
const getBase64 = useCallback((file: RcFile): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
}, []);
const htmlReg = /\.(html)$/;
const debouncedUpdate = useCallback(
debounce(
@@ -58,12 +59,33 @@ const UploadImg: React.FC<UploadImgProps> = ({
const context = await readWordContent(
item.originFileObj as RcFile
);
item.url = context;
} else if (excelReg.test(item.name)) {
const context = await readExcelContent(
item.originFileObj as RcFile
);
item.url = context;
} else if (epubReg.test(item.name)) {
const context = await readEpubContent(
item.originFileObj as RcFile
);
item.url = context;
} else if (pdfReg.test(item.name)) {
const context = await readPDFContent(
item.originFileObj as RcFile
);
item.url = context;
} else if (pptReg.test(item.name)) {
const context = await readPptxContent(
item.originFileObj as RcFile
);
item.url = context;
} else if (htmlReg.test(item.name)) {
const context = await readHtmlContent(
item.originFileObj as RcFile
);
item.url = context;
} else {
const context = await readBlob(item.originFileObj as RcFile);
item.url = context;
@@ -86,10 +108,10 @@ const UploadImg: React.FC<UploadImgProps> = ({
debouncedUpdate(files);
}
} catch (error) {
// console.log('error', error);
console.log('error', error);
}
},
[debouncedUpdate, getBase64]
[debouncedUpdate]
);
return (
@@ -0,0 +1,165 @@
import EditorWrap from '@/components/editor-wrap';
import HighlightCode from '@/components/highlight-code';
import { BulbOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Modal } from 'antd';
import React, { useEffect, useState } from 'react';
type ViewModalProps = {
documentList: string[];
parameters: any;
title: string;
open: boolean;
apiType?: string;
onCancel: () => void;
};
const langMap = {
shell: 'bash',
python: 'python',
javascript: 'javascript'
};
const langOptions = [
{ label: 'Curl', value: langMap.shell },
{ label: 'Python', value: langMap.python },
{ label: 'JavaScript', value: langMap.javascript }
];
const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const {
title,
open,
onCancel,
documentList = [],
parameters = {}
} = props || {};
const intl = useIntl();
const [codeValue, setCodeValue] = useState('');
const [lang, setLang] = useState(langMap.shell);
const BaseURL = `${window.location.origin}/v1/rerank`;
const generateCode = () => {
if (lang === langMap.shell) {
const code = `curl ${window.location.origin}/v1-openai \\\n-H "Content-Type: application/json" \\\n-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\\n-d '${JSON.stringify(
{
...parameters,
documents: documentList
},
null,
2
)}'`;
setCodeValue(code);
} else if (lang === langMap.javascript) {
const data = {
...parameters,
documents: documentList
};
const headers = {
'Content-type': 'application/json',
Authorization: `Bearer $\{YOUR_GPUSTACK_API_KEY}`
};
const code = `import axios from 'axios';\n\nconst url = "${BaseURL}";\n\nconst headers = ${JSON.stringify(headers, null, 2)};\n\nconst data = ${JSON.stringify(data, null, 2)};\n\naxios.post(url, data, { headers }).then((response) => {\n console.log(response.data);\n});`;
setCodeValue(code);
} else if (lang === langMap.python) {
const data = {
...parameters,
documents: documentList
};
const headers = {
'Content-type': 'application/json',
Authorization: `Bearer $\{YOUR_GPUSTACK_API_KEY}`
};
const code = `import requests\n\nurl="${BaseURL}"\n\nheaders = ${JSON.stringify(headers, null, 2)}\n\ndata=${JSON.stringify(data, null, 2)}\n\nresponse = requests.post(url, headers=headers, json=data)\n\nprint(response.json())`;
setCodeValue(code);
}
};
const handleOnChangeLang = (value: string) => {
setLang(value);
};
const handleClose = () => {
setLang(langMap.shell);
onCancel();
};
useEffect(() => {
generateCode();
}, [lang, parameters, documentList]);
return (
<>
<Modal
title={title}
open={open}
centered={true}
onCancel={handleClose}
destroyOnClose={true}
closeIcon={true}
maskClosable={false}
keyboard={false}
width={600}
footer={null}
>
<div style={{ marginBottom: '10px' }}>
{intl.formatMessage({ id: 'playground.viewcode.info' })}
</div>
<div>
<EditorWrap
copyText={codeValue}
langOptions={langOptions}
defaultValue={langMap.shell}
showHeader={true}
onChangeLang={handleOnChangeLang}
styles={{
wrapper: {
backgroundColor: 'var(--color-editor-dark)'
}
}}
>
<HighlightCode
height={380}
theme="dark"
code={codeValue}
lang={lang}
copyable={false}
></HighlightCode>
</EditorWrap>
<div
style={{ marginTop: 10, display: 'flex', alignItems: 'baseline' }}
>
<BulbOutlined className="m-r-8" />
<span>
{intl.formatMessage(
{ id: 'playground.viewcode.tips' },
{
here: (
<Button
type="link"
size="small"
href="#/api-keys"
target="_blank"
style={{ paddingInline: 2 }}
>
<span>
{' '}
{intl.formatMessage({
id: 'playground.viewcode.here'
})}
</span>
</Button>
)
}
)}
</span>
</div>
</div>
</Modal>
</>
);
};
export default ViewCodeModal;
+10 -5
View File
@@ -29,7 +29,7 @@ const Playground: React.FC = () => {
const [loaded, setLoaded] = useState(false);
const optionsList = [
{
label: intl.formatMessage({ id: 'menu.chat' }),
label: intl.formatMessage({ id: 'menu.playground.chat' }),
value: 'chat',
icon: <MessageOutlined />
},
@@ -46,8 +46,12 @@ const Playground: React.FC = () => {
];
const handleViewCode = useCallback(() => {
groundLeftRef.current?.viewCode?.();
}, [groundLeftRef]);
if (activeKey === 'reranker') {
groundRerankerRef.current?.viewCode?.();
} else if (activeKey === 'chat') {
groundLeftRef.current?.viewCode?.();
}
}, [groundLeftRef, groundRerankerRef, activeKey]);
const handleToggleCollapse = useCallback(() => {
if (activeKey === 'reranker') {
@@ -186,7 +190,7 @@ const Playground: React.FC = () => {
header={{
title: (
<div className="flex items-center">
{intl.formatMessage({ id: 'menu.playground' })}
{intl.formatMessage({ id: 'menu.playground.chat' })}
{
<Segmented
options={optionsList}
@@ -196,7 +200,8 @@ const Playground: React.FC = () => {
></Segmented>
}
</div>
)
),
breadcrumb: {}
}}
extra={renderExtra()}
className={classNames('playground-container', {
+124
View File
@@ -0,0 +1,124 @@
import IconFont from '@/components/icon-font';
import HotKeys from '@/config/hotkeys';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { Button, Space } from 'antd';
import classNames from 'classnames';
import _ from 'lodash';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { queryModelsList } from './apis';
import GroundReranker from './components/ground-reranker';
import './style/play-ground.less';
const PlaygroundRerank: React.FC = () => {
const intl = useIntl();
const groundLeftRef = useRef<any>(null);
const groundRerankerRef = useRef<any>(null);
const [rerankerModelList, setRerankerModelList] = useState<
Global.BaseOption<string>[]
>([]);
const [loaded, setLoaded] = useState(false);
const handleViewCode = useCallback(() => {
groundRerankerRef.current?.viewCode?.();
}, [groundRerankerRef]);
const handleToggleCollapse = useCallback(() => {
groundRerankerRef.current?.setCollapse?.();
}, [groundRerankerRef]);
useEffect(() => {
const getModelListByReranker = async () => {
try {
const params = {
reranker: true
};
const res = await queryModelsList(params);
const list = _.map(res.data || [], (item: any) => {
return {
value: item.id,
label: item.id
};
}) as Global.BaseOption<string>[];
return list;
} catch (error) {
console.error(error);
return [];
}
};
const fetchData = async () => {
try {
const [rerankerModelList] = await Promise.all([
getModelListByReranker()
]);
setRerankerModelList(rerankerModelList);
} catch (error) {
setLoaded(true);
}
};
fetchData();
}, []);
const renderExtra = () => {
return (
<Space key="buttons">
<Button
size="middle"
onClick={handleViewCode}
icon={<IconFont type="icon-code" className="font-size-16"></IconFont>}
>
{intl.formatMessage({ id: 'playground.viewcode' })}
</Button>
<Button
size="middle"
onClick={handleToggleCollapse}
icon={
<IconFont
type="icon-a-layout6-line"
className="font-size-16"
></IconFont>
}
></Button>
</Space>
);
};
useHotkeys(
HotKeys.RIGHT.join(','),
() => {
groundLeftRef.current?.setCollapse?.();
},
{
preventDefault: true
}
);
return (
<PageContainer
ghost
header={{
title: (
<div className="flex items-center">
{intl.formatMessage({ id: 'menu.playground.rerank' })}
</div>
),
breadcrumb: {}
}}
extra={renderExtra()}
className={classNames('playground-container chat')}
>
<div className="play-ground">
<div className="chat">
<GroundReranker
ref={groundRerankerRef}
modelList={rerankerModelList}
loaded={loaded}
></GroundReranker>
</div>
</div>
</PageContainer>
);
};
export default PlaygroundRerank;
@@ -33,6 +33,7 @@
.title {
display: flex;
align-items: center;
max-width: 300px;
}
}
}
+30 -2
View File
@@ -4,8 +4,7 @@
.rank {
margin-right: 10px;
max-width: 75px;
min-width: 55px;
min-width: 20px;
display: flex;
align-items: center;
justify-content: space-between;
@@ -14,6 +13,15 @@
margin-left: 5px;
color: var(--ant-rate-star-color);
}
.doc-index {
border: 1px solid var(--ant-color-border);
padding: 0 12px;
font-size: 12px;
border-radius: 12px;
line-height: 1.4em;
color: var(--ant-color-text-secondary);
}
}
.result {
@@ -26,6 +34,7 @@
margin-bottom: 0;
display: flex;
align-items: center;
line-height: 2;
}
.text {
@@ -42,4 +51,23 @@
font-weight: var(--font-weight-bold);
font-style: italic;
}
.content-item-content {
padding: 14px;
border-radius: 0 0 var(--border-radius-base) var(--border-radius-base);
}
.result-header {
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 4px 4px 0 0;
background: var(--ant-color-fill-tertiary);
padding: 0 14px;
border-bottom: 1px solid var(--ant-color-split);
.title {
font-weight: var(--font-weight-medium);
}
}
}
+29
View File
@@ -0,0 +1,29 @@
import ePub from 'epubjs';
export default function readEpubContent(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e: any) {
const arrayBuffer = e.target.result;
const book = ePub(arrayBuffer);
book.loaded?.spine?.then?.((spine: any) => {
const chapterPromises = spine.spineItems?.map?.((chapter: any) => {
return book.load(chapter.href).then((content: any) => {
return content.body?.textContent || '';
});
});
Promise.all(chapterPromises)
.then((chaptersText) => {
const result = chaptersText.join('');
resolve(result);
})
.catch((error) => {
reject(error);
});
});
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file);
});
}
+16
View File
@@ -0,0 +1,16 @@
import XLSX from 'xlsx';
export default function readExcelContent(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e: any) {
const arrayBuffer = e.target.result;
const workbook = XLSX.read(arrayBuffer, { type: 'string' });
const ws = workbook.Sheets[workbook.SheetNames[0]]; // get the first worksheet
const data = XLSX.utils.sheet_to_json(ws);
resolve(JSON.stringify(data));
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file);
});
}
-34
View File
@@ -1,6 +1,4 @@
import _ from 'lodash';
import mammoth from 'mammoth';
import XLSX from 'xlsx';
export const isNotEmptyValue = (value: any) => {
if (Array.isArray(value)) {
@@ -196,35 +194,3 @@ export function readBlob(blob: Blob): Promise<string> {
reader.readAsText(blob, 'utf-8');
});
}
export function readWordContent(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e: any) {
const arrayBuffer = e.target.result;
mammoth
.extractRawText({ arrayBuffer })
.then((result) => {
resolve(result.value);
})
.catch((error) => reject(error));
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file);
});
}
export function readExcelContent(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e: any) {
const arrayBuffer = e.target.result;
const workbook = XLSX.read(arrayBuffer, { type: 'string' });
const ws = workbook.Sheets[workbook.SheetNames[0]]; // get the first worksheet
const data = XLSX.utils.sheet_to_json(ws);
resolve(JSON.stringify(data));
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file);
});
}
+48
View File
@@ -0,0 +1,48 @@
import * as pdfjsLib from 'pdfjs-dist';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.mjs',
// @ts-ignore
import.meta.url
).href;
const readPDFContent = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async function (e: any) {
try {
const arrayBuffer = e.target.result;
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
const pdf = await loadingTask.promise;
const numPages = pdf.numPages;
const pagePromises = [];
for (let i = 1; i <= numPages; i++) {
const pagePromise = pdf.getPage(i).then(function (page) {
return page.getTextContent().then(function (textContent) {
return textContent.items
.map(function (item: any) {
return item.str;
})
.join(' ');
});
});
pagePromises.push(pagePromise);
}
const pageTexts = await Promise.all(pagePromises);
const result = pageTexts?.join(' ');
resolve(result);
} catch (error) {
reject(error);
}
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file);
});
};
export default readPDFContent;
+40
View File
@@ -0,0 +1,40 @@
import JSZip from 'jszip';
const readPptxContent = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e: any) {
const arrayBuffer = e.target.result;
JSZip.loadAsync(arrayBuffer).then(function (zip: any) {
const slideFiles = Object.keys(zip.files).filter(function (fileName) {
return (
fileName.startsWith('ppt/slides/slide') && fileName.endsWith('.xml')
);
});
let slideText = '';
const slidePromises = slideFiles.map((slideFile) =>
zip
.file(slideFile)
.async('string')
.then(function (content: any) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(content, 'application/xml');
const texts = xmlDoc.getElementsByTagName('a:t');
for (let i = 0; i < texts.length; i++) {
slideText += texts[i].textContent + '\n';
}
})
);
Promise.all(slidePromises).then(() => {
resolve(slideText);
});
});
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file);
});
};
export default readPptxContent;
+15
View File
@@ -0,0 +1,15 @@
export default function readHtmlContent(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e: any) {
const fileContent = e.target.result;
const parser = new DOMParser();
const doc = parser.parseFromString(fileContent, 'text/html');
const textContent = doc.body.textContent || '';
resolve(textContent);
};
reader.onerror = (error) => reject(error);
reader.readAsText(file);
});
}
+18
View File
@@ -0,0 +1,18 @@
import mammoth from 'mammoth';
export default function readWordContent(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e: any) {
const arrayBuffer = e.target.result;
mammoth
.extractRawText({ arrayBuffer })
.then((result) => {
resolve(result.value);
})
.catch((error) => reject(error));
};
reader.onerror = (error) => reject(error);
reader.readAsArrayBuffer(file);
});
}