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
@@ -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;