feat: playground rerank

This commit is contained in:
jialin
2024-10-14 10:27:46 +08:00
parent ecc683a6bd
commit 4c3dddc486
31 changed files with 1579 additions and 140 deletions
+3 -1
View File
@@ -39,6 +39,7 @@
"jotai": "^2.8.4",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"mammoth": "^1.8.0",
"marked": "^14.1.0",
"numeral": "^2.0.6",
"overlayscrollbars": "^2.10.0",
@@ -48,7 +49,8 @@
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.5.0",
"simplebar-react": "^3.2.6",
"umi-presets-pro": "^2.0.3"
"umi-presets-pro": "^2.0.3",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"devDependencies": {
"@types/marked": "^6.0.0",
+86
View File
@@ -83,6 +83,9 @@ dependencies:
lodash:
specifier: ^4.17.21
version: 4.17.21
mammoth:
specifier: ^1.8.0
version: 1.8.0
marked:
specifier: ^14.1.0
version: 14.1.0
@@ -113,6 +116,9 @@ dependencies:
umi-presets-pro:
specifier: ^2.0.3
version: 2.0.3(@babel/core@7.25.7)(@types/react-dom@18.3.0)(@types/react@18.3.1)(antd@5.20.6)(dva@2.5.0-beta.2)(rc-field-form@2.4.0)(react-dom@18.2.0)(react@18.2.0)(umi@4.3.24)
xlsx:
specifier: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
version: '@cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz'
devDependencies:
'@types/marked':
@@ -7066,6 +7072,11 @@ packages:
'@webassemblyjs/ast': 1.12.1
'@xtuc/long': 4.2.2
/@xmldom/xmldom@0.8.10:
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==, tarball: https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz}
engines: {node: '>=10.0.0'}
dev: false
/@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0):
resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==, tarball: https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz}
peerDependencies:
@@ -7866,6 +7877,10 @@ packages:
engines: {node: '>=8'}
dev: false
/bluebird@3.4.7:
resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==, tarball: https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz}
dev: false
/bn.js@4.12.0:
resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==, tarball: https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz}
dev: false
@@ -9014,6 +9029,10 @@ packages:
randombytes: 2.1.0
dev: false
/dingbat-to-unicode@1.0.1:
resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==, tarball: https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz}
dev: false
/dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -9110,6 +9129,12 @@ packages:
tslib: 2.6.2
dev: false
/duck@0.1.12:
resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==, tarball: https://registry.npmjs.org/duck/-/duck-0.1.12.tgz}
dependencies:
underscore: 1.13.7
dev: false
/duplexer2@0.1.4:
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
dependencies:
@@ -11928,6 +11953,15 @@ packages:
object.values: 1.2.0
dev: false
/jszip@3.10.1:
resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==, tarball: https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz}
dependencies:
lie: 3.3.0
pako: 1.0.11
readable-stream: 2.3.8
setimmediate: 1.0.5
dev: false
/keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
dependencies:
@@ -12028,6 +12062,12 @@ packages:
immediate: 3.0.6
dev: false
/lie@3.3.0:
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==, tarball: https://registry.npmjs.org/lie/-/lie-3.3.0.tgz}
dependencies:
immediate: 3.0.6
dev: false
/lightningcss-darwin-arm64@1.22.1:
resolution: {integrity: sha512-ldvElu+R0QimNTjsKpaZkUv3zf+uefzLy/R1R19jtgOfSRM+zjUCUgDhfEDRmVqJtMwYsdhMI2aJtJChPC6Osg==, tarball: https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.22.1.tgz}
engines: {node: '>= 12.0.0'}
@@ -12262,6 +12302,14 @@ packages:
js-tokens: 4.0.0
dev: false
/lop@0.4.2:
resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==, tarball: https://registry.npmjs.org/lop/-/lop-0.4.2.tgz}
dependencies:
duck: 0.1.12
option: 0.2.4
underscore: 1.13.7
dev: false
/lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==, tarball: https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz}
dependencies:
@@ -12306,6 +12354,23 @@ packages:
tmpl: 1.0.5
dev: false
/mammoth@1.8.0:
resolution: {integrity: sha512-pJNfxSk9IEGVpau+tsZFz22ofjUsl2mnA5eT8PjPs2n0BP+rhVte4Nez6FdgEuxv3IGI3afiV46ImKqTGDVlbA==, tarball: https://registry.npmjs.org/mammoth/-/mammoth-1.8.0.tgz}
engines: {node: '>=12.0.0'}
hasBin: true
dependencies:
'@xmldom/xmldom': 0.8.10
argparse: 1.0.10
base64-js: 1.5.1
bluebird: 3.4.7
dingbat-to-unicode: 1.0.1
jszip: 3.10.1
lop: 0.4.2
path-is-absolute: 1.0.1
underscore: 1.13.7
xmlbuilder: 10.1.1
dev: false
/map-age-cleaner@0.1.3:
resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==, tarball: https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz}
engines: {node: '>=6'}
@@ -13023,6 +13088,10 @@ packages:
yaml: 1.10.2
dev: false
/option@0.2.4:
resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==, tarball: https://registry.npmjs.org/option/-/option-0.2.4.tgz}
dev: false
/optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -18023,6 +18092,10 @@ packages:
which-boxed-primitive: 1.0.2
dev: false
/underscore@1.13.7:
resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==, tarball: https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz}
dev: false
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
dev: false
@@ -18592,6 +18665,11 @@ packages:
signal-exit: 3.0.7
dev: false
/xmlbuilder@10.1.1:
resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==, tarball: https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz}
engines: {node: '>=4.0'}
dev: false
/xtend@2.1.2:
resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==}
engines: {node: '>=0.4'}
@@ -18681,3 +18759,11 @@ packages:
/zwitch@1.0.5:
resolution: {integrity: sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==, tarball: https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz}
dev: false
'@cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz':
resolution: {tarball: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz}
name: xlsx
version: 0.20.3
engines: {node: '>=0.8'}
hasBin: true
dev: false
+115
View File
@@ -0,0 +1,115 @@
import { CloseOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Input, Tooltip } from 'antd';
import classNames from 'classnames';
import React, { useState } from 'react';
import './styles/row-textarea.less';
interface SystemMessageProps {
style?: React.CSSProperties;
value: string;
placeholder?: string;
label?: React.ReactNode;
onChange: (e: any) => void;
}
const RowTextarea: React.FC<SystemMessageProps> = (props) => {
const { value, onChange, style, label, placeholder } = props;
const intl = useIntl();
const rowTextAreaRef = React.useRef<any>(null);
const [autoSize, setAutoSize] = useState<{
minRows: number;
maxRows: number;
focus: boolean;
}>({ minRows: 1, maxRows: 1, focus: false });
const handleFocus = () => {
setAutoSize({
minRows: 3,
maxRows: 3,
focus: true
});
setTimeout(() => {
rowTextAreaRef.current?.focus?.({
cursor: 'end'
});
}, 50);
};
const handleBlur = (e: any) => {
setAutoSize({
minRows: 2,
maxRows: 2,
focus: false
});
};
const handleOnChange = (e: any) => {
onChange?.(e);
};
const handleClear = () => {
onChange?.({ target: { value: '' } });
};
return (
<div
className={classNames('row-textarea', {
focus: autoSize.focus
})}
style={{ ...style }}
>
{
<div
style={{ display: autoSize.focus ? 'block' : 'none' }}
className="textarea-wrapper"
>
<span className="textarea-label">{label}</span>
<Input.TextArea
className="custome-scrollbar"
ref={rowTextAreaRef}
placeholder={placeholder}
style={{
borderRadius: '0',
border: 'none'
}}
value={value}
autoSize={{
minRows: autoSize.minRows,
maxRows: autoSize.maxRows
}}
onFocus={handleFocus}
onBlur={handleBlur}
allowClear={false}
onChange={handleOnChange}
></Input.TextArea>
</div>
}
{!autoSize.focus && (
<div className="content-wrap" onClick={handleFocus}>
<div className="content">
<span className="title">{label}</span>
{value || (
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
{placeholder}
</span>
)}
</div>
{value && (
<Tooltip title={intl.formatMessage({ id: 'common.button.clear' })}>
<Button
className="clear-btn"
type="text"
icon={<CloseOutlined />}
size="small"
onClick={handleClear}
></Button>
</Tooltip>
)}
</div>
)}
</div>
);
};
export default React.memo(RowTextarea);
@@ -0,0 +1,60 @@
.row-textarea {
position: relative;
&.focus {
padding-top: 9px;
}
.content-wrap {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 20px;
cursor: pointer;
&:hover {
.clear-btn {
display: block;
}
}
}
textarea.ant-input {
background-color: transparent;
box-shadow: none;
}
.clear-btn {
display: none;
position: absolute;
right: 6px;
top: 6px;
}
.textarea-label {
position: relative;
top: 1px;
font-weight: var(--font-weight-bold);
padding-left: 14px;
}
.content {
flex: 1;
width: 100px;
height: 40px;
line-height: 24px;
padding: 8px 14px;
padding-right: 4px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
color: var(--ant-color-text-secondary);
.title {
font-weight: var(--font-weight-normal);
padding-right: 10px;
color: var(--ant-color-text);
}
}
}
+1
View File
@@ -64,6 +64,7 @@ html {
--seal-transition-func: cubic-bezier(0, 0, 1, 1);
--color-progress-green: rgba(84, 204, 152, 100%);
--color-border-1: rgba(217, 217, 217, 100%);
--ant-rate-star-color: #fadb14;
// ======== input ============
--ant-input-active-shadow: 0 0 0 2px rgba(5, 255, 105, 6%);
--ant-input-active-border-color: #007bff;
+1
View File
@@ -42,6 +42,7 @@ export default {
'playground.toolbar.compare4Model': '4-Model Comparison',
'playground.toolbar.compare6Model': '6-Model Comparison',
'playground.input.holder': 'Type <kbd>/</kbd> to input message',
'playground.input.keyword.holder': 'Type <kbd>/</kbd> to input your query',
'playground.compare.apply': 'Apply',
'playground.compare.applytoall': 'Apply to all models',
'playground.model.noavailable': 'No available models',
+1
View File
@@ -42,6 +42,7 @@ export default {
'playground.toolbar.compare4Model': '4 模型对比',
'playground.toolbar.compare6Model': '6 模型对比',
'playground.input.holder': '按 <kbd>/</kbd> 开始输入',
'playground.input.keyword.holder': '按 <kbd>/</kbd> 输入你的查询',
'playground.compare.apply': '应用',
'playground.compare.applytoall': '应用到所有模型',
'playground.model.noavailable': '无可用模型',
+31 -21
View File
@@ -367,26 +367,29 @@ const Models: React.FC<ModelsProps> = ({
setOpenAddModal(true);
};
const handleSelect = useCallback((val: any, row: ListItem) => {
if (val === 'edit') {
handleEdit(row);
}
if (val === 'chat') {
handleOpenPlayGround(row);
}
if (val === 'delete') {
handleDelete(row);
}
if (val === 'embedding') {
setEmbeddingParams({
params: {
input: 'Your text string goes here',
model: row.name
},
show: true
});
}
}, []);
const handleSelect = useCallback(
(val: any, row: ListItem) => {
if (val === 'edit') {
handleEdit(row);
}
if (val === 'chat') {
handleOpenPlayGround(row);
}
if (val === 'delete') {
handleDelete(row);
}
if (val === 'embedding') {
setEmbeddingParams({
params: {
input: 'Your text string goes here',
model: row.name
},
show: true
});
}
},
[handleEdit, handleOpenPlayGround, handleDelete, setEmbeddingParams]
);
const handleChildSelect = useCallback(
(val: any, row: ModelInstanceListItem, list: ModelInstanceListItem[]) => {
@@ -524,7 +527,14 @@ const Models: React.FC<ModelsProps> = ({
<AutoTooltip ghost>
<span className="m-r-5">{text}</span>
</AutoTooltip>
{record.embedding_only && (
{record.reranker && (
<span>
<Tag style={{ margin: 0 }} color="geekblue">
Reranker
</Tag>
</span>
)}
{record.embedding_only && !record.reranker && (
<span>
<Tag style={{ margin: 0 }} color="geekblue">
Embedding Only
+22 -17
View File
@@ -72,21 +72,24 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
setGpuOptions(list);
};
const initFormValue = useMemo(() => {
const result = setSourceRepoConfigValue(
props.data?.source || '',
props.data
);
useEffect(() => {
if (action === PageAction.EDIT && open) {
const result = setSourceRepoConfigValue(
props.data?.source || '',
props.data
);
return {
...result.values,
..._.omit(props.data, result.omits),
scheduleType: props.data?.gpu_selector ? 'manual' : 'auto',
gpu_selector: props.data?.gpu_selector
? `${props.data?.gpu_selector.worker_name}-${props.data?.gpu_selector.gpu_name}-${props.data?.gpu_selector.gpu_index}`
: null
};
}, [props.data]);
const formData = {
...result.values,
..._.omit(props.data, result.omits),
scheduleType: props.data?.gpu_selector ? 'manual' : 'auto',
gpu_selector: props.data?.gpu_selector
? `${props.data?.gpu_selector.worker_name}-${props.data?.gpu_selector.gpu_name}-${props.data?.gpu_selector.gpu_index}`
: null
};
form.setFieldsValue(formData);
}
}, [open]);
useEffect(() => {
setIsGGUF(props.data?.backend === backendOptionsMap.llamaBox);
@@ -245,6 +248,10 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
}
};
const handleOnClose = () => {
onCancel?.();
};
useEffect(() => {
getGPUList();
}, []);
@@ -256,6 +263,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
centered={true}
onOk={handleSumit}
onCancel={onCancel}
onClose={handleOnClose}
destroyOnClose={true}
closeIcon={true}
maskClosable={false}
@@ -294,9 +302,6 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
padding: 'var(--ant-modal-content-padding)',
paddingBlock: 0
}}
initialValues={{
...initFormValue
}}
>
<Form.Item<FormData>
name="name"
+1
View File
@@ -1,6 +1,7 @@
export interface ListItem {
source: string;
backend: string;
reranker: boolean;
huggingface_repo_id: string;
huggingface_file_name: string;
huggingface_filename: string;
+18
View File
@@ -4,6 +4,8 @@ export const CHAT_API = '/v1-openai/chat/completions';
export const OPENAI_MODELS = '/v1-openai/models';
export const RERANKER_API = '/rerank';
export async function execChatCompletions(params: any) {
return request(`${CHAT_API}`, {
method: 'POST',
@@ -17,3 +19,19 @@ export const queryModelsList = async (params: any) => {
params
});
};
export const rerankerQuery = async (
params: {
model: string;
query: string;
top_n: number;
documents: string[];
},
options?: any
) => {
return request(`${RERANKER_API}`, {
method: 'POST',
data: params,
cancelToken: options?.cancelToken
});
};
@@ -0,0 +1,49 @@
import AutoTooltip from '@/components/auto-tooltip';
import { DeleteOutlined, PaperClipOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Tooltip } from 'antd';
import classNames from 'classnames';
import React from 'react';
import '../style/file-list.less';
interface FileListProps {
fileList: { text: string; name: string; uid: number | string }[];
ghost?: boolean;
showIcon?: boolean;
onDelete?: (uid: number | string) => void;
}
const FileList: React.FC<FileListProps> = (props) => {
const { fileList, ghost, showIcon = true, onDelete } = props;
const intl = useIntl();
return (
<div className="file-list">
{fileList.map((file) => {
return (
<div key={file.uid} className={classNames('file-item', { ghost })}>
<span className="title">
{showIcon && <PaperClipOutlined className="m-r-5" />}
<AutoTooltip ghost> {file.name}</AutoTooltip>
</span>
{onDelete && (
<Tooltip
title={intl.formatMessage({ id: 'common.button.delete' })}
>
<Button
danger
size="small"
type="text"
className="delete-btn"
icon={<DeleteOutlined></DeleteOutlined>}
onClick={() => onDelete(file.uid)}
></Button>
</Tooltip>
)}
</div>
);
})}
</div>
);
};
export default React.memo(FileList);
@@ -0,0 +1,354 @@
import useOverlayScroller from '@/hooks/use-overlay-scroller';
import useRequestToken from '@/hooks/use-request-token';
import { ClearOutlined, InboxOutlined } from '@ant-design/icons';
import { useIntl, useSearchParams } from '@umijs/max';
import { Button, Spin } from 'antd';
import classNames from 'classnames';
import 'overlayscrollbars/overlayscrollbars.css';
import {
forwardRef,
memo,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';
import { rerankerQuery } from '../apis';
import { MessageItem } from '../config/types';
import '../style/ground-left.less';
import '../style/system-message-wrap.less';
import FileList from './file-list';
import InputList from './input-list';
import MessageInput from './message-input';
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';
interface MessageProps {
modelList: Global.BaseOption<string>[];
loaded?: boolean;
ref?: any;
}
const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
const { modelList } = props;
const messageId = useRef<number>(0);
const [messageList, setMessageList] = useState<MessageItem[]>([]);
const intl = useIntl();
const requestSource = useRequestToken();
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);
const [collapse, setCollapse] = useState(false);
const contentRef = useRef<any>('');
const controllerRef = useRef<any>(null);
const scroller = useRef<any>(null);
const currentMessageRef = useRef<any>(null);
const paramsRef = useRef<any>(null);
const messageListLengthCache = useRef<number>(0);
const requestToken = useRef<any>(null);
const [fileList, setFileList] = useState<
{ text: string; name: string; uid: number | string }[]
>([]);
const [textList, setTextList] = useState<
{ text: string; uid: number | string; name: string }[]
>([]);
const { initialize, updateScrollerPosition } = useOverlayScroller();
const { initialize: innitializeParams } = useOverlayScroller();
useImperativeHandle(ref, () => {
return {
viewCode() {
setShow(true);
},
setCollapse() {
setCollapse(!collapse);
}
};
});
const setMessageId = () => {
messageId.current = messageId.current + 1;
};
const handleStopConversation = () => {
requestToken.current?.cancel?.();
setLoading(false);
};
const submitMessage = async (current?: { role: string; content: string }) => {
if (!parameters.model) return;
try {
setLoading(true);
setMessageId();
setTokenResult(null);
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];
});
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 || '',
documents: [
...textList.map((item) => item.text),
...fileList.map((item) => item.text)
]
},
{
token: requestToken.current.token
}
);
console.log('result:', result);
setMessageId();
setTokenResult(result.usage);
setMessageList([
...currentMessageRef.current,
{
title: 'Result',
content: result.results?.map((item: any) => {
return {
uid: item.index,
text: `${item.document?.text?.slice(0, 100) || ''}`,
docIndex: item.index,
title: documentList[item.index]?.name || '',
score: item.relevance_score
};
}),
uid: messageId.current
}
]);
} catch (error: any) {
setTokenResult({
error: true,
errorMessage: error.response?.data?.error?.message
});
} finally {
setLoading(false);
}
};
const handleClear = () => {
if (!messageList.length) {
return;
}
setMessageId();
setMessageList([]);
setTokenResult(null);
};
const handleSendMessage = (message: Omit<MessageItem, 'uid'>) => {
const currentMessage =
message.content || message.imgs?.length ? message : undefined;
submitMessage(currentMessage);
};
const handleCloseViewCode = () => {
setShow(false);
};
const handleUpdateFileList = (
files: { text: string; name: string; uid: number | string }[]
) => {
console.log('files:', files);
setFileList((preList) => {
return [...preList, ...files];
});
};
const handleDeleteFile = (uid: number | string) => {
setFileList((preList) => {
return preList.filter((item) => item.uid !== uid);
});
};
const handleTextListChange = (
list: { text: string; uid: number | string; name: string }[]
) => {
setTextList(list);
};
const handleClearDocuments = () => {
setTextList([]);
setFileList([]);
};
useEffect(() => {
setMessageId();
setMessageList([]);
setTokenResult(null);
}, [parameters.model]);
useEffect(() => {
if (scroller.current) {
initialize(scroller.current);
}
}, [scroller.current, initialize]);
useEffect(() => {
if (paramsRef.current) {
innitializeParams(paramsRef.current);
}
}, [paramsRef.current, innitializeParams]);
useEffect(() => {
if (loading) {
updateScrollerPosition();
}
}, [messageList, loading]);
useEffect(() => {
if (messageList.length > messageListLengthCache.current) {
updateScrollerPosition();
}
messageListLengthCache.current = messageList.length;
}, [messageList.length]);
return (
<div className="ground-left-wrapper">
<div className="ground-left">
<div className="message-list-wrap" ref={scroller}>
<>
<div className="content">
<RerankMessage dataList={messageList} />
{loading && (
<Spin size="small">
<div style={{ height: '46px' }}></div>
</Spin>
)}
</div>
</>
</div>
{tokenResult && (
<div style={{ height: 40 }}>
<ReferenceParams
usage={tokenResult}
showOutput={false}
></ReferenceParams>
</div>
)}
<div className="ground-left-footer">
<MessageInput
scope="reranker"
loading={loading}
disabled={!parameters.model}
isEmpty={true}
handleSubmit={handleSendMessage}
handleAbortFetch={handleStopConversation}
clearAll={handleClear}
modelList={modelList}
placeholer={intl.formatMessage({
id: 'playground.input.keyword.holder'
})}
/>
</div>
</div>
<div
className={classNames('params-wrapper', {
collapsed: collapse
})}
style={{
paddingBottom: 80
}}
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}
onDelete={handleDeleteFile}
></FileList>
</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 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 .txt, .doc, .docx, .xls, .xlsx
</span>
</UploadFile>
</div>
</div>
<ViewCodeModal
open={show}
systemMessage={systemMessage}
messageList={messageList}
parameters={parameters}
onCancel={handleCloseViewCode}
title={intl.formatMessage({ id: 'playground.viewcode' })}
></ViewCodeModal>
</div>
);
});
export default memo(GroundReranker);
@@ -0,0 +1,109 @@
import RowTextarea from '@/components/seal-form/row-textarea';
import {
MinusCircleOutlined,
PlusCircleOutlined,
PlusOutlined
} from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Tooltip } from 'antd';
import React, { useRef } from 'react';
import '../style/input-list.less';
interface InputListProps {
textList: { text: string; uid: number | string; name: string }[];
onChange?: (
textList: { text: string; uid: number | string; name: string }[]
) => void;
}
const InputList: React.FC<InputListProps> = ({ textList, onChange }) => {
const intl = useIntl();
const messageId = useRef(0);
const setMessageId = () => {
messageId.current = messageId.current + 1;
};
const handleAdd = () => {
setMessageId();
const dataList = [...textList];
dataList.push({
text: '',
uid: messageId.current,
name: `Text ${dataList.length + 1}`
});
onChange?.(dataList);
};
const handleDelete = (text: { text: string; uid: number | string }) => {
const dataList = [...textList];
const index = dataList.findIndex((item) => item.uid === text.uid);
dataList.splice(index, 1);
onChange?.(dataList);
};
const handleTextChange = (
value: string,
text: { text: string; uid: number | string }
) => {
const dataList = [...textList];
const index = dataList.findIndex((item) => item.uid === text.uid);
dataList[index].text = value;
onChange?.(dataList);
};
return (
<div className="input-list">
{textList.length === 0 && (
<Button
block
onClick={handleAdd}
type="text"
style={{ backgroundColor: 'var(--ant-color-fill-secondary)' }}
>
<PlusOutlined />
Add Text
</Button>
)}
{textList.map((text, index) => {
return (
<div key={text.uid} className="input-item">
<div className="input-wrap">
<RowTextarea
label={`Text ${index + 1}`}
value={text.text}
onChange={(e) => handleTextChange(e.target.value, text)}
></RowTextarea>
</div>
<span className="btn-group">
<Tooltip
title={intl.formatMessage({ id: 'common.button.delete' })}
>
<Button
size="small"
type="text"
icon={<MinusCircleOutlined />}
onClick={() => handleDelete(text)}
></Button>
</Tooltip>
{index === textList.length - 1 && (
<Tooltip
title={intl.formatMessage({ id: 'common.button.add' })}
>
<Button
size="small"
type="text"
icon={<PlusCircleOutlined />}
onClick={handleAdd}
></Button>
</Tooltip>
)}
</span>
</div>
);
})}
</div>
);
};
export default React.memo(InputList);
@@ -1,6 +1,5 @@
import IconFont from '@/components/icon-font';
import HotKeys, { KeyMap } from '@/config/hotkeys';
import { platformCall } from '@/utils';
import { ClearOutlined, SendOutlined, SwapOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Divider, Input, Select, Tooltip } from 'antd';
@@ -61,18 +60,19 @@ interface MessageInputProps {
handleAbortFetch: () => void;
updateLayout?: (value: { span: number; count: number }) => void;
clearAll: () => void;
setModelSelections: (
setModelSelections?: (
modelList: (Global.BaseOption<string> & {
instanceId: symbol;
})[]
) => void;
presetPrompt: (list: CurrentMessage[]) => void;
addMessage: (message: CurrentMessage) => void;
presetPrompt?: (list: CurrentMessage[]) => void;
addMessage?: (message: CurrentMessage) => void;
loading: boolean;
showModelSelection?: boolean;
disabled: boolean;
isEmpty?: boolean;
scope: string;
placeholer?: string;
}
const MessageInput: React.FC<MessageInputProps> = ({
@@ -88,12 +88,11 @@ const MessageInput: React.FC<MessageInputProps> = ({
showModelSelection,
disabled,
isEmpty,
scope
scope,
placeholer
}) => {
const { TextArea } = Input;
const intl = useIntl();
const platform = platformCall();
// const [disabled, setDisabled] = useState(false);
const [open, setOpen] = useState(false);
const [focused, setFocused] = useState(false);
const [message, setMessage] = useState<CurrentMessage>({
@@ -130,11 +129,9 @@ const MessageInput: React.FC<MessageInputProps> = ({
resetMessage();
};
const onStop = () => {
// setDisabled(false);
handleAbortFetch();
};
const handleLayoutChange = (value: { span: number; count: number }) => {
console.log('layout change:', value);
updateLayout?.(value);
};
@@ -151,7 +148,6 @@ const MessageInput: React.FC<MessageInputProps> = ({
};
const handleUpdateModelSelections = (value: string[]) => {
console.log('update model selections:', value);
const list = value?.map?.((val) => {
return {
value: val,
@@ -159,7 +155,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
instanceId: Symbol(val)
};
});
setModelSelections(list);
setModelSelections?.(list);
};
const handleOpenPrompt = () => {
@@ -167,8 +163,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
};
const handleAddMessage = () => {
console.log('add message=====1');
addMessage({ ...message });
addMessage?.({ ...message });
resetMessage();
};
@@ -249,9 +244,14 @@ const MessageInput: React.FC<MessageInputProps> = ({
e.preventDefault();
const text = e.clipboardData.getData('text');
if (text) {
const startPos = e.target.selectionStart;
const endPos = e.target.selectionEnd;
setMessage?.({
...message,
content: message.content + text
content:
message.content.slice(0, startPos) +
text +
message.content.slice(endPos)
});
} else {
getPasteContent(e);
@@ -284,6 +284,10 @@ const MessageInput: React.FC<MessageInputProps> = ({
[message, handleDeleteLastImage]
);
const handleSelectPrompt = (list: CurrentMessage[]) => {
presetPrompt?.(list);
};
useHotkeys(
HotKeys.SUBMIT,
(e: any) => {
@@ -325,16 +329,23 @@ const MessageInput: React.FC<MessageInputProps> = ({
<div className="messageInput">
<div className="tool-bar">
<div className="actions">
<Button
type="text"
size="middle"
onClick={handleToggleRole}
icon={<SwapOutlined rotate={90} />}
>
{intl.formatMessage({ id: `playground.${message.role}` })}
</Button>
<Divider type="vertical" style={{ margin: 0 }} />
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
{scope !== 'reranker' && (
<>
<Button
type="text"
size="middle"
onClick={handleToggleRole}
icon={<SwapOutlined rotate={90} />}
>
{intl.formatMessage({ id: `playground.${message.role}` })}
</Button>
<Divider type="vertical" style={{ margin: 0 }} />
<UploadImg
handleUpdateImgList={handleUpdateImgList}
size="middle"
></UploadImg>
</>
)}
<Tooltip
title={intl.formatMessage({ id: 'playground.toolbar.clearmsg' })}
>
@@ -345,17 +356,6 @@ const MessageInput: React.FC<MessageInputProps> = ({
onClick={handleClearAll}
></Button>
</Tooltip>
{/* <Tooltip
title={intl.formatMessage({ id: 'playground.toolbar.prompts' })}
>
<Button
type="text"
icon={<ControlOutlined />}
size="middle"
onClick={handleOpenPrompt}
></Button>
</Tooltip> */}
{updateLayout && (
<>
<Divider type="vertical" style={{ margin: 0 }} />
@@ -391,18 +391,20 @@ const MessageInput: React.FC<MessageInputProps> = ({
></Select>
)}
<Tooltip
title={
<span>
[{KeyMap.ADD.textKeybinding}]{' '}
{scope !== 'reranker' && (
<Tooltip
title={
<span>
[{KeyMap.ADD.textKeybinding}]{' '}
{intl.formatMessage({ id: 'common.button.add' })}
</span>
}
>
<Button type="default" size="middle" onClick={handleAddMessage}>
{intl.formatMessage({ id: 'common.button.add' })}
</span>
}
>
<Button type="default" size="middle" onClick={handleAddMessage}>
{intl.formatMessage({ id: 'common.button.add' })}
</Button>
</Tooltip>
</Button>
</Tooltip>
)}
{!loading ? (
<Tooltip
title={
@@ -440,23 +442,39 @@ const MessageInput: React.FC<MessageInputProps> = ({
onDelete={handleDeleteImg}
></ThumbImg>
<div className="input-box">
<TextArea
ref={inputRef}
autoSize={{ minRows: 3, maxRows: 8 }}
onChange={handleInputChange}
value={message.content}
size="large"
variant="borderless"
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
onKeyDown={handleKeyDown}
onPaste={handleOnPaste}
></TextArea>
{scope !== 'reranker' ? (
<TextArea
ref={inputRef}
autoSize={{ minRows: 3, maxRows: 8 }}
onChange={handleInputChange}
value={message.content}
size="large"
variant="borderless"
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
onKeyDown={handleKeyDown}
onPaste={handleOnPaste}
></TextArea>
) : (
<TextArea
ref={inputRef}
autoSize={{ minRows: 3, maxRows: 8 }}
onChange={handleInputChange}
value={message.content}
size="large"
variant="borderless"
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
onKeyDown={handleKeyDown}
></TextArea>
)}
{!message.content && !focused && (
<span
className="holder"
dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'playground.input.holder' })
__html:
placeholer ??
intl.formatMessage({ id: 'playground.input.holder' })
}}
></span>
)}
@@ -464,7 +482,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
<PromptModal
open={open}
onCancel={() => setOpen(false)}
onSelect={presetPrompt}
onSelect={handleSelectPrompt}
></PromptModal>
</div>
);
@@ -15,6 +15,7 @@ interface MessageItemProps {
data: MessageItem;
editable?: boolean;
loading?: boolean;
actions?: string[];
updateMessage?: (message: MessageItem) => void;
onDelete?: () => void;
}
@@ -24,7 +25,8 @@ const ContentItem: React.FC<MessageItemProps> = ({
onDelete,
loading,
data,
editable
editable,
actions = ['upload', 'delete', 'copy']
}) => {
const intl = useIntl();
const inputRef = useRef<any>(null);
@@ -107,9 +109,12 @@ const ContentItem: React.FC<MessageItemProps> = ({
e.preventDefault();
const text = e.clipboardData.getData('text');
if (text) {
const startPos = e.target.selectionStart;
const endPos = e.target.selectionEnd;
updateMessage?.({
role: data.role,
content: data.content + text,
content:
data.content.slice(0, startPos) + text + data.content.slice(endPos),
uid: data.uid
});
} else {
@@ -176,13 +181,13 @@ const ContentItem: React.FC<MessageItemProps> = ({
<div className="content-item">
<div className="content-item-role">
<div className="role">
{intl.formatMessage({ id: `playground.${data.role}` })}
{data.title ?? intl.formatMessage({ id: `playground.${data.role}` })}
</div>
<div className="actions">
{editable && (
{actions.includes('upload') && (
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
)}
{data.content && (
{data.content && actions.includes('copy') && (
<CopyButton
text={data.content}
size="small"
@@ -191,7 +196,7 @@ const ContentItem: React.FC<MessageItemProps> = ({
fontSize="12px"
></CopyButton>
)}
{editable && (
{actions.includes('delete') && (
<Tooltip title={intl.formatMessage({ id: 'common.button.delete' })}>
<Button
size="small"
@@ -229,9 +234,7 @@ const ContentItem: React.FC<MessageItemProps> = ({
></Input.TextArea>
</div>
) : (
<div className="content-item-content">
<span>{data.content}</span>
</div>
<div className="content-item-content">{data.content}</div>
)}
</div>
);
@@ -29,6 +29,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList, loaded }) => {
stop: null,
temperature: 1,
top_p: 1,
top_n: 0,
max_tokens: 1024
});
const [spans, setSpans] = useState<{
@@ -5,10 +5,11 @@ import ContentItem from './content-item';
interface MessageContentProps {
loading?: boolean;
spans: {
spans?: {
span: number;
count: number;
};
actions?: string[];
editable?: boolean;
messageList: MessageItem[];
setMessageList?: (list: any) => void;
@@ -17,8 +18,8 @@ interface MessageContentProps {
const MessageContent: React.FC<MessageContentProps> = ({
setMessageList,
messageList,
spans,
editable
editable,
actions = ['upload', 'delete', 'copy']
}) => {
const updateMessage = (index: number, message: MessageItem) => {
const newMessageList = [...messageList];
@@ -41,6 +42,7 @@ const MessageContent: React.FC<MessageContentProps> = ({
key={item.uid}
data={item}
editable={editable}
actions={actions}
onDelete={() => handleDelete(index)}
updateMessage={(data) => updateMessage(index, data)}
/>
@@ -5,6 +5,7 @@ import _ from 'lodash';
import '../style/reference-params.less';
interface ReferenceParamsProps {
showOutput?: boolean;
usage: {
error?: boolean;
errorMessage?: string;
@@ -19,7 +20,7 @@ interface ReferenceParamsProps {
const ReferenceParams = (props: ReferenceParamsProps) => {
const intl = useIntl();
const { usage } = props;
const { usage, showOutput = true } = props;
if (!usage || _.isEmpty(usage)) {
return null;
}
@@ -63,25 +64,27 @@ const ReferenceParams = (props: ReferenceParamsProps) => {
</Tooltip>
</span>
<span className="usage">
<Tooltip
title={
<Space>
<span>
TPOT: {_.round(usage.time_per_output_token_ms, 2) || 0} ms
</span>
<span>
TTFT: {_.round(usage.time_to_first_token_ms, 2) || 0} ms
</span>
</Space>
}
>
<span>
{intl.formatMessage({ id: 'playground.tokenoutput' })}:{' '}
{_.round(usage.tokens_per_second, 2) || 0} Tokens/s
</span>
</Tooltip>
</span>
{showOutput && (
<span className="usage">
<Tooltip
title={
<Space>
<span>
TPOT: {_.round(usage.time_per_output_token_ms, 2) || 0} ms
</span>
<span>
TTFT: {_.round(usage.time_to_first_token_ms, 2) || 0} ms
</span>
</Space>
}
>
<span>
{intl.formatMessage({ id: 'playground.tokenoutput' })}:{' '}
{_.round(usage.tokens_per_second, 2) || 0} Tokens/s
</span>
</Tooltip>
</span>
)}
</div>
);
};
@@ -0,0 +1,56 @@
import { StarFilled } from '@ant-design/icons';
import { Tooltip } from 'antd';
import _ from 'lodash';
import React from 'react';
import '../style/content-item.less';
import '../style/rerank-message.less';
interface RerankMessageProps {
dataList: { title?: string; content: any; uid: number | string }[];
}
const RerankMessage: React.FC<RerankMessageProps> = ({ dataList }) => {
return (
<div className="rerank-message">
{dataList.map((item) => {
return (
<div className="content-item" key={item.uid}>
<div className="content-item-role">
<span className="role">{item.title}</span>
</div>
<div className="content-item-content">
{Array.isArray(item.content) ? (
<div className="result">
{item.content.map((sItem, sIndex) => {
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>
</dt>
<dd className="text">{sItem.text}</dd>
<dd className="doc-name">{sItem.title}</dd>
</dl>
);
})}
</div>
) : (
<div className="content-item-text">{item.content}</div>
)}
</div>
</div>
);
})}
</div>
);
};
export default React.memo(RerankMessage);
@@ -0,0 +1,188 @@
import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select';
import { INPUT_WIDTH } from '@/constants';
import { InfoCircleOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Form, InputNumber, Tooltip } from 'antd';
import _ from 'lodash';
import { memo, useCallback, useEffect, useId } from 'react';
import CustomLabelStyles from '../style/custom-label.less';
type ParamsSettingsFormProps = {
top_n?: number;
model?: string;
};
type ParamsSettingsProps = {
selectedModel?: string;
showModelSelector?: boolean;
params?: ParamsSettingsFormProps;
model?: string;
modelList: Global.BaseOption<string>[];
onValuesChange?: (changeValues: any, value: Record<string, any>) => void;
setParams: (params: any) => void;
globalParams?: ParamsSettingsFormProps;
};
const ParamsSettings: React.FC<ParamsSettingsProps> = ({
selectedModel,
setParams,
globalParams,
onValuesChange,
modelList,
showModelSelector = true
}) => {
const intl = useIntl();
const initialValues = {
top_n: 1
};
const [form] = Form.useForm();
const formId = useId();
useEffect(() => {
if (showModelSelector) {
form.setFieldsValue({
model: selectedModel || _.get(modelList, '[0].value'),
...initialValues
});
setParams({
model: selectedModel || _.get(modelList, '[0].value'),
...initialValues
});
} else {
form.setFieldsValue({
model: selectedModel || '',
...initialValues
});
setParams({
model: selectedModel || '',
...initialValues
});
}
}, [modelList, showModelSelector, selectedModel]);
const handleOnFinish = (values: any) => {
console.log('handleOnFinish', values);
};
const handleOnFinishFailed = (errorInfo: any) => {
console.log('handleOnFinishFailed', errorInfo);
};
const handleValuesChange = useCallback(
(changedValues: any, allValues: any) => {
setParams?.(allValues);
onValuesChange?.(changedValues, allValues);
},
[onValuesChange, setParams]
);
const handleFieldValueChange = useCallback(
(val: any, field: string) => {
const values = form.getFieldsValue();
form.setFieldsValue({
...values,
[field]: val
});
setParams({
...values,
[field]: val
});
onValuesChange?.(
{ [field]: val },
{
...values,
[field]: val
}
);
},
[form, setParams, onValuesChange]
);
useEffect(() => {
form.setFieldsValue(globalParams);
}, [globalParams]);
const renderLabel = (args: {
field: string;
label: string;
description: string;
}) => {
return (
<span
className={CustomLabelStyles.label}
style={{ width: INPUT_WIDTH.mini }}
>
<span className="text">
{args.description ? (
<Tooltip title={args.description}>
<span> {args.label}</span>
<span className="m-l-5">
<InfoCircleOutlined />
</span>
</Tooltip>
) : (
<span>{args.label}</span>
)}
</span>
<InputNumber
className="label-val"
variant="outlined"
size="small"
value={form.getFieldValue(args.field)}
controls={false}
onChange={(val) => handleFieldValueChange(val, args.field)}
></InputNumber>
</span>
);
};
return (
<Form
name={formId}
form={form}
onValuesChange={handleValuesChange}
onFinish={handleOnFinish}
onFinishFailed={handleOnFinishFailed}
>
<div>
{
<>
<Form.Item<ParamsSettingsFormProps>
name="model"
rules={[
{
required: true,
message: intl.formatMessage(
{
id: 'common.form.rule.select'
},
{ name: intl.formatMessage({ id: 'playground.model' }) }
)
}
]}
>
<SealSelect
showSearch={true}
options={modelList}
label={intl.formatMessage({ id: 'playground.model' })}
></SealSelect>
</Form.Item>
</>
}
<Form.Item<ParamsSettingsFormProps>
name="top_n"
rules={[{ required: true }]}
>
<SealInput.Number
style={{ width: '100%' }}
label="Top N"
min={1}
></SealInput.Number>
</Form.Item>
</div>
</Form>
);
};
export default memo(ParamsSettings);
@@ -0,0 +1,118 @@
import { readBlob, readExcelContent, readWordContent } from '@/utils';
import { PaperClipOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Tooltip, Upload } from 'antd';
import type { UploadFile } from 'antd/es/upload';
import { RcFile } from 'antd/es/upload';
import { debounce } from 'lodash';
import React, { useCallback, useRef } from 'react';
interface UploadImgProps {
size?: 'small' | 'middle' | 'large';
accept?: string;
children?: React.ReactNode;
handleUpdateFileList: (
fileList: { text: string; name: string; uid: number | string }[]
) => void;
}
const UploadImg: React.FC<UploadImgProps> = ({
handleUpdateFileList,
size = 'small',
accept = '.txt,.doc,.docx',
children
}) => {
const intl = useIntl();
const uploadRef = useRef<any>(null);
const wordReg = /\.(doc|docx)$/;
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 debouncedUpdate = useCallback(
debounce(
(files: { text: string; name: string; uid: number | string }[]) => {
handleUpdateFileList(files);
},
300
),
[handleUpdateFileList, intl]
);
const handleChange = useCallback(
async (info: any) => {
try {
const { fileList } = info;
const newFileList = await Promise.all(
fileList.map(async (item: UploadFile) => {
if (wordReg.test(item.name)) {
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 {
const context = await readBlob(item.originFileObj as RcFile);
item.url = context;
}
return item;
})
);
if (newFileList.length > 0) {
const files = newFileList
.filter((sitem) => sitem.url)
.map((item: UploadFile) => {
return {
text: item.url as string,
name: item.name as string,
uid: item.uid
};
});
debouncedUpdate(files);
}
} catch (error) {
// console.log('error', error);
}
},
[debouncedUpdate, getBase64]
);
return (
<>
<Upload.Dragger
ref={uploadRef}
multiple
action="/"
accept={accept}
fileList={[]}
beforeUpload={(file) => false}
onChange={handleChange}
>
{children ? (
children
) : (
<Tooltip title={intl.formatMessage({ id: 'playground.upload' })}>
<Button size={size} icon={<PaperClipOutlined />} />
</Tooltip>
)}
</Upload.Dragger>
</>
);
};
export default React.memo(UploadImg);
@@ -7,12 +7,16 @@ import { debounce } from 'lodash';
import React, { useCallback, useRef } from 'react';
interface UploadImgProps {
size?: 'small' | 'middle' | 'large';
handleUpdateImgList: (
imgList: { dataUrl: string; uid: number | string }[]
) => void;
}
const UploadImg: React.FC<UploadImgProps> = ({ handleUpdateImgList }) => {
const UploadImg: React.FC<UploadImgProps> = ({
handleUpdateImgList,
size = 'small'
}) => {
const intl = useIntl();
const uploadRef = useRef<any>(null);
@@ -74,7 +78,7 @@ const UploadImg: React.FC<UploadImgProps> = ({ handleUpdateImgList }) => {
onChange={handleChange}
>
<Tooltip title={intl.formatMessage({ id: 'playground.img.upload' })}>
<Button size="small" type="text" icon={<PictureOutlined />}></Button>
<Button size={size} type="text" icon={<PictureOutlined />}></Button>
</Tooltip>
</Upload>
</>
+1
View File
@@ -7,6 +7,7 @@ export interface ModelSelectionItem extends Global.BaseOption<string> {
export interface MessageItem {
role: string;
content: string;
title?: string;
imgs?: { uid: string | number; dataUrl: string }[];
uid: number;
}
+64 -6
View File
@@ -1,6 +1,10 @@
import IconFont from '@/components/icon-font';
import HotKeys from '@/config/hotkeys';
import { MessageOutlined, OneToOneOutlined } from '@ant-design/icons';
import {
FileSearchOutlined,
MessageOutlined,
OneToOneOutlined
} from '@ant-design/icons';
import { PageContainer } from '@ant-design/pro-components';
import { useIntl } from '@umijs/max';
import { Button, Segmented, Space, Tabs, TabsProps } from 'antd';
@@ -10,6 +14,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { queryModelsList } from './apis';
import GroundLeft from './components/ground-left';
import GroundReranker from './components/ground-reranker';
import MultipleChat from './components/multiple-chat';
import './style/play-ground.less';
@@ -17,7 +22,11 @@ const Playground: React.FC = () => {
const intl = useIntl();
const [activeKey, setActiveKey] = useState('chat');
const groundLeftRef = useRef<any>(null);
const groundRerankerRef = useRef<any>(null);
const [modelList, setModelList] = useState<Global.BaseOption<string>[]>([]);
const [rerankerModelList, setRerankerModelList] = useState<
Global.BaseOption<string>[]
>([]);
const [loaded, setLoaded] = useState(false);
const optionsList = [
{
@@ -29,6 +38,11 @@ const Playground: React.FC = () => {
label: intl.formatMessage({ id: 'menu.compare' }),
value: 'compare',
icon: <OneToOneOutlined />
},
{
label: 'Rerank',
value: 'reranker',
icon: <FileSearchOutlined />
}
];
@@ -37,8 +51,12 @@ const Playground: React.FC = () => {
}, [groundLeftRef]);
const handleToggleCollapse = useCallback(() => {
if (activeKey === 'reranker') {
groundRerankerRef.current?.setCollapse?.();
return;
}
groundLeftRef.current?.setCollapse?.();
}, [groundLeftRef]);
}, [groundLeftRef, groundRerankerRef, activeKey]);
const items: TabsProps['items'] = [
{
@@ -52,6 +70,17 @@ const Playground: React.FC = () => {
key: 'compare',
label: 'Compare',
children: <MultipleChat modelList={modelList} loaded={loaded} />
},
{
key: 'reranker',
label: 'Reranker',
children: (
<GroundReranker
ref={groundRerankerRef}
modelList={rerankerModelList}
loaded={loaded}
></GroundReranker>
)
}
];
@@ -68,14 +97,43 @@ const Playground: React.FC = () => {
label: item.id
};
}) as Global.BaseOption<string>[];
setModelList(list);
return list;
} catch (error) {
console.error(error);
} finally {
return [];
}
};
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 [modelist, rerankerModelList] = await Promise.all([
getModelList(),
getModelListByReranker()
]);
setModelList(modelist);
setRerankerModelList(rerankerModelList);
} catch (error) {
setLoaded(true);
}
};
getModelList();
fetchData();
}, []);
const renderExtra = () => {
@@ -136,7 +194,7 @@ const Playground: React.FC = () => {
extra={renderExtra()}
className={classNames('playground-container', {
compare: activeKey === 'compare',
chat: activeKey === 'chat'
chat: activeKey !== 'compare'
})}
>
<div className="play-ground">
+1 -1
View File
@@ -31,7 +31,7 @@
&-content {
word-break: break-word;
padding: 8px;
padding: 8px 14px;
min-height: 38px;
border-radius: var(--border-radius-mini);
background-color: var(--ant-color-fill-tertiary);
+38
View File
@@ -0,0 +1,38 @@
.file-list {
display: flex;
flex-direction: column;
gap: 8px;
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--ant-color-split);
padding: 8px 14px;
padding-right: 6px;
cursor: pointer;
height: 41px;
transition: background-color 0.3s ease;
.delete-btn {
display: none;
}
&:hover {
background-color: var(--ant-color-fill-tertiary);
.delete-btn {
display: block;
}
}
&.ghost {
background-color: transparent;
}
.title {
display: flex;
align-items: center;
}
}
}
@@ -2,6 +2,7 @@
display: flex;
.params-wrapper {
position: relative;
overflow-x: hidden;
width: 390px;
border-left: 1px solid var(--ant-color-split);
@@ -50,4 +51,11 @@
}
}
}
.ant-upload-wrapper .ant-upload-drag {
background-color: var(--color-fill-sider);
display: flex;
flex-direction: column;
justify-content: center;
}
}
@@ -0,0 +1,39 @@
.input-list {
display: flex;
flex-direction: column;
gap: 8px;
.input-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0;
cursor: pointer;
transition: background-color 0.3s ease;
border-bottom: 1px solid var(--ant-color-split);
&:focus-within {
background-color: transparent;
}
.input-wrap {
flex: 1;
}
.btn-group {
width: 60px;
margin-left: 8px;
display: flex;
gap: 8px;
}
&:hover {
background-color: var(--ant-color-fill-tertiary);
.btn-group {
display: flex;
gap: 8px;
}
}
}
}
@@ -0,0 +1,45 @@
.rerank-message {
display: flex;
flex-direction: column;
.rank {
margin-right: 10px;
max-width: 75px;
min-width: 55px;
display: flex;
align-items: center;
justify-content: space-between;
.score {
margin-left: 5px;
color: var(--ant-rate-star-color);
}
}
.result {
display: flex;
flex-direction: column;
gap: 10px;
}
.content-item-text {
margin-bottom: 0;
display: flex;
align-items: center;
}
.text {
flex: 1;
width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-bottom: 0;
}
.doc-name {
margin-bottom: 0;
font-weight: var(--font-weight-bold);
font-style: italic;
}
}
+45
View File
@@ -1,4 +1,7 @@
import _ from 'lodash';
import mammoth from 'mammoth';
import XLSX from 'xlsx';
export const isNotEmptyValue = (value: any) => {
if (Array.isArray(value)) {
return value.length > 0;
@@ -183,3 +186,45 @@ export function loadLanguageConfig(language: string) {
return languageConfig;
}
export function readBlob(blob: Blob): Promise<string> {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = function (e: any) {
resolve(e.target.result);
};
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);
});
}