feat: playground rerank
This commit is contained in:
+3
-1
@@ -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",
|
||||
|
||||
Generated
+86
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': '无可用模型',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,6 +1,7 @@
|
||||
export interface ListItem {
|
||||
source: string;
|
||||
backend: string;
|
||||
reranker: boolean;
|
||||
huggingface_repo_id: string;
|
||||
huggingface_file_name: string;
|
||||
huggingface_filename: string;
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user