feat: playground add submenu
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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;
|
||||
@@ -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', {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user