Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d654bcd66 | ||
|
|
5f8e72c6ae | ||
|
|
5dde5cae50 | ||
|
|
f730548c8f | ||
|
|
a185bd9463 | ||
|
|
abdc78e123 | ||
|
|
ba49fa6c88 | ||
|
|
870ffa9dc1 | ||
|
|
8afaf1a60e | ||
|
|
6974ac8e14 | ||
|
|
9f274ee17e | ||
|
|
673bcde719 | ||
|
|
6eaf5bb463 | ||
|
|
b463eedb3f | ||
|
|
90350f99ad | ||
|
|
8576015633 | ||
|
|
f5edf39027 | ||
|
|
751781e3e5 | ||
|
|
bc9129bcbe | ||
|
|
c442a33d5e | ||
|
|
902bb06353 | ||
|
|
a0a1b8b3e8 | ||
|
|
eb5cf9da8d | ||
|
|
57386eeb55 | ||
|
|
33a21f2aaa | ||
|
|
d6fd498489 | ||
|
|
386f2969eb | ||
|
|
2fea3a4b40 | ||
|
|
b9c9fe7a97 | ||
|
|
594c4cd670 | ||
|
|
e10c5e28b5 | ||
|
|
fbefb2014d | ||
|
|
909da7edb5 | ||
|
|
349676d908 | ||
|
|
a47f7cc0ce | ||
|
|
8fbdb884c7 | ||
|
|
ec68ce082e | ||
|
|
2b61e3dfd3 | ||
|
|
4c3dddc486 | ||
|
|
ecc683a6bd | ||
|
|
7b2098cf33 | ||
|
|
3784d86344 | ||
|
|
2b22481443 |
+9
-1
@@ -1,4 +1,12 @@
|
|||||||
const proxyTableList = ['cli', 'v1', 'auth', 'v1-openai', 'version', 'proxy'];
|
const proxyTableList = [
|
||||||
|
'cli',
|
||||||
|
'v1',
|
||||||
|
'auth',
|
||||||
|
'v1-openai',
|
||||||
|
'version',
|
||||||
|
'proxy',
|
||||||
|
'update'
|
||||||
|
];
|
||||||
|
|
||||||
// @ts-ingore
|
// @ts-ingore
|
||||||
export default function createProxyTable(target?: string) {
|
export default function createProxyTable(target?: string) {
|
||||||
|
|||||||
+12
-4
@@ -7,23 +7,28 @@
|
|||||||
"format": "prettier --cache --write .",
|
"format": "prettier --cache --write .",
|
||||||
"postinstall": "max setup",
|
"postinstall": "max setup",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
|
"preview": "max preview",
|
||||||
"setup": "max setup",
|
"setup": "max setup",
|
||||||
"start": "npm run dev"
|
"start": "npm run dev"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"immer": "^9.0.6"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.3.7",
|
"@ant-design/icons": "^5.5.1",
|
||||||
"@ant-design/pro-components": "^2.7.1",
|
"@ant-design/pro-components": "^2.7.18",
|
||||||
"@huggingface/gguf": "^0.1.7",
|
"@huggingface/gguf": "^0.1.7",
|
||||||
"@huggingface/hub": "^0.15.1",
|
"@huggingface/hub": "^0.15.1",
|
||||||
"@huggingface/tasks": "^0.11.6",
|
"@huggingface/tasks": "^0.11.6",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
|
"@orcid/bibtex-parse-js": "^0.0.25",
|
||||||
"@react-hook/resize-observer": "^2.0.2",
|
"@react-hook/resize-observer": "^2.0.2",
|
||||||
"@types/lodash": "^4.17.4",
|
"@types/lodash": "^4.17.4",
|
||||||
"@umijs/max": "^4.2.11",
|
"@umijs/max": "^4.2.11",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"antd": "^5.18.3",
|
"antd": "^5.20.6",
|
||||||
"antd-style": "^3.6.2",
|
"antd-style": "^3.6.2",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
@@ -36,16 +41,19 @@
|
|||||||
"jotai": "^2.8.4",
|
"jotai": "^2.8.4",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mammoth": "^1.8.0",
|
||||||
"marked": "^14.1.0",
|
"marked": "^14.1.0",
|
||||||
"numeral": "^2.0.6",
|
"numeral": "^2.0.6",
|
||||||
"overlayscrollbars": "^2.10.0",
|
"overlayscrollbars": "^2.10.0",
|
||||||
"overlayscrollbars-react": "^0.5.6",
|
"overlayscrollbars-react": "^0.5.6",
|
||||||
"query-string": "^9.0.0",
|
"query-string": "^9.0.0",
|
||||||
|
"rc-virtual-list": "^3.14.8",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys-hook": "^4.5.0",
|
"react-hotkeys-hook": "^4.5.0",
|
||||||
"simplebar-react": "^3.2.6",
|
"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": {
|
"devDependencies": {
|
||||||
"@types/marked": "^6.0.0",
|
"@types/marked": "^6.0.0",
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ export default (api: IApi) => {
|
|||||||
api.modifyHTML(($) => {
|
api.modifyHTML(($) => {
|
||||||
const info = JSON.parse(process.env.VERSION || '{}');
|
const info = JSON.parse(process.env.VERSION || '{}');
|
||||||
const env = process.env.NODE_ENV;
|
const env = process.env.NODE_ENV;
|
||||||
|
|
||||||
|
$('html').attr('data-env', env);
|
||||||
|
|
||||||
$('html').attr(
|
$('html').attr(
|
||||||
'data-version',
|
'data-version',
|
||||||
env === 'production'
|
env === 'production' ? info.version || info.commitId : `${info.commitId}`
|
||||||
? info.version || info.commitId
|
|
||||||
: `dev-${info.commitId}`
|
|
||||||
);
|
);
|
||||||
if (env === 'production') {
|
if (env === 'production') {
|
||||||
$('script[src^="/js/umi"]').first?.().remove?.();
|
$('script[src^="/js/umi"]').first?.().remove?.();
|
||||||
|
|||||||
Generated
+1589
-1031
File diff suppressed because it is too large
Load Diff
+16
-2
@@ -1,9 +1,10 @@
|
|||||||
import { GPUStackVersionAtom } from '@/atoms/user';
|
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
|
||||||
import { setAtomStorage } from '@/atoms/utils';
|
import { setAtomStorage } from '@/atoms/utils';
|
||||||
import { requestConfig } from '@/request-config';
|
import { requestConfig } from '@/request-config';
|
||||||
import {
|
import {
|
||||||
queryCurrentUserState,
|
queryCurrentUserState,
|
||||||
queryVersionInfo
|
queryVersionInfo,
|
||||||
|
updateCheck
|
||||||
} from '@/services/profile/apis';
|
} from '@/services/profile/apis';
|
||||||
import { RequestConfig, history } from '@umijs/max';
|
import { RequestConfig, history } from '@umijs/max';
|
||||||
|
|
||||||
@@ -16,11 +17,24 @@ export async function getInitialState(): Promise<{
|
|||||||
}> {
|
}> {
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
|
|
||||||
|
const getUpdateCheck = async () => {
|
||||||
|
try {
|
||||||
|
const data = await updateCheck();
|
||||||
|
setAtomStorage(UpdateCheckAtom, data);
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('updateCheck error', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchUserInfo = async (): Promise<Global.UserInfo> => {
|
const fetchUserInfo = async (): Promise<Global.UserInfo> => {
|
||||||
try {
|
try {
|
||||||
const data = await queryCurrentUserState({
|
const data = await queryCurrentUserState({
|
||||||
skipErrorHandler: true
|
skipErrorHandler: true
|
||||||
});
|
});
|
||||||
|
if (data.is_admin) {
|
||||||
|
getUpdateCheck();
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
history.push(loginPath);
|
history.push(loginPath);
|
||||||
|
|||||||
@@ -136,6 +136,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-size-0 {
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.font-size-12 {
|
.font-size-12 {
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
}
|
}
|
||||||
@@ -148,6 +152,10 @@
|
|||||||
font-size: var(--font-size-large);
|
font-size: var(--font-size-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-size-20 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.font-400 {
|
.font-400 {
|
||||||
font-weight: var(--font-weight-normal);
|
font-weight: var(--font-weight-normal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ export const GPUStackVersionAtom = atom<{
|
|||||||
git_commit: ''
|
git_commit: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const UpdateCheckAtom = atom<{
|
||||||
|
latest_version: string;
|
||||||
|
}>({
|
||||||
|
latest_version: ''
|
||||||
|
});
|
||||||
|
|
||||||
export const initialPasswordAtom = atomWithStorage<string>(
|
export const initialPasswordAtom = atomWithStorage<string>(
|
||||||
'initialPassword',
|
'initialPassword',
|
||||||
''
|
''
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Image as AntImage, ImageProps } from 'antd';
|
||||||
|
import { round } from 'lodash';
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const AutoImage: React.FC<ImageProps & { height: number }> = (props) => {
|
||||||
|
const { height = 100, ...rest } = props;
|
||||||
|
const [width, setWidth] = useState(0);
|
||||||
|
|
||||||
|
const getImgRatio = useCallback((url: string): Promise<{ ratio: number }> => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
resolve({ ratio: round(img.width / img.height, 2) });
|
||||||
|
};
|
||||||
|
img.onerror = () => {
|
||||||
|
resolve({ ratio: 1 });
|
||||||
|
};
|
||||||
|
img.src = url;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOnLoad = useCallback(async () => {
|
||||||
|
const { ratio } = await getImgRatio(props.src || '');
|
||||||
|
setWidth(height * ratio);
|
||||||
|
}, [getImgRatio, height, props.src]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleOnLoad();
|
||||||
|
}, [handleOnLoad]);
|
||||||
|
|
||||||
|
return <AntImage {...rest} height={height} width={width} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AutoImage;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Tag, Tooltip, type TagProps } from 'antd';
|
import { Tag, Tooltip, type TagProps } from 'antd';
|
||||||
import debounce from 'lodash/debounce';
|
import { throttle } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@@ -7,12 +7,14 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import TitleTip from './title-tip';
|
||||||
|
|
||||||
// type TagProps = React.ComponentProps<typeof Tag>;
|
// type TagProps = React.ComponentProps<typeof Tag>;
|
||||||
|
|
||||||
interface AutoTooltipProps extends Omit<TagProps, 'title'> {
|
interface AutoTooltipProps extends Omit<TagProps, 'title'> {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
maxWidth?: number | string;
|
maxWidth?: number | string;
|
||||||
|
minWidth?: number | string;
|
||||||
color?: string;
|
color?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
ghost?: boolean;
|
ghost?: boolean;
|
||||||
@@ -24,6 +26,7 @@ interface AutoTooltipProps extends Omit<TagProps, 'title'> {
|
|||||||
const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
||||||
children,
|
children,
|
||||||
maxWidth = '100%',
|
maxWidth = '100%',
|
||||||
|
minWidth,
|
||||||
ghost = false,
|
ghost = false,
|
||||||
title,
|
title,
|
||||||
showTitle = false,
|
showTitle = false,
|
||||||
@@ -32,6 +35,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
const [isOverflowing, setIsOverflowing] = useState(false);
|
const [isOverflowing, setIsOverflowing] = useState(false);
|
||||||
|
const resizeObserver = useRef<ResizeObserver>();
|
||||||
|
|
||||||
const checkOverflow = useCallback(() => {
|
const checkOverflow = useCallback(() => {
|
||||||
if (contentRef.current) {
|
if (contentRef.current) {
|
||||||
@@ -40,19 +44,14 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
}
|
}
|
||||||
}, [contentRef.current]);
|
}, [contentRef.current]);
|
||||||
|
|
||||||
const debouncedCheckOverflow = useMemo(
|
|
||||||
() => debounce(checkOverflow, 200),
|
|
||||||
[checkOverflow]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkOverflow();
|
const debouncedCheckOverflow = throttle(checkOverflow, 200);
|
||||||
window.addEventListener('resize', debouncedCheckOverflow);
|
window.addEventListener('resize', debouncedCheckOverflow);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', debouncedCheckOverflow);
|
window.removeEventListener('resize', debouncedCheckOverflow);
|
||||||
debouncedCheckOverflow.cancel();
|
debouncedCheckOverflow.cancel();
|
||||||
};
|
};
|
||||||
}, [checkOverflow, debouncedCheckOverflow]);
|
}, [checkOverflow]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkOverflow();
|
checkOverflow();
|
||||||
@@ -61,6 +60,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
const tagStyle = useMemo(
|
const tagStyle = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
maxWidth,
|
maxWidth,
|
||||||
|
minWidth,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis',
|
textOverflow: 'ellipsis',
|
||||||
whiteSpace: 'nowrap' as const,
|
whiteSpace: 'nowrap' as const,
|
||||||
@@ -71,7 +71,21 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={isOverflowing || showTitle ? title || children : ''}
|
overlayInnerStyle={{ paddingInline: 0 }}
|
||||||
|
destroyTooltipOnHide={false}
|
||||||
|
title={
|
||||||
|
isOverflowing || showTitle ? (
|
||||||
|
<TitleTip
|
||||||
|
isOverflowing={isOverflowing}
|
||||||
|
title={title}
|
||||||
|
showTitle={showTitle}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</TitleTip>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
{...tooltipProps}
|
{...tooltipProps}
|
||||||
>
|
>
|
||||||
{ghost ? (
|
{ghost ? (
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface TitleTipProps {
|
||||||
|
isOverflowing: boolean;
|
||||||
|
showTitle: boolean;
|
||||||
|
title: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TitleTip: React.FC<TitleTipProps> = (props) => {
|
||||||
|
const { isOverflowing, showTitle, title, children } = props;
|
||||||
|
const { initialize } = useOverlayScroller();
|
||||||
|
const scrollRef = React.useRef<any>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (scrollRef.current) {
|
||||||
|
initialize(scrollRef.current);
|
||||||
|
}
|
||||||
|
}, [initialize, scrollRef.current]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ maxHeight: 200, overflowY: 'auto' }} ref={scrollRef}>
|
||||||
|
<div style={{ width: 'max-content', maxWidth: 250, paddingInline: 10 }}>
|
||||||
|
{isOverflowing || showTitle ? title || children : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(TitleTip);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import bibtexParse from '@orcid/bibtex-parse-js';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
/*
|
||||||
|
@inproceedings{Lysenko:2010:GMC:1839778.1839781,\
|
||||||
|
author = {Lysenko, Mikola and Nelaturi, Saigopal and Shapiro, Vadim},\
|
||||||
|
title = {Group morphology with convolution algebras},\
|
||||||
|
booktitle = {Proceedings of the 14th ACM Symposium on Solid and Physical Modeling},\
|
||||||
|
series = {SPM '10},\
|
||||||
|
year = {2010},\
|
||||||
|
isbn = {978-1-60558-984-8},\
|
||||||
|
location = {Haifa, Israel},\
|
||||||
|
pages = {11--22},\
|
||||||
|
numpages = {12},\
|
||||||
|
url = {http://doi.acm.org/10.1145/1839778.1839781},\
|
||||||
|
doi = {10.1145/1839778.1839781},\
|
||||||
|
acmid = {1839781},\
|
||||||
|
publisher = {ACM},\
|
||||||
|
address = {New York, NY, USA},\
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const BibTeXViewer: React.FC<{ data: string }> = ({ data }) => {
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const dataList = bibtexParse.toJSON(data);
|
||||||
|
return (
|
||||||
|
<ol>
|
||||||
|
{dataList.map((item: any, index: number) => (
|
||||||
|
<li key={index} style={{ lineHeight: 2 }}>
|
||||||
|
<Typography.Link href={item.entryTags?.url} target="_blank">
|
||||||
|
{item.entryTags?.title}.{' '}
|
||||||
|
</Typography.Link>
|
||||||
|
<Typography.Text>{item.entryTags?.author}. </Typography.Text>
|
||||||
|
<Typography.Text>[{item.entryTags?.year}] </Typography.Text>
|
||||||
|
{item.entryTags?.journal && (
|
||||||
|
<Typography.Text>.({item.entryTags?.journal})</Typography.Text>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BibTeXViewer;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { throttle } from 'echarts/core';
|
import { throttle } from 'lodash';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import echarts, { ECOption } from '.';
|
import echarts, { ECOption } from '.';
|
||||||
|
|
||||||
@@ -9,6 +9,8 @@ const Chart: React.FC<{
|
|||||||
}> = ({ options, width, height }) => {
|
}> = ({ options, width, height }) => {
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
const chart = useRef<echarts.EChartsType>();
|
const chart = useRef<echarts.EChartsType>();
|
||||||
|
const resizeable = useRef(false);
|
||||||
|
const resizeObserver = useRef<ResizeObserver>();
|
||||||
|
|
||||||
const init = useCallback(() => {
|
const init = useCallback(() => {
|
||||||
if (container.current) {
|
if (container.current) {
|
||||||
@@ -30,27 +32,48 @@ const Chart: React.FC<{
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (container.current) {
|
||||||
init();
|
init();
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
chart.current?.dispose();
|
chart.current?.dispose();
|
||||||
};
|
};
|
||||||
}, [init]);
|
}, [init]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
resizeable.current = false;
|
||||||
resize();
|
resize();
|
||||||
setOption(options);
|
setOption(options);
|
||||||
}, [options]);
|
}, [options]);
|
||||||
|
|
||||||
// resize on window resize
|
useEffect(() => {
|
||||||
|
let timer: any = null;
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
resizeable.current = true;
|
||||||
|
}, 300);
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = throttle(() => {
|
const handleResize = throttle(() => {
|
||||||
resize();
|
if (resizeable.current) {
|
||||||
|
chart.current?.resize();
|
||||||
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
|
if (container.current) {
|
||||||
|
resizeObserver.current = new ResizeObserver(handleResize);
|
||||||
|
|
||||||
|
resizeObserver.current.observe(container.current);
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('resize', handleResize);
|
if (container.current) {
|
||||||
|
resizeObserver.current?.unobserve(container.current);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [resize]);
|
}, []);
|
||||||
|
|
||||||
return <div ref={container} style={{ width: width, height }}></div>;
|
return <div ref={container} style={{ width: width, height }}></div>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type {
|
import type {
|
||||||
// 系列类型的定义后缀都为 SeriesOption
|
|
||||||
BarSeriesOption,
|
BarSeriesOption,
|
||||||
GaugeSeriesOption,
|
GaugeSeriesOption,
|
||||||
LineSeriesOption
|
LineSeriesOption
|
||||||
@@ -8,18 +7,16 @@ import { BarChart, GaugeChart, LineChart } from 'echarts/charts';
|
|||||||
import type {
|
import type {
|
||||||
DatasetComponentOption,
|
DatasetComponentOption,
|
||||||
GridComponentOption,
|
GridComponentOption,
|
||||||
// 组件类型的定义后缀都为 ComponentOption
|
|
||||||
TitleComponentOption,
|
TitleComponentOption,
|
||||||
TooltipComponentOption
|
TooltipComponentOption
|
||||||
} from 'echarts/components';
|
} from 'echarts/components';
|
||||||
import {
|
import {
|
||||||
// 数据集组件
|
|
||||||
DatasetComponent,
|
DatasetComponent,
|
||||||
GridComponent,
|
GridComponent,
|
||||||
LegendComponent,
|
LegendComponent,
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
TooltipComponent,
|
TooltipComponent,
|
||||||
// 内置数据转换器组件 (filter, sort)
|
// (filter, sort)
|
||||||
TransformComponent
|
TransformComponent
|
||||||
} from 'echarts/components';
|
} from 'echarts/components';
|
||||||
import type { ComposeOption } from 'echarts/core';
|
import type { ComposeOption } from 'echarts/core';
|
||||||
@@ -27,7 +24,6 @@ import * as echarts from 'echarts/core';
|
|||||||
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
||||||
import { CanvasRenderer } from 'echarts/renderers';
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
|
||||||
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
|
|
||||||
type ECOption = ComposeOption<
|
type ECOption = ComposeOption<
|
||||||
| BarSeriesOption
|
| BarSeriesOption
|
||||||
| LineSeriesOption
|
| LineSeriesOption
|
||||||
@@ -38,7 +34,7 @@ type ECOption = ComposeOption<
|
|||||||
| GaugeSeriesOption
|
| GaugeSeriesOption
|
||||||
>;
|
>;
|
||||||
|
|
||||||
// 注册必须的组件
|
// register components and charts
|
||||||
echarts.use([
|
echarts.use([
|
||||||
LegendComponent,
|
LegendComponent,
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const Footer: React.FC = () => {
|
|||||||
const showVersion = () => {
|
const showVersion = () => {
|
||||||
Modal.info({
|
Modal.info({
|
||||||
...modalConfig,
|
...modalConfig,
|
||||||
|
width: 460,
|
||||||
content: <VersionInfo intl={intl} />
|
content: <VersionInfo intl={intl} />
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,7 +73,11 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<code
|
<code
|
||||||
className={highlightedCode.className}
|
style={{ minHeight: height }}
|
||||||
|
className={classNames(highlightedCode.className, {
|
||||||
|
dark: props.theme === 'dark',
|
||||||
|
light: props.theme === 'light'
|
||||||
|
})}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: highlightedCode.value
|
__html: highlightedCode.value
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.light {
|
||||||
&:hover {
|
&:hover {
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background-color: var(--color-scrollbar-thumb);
|
background-color: var(--color-scrollbar-thumb);
|
||||||
@@ -28,6 +29,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
&:hover {
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--scrollbar-handle-light-bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.code-pre {
|
.code-pre {
|
||||||
padding-inline: 12px 12px;
|
padding-inline: 12px 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createFromIconfontCN } from '@ant-design/icons';
|
import { createFromIconfontCN } from '@ant-design/icons';
|
||||||
|
|
||||||
const IconFont = createFromIconfontCN({
|
const IconFont = createFromIconfontCN({
|
||||||
scriptUrl: '//at.alicdn.com/t/c/font_4613488_4jkdkc8jcf7.js'
|
scriptUrl: '//at.alicdn.com/t/c/font_4613488_l9554igzbh.js'
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IconFont;
|
export default IconFont;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const Wrapper: React.FC<{
|
|||||||
label?: string;
|
label?: string;
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}> = ({ children, label, description }) => {
|
}> = ({ children, label, description, ...rest }) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles['wrapper']}>
|
<div className={styles['wrapper']}>
|
||||||
{label && (
|
{label && (
|
||||||
@@ -14,7 +14,9 @@ const Wrapper: React.FC<{
|
|||||||
<LabelInfo label={label} description={description}></LabelInfo>
|
<LabelInfo label={label} description={description}></LabelInfo>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{children}
|
{React.isValidElement(children)
|
||||||
|
? React.cloneElement(children, { ...rest })
|
||||||
|
: children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import ListItem from './list-item';
|
|||||||
interface ListInputProps {
|
interface ListInputProps {
|
||||||
dataList: string[];
|
dataList: string[];
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: React.ReactNode;
|
||||||
btnText?: string;
|
btnText?: string;
|
||||||
options?: Global.HintOptions[];
|
options?: Global.HintOptions[];
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|||||||
@@ -1,9 +1,30 @@
|
|||||||
.logs-viewer-wrap-w2 {
|
.logs-viewer-wrap-w2 {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.copy {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
z-index: 100;
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: rgba(255, 255, 255, 70%);
|
||||||
|
background-color: rgba(71, 71, 71, 100%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgba(255, 255, 255, 90%) !important;
|
||||||
|
background-color: rgba(71, 71, 71, 100%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
padding: 5px 0 5px 10px;
|
padding: 5px 0 5px 10px;
|
||||||
background-color: var(--color-logs-bg);
|
background-color: var(--color-logs-bg);
|
||||||
border-radius: var(--border-radius-mini);
|
border-radius: var(--border-radius-mini);
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
|
font-family: monospace, Menlo, Courier, 'Courier New', Consolas, Monaco,
|
||||||
|
'Liberation Mono' !important;
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
@@ -14,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
height: 100%;
|
min-height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
color: var(--color-logs-text);
|
color: var(--color-logs-text);
|
||||||
@@ -26,8 +47,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.xterm {
|
.xterm {
|
||||||
// height: 100% !important;
|
|
||||||
|
|
||||||
.xterm-viewport {
|
.xterm-viewport {
|
||||||
overflow-y: auto !important;
|
overflow-y: auto !important;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
import useSetChunkFetch from '@/hooks/use-chunk-fetch';
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||||
import { Terminal } from '@xterm/xterm';
|
|
||||||
import '@xterm/xterm/css/xterm.css';
|
import '@xterm/xterm/css/xterm.css';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import useSize from './use-size';
|
import useParseAnsi from './parse-ansi';
|
||||||
|
|
||||||
interface LogsViewerProps {
|
interface LogsViewerProps {
|
||||||
height: number;
|
height: number;
|
||||||
@@ -15,39 +14,47 @@ interface LogsViewerProps {
|
|||||||
params?: object;
|
params?: object;
|
||||||
}
|
}
|
||||||
const LogsViewer: React.FC<LogsViewerProps> = (props) => {
|
const LogsViewer: React.FC<LogsViewerProps> = (props) => {
|
||||||
const { height, content, url } = props;
|
const { height, url } = props;
|
||||||
const { setChunkRequest } = useSetChunkRequest();
|
const {
|
||||||
|
initialize,
|
||||||
|
updateScrollerPosition,
|
||||||
|
generateInstance,
|
||||||
|
scrollEventElement,
|
||||||
|
instance,
|
||||||
|
initialized
|
||||||
|
} = useOverlayScroller({
|
||||||
|
theme: 'os-theme-light'
|
||||||
|
});
|
||||||
|
const { isClean, parseAnsi } = useParseAnsi();
|
||||||
|
const { setChunkFetch } = useSetChunkFetch();
|
||||||
const chunkRequedtRef = useRef<any>(null);
|
const chunkRequedtRef = useRef<any>(null);
|
||||||
const scroller = useRef<any>({});
|
const scroller = useRef<any>({});
|
||||||
const termRef = useRef<any>({});
|
const cacheDataRef = useRef<any>('');
|
||||||
const termwrapRef = useRef<any>({});
|
const uidRef = useRef<any>(0);
|
||||||
const fitAddonRef = useRef<any>({});
|
const [logs, setLogs] = useState<any[]>([]);
|
||||||
const cacheDataRef = useRef<any>(null);
|
const stopScroll = useRef(false);
|
||||||
const [logs, setLogs] = useState('');
|
|
||||||
const size = useSize(scroller);
|
|
||||||
|
|
||||||
const throttleScroll = _.throttle(() => {
|
const setId = () => {
|
||||||
termRef.current?.scrollToBottom?.();
|
uidRef.current += 1;
|
||||||
}, 100);
|
return uidRef.current;
|
||||||
|
|
||||||
const updateContent = useCallback(
|
|
||||||
_.throttle((data: string) => {
|
|
||||||
cacheDataRef.current = '';
|
|
||||||
termRef.current?.clear?.();
|
|
||||||
cacheDataRef.current = data;
|
|
||||||
termRef.current?.write?.(data);
|
|
||||||
setLogs(data);
|
|
||||||
}, 100),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const fitTerm = () => {
|
|
||||||
fitAddonRef.current?.fit?.();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateContent = useCallback(
|
||||||
|
(data: string) => {
|
||||||
|
if (isClean(data)) {
|
||||||
|
cacheDataRef.current = data;
|
||||||
|
} else {
|
||||||
|
cacheDataRef.current += data;
|
||||||
|
}
|
||||||
|
const res = parseAnsi(cacheDataRef.current, setId);
|
||||||
|
setLogs(res);
|
||||||
|
},
|
||||||
|
[setLogs, setId]
|
||||||
|
);
|
||||||
|
|
||||||
const createChunkConnection = async () => {
|
const createChunkConnection = async () => {
|
||||||
chunkRequedtRef.current?.current?.cancel?.();
|
chunkRequedtRef.current?.current?.abort?.();
|
||||||
chunkRequedtRef.current = setChunkRequest({
|
chunkRequedtRef.current = setChunkFetch({
|
||||||
url,
|
url,
|
||||||
params: {
|
params: {
|
||||||
...props.params,
|
...props.params,
|
||||||
@@ -58,69 +65,84 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const initTerm = () => {
|
const debounceResetStopScroll = _.debounce(() => {
|
||||||
termRef.current?.dispose?.();
|
stopScroll.current = false;
|
||||||
termRef.current = new Terminal({
|
}, 30000);
|
||||||
lineHeight: 1.2,
|
|
||||||
fontSize: 13,
|
const handleOnWheel = useCallback(
|
||||||
fontFamily:
|
(e: any) => {
|
||||||
"monospace,Menlo,Courier,'Courier New',Consolas,Monaco, 'Liberation Mono'",
|
const scrollTop = scrollEventElement?.scrollTop;
|
||||||
disableStdin: true,
|
const scrollHeight = scrollEventElement?.scrollHeight;
|
||||||
convertEol: true,
|
const clientHeight = scrollEventElement?.clientHeight;
|
||||||
theme: {
|
if (scrollTop + clientHeight >= scrollHeight) {
|
||||||
background: '#1e1e1e',
|
stopScroll.current = false;
|
||||||
foreground: 'rgba(255,255,255,0.8)'
|
} else {
|
||||||
|
stopScroll.current = true;
|
||||||
|
}
|
||||||
|
debounceResetStopScroll();
|
||||||
},
|
},
|
||||||
cursorInactiveStyle: 'none',
|
[debounceResetStopScroll, scrollEventElement]
|
||||||
smoothScrollDuration: 0
|
);
|
||||||
});
|
|
||||||
fitAddonRef.current = new FitAddon();
|
|
||||||
termRef.current.loadAddon(fitAddonRef.current);
|
|
||||||
termRef.current.open(termwrapRef.current);
|
|
||||||
|
|
||||||
// add event
|
const debounceUpdateScrollerPosition = _.debounce(() => {
|
||||||
// termRef.current.onLineFeed((e: any) => {
|
generateInstance();
|
||||||
// if (cacheDataRef.current) {
|
updateScrollerPosition(0);
|
||||||
// throttleScroll();
|
}, 200);
|
||||||
// }
|
|
||||||
// });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleResize = _.throttle(() => {
|
const copyText = useMemo(() => {
|
||||||
fitTerm();
|
if (!logs.length) {
|
||||||
}, 100);
|
return '';
|
||||||
|
}
|
||||||
|
return logs?.map((item) => item.content).join('\n');
|
||||||
|
}, [logs]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
createChunkConnection();
|
createChunkConnection();
|
||||||
return () => {
|
return () => {
|
||||||
chunkRequedtRef.current?.current?.cancel?.();
|
chunkRequedtRef.current?.current?.abort?.();
|
||||||
};
|
};
|
||||||
}, [url, props.params]);
|
}, [url, props.params]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (termwrapRef.current) {
|
if (scroller.current) {
|
||||||
initTerm();
|
initialize(scroller.current);
|
||||||
}
|
}
|
||||||
return () => {
|
}, [scroller.current, initialize]);
|
||||||
termRef.current?.dispose?.();
|
|
||||||
};
|
|
||||||
}, [termwrapRef.current]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (size) {
|
if (logs.length && !stopScroll.current && instance) {
|
||||||
handleResize();
|
updateScrollerPosition(0);
|
||||||
|
} else if (logs.length && !stopScroll.current && scroller.current) {
|
||||||
|
if (!initialized) {
|
||||||
|
initialize(scroller.current);
|
||||||
}
|
}
|
||||||
}, [size]);
|
if (!instance) {
|
||||||
|
debounceUpdateScrollerPosition();
|
||||||
useEffect(() => {
|
} else {
|
||||||
throttleScroll();
|
updateScrollerPosition(0);
|
||||||
}, [logs]);
|
}
|
||||||
|
}
|
||||||
|
}, [logs, stopScroll.current, instance, scroller.current]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="logs-viewer-wrap-w2">
|
<div className="logs-viewer-wrap-w2">
|
||||||
<div className="wrap" style={{ height: height }} ref={scroller}>
|
{/* <span className="copy">
|
||||||
|
<CopyButton text={copyText} type="text" size="small"></CopyButton>
|
||||||
|
</span> */}
|
||||||
|
<div
|
||||||
|
className="wrap"
|
||||||
|
style={{ height: height }}
|
||||||
|
ref={scroller}
|
||||||
|
onWheel={handleOnWheel}
|
||||||
|
>
|
||||||
<div className={classNames('content')}>
|
<div className={classNames('content')}>
|
||||||
<div className="text" ref={termwrapRef}></div>
|
{_.map(logs, (item: any, index: number) => {
|
||||||
|
return (
|
||||||
|
<div key={item.uid} className="text">
|
||||||
|
{item.content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g;
|
||||||
|
|
||||||
|
const useParseAnsi = () => {
|
||||||
|
const lastIndex = useRef(0);
|
||||||
|
|
||||||
|
const removeBrackets = useCallback((str: string) => {
|
||||||
|
return str?.replace?.(/^\(…\)/, '');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isClean = useCallback((ansiStr: string) => {
|
||||||
|
let input = ansiStr.replace(/\r\n/g, '\n');
|
||||||
|
let match = controlSeqRegex.exec(input) || [];
|
||||||
|
const command = match?.[3];
|
||||||
|
const n = parseInt(match?.[1], 10) || 1;
|
||||||
|
return command === 'J' && n === 2;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const parseAnsi = useCallback((inputStr: string, setId: () => number) => {
|
||||||
|
let cursorRow = 0; // current row
|
||||||
|
let cursorCol = 0; // current column
|
||||||
|
// screen content array
|
||||||
|
let screen = [['']];
|
||||||
|
// replace carriage return and newline characters in the text
|
||||||
|
let input = inputStr.replace(/\r\n/g, '\n');
|
||||||
|
|
||||||
|
lastIndex.current = 0;
|
||||||
|
|
||||||
|
// handle the \r and \n characters in the text
|
||||||
|
const handleText = (text: string) => {
|
||||||
|
let processed = '';
|
||||||
|
for (let char of text) {
|
||||||
|
if (char === '\r') {
|
||||||
|
cursorCol = 0; // move to the beginning of the line
|
||||||
|
} else if (char === '\n') {
|
||||||
|
cursorRow++; // move to the next line
|
||||||
|
cursorCol = 0; // move to the beginning of the line
|
||||||
|
screen[cursorRow] = screen[cursorRow] || ['']; // create a new line if it does not exist
|
||||||
|
} else {
|
||||||
|
// add the character to the screen content array
|
||||||
|
screen[cursorRow][cursorCol] = char;
|
||||||
|
cursorCol++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return processed;
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = ''; // output text
|
||||||
|
|
||||||
|
let match;
|
||||||
|
|
||||||
|
// ANSI color map
|
||||||
|
const colorMap: Record<string, string> = {
|
||||||
|
'30': 'black',
|
||||||
|
'31': 'red',
|
||||||
|
'32': 'green',
|
||||||
|
'33': 'yellow',
|
||||||
|
'34': 'blue',
|
||||||
|
'35': 'magenta',
|
||||||
|
'36': 'cyan',
|
||||||
|
'37': 'white'
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentStyle = ''; // current text style
|
||||||
|
|
||||||
|
// match ANSI control characters
|
||||||
|
while ((match = controlSeqRegex.exec(input)) !== null) {
|
||||||
|
// handle text before the control character
|
||||||
|
let textBeforeControl = input.slice(lastIndex.current, match.index);
|
||||||
|
output += handleText(textBeforeControl); // add the processed text to the output
|
||||||
|
lastIndex.current = controlSeqRegex.lastIndex; // update the last index
|
||||||
|
|
||||||
|
const n = parseInt(match[1], 10) || 1;
|
||||||
|
const m = parseInt(match[2], 10) || 1;
|
||||||
|
const command = match[3];
|
||||||
|
console.log('command', {
|
||||||
|
command,
|
||||||
|
cursorRow,
|
||||||
|
n
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle ANSI control characters
|
||||||
|
switch (command) {
|
||||||
|
case 'A': // up
|
||||||
|
cursorRow = Math.max(0, cursorRow - n);
|
||||||
|
if (cursorRow === 0) {
|
||||||
|
// screen = [['']];
|
||||||
|
// cursorCol = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'B': // down
|
||||||
|
cursorRow += n;
|
||||||
|
break;
|
||||||
|
case 'C': // right
|
||||||
|
cursorCol += n;
|
||||||
|
break;
|
||||||
|
case 'D': // left
|
||||||
|
cursorCol = Math.max(0, cursorCol - n);
|
||||||
|
break;
|
||||||
|
case 'H': // move the cursor to the specified position (n, m)
|
||||||
|
cursorRow = Math.max(0, n - 1);
|
||||||
|
cursorCol = Math.max(0, m - 1);
|
||||||
|
break;
|
||||||
|
case 'J': // clear the screen
|
||||||
|
if (n === 2) {
|
||||||
|
console.log('clear====');
|
||||||
|
screen = [['']];
|
||||||
|
cursorRow = 0;
|
||||||
|
cursorCol = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'm': // color
|
||||||
|
if (match[1] === '0') {
|
||||||
|
currentStyle = '';
|
||||||
|
} else if (colorMap[match[1]]) {
|
||||||
|
currentStyle = `color: ${colorMap[match[1]]};`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the row and column are within the screen content array
|
||||||
|
while (screen.length <= cursorRow) {
|
||||||
|
screen.push(['']);
|
||||||
|
}
|
||||||
|
while (screen[cursorRow].length <= cursorCol) {
|
||||||
|
screen[cursorRow].push('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle the remaining text
|
||||||
|
output += handleText(input.slice(lastIndex.current));
|
||||||
|
|
||||||
|
let result = [];
|
||||||
|
for (let row = 0; row < screen.length; row++) {
|
||||||
|
let rowContent = screen[row].join('');
|
||||||
|
result.push({
|
||||||
|
content: removeBrackets(rowContent),
|
||||||
|
uid: setId()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result.push({
|
||||||
|
content: output,
|
||||||
|
uid: setId()
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { parseAnsi, isClean };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useParseAnsi;
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { useRef, useState } from 'react';
|
||||||
|
|
||||||
|
const usePaseByLine = () => {
|
||||||
|
const [result, setResult] = useState<any[]>([]);
|
||||||
|
const uidRef = useRef(0);
|
||||||
|
|
||||||
|
const cursorRow = useRef(0); // current row
|
||||||
|
const cursorCol = useRef(0); // current column
|
||||||
|
// screen.current content array
|
||||||
|
const screen = useRef([['']]);
|
||||||
|
// replace carriage return and newline characters in the text
|
||||||
|
// let input = inputStr.replace(/\r\n/g, '\n')
|
||||||
|
|
||||||
|
const removeBrackets = (str: string) => {
|
||||||
|
return str?.replace?.(/^\(\.*?\)/, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const setId = () => {
|
||||||
|
uidRef.current += 1;
|
||||||
|
return uidRef.current;
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle the \r and \n characters in the text
|
||||||
|
const handleText = (text: string) => {
|
||||||
|
for (let char of text) {
|
||||||
|
if (char === '\r') {
|
||||||
|
cursorCol.current = 0; // move to the beginning of the line
|
||||||
|
} else if (char === '\n') {
|
||||||
|
cursorRow.current += 1; // move to the next line
|
||||||
|
cursorCol.current = 0; // move to the beginning of the line
|
||||||
|
screen.current[cursorRow.current] = screen.current[
|
||||||
|
cursorRow.current
|
||||||
|
] || ['']; // create a new line if it does not exist
|
||||||
|
} else {
|
||||||
|
// add the character to the screen.current content array
|
||||||
|
screen.current[cursorRow.current][cursorCol.current] = char;
|
||||||
|
cursorCol.current += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ANSI
|
||||||
|
const controlSeqRegex = /\x1b\[(\d*);?(\d*)?([A-DJKHfm])/g;
|
||||||
|
|
||||||
|
// ANSI color map
|
||||||
|
const colorMap: Record<string, string> = {
|
||||||
|
'30': 'black',
|
||||||
|
'31': 'red',
|
||||||
|
'32': 'green',
|
||||||
|
'33': 'yellow',
|
||||||
|
'34': 'blue',
|
||||||
|
'35': 'magenta',
|
||||||
|
'36': 'cyan',
|
||||||
|
'37': 'white'
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateResult = () => {
|
||||||
|
let res = [];
|
||||||
|
for (let row = 0; row < screen.current.length; row++) {
|
||||||
|
console.log('screen.current[row]', screen.current[row]);
|
||||||
|
let rowContent = screen.current[row].join('');
|
||||||
|
res.push({
|
||||||
|
content: removeBrackets(rowContent),
|
||||||
|
uid: setId()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log('res', res);
|
||||||
|
setResult(res);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLine = (line: string) => {
|
||||||
|
let match;
|
||||||
|
let lastIndex = 0;
|
||||||
|
let currentStyle = ''; // current text style
|
||||||
|
// match ANSI control characters
|
||||||
|
while ((match = controlSeqRegex.exec(line)) !== null) {
|
||||||
|
// handle text before the control character
|
||||||
|
let textBeforeControl = line.slice(lastIndex, match.index);
|
||||||
|
|
||||||
|
handleText(textBeforeControl); // add the processed text to the output
|
||||||
|
lastIndex = controlSeqRegex.lastIndex; // update the last index
|
||||||
|
|
||||||
|
const n = parseInt(match[1], 10) || 1;
|
||||||
|
const m = parseInt(match[2], 10) || 1;
|
||||||
|
const command = match[3];
|
||||||
|
|
||||||
|
// handle ANSI control characters
|
||||||
|
switch (command) {
|
||||||
|
case 'A': // up
|
||||||
|
cursorRow.current = Math.max(0, cursorRow.current - n);
|
||||||
|
break;
|
||||||
|
case 'B': // down
|
||||||
|
cursorRow.current += n;
|
||||||
|
break;
|
||||||
|
case 'C': // right
|
||||||
|
cursorCol.current += n;
|
||||||
|
break;
|
||||||
|
case 'D': // left
|
||||||
|
cursorCol.current = Math.max(0, cursorCol.current - n);
|
||||||
|
break;
|
||||||
|
case 'H': // move the cursor to the specified position (n, m)
|
||||||
|
cursorRow.current = Math.max(0, n - 1);
|
||||||
|
cursorCol.current = Math.max(0, m - 1);
|
||||||
|
break;
|
||||||
|
case 'J': // clear the screen.current
|
||||||
|
if (n === 2) {
|
||||||
|
screen.current = [['']];
|
||||||
|
cursorRow.current = 0;
|
||||||
|
cursorCol.current = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'm': // color
|
||||||
|
if (match[1] === '0') {
|
||||||
|
currentStyle = '';
|
||||||
|
} else if (colorMap[match[1]]) {
|
||||||
|
currentStyle = `color: ${colorMap[match[1]]};`;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the row and column are within the screen.current content array
|
||||||
|
while (screen.current.length <= cursorRow.current) {
|
||||||
|
screen.current.push(['']);
|
||||||
|
}
|
||||||
|
while (screen.current[cursorRow.current].length <= cursorCol.current) {
|
||||||
|
screen.current[cursorRow.current].push('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleText(line.slice(lastIndex));
|
||||||
|
updateResult();
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleLine,
|
||||||
|
result
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default usePaseByLine;
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 1.2em;
|
||||||
|
|
||||||
th {
|
th {
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { EyeOutlined } from '@ant-design/icons';
|
import { EyeOutlined } from '@ant-design/icons';
|
||||||
import { Image, Typography } from 'antd';
|
import { Checkbox, Image, Typography } from 'antd';
|
||||||
import { unescape } from 'lodash';
|
import { unescape } from 'lodash';
|
||||||
import { TokensList, marked } from 'marked';
|
import { TokensList, marked } from 'marked';
|
||||||
import React, { Fragment, useCallback, useEffect } from 'react';
|
import React, { Fragment, useCallback, useEffect } from 'react';
|
||||||
@@ -38,12 +38,16 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
'list_item',
|
'list_item',
|
||||||
'br',
|
'br',
|
||||||
'html',
|
'html',
|
||||||
'escape'
|
'escape',
|
||||||
|
'del',
|
||||||
|
'blockquote',
|
||||||
|
'checkbox'
|
||||||
|
// 'bibtex'
|
||||||
];
|
];
|
||||||
|
|
||||||
renderer.link = ({ href, title, text }) => {
|
// renderer.link = ({ href, title, text }) => {
|
||||||
return `<a href="${href}" title="${title || ''}" target="_blank" rel="noopener noreferrer">${text}</a>`;
|
// return `<a href="${href}" title="${title || ''}" target="_blank" rel="noopener noreferrer">${text}</a>`;
|
||||||
};
|
// };
|
||||||
|
|
||||||
const isValidURL = useCallback((url: string) => {
|
const isValidURL = useCallback((url: string) => {
|
||||||
const pattern = /^(https?:\/\/|\/\/)([^\s/$.?#].[^\s]*)$/;
|
const pattern = /^(https?:\/\/|\/\/)([^\s/$.?#].[^\s]*)$/;
|
||||||
@@ -51,8 +55,22 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
return pattern.test(url);
|
return pattern.test(url);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const generateImgSrc = useCallback(
|
||||||
|
(src: string | null) => {
|
||||||
|
if (!src) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (generateImgLink) {
|
||||||
|
return isValidURL(src) ? src : generateImgLink(src);
|
||||||
|
}
|
||||||
|
return src;
|
||||||
|
},
|
||||||
|
[generateImgLink]
|
||||||
|
);
|
||||||
|
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
(token: any, render: any) => {
|
(token: any, render: any) => {
|
||||||
|
console.log('token====', token);
|
||||||
if (!reDefineTypes.includes(token.type)) {
|
if (!reDefineTypes.includes(token.type)) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@@ -88,6 +106,18 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
htmlstr = <li>{text}</li>;
|
htmlstr = <li>{text}</li>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (token.type === 'del') {
|
||||||
|
htmlstr = <del>{text}</del>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.type === 'blockquote') {
|
||||||
|
htmlstr = <blockquote>{text}</blockquote>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.type === 'checkbox') {
|
||||||
|
htmlstr = <Checkbox value={token.checked} />;
|
||||||
|
}
|
||||||
|
|
||||||
if (token.type === 'br') {
|
if (token.type === 'br') {
|
||||||
htmlstr = <br />;
|
htmlstr = <br />;
|
||||||
}
|
}
|
||||||
@@ -97,10 +127,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token.type === 'image') {
|
if (token.type === 'image') {
|
||||||
let href = token.href;
|
let href = generateImgSrc(token.href);
|
||||||
if (!isValidURL(token.href)) {
|
|
||||||
href = generateImgLink ? generateImgLink(token.href) : token.href;
|
|
||||||
}
|
|
||||||
htmlstr = (
|
htmlstr = (
|
||||||
<Image
|
<Image
|
||||||
src={href}
|
src={href}
|
||||||
@@ -126,11 +153,13 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
if (token.type === 'paragraph') {
|
if (token.type === 'paragraph') {
|
||||||
htmlstr = <Paragraph> {text}</Paragraph>;
|
htmlstr = <Paragraph> {text}</Paragraph>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.type === 'code') {
|
if (token.type === 'code') {
|
||||||
htmlstr = (
|
htmlstr = (
|
||||||
<HighlightCode theme={theme} code={token.text} lang={token.lang} />
|
<HighlightCode theme={theme} code={token.text} lang={token.lang} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token.type === 'link') {
|
if (token.type === 'link') {
|
||||||
htmlstr = (
|
htmlstr = (
|
||||||
<Link
|
<Link
|
||||||
@@ -149,7 +178,7 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
|
|
||||||
return htmlstr;
|
return htmlstr;
|
||||||
},
|
},
|
||||||
[generateImgLink]
|
[generateImgSrc]
|
||||||
);
|
);
|
||||||
const renderTokens = (tokens: TokensList): any => {
|
const renderTokens = (tokens: TokensList): any => {
|
||||||
return tokens?.map((token: any, index: number) => {
|
return tokens?.map((token: any, index: number) => {
|
||||||
@@ -164,11 +193,9 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
const imgs = document.querySelectorAll('.markdown-viewer img');
|
const imgs = document.querySelectorAll('.markdown-viewer img');
|
||||||
imgs.forEach((img) => {
|
imgs.forEach((img) => {
|
||||||
const src = img.getAttribute('src');
|
const src = img.getAttribute('src');
|
||||||
if (src && !isValidURL(src)) {
|
img.setAttribute('src', generateImgSrc(src));
|
||||||
img.setAttribute('src', generateImgLink ? generateImgLink(src) : src);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}, [content, generateImgLink]);
|
}, [content, generateImgSrc]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ interface WrapperProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
description?: string;
|
description?: React.ReactNode;
|
||||||
variant?: string;
|
variant?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,7 @@ const TableRow: React.FC<
|
|||||||
|
|
||||||
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({
|
||||||
dataList: childrenData,
|
dataList: childrenData,
|
||||||
|
limit: 100,
|
||||||
setDataList: setChildrenData
|
setDataList: setChildrenData
|
||||||
// callback: (list) => renderChildren?.(list)
|
// callback: (list) => renderChildren?.(list)
|
||||||
});
|
});
|
||||||
@@ -118,7 +119,7 @@ const TableRow: React.FC<
|
|||||||
|
|
||||||
const updateChildrenHandler = (list: any) => {
|
const updateChildrenHandler = (list: any) => {
|
||||||
_.each(list, (data: any) => {
|
_.each(list, (data: any) => {
|
||||||
updateChunkedList(data, childrenDataRef.current);
|
updateChunkedList(data);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
|
|||||||
if (selectedRowKeys?.length === 0) {
|
if (selectedRowKeys?.length === 0) {
|
||||||
setSelectAll(false);
|
setSelectAll(false);
|
||||||
setIndeterminate(false);
|
setIndeterminate(false);
|
||||||
} else if (selectedRowKeys?.length === props.dataSource.length) {
|
} else if (
|
||||||
|
selectedRowKeys?.length === props.dataSource.length &&
|
||||||
|
selectedRowKeys.length > 0
|
||||||
|
) {
|
||||||
setSelectAll(true);
|
setSelectAll(true);
|
||||||
setIndeterminate(false);
|
setIndeterminate(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -54,7 +57,7 @@ const SealTable: React.FC<SealTableProps & { pagination: PaginationProps }> = (
|
|||||||
setIndeterminate(true);
|
setIndeterminate(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [rowSelection]);
|
}, [rowSelection, props.dataSource]);
|
||||||
|
|
||||||
const handleSelectAllChange = (e: any) => {
|
const handleSelectAllChange = (e: any) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
height: 68px;
|
min-height: 68px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.cell-content {
|
.cell-content {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-left {
|
&-left {
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.row-prefix-wrapper {
|
.row-prefix-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
padding-left: var(--ant-table-cell-padding-inline);
|
padding-left: var(--ant-table-cell-padding-inline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
.version-box {
|
.version-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 40px;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
@@ -23,20 +22,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ver {
|
.ver {
|
||||||
|
margin-bottom: 20px;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
font-size: var(--font-size-middle);
|
font-size: var(--font-size-middle);
|
||||||
|
color: var(--ant-color-text-secondary);
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
width: 60px;
|
|
||||||
font-size: var(--font-size-middle);
|
font-size: var(--font-size-middle);
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.val {
|
.dev.val {
|
||||||
color: var(--ant-color-text-secondary);
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tl {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-block: 20px 4px;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
border-top: 1px solid var(--ant-color-split);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,80 @@
|
|||||||
import Logo from '@/assets/images/gpustack-logo.png';
|
import Logo from '@/assets/images/gpustack-logo.png';
|
||||||
import { GPUStackVersionAtom } from '@/atoms/user';
|
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
|
||||||
import { getAtomStorage } from '@/atoms/utils';
|
import { getAtomStorage } from '@/atoms/utils';
|
||||||
|
import externalLinks from '@/constants/external-links';
|
||||||
|
import { Button } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
|
|
||||||
const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
|
||||||
// get the data attr from html
|
const latestVersion = getAtomStorage(UpdateCheckAtom).latest_version;
|
||||||
const version = document.documentElement.getAttribute('data-version');
|
const currentVersion = getAtomStorage(GPUStackVersionAtom)?.version;
|
||||||
|
|
||||||
|
const isProd =
|
||||||
|
currentVersion !== '0.0.0' && currentVersion?.indexOf('rc') === -1;
|
||||||
|
|
||||||
|
const uiVersion = document.documentElement.getAttribute('data-version');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="version-box">
|
<div className="version-box">
|
||||||
<div className="img">
|
<div className="img">
|
||||||
<img src={Logo} alt="logo" />
|
<img src={Logo} alt="logo" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<div className="ver">
|
<div className="ver">
|
||||||
|
{isProd && (
|
||||||
<span className="label">
|
<span className="label">
|
||||||
{' '}
|
{intl.formatMessage({ id: 'common.footer.version' })}
|
||||||
{intl.formatMessage({ id: 'common.footer.version.server' })}
|
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
|
{isProd ? (
|
||||||
<span className="val">
|
<span className="val">
|
||||||
{getAtomStorage(GPUStackVersionAtom)?.version ||
|
{getAtomStorage(GPUStackVersionAtom)?.version ||
|
||||||
getAtomStorage(GPUStackVersionAtom)?.git_commit}
|
getAtomStorage(GPUStackVersionAtom)?.git_commit}
|
||||||
</span>
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="val dev">
|
||||||
|
<span className="item">
|
||||||
|
<span className="tl">
|
||||||
|
{' '}
|
||||||
|
{intl.formatMessage({ id: 'common.footer.version.server' })}
|
||||||
|
</span>
|
||||||
|
{currentVersion === '0.0.0'
|
||||||
|
? getAtomStorage(GPUStackVersionAtom)?.git_commit
|
||||||
|
: getAtomStorage(GPUStackVersionAtom)?.version}
|
||||||
|
</span>
|
||||||
|
<span className="item">
|
||||||
|
<span className="tl">UI</span>
|
||||||
|
{uiVersion}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="ver">
|
{getAtomStorage(userAtom)?.is_admin && isProd && (
|
||||||
<span className="label">UI </span>
|
<div className="upgrade">
|
||||||
<span className="val"> {version}</span>
|
<span className="m-l-5">
|
||||||
</div>
|
{latestVersion &&
|
||||||
|
latestVersion !== currentVersion &&
|
||||||
|
latestVersion !== '0.0.0'
|
||||||
|
? intl.formatMessage(
|
||||||
|
{ id: 'users.version.update' },
|
||||||
|
{ version: latestVersion }
|
||||||
|
)
|
||||||
|
: intl.formatMessage(
|
||||||
|
{ id: 'users.version.islatest' },
|
||||||
|
{ version: currentVersion }
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
href={externalLinks.release}
|
||||||
|
target="_blank"
|
||||||
|
style={{ paddingInline: 0 }}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'common.text.changelog' })}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ export default {
|
|||||||
documentation: 'https://docs.gpustack.ai/',
|
documentation: 'https://docs.gpustack.ai/',
|
||||||
github: 'https://github.com/gpustack/gpustack',
|
github: 'https://github.com/gpustack/gpustack',
|
||||||
discord: 'https://discord.gg/VXYJzuaqwD',
|
discord: 'https://discord.gg/VXYJzuaqwD',
|
||||||
site: 'https://gpustack.ai/'
|
site: 'https://gpustack.ai/',
|
||||||
|
release: 'https://github.com/gpustack/gpustack/releases'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ html {
|
|||||||
--color-scrollbar-thumb: rgba(193, 193, 193, 80%);
|
--color-scrollbar-thumb: rgba(193, 193, 193, 80%);
|
||||||
--scrollbar-size: 6px;
|
--scrollbar-size: 6px;
|
||||||
--scrollbar-handle-bg: rgba(0, 0, 0, 44%);
|
--scrollbar-handle-bg: rgba(0, 0, 0, 44%);
|
||||||
|
--scrollbar-handle-light-bg: rgba(255, 255, 255, 44%);
|
||||||
--scrollbar-handle-hover-bg: rgba(0, 0, 0, 55%);
|
--scrollbar-handle-hover-bg: rgba(0, 0, 0, 55%);
|
||||||
--color-editor-dark: #282c34;
|
--color-editor-dark: #282c34;
|
||||||
--color-editor-light: #fafafa;
|
--color-editor-light: #fafafa;
|
||||||
@@ -64,6 +65,8 @@ html {
|
|||||||
--seal-transition-func: cubic-bezier(0, 0, 1, 1);
|
--seal-transition-func: cubic-bezier(0, 0, 1, 1);
|
||||||
--color-progress-green: rgba(84, 204, 152, 100%);
|
--color-progress-green: rgba(84, 204, 152, 100%);
|
||||||
--color-border-1: rgba(217, 217, 217, 100%);
|
--color-border-1: rgba(217, 217, 217, 100%);
|
||||||
|
--ant-rate-star-color: #fadb14;
|
||||||
|
--color-fill-mask: rgba(255, 255, 255, 60%);
|
||||||
// ======== input ============
|
// ======== input ============
|
||||||
--ant-input-active-shadow: 0 0 0 2px rgba(5, 255, 105, 6%);
|
--ant-input-active-shadow: 0 0 0 2px rgba(5, 255, 105, 6%);
|
||||||
--ant-input-active-border-color: #007bff;
|
--ant-input-active-border-color: #007bff;
|
||||||
@@ -169,6 +172,10 @@ body {
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-modal-confirm-title {
|
.ant-modal-confirm-title {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -381,6 +388,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-layout-container {
|
.ant-pro-layout-container {
|
||||||
|
overflow-x: auto;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
// background-color: var(--color-fill-2);
|
// background-color: var(--color-fill-2);
|
||||||
}
|
}
|
||||||
@@ -432,6 +440,26 @@ body {
|
|||||||
font-size: var(--font-size-middle);
|
font-size: var(--font-size-middle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.new-icon {
|
||||||
|
position: relative;
|
||||||
|
bottom: 3px;
|
||||||
|
display: flex;
|
||||||
|
color: #fff;
|
||||||
|
width: 36px;
|
||||||
|
height: 15px;
|
||||||
|
margin-left: 5px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--ant-orange-5);
|
||||||
|
border-radius: 8px 8px 8px 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
|
||||||
|
.text {
|
||||||
|
transform: scale(0.8);
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,6 +472,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.user-menu-collapsed {
|
&.user-menu-collapsed {
|
||||||
|
.ant-menu-submenu-title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--color-text-1) !important;
|
||||||
|
padding-inline: unset !important;
|
||||||
|
width: 100%;
|
||||||
|
margin-inline: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.user-avatar.ant-menu-submenu {
|
.user-avatar.ant-menu-submenu {
|
||||||
.ant-menu-submenu-title {
|
.ant-menu-submenu-title {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
@@ -477,6 +515,28 @@ body {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-modal-centered.ant-modal-wrap {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: var(--scrollbar-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--scrollbar-handle-bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.custome-scrollbar {
|
.custome-scrollbar {
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: var(--scrollbar-size);
|
width: var(--scrollbar-size);
|
||||||
@@ -649,6 +709,10 @@ body {
|
|||||||
|
|
||||||
.ant-pro-page-container {
|
.ant-pro-page-container {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
|
.ant-page-header-heading {
|
||||||
|
min-width: max-content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes skeleton-loading {
|
@keyframes skeleton-loading {
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
import { split } from 'lodash';
|
||||||
|
import qs from 'query-string';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
interface RequestConfig {
|
||||||
|
url: string;
|
||||||
|
handler: (data: any) => any;
|
||||||
|
beforeReconnect?: () => void;
|
||||||
|
params?: object;
|
||||||
|
byLine?: boolean;
|
||||||
|
contentType?: 'json' | 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSetChunkFetch = () => {
|
||||||
|
const axiosToken = useRef<any>(null);
|
||||||
|
const requestConfig = useRef<any>({});
|
||||||
|
const chunkDataRef = useRef<any>([]);
|
||||||
|
const bufferCacheRef = useRef<any>('');
|
||||||
|
|
||||||
|
const readTextEventStreamData = async (
|
||||||
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
||||||
|
decoder: TextDecoder,
|
||||||
|
callback: (data: any) => void
|
||||||
|
) => {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunk = decoder.decode(value, { stream: true });
|
||||||
|
|
||||||
|
callback(chunk);
|
||||||
|
// console.log('chunkDataRef.current===2', chunkDataRef.current);
|
||||||
|
|
||||||
|
await readTextEventStreamData(reader, decoder, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
const readTextEventStreamDataByLine = async (
|
||||||
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
||||||
|
decoder: TextDecoder,
|
||||||
|
callback: (data: any) => void
|
||||||
|
) => {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferCacheRef.current += decoder.decode(value, { stream: true });
|
||||||
|
const lines = split(bufferCacheRef.current, /\r?\n/);
|
||||||
|
bufferCacheRef.current = lines.pop();
|
||||||
|
for (const line of lines) {
|
||||||
|
callback(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
await readTextEventStreamDataByLine(reader, decoder, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
const readTextEventStreamDataByLineWithBuffer = async (
|
||||||
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
||||||
|
decoder: TextDecoder,
|
||||||
|
callback: (data: any) => void
|
||||||
|
) => {
|
||||||
|
await readTextEventStreamDataByLine(reader, decoder, callback);
|
||||||
|
|
||||||
|
if (bufferCacheRef.current.length > 0) {
|
||||||
|
callback(bufferCacheRef.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchChunkRequest = async ({
|
||||||
|
url,
|
||||||
|
handler,
|
||||||
|
byLine = false,
|
||||||
|
params = {}
|
||||||
|
}: RequestConfig) => {
|
||||||
|
axiosToken.current?.abort?.();
|
||||||
|
axiosToken.current = new AbortController();
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`v1${url}?${qs.stringify({
|
||||||
|
...params,
|
||||||
|
watch: true
|
||||||
|
})}`,
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
body: null,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream'
|
||||||
|
},
|
||||||
|
signal: axiosToken.current.signal
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader =
|
||||||
|
response?.body?.getReader() as ReadableStreamDefaultReader<Uint8Array>;
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
|
||||||
|
await readTextEventStreamData(reader, decoder, handler);
|
||||||
|
|
||||||
|
console.log('chunkDataRef.current===1', chunkDataRef.current);
|
||||||
|
} catch (error) {
|
||||||
|
// handle error
|
||||||
|
console.log('error============', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return axiosToken.current;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setChunkFetch = (config: RequestConfig) => {
|
||||||
|
requestConfig.current = { ...config };
|
||||||
|
fetchChunkRequest(requestConfig.current);
|
||||||
|
return axiosToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleUnload = () => {
|
||||||
|
axiosToken.current?.abort?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', handleUnload);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
axiosToken.current?.abort?.();
|
||||||
|
window.removeEventListener('beforeunload', handleUnload);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
setChunkFetch
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSetChunkFetch;
|
||||||
@@ -172,7 +172,7 @@ const useSetChunkRequest = () => {
|
|||||||
|
|
||||||
let result = response;
|
let result = response;
|
||||||
let cres = '';
|
let cres = '';
|
||||||
console.log('chunkrequest============e==', e);
|
console.log('chunkrequest============e==', result);
|
||||||
if (contentType === 'json') {
|
if (contentType === 'json') {
|
||||||
const currentRes = sliceData(response, e.loaded, loadedSize);
|
const currentRes = sliceData(response, e.loaded, loadedSize);
|
||||||
result = parseData(currentRes);
|
result = parseData(currentRes);
|
||||||
@@ -194,7 +194,6 @@ const useSetChunkRequest = () => {
|
|||||||
retryCount.current -= 1;
|
retryCount.current -= 1;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error=============', error);
|
|
||||||
if (!axios.isCancel(error)) {
|
if (!axios.isCancel(error)) {
|
||||||
setRequestReadyState(4);
|
setRequestReadyState(4);
|
||||||
if (retryCount.current > 0) {
|
if (retryCount.current > 0) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import {
|
import {
|
||||||
useOverlayScrollbars,
|
UseOverlayScrollbarsParams,
|
||||||
UseOverlayScrollbarsParams
|
useOverlayScrollbars
|
||||||
} from 'overlayscrollbars-react';
|
} from 'overlayscrollbars-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ export const overlaySollerOptions: UseOverlayScrollbarsParams = {
|
|||||||
x: 'hidden'
|
x: 'hidden'
|
||||||
},
|
},
|
||||||
scrollbars: {
|
scrollbars: {
|
||||||
|
theme: 'os-theme-light',
|
||||||
autoHide: 'scroll',
|
autoHide: 'scroll',
|
||||||
autoHideDelay: 600,
|
autoHideDelay: 600,
|
||||||
clickScroll: 'instant'
|
clickScroll: 'instant'
|
||||||
@@ -22,51 +23,97 @@ export const overlaySollerOptions: UseOverlayScrollbarsParams = {
|
|||||||
defer: true
|
defer: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useOverlayScroller() {
|
export default function useOverlayScroller(options?: any) {
|
||||||
const scrollEventElement = React.useRef<any>(null);
|
const scrollEventElement = React.useRef<any>(null);
|
||||||
const instanceRef = React.useRef<any>(null);
|
const instanceRef = React.useRef<any>(null);
|
||||||
|
const initialized = React.useRef(false);
|
||||||
const [initialize, instance] = useOverlayScrollbars({
|
const [initialize, instance] = useOverlayScrollbars({
|
||||||
...overlaySollerOptions
|
options: {
|
||||||
|
update: {
|
||||||
|
debounce: 0
|
||||||
|
},
|
||||||
|
overflow: {
|
||||||
|
x: 'hidden'
|
||||||
|
},
|
||||||
|
scrollbars: {
|
||||||
|
theme: options?.theme || 'os-theme-dark',
|
||||||
|
autoHide: 'scroll',
|
||||||
|
autoHideDelay: 600,
|
||||||
|
clickScroll: 'instant'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defer: true
|
||||||
});
|
});
|
||||||
|
|
||||||
instanceRef.current = instance?.();
|
instanceRef.current = instance?.();
|
||||||
scrollEventElement.current =
|
scrollEventElement.current =
|
||||||
instanceRef.current?.elements()?.scrollEventElement;
|
instanceRef.current?.elements()?.scrollEventElement;
|
||||||
|
|
||||||
const throttledScroll = React.useMemo(
|
const throttledScroll = React.useCallback(
|
||||||
() =>
|
|
||||||
throttle(() => {
|
throttle(() => {
|
||||||
scrollEventElement.current?.scrollTo?.({
|
scrollEventElement.current?.scrollTo?.({
|
||||||
top: scrollEventElement.current.scrollHeight,
|
top: scrollEventElement.current.scrollHeight,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
});
|
});
|
||||||
instanceRef.current?.update?.();
|
instanceRef.current?.update?.();
|
||||||
}, 300),
|
}, 100),
|
||||||
[scrollEventElement, instanceRef]
|
[(scrollEventElement.current, instanceRef.current)]
|
||||||
);
|
);
|
||||||
|
|
||||||
const throttledUpdateScrollerPosition = React.useCallback(() => {
|
const scrollauto = React.useCallback(() => {
|
||||||
|
scrollEventElement.current?.scrollTo?.({
|
||||||
|
top: scrollEventElement.current.scrollHeight,
|
||||||
|
behavior: 'auto'
|
||||||
|
});
|
||||||
|
instanceRef.current?.update?.();
|
||||||
|
}, [scrollEventElement.current, instanceRef.current]);
|
||||||
|
|
||||||
|
const throttledUpdateScrollerPosition = React.useCallback(
|
||||||
|
(delay?: number) => {
|
||||||
|
if (delay === 0) {
|
||||||
|
scrollauto();
|
||||||
|
} else {
|
||||||
throttledScroll();
|
throttledScroll();
|
||||||
}, [throttledScroll]);
|
}
|
||||||
|
},
|
||||||
|
[throttledScroll, scrollauto]
|
||||||
|
);
|
||||||
|
|
||||||
// const createInstance = React.useCallback((el: any) => {
|
const generateInstance = () => {
|
||||||
// if (el) {
|
instanceRef.current = instance?.();
|
||||||
// instanceRef.current?.destroy?.();
|
scrollEventElement.current =
|
||||||
// initialize(el);
|
instanceRef.current?.elements()?.scrollEventElement;
|
||||||
// instanceRef.current = instance?.();
|
|
||||||
// scrollEventElement.current =
|
|
||||||
// instanceRef.current?.elements()?.scrollEventElement;
|
|
||||||
// }
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
instanceRef.current?.destroy?.();
|
|
||||||
};
|
};
|
||||||
}, []);
|
|
||||||
|
const createInstance = React.useCallback(
|
||||||
|
(el: any) => {
|
||||||
|
if (instanceRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (el) {
|
||||||
|
initialize(el);
|
||||||
|
initialized.current = true;
|
||||||
|
console.log(
|
||||||
|
'createInstance===2',
|
||||||
|
initialized.current,
|
||||||
|
instanceRef.current,
|
||||||
|
instance?.(),
|
||||||
|
instance
|
||||||
|
);
|
||||||
|
instanceRef.current = instance?.();
|
||||||
|
scrollEventElement.current =
|
||||||
|
instanceRef.current?.elements()?.scrollEventElement;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[initialize, instance]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initialize,
|
initialize: createInstance,
|
||||||
instance: instanceRef.current,
|
instance: instanceRef.current,
|
||||||
|
scrollEventElement: scrollEventElement.current,
|
||||||
|
initialized: initialized.current,
|
||||||
|
generateInstance,
|
||||||
updateScrollerPosition: throttledUpdateScrollerPosition
|
updateScrollerPosition: throttledUpdateScrollerPosition
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,15 @@ export default function useTableRowSelection() {
|
|||||||
setSelectedRowKeys([]);
|
setSelectedRowKeys([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeSelectedKey = (rowKey: React.Key) => {
|
||||||
|
setSelectedRowKeys((keys) => keys.filter((key) => key !== rowKey));
|
||||||
|
};
|
||||||
|
|
||||||
const rowSelection = {
|
const rowSelection = {
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
clearSelections,
|
clearSelections,
|
||||||
onChange: onSelectChange
|
onChange: onSelectChange,
|
||||||
|
removeSelectedKey
|
||||||
};
|
};
|
||||||
|
|
||||||
return rowSelection;
|
return rowSelection;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface ChunkedCollection {
|
|||||||
// Only used to update lists without nested state
|
// Only used to update lists without nested state
|
||||||
export function useUpdateChunkedList(options: {
|
export function useUpdateChunkedList(options: {
|
||||||
dataList?: any[];
|
dataList?: any[];
|
||||||
|
limit?: number;
|
||||||
setDataList: (args: any) => void;
|
setDataList: (args: any) => void;
|
||||||
callback?: (args: any) => void;
|
callback?: (args: any) => void;
|
||||||
filterFun?: (args: any) => boolean;
|
filterFun?: (args: any) => boolean;
|
||||||
@@ -19,6 +20,7 @@ export function useUpdateChunkedList(options: {
|
|||||||
const cacheDataListRef = useRef<any[]>(options.dataList || []);
|
const cacheDataListRef = useRef<any[]>(options.dataList || []);
|
||||||
const timerRef = useRef<any>(null);
|
const timerRef = useRef<any>(null);
|
||||||
const countRef = useRef<number>(0);
|
const countRef = useRef<number>(0);
|
||||||
|
const limit = options.limit || 10;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cacheDataListRef.current = [...(options.dataList || [])];
|
cacheDataListRef.current = [...(options.dataList || [])];
|
||||||
@@ -68,7 +70,7 @@ export function useUpdateChunkedList(options: {
|
|||||||
cacheDataListRef.current = [
|
cacheDataListRef.current = [
|
||||||
...newDataList,
|
...newDataList,
|
||||||
...cacheDataListRef.current
|
...cacheDataListRef.current
|
||||||
].slice(0, 10);
|
].slice(0, limit);
|
||||||
}
|
}
|
||||||
// DELETE
|
// DELETE
|
||||||
if (data?.type === WatchEventType.DELETE) {
|
if (data?.type === WatchEventType.DELETE) {
|
||||||
@@ -91,7 +93,7 @@ export function useUpdateChunkedList(options: {
|
|||||||
} else if (updateIndex === -1) {
|
} else if (updateIndex === -1) {
|
||||||
cacheDataListRef.current = [
|
cacheDataListRef.current = [
|
||||||
updateItem,
|
updateItem,
|
||||||
...cacheDataListRef.current.slice(0, 9)
|
...cacheDataListRef.current.slice(0, limit - 1)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+42
-4
@@ -1,10 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { userAtom } from '@/atoms/user';
|
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
|
||||||
import ShortCuts, {
|
import ShortCuts, {
|
||||||
modalConfig as ShortCutsConfig
|
modalConfig as ShortCutsConfig
|
||||||
} from '@/components/short-cuts';
|
} from '@/components/short-cuts';
|
||||||
import VersionInfo, { modalConfig } from '@/components/version-info';
|
import VersionInfo, { modalConfig } from '@/components/version-info';
|
||||||
|
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||||
import { logout } from '@/pages/login/apis';
|
import { logout } from '@/pages/login/apis';
|
||||||
import { useAccessMarkedRoutes } from '@@/plugin-access';
|
import { useAccessMarkedRoutes } from '@@/plugin-access';
|
||||||
import { useModel } from '@@/plugin-model';
|
import { useModel } from '@@/plugin-model';
|
||||||
@@ -23,12 +24,14 @@ import {
|
|||||||
} from '@umijs/max';
|
} from '@umijs/max';
|
||||||
import { Button, Modal } from 'antd';
|
import { Button, Modal } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import Exception from './Exception';
|
import Exception from './Exception';
|
||||||
import './Layout.css';
|
import './Layout.css';
|
||||||
import { LogoIcon, SLogoIcon } from './Logo';
|
import { LogoIcon, SLogoIcon } from './Logo';
|
||||||
import { getRightRenderContent } from './rightRender';
|
import { getRightRenderContent } from './rightRender';
|
||||||
import { patchRoutes } from './runtime';
|
import { patchRoutes } from './runtime';
|
||||||
|
|
||||||
const loginPath = '/login';
|
const loginPath = '/login';
|
||||||
|
|
||||||
type InitialStateType = {
|
type InitialStateType = {
|
||||||
@@ -87,7 +90,11 @@ const mapRoutes = (routes: IRoute[], role: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default (props: any) => {
|
export default (props: any) => {
|
||||||
|
const { initialize: initialize } = useOverlayScroller();
|
||||||
|
const { initialize: initializeMenu } = useOverlayScroller();
|
||||||
const [userInfo] = useAtom(userAtom);
|
const [userInfo] = useAtom(userAtom);
|
||||||
|
const [version] = useAtom(GPUStackVersionAtom);
|
||||||
|
const [updateCheck] = useAtom(UpdateCheckAtom);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
@@ -115,6 +122,7 @@ export default (props: any) => {
|
|||||||
const showVersion = () => {
|
const showVersion = () => {
|
||||||
Modal.info({
|
Modal.info({
|
||||||
...modalConfig,
|
...modalConfig,
|
||||||
|
width: 460,
|
||||||
content: <VersionInfo intl={intl} />
|
content: <VersionInfo intl={intl} />
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -169,6 +177,32 @@ export default (props: any) => {
|
|||||||
[location.pathname]
|
[location.pathname]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const showUpgrade = useMemo(() => {
|
||||||
|
return (
|
||||||
|
initialState?.currentUser?.is_admin &&
|
||||||
|
updateCheck.latest_version &&
|
||||||
|
updateCheck.latest_version !== version?.version &&
|
||||||
|
updateCheck.latest_version !== '0.0.0' &&
|
||||||
|
updateCheck.latest_version?.indexOf('rc') === -1
|
||||||
|
);
|
||||||
|
}, [updateCheck, version, initialState]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const body = document.querySelector('body');
|
||||||
|
if (body) {
|
||||||
|
initialize(body);
|
||||||
|
}
|
||||||
|
}, [initialize]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const menuWrap = document.querySelector(
|
||||||
|
'.ant-menu.ant-menu-root'
|
||||||
|
)?.parentElement;
|
||||||
|
if (menuWrap) {
|
||||||
|
initializeMenu(menuWrap);
|
||||||
|
}
|
||||||
|
}, [initializeMenu]);
|
||||||
|
|
||||||
const renderMenuHeader = useCallback(
|
const renderMenuHeader = useCallback(
|
||||||
(logo, title) => {
|
(logo, title) => {
|
||||||
return (
|
return (
|
||||||
@@ -206,12 +240,13 @@ export default (props: any) => {
|
|||||||
setInitialState,
|
setInitialState,
|
||||||
intl,
|
intl,
|
||||||
siderWidth: layoutProps.siderWidth,
|
siderWidth: layoutProps.siderWidth,
|
||||||
collapsed: layoutProps.collapsed
|
collapsed: layoutProps.collapsed,
|
||||||
|
showUpgrade
|
||||||
});
|
});
|
||||||
|
|
||||||
return dom;
|
return dom;
|
||||||
},
|
},
|
||||||
[intl]
|
[intl, version, updateCheck]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -225,6 +260,9 @@ export default (props: any) => {
|
|||||||
layout="side"
|
layout="side"
|
||||||
disableMobile={true}
|
disableMobile={true}
|
||||||
siderWidth={220}
|
siderWidth={220}
|
||||||
|
onCollapse={(collapsed) => {
|
||||||
|
setCollapsed(collapsed);
|
||||||
|
}}
|
||||||
onMenuHeaderClick={(e) => {
|
onMenuHeaderClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
+32
-14
@@ -26,7 +26,7 @@ export const getRightRenderContent = (opts: {
|
|||||||
siderWidth: number;
|
siderWidth: number;
|
||||||
intl: any;
|
intl: any;
|
||||||
}) => {
|
}) => {
|
||||||
const { intl, collapsed, siderWidth } = opts;
|
const { intl, collapsed, siderWidth, showUpgrade } = opts;
|
||||||
|
|
||||||
const allLocals = getAllLocales();
|
const allLocals = getAllLocales();
|
||||||
if (opts.runtimeConfig.rightRender) {
|
if (opts.runtimeConfig.rightRender) {
|
||||||
@@ -99,11 +99,6 @@ export const getRightRenderContent = (opts: {
|
|||||||
icon: <InfoCircleOutlined />,
|
icon: <InfoCircleOutlined />,
|
||||||
label: intl.formatMessage({ id: 'common.button.version' })
|
label: intl.formatMessage({ id: 'common.button.version' })
|
||||||
}
|
}
|
||||||
// {
|
|
||||||
// key: 'shortcuts',
|
|
||||||
// icon: <IconFont type="icon-keyboard"></IconFont>,
|
|
||||||
// label: intl.formatMessage({ id: 'common.button.shortcut' })
|
|
||||||
// }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const helpMenu = {
|
const helpMenu = {
|
||||||
@@ -113,15 +108,29 @@ export const getRightRenderContent = (opts: {
|
|||||||
: 'user-menu-container',
|
: 'user-menu-container',
|
||||||
mode: 'vertical',
|
mode: 'vertical',
|
||||||
expandIcon: false,
|
expandIcon: false,
|
||||||
inlineCollapsed: collapsed,
|
// inlineCollapsed: collapsed,
|
||||||
triggerSubMenuAction: 'hover',
|
triggerSubMenuAction: 'hover',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'help',
|
key: 'help',
|
||||||
icon: <QuestionCircleOutlined />,
|
icon: <QuestionCircleOutlined />,
|
||||||
label: (
|
label: (
|
||||||
<span className="sub-title">
|
<span className="sub-title ">
|
||||||
{intl?.formatMessage?.({ id: 'common.button.help' })}
|
<span className="flex-center">
|
||||||
|
<span>{intl?.formatMessage?.({ id: 'common.button.help' })}</span>
|
||||||
|
{showUpgrade && (
|
||||||
|
<span
|
||||||
|
className="m-l-5"
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: 'var(--ant-orange-5)'
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
children: helpList.map((item) => ({
|
children: helpList.map((item) => ({
|
||||||
@@ -130,7 +139,16 @@ export const getRightRenderContent = (opts: {
|
|||||||
<span className="flex flex-center">
|
<span className="flex flex-center">
|
||||||
{item.icon}
|
{item.icon}
|
||||||
{item.key === 'version' ? (
|
{item.key === 'version' ? (
|
||||||
|
<>
|
||||||
<a className="m-l-8">{item.label}</a>
|
<a className="m-l-8">{item.label}</a>
|
||||||
|
{showUpgrade && (
|
||||||
|
<span className="new-icon">
|
||||||
|
<span className="text">
|
||||||
|
{intl.formatMessage({ id: 'common.text.new' })}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<a
|
<a
|
||||||
className="m-l-8"
|
className="m-l-8"
|
||||||
@@ -163,7 +181,7 @@ export const getRightRenderContent = (opts: {
|
|||||||
: 'user-menu-container',
|
: 'user-menu-container',
|
||||||
mode: 'vertical',
|
mode: 'vertical',
|
||||||
expandIcon: false,
|
expandIcon: false,
|
||||||
inlineCollapsed: collapsed,
|
// inlineCollapsed: collapsed,
|
||||||
triggerSubMenuAction: 'hover',
|
triggerSubMenuAction: 'hover',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@@ -196,7 +214,7 @@ export const getRightRenderContent = (opts: {
|
|||||||
: 'user-menu-container',
|
: 'user-menu-container',
|
||||||
mode: 'vertical',
|
mode: 'vertical',
|
||||||
expandIcon: false,
|
expandIcon: false,
|
||||||
inlineCollapsed: collapsed,
|
// inlineCollapsed: collapsed,
|
||||||
triggerSubMenuAction: 'hover',
|
triggerSubMenuAction: 'hover',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
@@ -263,19 +281,19 @@ export const getRightRenderContent = (opts: {
|
|||||||
<Menu
|
<Menu
|
||||||
{...helpMenu}
|
{...helpMenu}
|
||||||
style={{
|
style={{
|
||||||
width: collapsed ? 64 : `calc(${siderWidth}px - 16px)`
|
width: collapsed ? 40 : `calc(${siderWidth}px - 16px)`
|
||||||
}}
|
}}
|
||||||
></Menu>
|
></Menu>
|
||||||
<Menu
|
<Menu
|
||||||
{...langMenu}
|
{...langMenu}
|
||||||
style={{
|
style={{
|
||||||
width: collapsed ? 64 : `calc(${siderWidth}px - 16px)`
|
width: collapsed ? 40 : `calc(${siderWidth}px - 16px)`
|
||||||
}}
|
}}
|
||||||
></Menu>
|
></Menu>
|
||||||
<Menu
|
<Menu
|
||||||
{...userMenu}
|
{...userMenu}
|
||||||
style={{
|
style={{
|
||||||
width: collapsed ? 64 : `calc(${siderWidth}px - 16px)`,
|
width: collapsed ? 40 : `calc(${siderWidth}px - 16px)`,
|
||||||
marginTop: 20
|
marginTop: 20
|
||||||
}}
|
}}
|
||||||
></Menu>
|
></Menu>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export default {
|
|||||||
'common.tips.title': 'Tips:',
|
'common.tips.title': 'Tips:',
|
||||||
'common.input.range.start': 'start value',
|
'common.input.range.start': 'start value',
|
||||||
'common.input.range.end': 'end value',
|
'common.input.range.end': 'end value',
|
||||||
'common.footer.version': 'Versions',
|
'common.footer.version': 'Version',
|
||||||
'common.footer.version.title': 'Version Information',
|
'common.footer.version.title': 'Version Information',
|
||||||
'common.footer.version.web': 'UI',
|
'common.footer.version.web': 'UI',
|
||||||
'common.footer.version.server': 'Server',
|
'common.footer.version.server': 'Server',
|
||||||
@@ -206,5 +206,9 @@ export default {
|
|||||||
'common.title.delete.confirm': 'Confirm delete',
|
'common.title.delete.confirm': 'Confirm delete',
|
||||||
'common.button.addLabel': 'Add Labels',
|
'common.button.addLabel': 'Add Labels',
|
||||||
'common.button.addSelector': 'Add Selectors',
|
'common.button.addSelector': 'Add Selectors',
|
||||||
'common.button.addParams': 'Add Parameter'
|
'common.button.addParams': 'Add Parameter',
|
||||||
|
'common.text.here': 'here',
|
||||||
|
'common.text.latest': 'Latest',
|
||||||
|
'common.text.new': 'New',
|
||||||
|
'common.text.changelog': 'Release Notes'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export default {
|
|||||||
'models.form.manual.schedule': 'Manual Schedule',
|
'models.form.manual.schedule': 'Manual Schedule',
|
||||||
'models.table.gpuindex': 'GPU Index',
|
'models.table.gpuindex': 'GPU Index',
|
||||||
'models.table.backend': 'Backends',
|
'models.table.backend': 'Backends',
|
||||||
'models.table.acrossworker': 'Distribution Across Workers',
|
'models.table.acrossworker': 'Distributed Across Workers',
|
||||||
'models.table.cpuoffload': 'CPU Offload',
|
'models.table.cpuoffload': 'CPU Offload',
|
||||||
'models.table.layers': 'Layers',
|
'models.table.layers': 'Layers',
|
||||||
'models.form.backend': 'Backend',
|
'models.form.backend': 'Backend',
|
||||||
@@ -64,5 +64,6 @@ export default {
|
|||||||
'models.form.backend_parameters.llamabox.placeholder':
|
'models.form.backend_parameters.llamabox.placeholder':
|
||||||
'e.g., --ctx-size=8192',
|
'e.g., --ctx-size=8192',
|
||||||
'models.form.backend_parameters.vllm.placeholder':
|
'models.form.backend_parameters.vllm.placeholder':
|
||||||
'e.g., --max-model-len=8192'
|
'e.g., --max-model-len=8192',
|
||||||
|
'models.form.backend_parameters.vllm.tips': 'More {backend} parameter details'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export default {
|
|||||||
'playground.toolbar.compare4Model': '4-Model Comparison',
|
'playground.toolbar.compare4Model': '4-Model Comparison',
|
||||||
'playground.toolbar.compare6Model': '6-Model Comparison',
|
'playground.toolbar.compare6Model': '6-Model Comparison',
|
||||||
'playground.input.holder': 'Type <kbd>/</kbd> to input message',
|
'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.apply': 'Apply',
|
||||||
'playground.compare.applytoall': 'Apply to all models',
|
'playground.compare.applytoall': 'Apply to all models',
|
||||||
'playground.model.noavailable': 'No available models',
|
'playground.model.noavailable': 'No available models',
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default {
|
|||||||
'resources.table.vramutilization': 'VRAM Utilization',
|
'resources.table.vramutilization': 'VRAM Utilization',
|
||||||
'resources.table.total': 'Total',
|
'resources.table.total': 'Total',
|
||||||
'resources.table.used': 'Used',
|
'resources.table.used': 'Used',
|
||||||
|
'resources.table.allocated': 'Allocated',
|
||||||
'resources.table.wokers': 'workers',
|
'resources.table.wokers': 'workers',
|
||||||
'resources.worker.linuxormaxos': 'Linux or MacOS',
|
'resources.worker.linuxormaxos': 'Linux or MacOS',
|
||||||
'resources.table.unified': 'Unified Memory',
|
'resources.table.unified': 'Unified Memory',
|
||||||
|
|||||||
@@ -25,5 +25,7 @@ export default {
|
|||||||
'users.password.confirm': 'Confirm New Password',
|
'users.password.confirm': 'Confirm New Password',
|
||||||
'users.password.confirm.empty': 'Please confirm the new password.',
|
'users.password.confirm.empty': 'Please confirm the new password.',
|
||||||
'users.password.confirm.error': 'The two passwords entered do not match.',
|
'users.password.confirm.error': 'The two passwords entered do not match.',
|
||||||
'users.login.title': 'Log in to {name}'
|
'users.login.title': 'Log in to {name}',
|
||||||
|
'users.version.islatest': 'GPUStack {version} is the latest version',
|
||||||
|
'users.version.update': 'GPUStack {version} is available'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -199,5 +199,9 @@ export default {
|
|||||||
'common.title.delete.confirm': '确认删除',
|
'common.title.delete.confirm': '确认删除',
|
||||||
'common.button.addLabel': '添加标签',
|
'common.button.addLabel': '添加标签',
|
||||||
'common.button.addSelector': '添加选择器',
|
'common.button.addSelector': '添加选择器',
|
||||||
'common.button.addParams': '添加参数'
|
'common.button.addParams': '添加参数',
|
||||||
|
'common.text.here': '这里',
|
||||||
|
'common.text.latest': '最新',
|
||||||
|
'common.text.new': '新',
|
||||||
|
'common.text.changelog': '更新日志'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,5 +63,6 @@ export default {
|
|||||||
'models.form.backend_parameters.llamabox.placeholder':
|
'models.form.backend_parameters.llamabox.placeholder':
|
||||||
'例如,--ctx-size=8192',
|
'例如,--ctx-size=8192',
|
||||||
'models.form.backend_parameters.vllm.placeholder':
|
'models.form.backend_parameters.vllm.placeholder':
|
||||||
'例如,--max-model-len=8192'
|
'例如,--max-model-len=8192',
|
||||||
|
'models.form.backend_parameters.vllm.tips': '更多 {backend} 参数说明查看'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export default {
|
|||||||
'playground.toolbar.compare4Model': '4 模型对比',
|
'playground.toolbar.compare4Model': '4 模型对比',
|
||||||
'playground.toolbar.compare6Model': '6 模型对比',
|
'playground.toolbar.compare6Model': '6 模型对比',
|
||||||
'playground.input.holder': '按 <kbd>/</kbd> 开始输入',
|
'playground.input.holder': '按 <kbd>/</kbd> 开始输入',
|
||||||
|
'playground.input.keyword.holder': '按 <kbd>/</kbd> 输入你的查询',
|
||||||
'playground.compare.apply': '应用',
|
'playground.compare.apply': '应用',
|
||||||
'playground.compare.applytoall': '应用到所有模型',
|
'playground.compare.applytoall': '应用到所有模型',
|
||||||
'playground.model.noavailable': '无可用模型',
|
'playground.model.noavailable': '无可用模型',
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default {
|
|||||||
'resources.table.utilization': '利用率',
|
'resources.table.utilization': '利用率',
|
||||||
'resources.table.total': '总量',
|
'resources.table.total': '总量',
|
||||||
'resources.table.used': '已用',
|
'resources.table.used': '已用',
|
||||||
|
'resources.table.allocated': '已分配',
|
||||||
'resources.table.wokers': 'workers',
|
'resources.table.wokers': 'workers',
|
||||||
'resources.table.unified': '统一内存',
|
'resources.table.unified': '统一内存',
|
||||||
'resources.worker.linuxormaxos': 'Linux 或 MacOS',
|
'resources.worker.linuxormaxos': 'Linux 或 MacOS',
|
||||||
|
|||||||
@@ -24,5 +24,7 @@ export default {
|
|||||||
'users.password.confirm': '确认新密码',
|
'users.password.confirm': '确认新密码',
|
||||||
'users.password.confirm.empty': '请确认新密码',
|
'users.password.confirm.empty': '请确认新密码',
|
||||||
'users.password.confirm.error': '两次输入的密码不一致',
|
'users.password.confirm.error': '两次输入的密码不一致',
|
||||||
'users.login.title': '登录 {name}'
|
'users.login.title': '登录 {name}',
|
||||||
|
'users.version.islatest': 'GPUStack {version} 已是最新版本',
|
||||||
|
'users.version.update': 'GPUStack {version} 版本可供更新'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import DeleteModal from '@/components/delete-modal';
|
import DeleteModal from '@/components/delete-modal';
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
@@ -218,9 +219,15 @@ const APIKeys: React.FC = () => {
|
|||||||
title={intl.formatMessage({ id: 'common.table.name' })}
|
title={intl.formatMessage({ id: 'common.table.name' })}
|
||||||
dataIndex="name"
|
dataIndex="name"
|
||||||
key="name"
|
key="name"
|
||||||
width={400}
|
|
||||||
ellipsis={{
|
ellipsis={{
|
||||||
showTitle: true
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost style={{ maxWidth: 400 }}>
|
||||||
|
{text}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -228,10 +235,19 @@ const APIKeys: React.FC = () => {
|
|||||||
title={intl.formatMessage({ id: 'apikeys.form.expiretime' })}
|
title={intl.formatMessage({ id: 'apikeys.form.expiretime' })}
|
||||||
dataIndex="expires_at"
|
dataIndex="expires_at"
|
||||||
key="expiration"
|
key="expiration"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
render={(text, record) => {
|
render={(text, record) => {
|
||||||
return text
|
return (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{text
|
||||||
? dayjs(text).format('YYYY-MM-DD HH:mm:ss')
|
? dayjs(text).format('YYYY-MM-DD HH:mm:ss')
|
||||||
: intl.formatMessage({ id: 'apikeys.form.expiration.never' });
|
: intl.formatMessage({
|
||||||
|
id: 'apikeys.form.expiration.never'
|
||||||
|
})}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
@@ -239,7 +255,10 @@ const APIKeys: React.FC = () => {
|
|||||||
dataIndex="description"
|
dataIndex="description"
|
||||||
key="description"
|
key="description"
|
||||||
ellipsis={{
|
ellipsis={{
|
||||||
showTitle: true
|
showTitle: false
|
||||||
|
}}
|
||||||
|
render={(text, record) => {
|
||||||
|
return <AutoTooltip ghost>{text}</AutoTooltip>;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
@@ -250,13 +269,23 @@ const APIKeys: React.FC = () => {
|
|||||||
sortOrder={sortOrder}
|
sortOrder={sortOrder}
|
||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
sorter={false}
|
sorter={false}
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
render={(text, record) => {
|
render={(text, record) => {
|
||||||
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
return (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Column
|
<Column
|
||||||
title={intl.formatMessage({ id: 'common.table.operation' })}
|
title={intl.formatMessage({ id: 'common.table.operation' })}
|
||||||
key="operation"
|
key="operation"
|
||||||
|
ellipsis={{
|
||||||
|
showTitle: false
|
||||||
|
}}
|
||||||
render={(text, record: ListItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<Space size={20}>
|
<Space size={20}>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import AutoTooltip from '@/components/auto-tooltip';
|
||||||
import PageTools from '@/components/page-tools';
|
import PageTools from '@/components/page-tools';
|
||||||
import { convertFileSize } from '@/utils';
|
import { convertFileSize } from '@/utils';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
@@ -12,18 +13,27 @@ const ActiveTable = () => {
|
|||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'common.table.name' }),
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
key: 'name'
|
key: 'name',
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: any, record: any) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
<span>{text}</span>
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'dashboard.allocatevram' }),
|
title: intl.formatMessage({ id: 'dashboard.allocatevram' }),
|
||||||
dataIndex: 'resource_claim.memory',
|
dataIndex: 'resource_claim.memory',
|
||||||
key: 'vram',
|
key: 'vram',
|
||||||
|
ellipsis: true,
|
||||||
render: (text: any, record: any) => {
|
render: (text: any, record: any) => {
|
||||||
return (
|
return (
|
||||||
<span>
|
<AutoTooltip ghost>
|
||||||
{convertFileSize(record.resource_claim?.vram || 0)} /{' '}
|
{convertFileSize(record.resource_claim?.vram || 0)} /{' '}
|
||||||
{convertFileSize(record.resource_claim?.ram || 0)}
|
{convertFileSize(record.resource_claim?.ram || 0)}
|
||||||
</span>
|
</AutoTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -35,7 +45,15 @@ const ActiveTable = () => {
|
|||||||
{
|
{
|
||||||
title: intl.formatMessage({ id: 'dashboard.tokens' }),
|
title: intl.formatMessage({ id: 'dashboard.tokens' }),
|
||||||
dataIndex: 'token_count',
|
dataIndex: 'token_count',
|
||||||
key: 'token_count'
|
key: 'token_count',
|
||||||
|
ellipsis: true,
|
||||||
|
render: (text: any, record: any) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
<span>{text}</span>
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ import _ from 'lodash';
|
|||||||
import { memo, useContext, useMemo } from 'react';
|
import { memo, useContext, useMemo } from 'react';
|
||||||
import { DashboardContext } from '../config/dashboard-context';
|
import { DashboardContext } from '../config/dashboard-context';
|
||||||
|
|
||||||
const chartColorMap = {
|
|
||||||
tickLineColor: 'rgba(217,217,217,0.5)',
|
|
||||||
axislabelColor: 'rgba(0, 0, 0, 0.4)'
|
|
||||||
};
|
|
||||||
|
|
||||||
const TypeKeyMap = {
|
const TypeKeyMap = {
|
||||||
cpu: {
|
cpu: {
|
||||||
label: 'CPU',
|
label: 'CPU',
|
||||||
@@ -37,78 +32,6 @@ const TypeKeyMap = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const option = {
|
|
||||||
title: {
|
|
||||||
text: ''
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
itemWidth: 8,
|
|
||||||
itemHeight: 8,
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
left: 0,
|
|
||||||
right: 20,
|
|
||||||
bottom: 20,
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
formatter(params: any) {
|
|
||||||
let result = `<span class="tooltip-x-name">${params[0].axisValue}</span>`;
|
|
||||||
params.forEach((item: any) => {
|
|
||||||
result += `<span class="tooltip-item">
|
|
||||||
<span class="tooltip-item-name">
|
|
||||||
<span style="display:inline-block;margin-right:5px;border-radius:8px;width:8px;height:8px;background-color:${item.color};"></span>
|
|
||||||
<span class="tooltip-title">${item.seriesName}</span>:
|
|
||||||
</span>
|
|
||||||
<span class="tooltip-value">${item.data.value}</span>
|
|
||||||
</span>`;
|
|
||||||
});
|
|
||||||
return `<div class="tooltip-wrapper">${result}</div>`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
boundaryGap: true,
|
|
||||||
axisTick: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: chartColorMap.tickLineColor
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: chartColorMap.axislabelColor,
|
|
||||||
// fontFamily: 'unset',
|
|
||||||
fontSize: 12
|
|
||||||
},
|
|
||||||
axisLine: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
max: 100,
|
|
||||||
min: 0,
|
|
||||||
splitLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
type: 'dashed'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: chartColorMap.axislabelColor,
|
|
||||||
// fontFamily: 'unset',
|
|
||||||
fontSize: 12
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
type: 'value'
|
|
||||||
},
|
|
||||||
series: []
|
|
||||||
};
|
|
||||||
|
|
||||||
const UtilizationOvertime: React.FC = () => {
|
const UtilizationOvertime: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const data = useContext(DashboardContext)?.system_load?.history || {};
|
const data = useContext(DashboardContext)?.system_load?.history || {};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { PageAction } from '@/config';
|
|||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons';
|
import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Collapse,
|
Collapse,
|
||||||
@@ -76,6 +77,19 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
return backend === backendOptionsMap.llamaBox ? llamaConfig : vllmConfig;
|
return backend === backendOptionsMap.llamaBox ? llamaConfig : vllmConfig;
|
||||||
}, [backend]);
|
}, [backend]);
|
||||||
|
|
||||||
|
const backendParamsTips = useMemo(() => {
|
||||||
|
if (backend === backendOptionsMap.llamaBox) {
|
||||||
|
return {
|
||||||
|
backend: 'llama-box',
|
||||||
|
link: 'https://github.com/gpustack/llama-box?tab=readme-ov-file#usage'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
backend: 'vLLM',
|
||||||
|
link: 'https://docs.vllm.ai/en/stable/serving/openai_compatible_server.html#command-line-arguments-for-the-server'
|
||||||
|
};
|
||||||
|
}, [backend]);
|
||||||
|
|
||||||
const renderSelectTips = (list: Array<{ title: string; tips: string }>) => {
|
const renderSelectTips = (list: Array<{ title: string; tips: string }>) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -252,6 +266,21 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
|
|||||||
dataList={form.getFieldValue('backend_parameters') || []}
|
dataList={form.getFieldValue('backend_parameters') || []}
|
||||||
onChange={handleBackendParametersChange}
|
onChange={handleBackendParametersChange}
|
||||||
options={paramsConfig}
|
options={paramsConfig}
|
||||||
|
description={
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{ id: 'models.form.backend_parameters.vllm.tips' },
|
||||||
|
{ backend: backendParamsTips.backend }
|
||||||
|
)}{' '}
|
||||||
|
<Typography.Link
|
||||||
|
style={{ color: 'var(--ant-blue-4)' }}
|
||||||
|
href={backendParamsTips.link}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'common.text.here' })}
|
||||||
|
</Typography.Link>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
></ListInput>
|
></ListInput>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{isGGUF && (
|
{isGGUF && (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { PageActionType } from '@/config/types';
|
|||||||
import { CloseOutlined } from '@ant-design/icons';
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Drawer } from 'antd';
|
import { Button, Drawer } from 'antd';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { modelSourceMap } from '../config';
|
import { modelSourceMap } from '../config';
|
||||||
import { FormData, ListItem } from '../config/types';
|
import { FormData, ListItem } from '../config/types';
|
||||||
@@ -60,15 +61,21 @@ const AddModal: React.FC<AddModalProps> = (props) => {
|
|||||||
form.current?.submit?.();
|
form.current?.submit?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debounceFetchModelFiles = debounce(() => {
|
||||||
|
modelFileRef.current?.fetchModelFiles?.();
|
||||||
|
}, 300);
|
||||||
|
|
||||||
const handleSetIsGGUF = (flag: boolean) => {
|
const handleSetIsGGUF = (flag: boolean) => {
|
||||||
setIsGGUF(flag);
|
setIsGGUF(flag);
|
||||||
if (flag) {
|
if (flag) {
|
||||||
setTimeout(() => {
|
debounceFetchModelFiles();
|
||||||
modelFileRef.current?.fetchModelFiles?.();
|
|
||||||
}, 50);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleSelectModelFile({ fakeName: '' });
|
||||||
|
}, [selectedModel]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
setSelectedModel({});
|
setSelectedModel({});
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
const fileList = _.filter(_.get(data, ['Data', 'Files']), (file: any) => {
|
const fileList = _.filter(_.get(data, ['Data', 'Files']), (file: any) => {
|
||||||
return filterRegGGUF.test(file.Path) || _.includes(file.Path, '.gguf');
|
return filterRegGGUF.test(file.Path) && file.Type === 'blob';
|
||||||
});
|
});
|
||||||
|
|
||||||
const list = _.map(fileList, (item: any) => {
|
const list = _.map(fileList, (item: any) => {
|
||||||
@@ -261,7 +261,7 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="files-wrap">
|
||||||
<TitleWrapper>
|
<TitleWrapper>
|
||||||
<span className="title">
|
<span className="title">
|
||||||
{intl.formatMessage({ id: 'models.available.files' })} (
|
{intl.formatMessage({ id: 'models.available.files' })} (
|
||||||
@@ -282,13 +282,20 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
style={{ width: '120px' }}
|
style={{ width: '120px' }}
|
||||||
></Select>
|
></Select>
|
||||||
</TitleWrapper>
|
</TitleWrapper>
|
||||||
|
{dataSource.loading && (
|
||||||
|
<div className="spin-wrapper">
|
||||||
|
<Spin
|
||||||
|
spinning={dataSource.loading}
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
></Spin>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<SimpleBar
|
<SimpleBar
|
||||||
style={{
|
style={{
|
||||||
height: collapsed ? 'max-content' : 'calc(100vh - 300px)'
|
height: collapsed ? 'max-content' : 'calc(100vh - 300px)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ padding: '16px 24px' }}>
|
<div style={{ padding: '16px 24px' }}>
|
||||||
<Spin spinning={dataSource.loading} style={{ minHeight: 100 }}>
|
|
||||||
{dataSource.fileList.length ? (
|
{dataSource.fileList.length ? (
|
||||||
<Row gutter={[16, 24]}>
|
<Row gutter={[16, 24]}>
|
||||||
{_.map(dataSource.fileList, (item: any) => {
|
{_.map(dataSource.fileList, (item: any) => {
|
||||||
@@ -358,7 +365,6 @@ const HFModelFile: React.FC<HFModelFileProps> = forwardRef((props, ref) => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
</Spin>
|
|
||||||
</div>
|
</div>
|
||||||
</SimpleBar>
|
</SimpleBar>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -64,27 +64,16 @@ const HFModelItem: React.FC<HFModelItemProps> = (props) => {
|
|||||||
<div className="info">
|
<div className="info">
|
||||||
{SUPPORTEDSOURCE.includes(props.source || '') ? (
|
{SUPPORTEDSOURCE.includes(props.source || '') ? (
|
||||||
<div className="info-item">
|
<div className="info-item">
|
||||||
{/* {props.task && (
|
|
||||||
<Tag
|
|
||||||
className="tag-item"
|
|
||||||
color="gold"
|
|
||||||
style={{
|
|
||||||
marginRight: 0
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ opacity: 0.65 }}>{props.task}</span>
|
|
||||||
</Tag>
|
|
||||||
)} */}
|
|
||||||
<span>
|
<span>
|
||||||
{dayjs().to(
|
{dayjs().to(
|
||||||
dayjs(dayjs(props.updatedAt).format('YYYY-MM-DD HH:mm:ss'))
|
dayjs(dayjs(props.updatedAt).format('YYYY-MM-DD HH:mm:ss'))
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span className="flex-center">
|
||||||
<HeartOutlined className="m-r-5" />
|
<HeartOutlined className="m-r-5" />
|
||||||
{props.likes}
|
{props.likes}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span className="flex-center">
|
||||||
<DownloadOutlined className="m-r-5" />
|
<DownloadOutlined className="m-r-5" />
|
||||||
{formatNumber(props.downloads)}
|
{formatNumber(props.downloads)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
return {
|
return {
|
||||||
worker_name: data?.name,
|
worker_name: data?.name,
|
||||||
worker_ip: data?.ip,
|
worker_ip: data?.ip,
|
||||||
port: data?.port,
|
port: '',
|
||||||
gpu_index: item.gpu_index
|
gpu_index: item.gpu_index
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -125,7 +125,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
{
|
{
|
||||||
worker_name: `${row.worker_name}`,
|
worker_name: `${row.worker_name}`,
|
||||||
worker_ip: `${row.worker_ip}`,
|
worker_ip: `${row.worker_ip}`,
|
||||||
port: row.port,
|
port: '',
|
||||||
gpu_index: `${row.gpu_indexes?.sort?.()} (main)`
|
gpu_index: `${row.gpu_indexes?.sort?.()} (main)`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -60,6 +60,23 @@ const ModelCard: React.FC<{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeMetadata = useCallback((str: string) => {
|
||||||
|
let indexes = [];
|
||||||
|
let index = str.indexOf('---');
|
||||||
|
|
||||||
|
while (index !== -1) {
|
||||||
|
indexes.push(index);
|
||||||
|
if (indexes.length >= 2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index = str.indexOf('---', index + 1);
|
||||||
|
}
|
||||||
|
if (indexes.length >= 2) {
|
||||||
|
return str.slice(indexes[1] + 3);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// huggingface model card data
|
// huggingface model card data
|
||||||
const getHuggingfaceModelDetail = async () => {
|
const getHuggingfaceModelDetail = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -74,7 +91,10 @@ const ModelCard: React.FC<{
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
setModelData(modelcard);
|
setModelData(modelcard);
|
||||||
setReadmeText(readme);
|
// remove the meta data from readme
|
||||||
|
const newReadme = removeMetadata(readme);
|
||||||
|
|
||||||
|
setReadmeText(newReadme);
|
||||||
const isGGUF = modelcard.tags?.includes('gguf');
|
const isGGUF = modelcard.tags?.includes('gguf');
|
||||||
setIsGGUF(isGGUF);
|
setIsGGUF(isGGUF);
|
||||||
setIsGGUFModel(isGGUF);
|
setIsGGUFModel(isGGUF);
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import { modelSourceMap, modelSourceValueMap } from '../config';
|
|||||||
|
|
||||||
const SearchInput: React.FC<{
|
const SearchInput: React.FC<{
|
||||||
modelSource: string;
|
modelSource: string;
|
||||||
|
onChange: (e: any) => void;
|
||||||
onSearch: (e: any) => void;
|
onSearch: (e: any) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { onSearch, modelSource } = props;
|
const { onSearch, onChange, modelSource } = props;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ const SearchInput: React.FC<{
|
|||||||
<Input
|
<Input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
onPressEnter={onSearch}
|
onPressEnter={onSearch}
|
||||||
|
onChange={onChange}
|
||||||
allowClear
|
allowClear
|
||||||
placeholder={intl.formatMessage(
|
placeholder={intl.formatMessage(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
}
|
}
|
||||||
axiosTokenRef.current?.abort?.();
|
axiosTokenRef.current?.abort?.();
|
||||||
axiosTokenRef.current = new AbortController();
|
axiosTokenRef.current = new AbortController();
|
||||||
if (dataSource.loading) return;
|
|
||||||
const sort = sortType ?? dataSource.sortType;
|
const sort = sortType ?? dataSource.sortType;
|
||||||
try {
|
try {
|
||||||
setDataSource((pre) => {
|
setDataSource((pre) => {
|
||||||
@@ -174,7 +173,9 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
},
|
},
|
||||||
[dataSource]
|
[dataSource]
|
||||||
);
|
);
|
||||||
|
const handleSearchInputChange = useCallback((e: any) => {
|
||||||
|
searchInputRef.current = e.target.value;
|
||||||
|
}, []);
|
||||||
const handlerSearchModels = useCallback(
|
const handlerSearchModels = useCallback(
|
||||||
async (e: any) => {
|
async (e: any) => {
|
||||||
searchInputRef.current = e.target.value;
|
searchInputRef.current = e.target.value;
|
||||||
@@ -208,7 +209,6 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFilterGGUFChange = (e: any) => {
|
const handleFilterGGUFChange = (e: any) => {
|
||||||
console.log('filterggufChange:', e.target.checked);
|
|
||||||
filterGGUFRef.current = e.target.checked;
|
filterGGUFRef.current = e.target.checked;
|
||||||
handleOnSearchRepo();
|
handleOnSearchRepo();
|
||||||
};
|
};
|
||||||
@@ -218,6 +218,7 @@ const SearchModel: React.FC<SearchInputProps> = (props) => {
|
|||||||
<>
|
<>
|
||||||
<SearchInput
|
<SearchInput
|
||||||
onSearch={handlerSearchModels}
|
onSearch={handlerSearchModels}
|
||||||
|
onChange={handleSearchInputChange}
|
||||||
modelSource={modelSource}
|
modelSource={modelSource}
|
||||||
></SearchInput>
|
></SearchInput>
|
||||||
<div className={SearchStyle.filter}>
|
<div className={SearchStyle.filter}>
|
||||||
|
|||||||
@@ -96,7 +96,13 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
source: modelSourceMap.huggingface_value
|
source: modelSourceMap.huggingface_value
|
||||||
});
|
});
|
||||||
const [currentData, setCurrentData] = useState<ListItem>({} as ListItem);
|
const [currentData, setCurrentData] = useState<ListItem>({} as ListItem);
|
||||||
const [currentInstanceUrl, setCurrentInstanceUrl] = useState<string>('');
|
const [currentInstance, setCurrentInstance] = useState<{
|
||||||
|
url: string;
|
||||||
|
status: string;
|
||||||
|
}>({
|
||||||
|
url: '',
|
||||||
|
status: ''
|
||||||
|
});
|
||||||
const modalRef = useRef<any>(null);
|
const modalRef = useRef<any>(null);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
@@ -199,11 +205,11 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
key: 'chat',
|
key: 'chat',
|
||||||
icon: <WechatWorkOutlined />
|
icon: <WechatWorkOutlined />
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: 'common.button.viewcode',
|
// label: 'common.button.viewcode',
|
||||||
key: 'embedding',
|
// key: 'embedding',
|
||||||
icon: <IconFont type="icon-code" />
|
// icon: <IconFont type="icon-code" />
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
label: 'common.button.delete',
|
label: 'common.button.delete',
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
@@ -220,7 +226,10 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
return record.ready_replicas > 0 && !record.embedding_only;
|
return record.ready_replicas > 0 && !record.embedding_only;
|
||||||
}
|
}
|
||||||
if (action.key === 'embedding') {
|
if (action.key === 'embedding') {
|
||||||
return record.embedding_only && record.ready_replicas > 0;
|
return (
|
||||||
|
(record.embedding_only || record.reranker) &&
|
||||||
|
record.ready_replicas > 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -308,6 +317,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
async onOk() {
|
async onOk() {
|
||||||
await deleteModel(row.id);
|
await deleteModel(row.id);
|
||||||
updateExpandedRowKeys([row.id]);
|
updateExpandedRowKeys([row.id]);
|
||||||
|
rowSelection.removeSelectedKey(row.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -329,7 +339,10 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
|
|
||||||
const handleViewLogs = async (row: any) => {
|
const handleViewLogs = async (row: any) => {
|
||||||
try {
|
try {
|
||||||
setCurrentInstanceUrl(`${MODEL_INSTANCE_API}/${row.id}/logs`);
|
setCurrentInstance({
|
||||||
|
url: `${MODEL_INSTANCE_API}/${row.id}/logs`,
|
||||||
|
status: row.status
|
||||||
|
});
|
||||||
setOpenLogModal(true);
|
setOpenLogModal(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error:', error);
|
console.log('error:', error);
|
||||||
@@ -367,7 +380,8 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
setOpenAddModal(true);
|
setOpenAddModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelect = useCallback((val: any, row: ListItem) => {
|
const handleSelect = useCallback(
|
||||||
|
(val: any, row: ListItem) => {
|
||||||
if (val === 'edit') {
|
if (val === 'edit') {
|
||||||
handleEdit(row);
|
handleEdit(row);
|
||||||
}
|
}
|
||||||
@@ -386,7 +400,9 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
show: true
|
show: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[handleEdit, handleOpenPlayGround, handleDelete, setEmbeddingParams]
|
||||||
|
);
|
||||||
|
|
||||||
const handleChildSelect = useCallback(
|
const handleChildSelect = useCallback(
|
||||||
(val: any, row: ModelInstanceListItem, list: ModelInstanceListItem[]) => {
|
(val: any, row: ModelInstanceListItem, list: ModelInstanceListItem[]) => {
|
||||||
@@ -417,10 +433,10 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
|
|
||||||
const generateSource = useCallback((record: ListItem) => {
|
const generateSource = useCallback((record: ListItem) => {
|
||||||
if (record.source === modelSourceMap.modelscope_value) {
|
if (record.source === modelSourceMap.modelscope_value) {
|
||||||
return `${modelSourceMap.modelScope}/${record.model_scope_file_path || record.model_scope_model_id}`;
|
return `${modelSourceMap.modelScope}/${record.model_scope_model_id}`;
|
||||||
}
|
}
|
||||||
if (record.source === modelSourceMap.huggingface_value) {
|
if (record.source === modelSourceMap.huggingface_value) {
|
||||||
return `${modelSourceMap.huggingface}/${record.huggingface_filename || record.huggingface_repo_id}`;
|
return `${modelSourceMap.huggingface}/${record.huggingface_repo_id}`;
|
||||||
}
|
}
|
||||||
return `${modelSourceMap.ollama_library}/${record.ollama_library_model_name}`;
|
return `${modelSourceMap.ollama_library}/${record.ollama_library_model_name}`;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -518,18 +534,37 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
render={(text, record: ListItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className="flex-center flex-wrap"
|
className="flex-center"
|
||||||
style={{ maxWidth: '100%' }}
|
style={{
|
||||||
|
maxWidth: '100%'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<AutoTooltip ghost>
|
<AutoTooltip ghost>
|
||||||
<span className="m-r-5">{text}</span>
|
<span className="m-r-5">{text}</span>
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
{record.embedding_only && (
|
{record.reranker && (
|
||||||
<span>
|
<Tag
|
||||||
<Tag style={{ margin: 0 }} color="geekblue">
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
opacity: 0.8,
|
||||||
|
transform: 'scale(0.9)'
|
||||||
|
}}
|
||||||
|
color="geekblue"
|
||||||
|
>
|
||||||
|
Reranker
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
{record.embedding_only && !record.reranker && (
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
opacity: 0.8,
|
||||||
|
transform: 'scale(0.9)'
|
||||||
|
}}
|
||||||
|
color="geekblue"
|
||||||
|
>
|
||||||
Embedding Only
|
Embedding Only
|
||||||
</Tag>
|
</Tag>
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -542,8 +577,8 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
span={6}
|
span={6}
|
||||||
render={(text, record: ListItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<span className="flex flex-column">
|
<span className="flex flex-column" style={{ width: '100%' }}>
|
||||||
<span>{generateSource(record)}</span>
|
<AutoTooltip ghost>{generateSource(record)}</AutoTooltip>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@@ -560,7 +595,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
}}
|
}}
|
||||||
render={(text, record: ListItem) => {
|
render={(text, record: ListItem) => {
|
||||||
return (
|
return (
|
||||||
<span style={{ paddingLeft: 7 }}>
|
<span style={{ paddingLeft: 10, minWidth: '33px' }}>
|
||||||
{record.ready_replicas} / {record.replicas}
|
{record.ready_replicas} / {record.replicas}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -575,7 +610,11 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
sortOrder={sortOrder}
|
sortOrder={sortOrder}
|
||||||
sorter={false}
|
sorter={false}
|
||||||
render={(text, row) => {
|
render={(text, row) => {
|
||||||
return dayjs(text).format('YYYY-MM-DD HH:mm:ss');
|
return (
|
||||||
|
<AutoTooltip ghost>
|
||||||
|
{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SealColumn
|
<SealColumn
|
||||||
@@ -613,7 +652,7 @@ const Models: React.FC<ModelsProps> = ({
|
|||||||
onOk={handleCreateModel}
|
onOk={handleCreateModel}
|
||||||
></DeployModal>
|
></DeployModal>
|
||||||
<ViewLogsModal
|
<ViewLogsModal
|
||||||
url={currentInstanceUrl}
|
url={currentInstance.url}
|
||||||
open={openLogModal}
|
open={openLogModal}
|
||||||
onCancel={handleLogModalCancel}
|
onCancel={handleLogModalCancel}
|
||||||
></ViewLogsModal>
|
></ViewLogsModal>
|
||||||
|
|||||||
@@ -72,32 +72,23 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
setGpuOptions(list);
|
setGpuOptions(list);
|
||||||
};
|
};
|
||||||
|
|
||||||
const initFormValue = () => {
|
useEffect(() => {
|
||||||
if (action === PageAction.CREATE && open) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
source: modelSourceMap.huggingface_value,
|
|
||||||
replicas: 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (action === PageAction.EDIT && open) {
|
if (action === PageAction.EDIT && open) {
|
||||||
const result = setSourceRepoConfigValue(
|
const result = setSourceRepoConfigValue(
|
||||||
props.data?.source || '',
|
props.data?.source || '',
|
||||||
props.data
|
props.data
|
||||||
);
|
);
|
||||||
|
|
||||||
form.setFieldsValue({
|
const formData = {
|
||||||
...result.values,
|
...result.values,
|
||||||
..._.omit(props.data, result.omits),
|
..._.omit(props.data, result.omits),
|
||||||
scheduleType: props.data?.gpu_selector ? 'manual' : 'auto',
|
scheduleType: props.data?.gpu_selector ? 'manual' : 'auto',
|
||||||
gpu_selector: props.data?.gpu_selector
|
gpu_selector: props.data?.gpu_selector
|
||||||
? `${props.data?.gpu_selector.worker_name}-${props.data?.gpu_selector.gpu_name}-${props.data?.gpu_selector.gpu_index}`
|
? `${props.data?.gpu_selector.worker_name}-${props.data?.gpu_selector.gpu_name}-${props.data?.gpu_selector.gpu_index}`
|
||||||
: null
|
: null
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
form.setFieldsValue(formData);
|
||||||
useEffect(() => {
|
}
|
||||||
initFormValue();
|
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -257,6 +248,10 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOnClose = () => {
|
||||||
|
onCancel?.();
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getGPUList();
|
getGPUList();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -268,6 +263,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
centered={true}
|
centered={true}
|
||||||
onOk={handleSumit}
|
onOk={handleSumit}
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
|
onClose={handleOnClose}
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
closeIcon={true}
|
closeIcon={true}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
@@ -391,9 +387,7 @@ const UpdateModal: React.FC<AddModalProps> = (props) => {
|
|||||||
form={form}
|
form={form}
|
||||||
gpuOptions={gpuOptions}
|
gpuOptions={gpuOptions}
|
||||||
action={PageAction.EDIT}
|
action={PageAction.EDIT}
|
||||||
isGGUF={
|
isGGUF={props.data?.backend === backendOptionsMap.llamaBox}
|
||||||
form.getFieldValue('backend') === backendOptionsMap.llamaBox
|
|
||||||
}
|
|
||||||
></AdvanceConfig>
|
></AdvanceConfig>
|
||||||
</Form>
|
</Form>
|
||||||
</SimpleBar>
|
</SimpleBar>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import LogsViewer from '@/components/logs-viewer';
|
import LogsViewer from '@/components/logs-viewer/index';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Modal } from 'antd';
|
import { Modal } from 'antd';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
@@ -6,6 +6,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
type ViewModalProps = {
|
type ViewModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
|
autoScroll?: boolean;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,7 +42,10 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
<span className="flex flex-center">
|
<span className="flex flex-center">
|
||||||
<span> {intl.formatMessage({ id: 'common.button.viewlog' })}</span>
|
<span style={{ fontWeight: 'var(--font-weight-bold)' }}>
|
||||||
|
{' '}
|
||||||
|
{intl.formatMessage({ id: 'common.button.viewlog' })}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
open={open}
|
open={open}
|
||||||
|
|||||||
@@ -242,3 +242,32 @@ export const setbackendParameters = (data: any) => {
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getVllmCliArgs = (inputString: string) => {
|
||||||
|
const arrayOutput = inputString
|
||||||
|
.split(/\s*\[\s*|\s*\]\s*/g) // Split by '[' or ']', removing extra spaces
|
||||||
|
.filter((item) => item) // Remove empty items
|
||||||
|
.map((item) => item.trim()); // Trim spaces around each item
|
||||||
|
|
||||||
|
const result = arrayOutput.map((item) => {
|
||||||
|
const parts = item.split(' ');
|
||||||
|
const label = parts[0].trim(); // Remove leading dashes
|
||||||
|
const value = parts[0].trim();
|
||||||
|
|
||||||
|
if (parts.length === 1) {
|
||||||
|
return { label, value, options: [] };
|
||||||
|
} else if (parts.length > 1) {
|
||||||
|
const optionPart = parts[1];
|
||||||
|
if (optionPart.startsWith('{') && optionPart.endsWith('}')) {
|
||||||
|
const options = optionPart
|
||||||
|
.slice(1, -1)
|
||||||
|
.split(',')
|
||||||
|
.map((opt) => opt.trim());
|
||||||
|
return { label, value, options };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { label, value, options: [] }; // Fallback
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export interface ListItem {
|
export interface ListItem {
|
||||||
source: string;
|
source: string;
|
||||||
backend: string;
|
backend: string;
|
||||||
|
reranker: boolean;
|
||||||
huggingface_repo_id: string;
|
huggingface_repo_id: string;
|
||||||
huggingface_file_name: string;
|
huggingface_file_name: string;
|
||||||
huggingface_filename: string;
|
huggingface_filename: string;
|
||||||
|
|||||||
@@ -97,7 +97,12 @@ const options = [
|
|||||||
{
|
{
|
||||||
label: '--tool-call-parser',
|
label: '--tool-call-parser',
|
||||||
value: '--tool-call-parser',
|
value: '--tool-call-parser',
|
||||||
options: ['mistral', 'hermes']
|
options: ['mistral', 'hermes', 'internlm', 'llama3_json']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '--tool-parser-plugin',
|
||||||
|
value: '--tool-parser-plugin',
|
||||||
|
options: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '--model',
|
label: '--model',
|
||||||
@@ -312,6 +317,7 @@ const options = [
|
|||||||
'qqq',
|
'qqq',
|
||||||
'experts_int8',
|
'experts_int8',
|
||||||
'neuron_quant',
|
'neuron_quant',
|
||||||
|
'ipex',
|
||||||
'None'
|
'None'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -365,6 +371,11 @@ const options = [
|
|||||||
value: '--limit-mm-per-prompt',
|
value: '--limit-mm-per-prompt',
|
||||||
options: []
|
options: []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '--mm-processor-kwargs',
|
||||||
|
value: '--mm-processor-kwargs',
|
||||||
|
options: []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '--enable-lora',
|
label: '--enable-lora',
|
||||||
value: '--enable-lora',
|
value: '--enable-lora',
|
||||||
@@ -430,6 +441,11 @@ const options = [
|
|||||||
value: '--num-scheduler-steps',
|
value: '--num-scheduler-steps',
|
||||||
options: []
|
options: []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '--multi-step-stream-outputs',
|
||||||
|
value: '--multi-step-stream-outputs',
|
||||||
|
options: []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '--scheduler-delay-factor',
|
label: '--scheduler-delay-factor',
|
||||||
value: '--scheduler-delay-factor',
|
value: '--scheduler-delay-factor',
|
||||||
@@ -467,6 +483,7 @@ const options = [
|
|||||||
'qqq',
|
'qqq',
|
||||||
'experts_int8',
|
'experts_int8',
|
||||||
'neuron_quant',
|
'neuron_quant',
|
||||||
|
'ipex',
|
||||||
'None'
|
'None'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -475,6 +492,11 @@ const options = [
|
|||||||
value: '--num-speculative-tokens',
|
value: '--num-speculative-tokens',
|
||||||
options: []
|
options: []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '--speculative-disable-mqa-scorer',
|
||||||
|
value: '--speculative-disable-mqa-scorer',
|
||||||
|
options: []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '--speculative-draft-tensor-parallel-size',
|
label: '--speculative-draft-tensor-parallel-size',
|
||||||
value: '--speculative-draft-tensor-parallel-size',
|
value: '--speculative-draft-tensor-parallel-size',
|
||||||
@@ -565,6 +587,11 @@ const options = [
|
|||||||
value: '--override-neuron-config',
|
value: '--override-neuron-config',
|
||||||
options: []
|
options: []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '--scheduling-policy',
|
||||||
|
value: '--scheduling-policy',
|
||||||
|
options: ['fcfs', 'priority']
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '--disable-log-requests',
|
label: '--disable-log-requests',
|
||||||
value: '--disable-log-requests',
|
value: '--disable-log-requests',
|
||||||
@@ -574,6 +601,11 @@ const options = [
|
|||||||
label: '--max-log-len',
|
label: '--max-log-len',
|
||||||
value: '--max-log-len',
|
value: '--max-log-len',
|
||||||
options: []
|
options: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '--disable-fastapi-docs',
|
||||||
|
value: '--disable-fastapi-docs',
|
||||||
|
options: []
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.files-wrap {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.spin-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 100;
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
padding-top: 150px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: var(--color-fill-mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
padding: 16px @padding;
|
padding: 16px @padding;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ export const CHAT_API = '/v1-openai/chat/completions';
|
|||||||
|
|
||||||
export const OPENAI_MODELS = '/v1-openai/models';
|
export const OPENAI_MODELS = '/v1-openai/models';
|
||||||
|
|
||||||
|
export const RERANKER_API = '/rerank';
|
||||||
|
|
||||||
export async function execChatCompletions(params: any) {
|
export async function execChatCompletions(params: any) {
|
||||||
return request(`${CHAT_API}`, {
|
return request(`${CHAT_API}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -17,3 +19,19 @@ export const queryModelsList = async (params: any) => {
|
|||||||
params
|
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,51 @@
|
|||||||
|
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;
|
||||||
|
textListCount: number;
|
||||||
|
onDelete?: (uid: number | string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileList: React.FC<FileListProps> = (props) => {
|
||||||
|
const { textListCount, fileList, ghost, showIcon = true, onDelete } = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
return (
|
||||||
|
<div className="file-list">
|
||||||
|
{fileList.map((file, index) => {
|
||||||
|
return (
|
||||||
|
<div key={file.uid} className={classNames('file-item', { ghost })}>
|
||||||
|
<span className="title">
|
||||||
|
<span className="m-r-5">{index + 1 + textListCount}.</span>
|
||||||
|
{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);
|
||||||
@@ -10,11 +10,12 @@ import {
|
|||||||
memo,
|
memo,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { CHAT_API } from '../apis';
|
import { CHAT_API } from '../apis';
|
||||||
import { Roles } from '../config';
|
import { Roles, generateMessages } from '../config';
|
||||||
import { MessageItem } from '../config/types';
|
import { MessageItem } from '../config/types';
|
||||||
import '../style/ground-left.less';
|
import '../style/ground-left.less';
|
||||||
import '../style/system-message-wrap.less';
|
import '../style/system-message-wrap.less';
|
||||||
@@ -62,10 +63,18 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
},
|
},
|
||||||
setCollapse() {
|
setCollapse() {
|
||||||
setCollapse(!collapse);
|
setCollapse(!collapse);
|
||||||
}
|
},
|
||||||
|
collapse: collapse
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const viewCodeMessage = useMemo(() => {
|
||||||
|
return generateMessages([
|
||||||
|
{ role: Roles.System, content: systemMessage },
|
||||||
|
...messageList
|
||||||
|
]);
|
||||||
|
}, [messageList, systemMessage]);
|
||||||
|
|
||||||
const setMessageId = () => {
|
const setMessageId = () => {
|
||||||
messageId.current = messageId.current + 1;
|
messageId.current = messageId.current + 1;
|
||||||
};
|
};
|
||||||
@@ -132,48 +141,22 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
setMessageList((pre) => {
|
setMessageList((pre) => {
|
||||||
return [...pre, ...currentMessageRef.current];
|
return [...pre, ...currentMessageRef.current];
|
||||||
});
|
});
|
||||||
const formatMessages = _.map(
|
|
||||||
[...messageList, ...currentMessageRef.current],
|
const messageParams = [
|
||||||
(item: MessageItem) => {
|
{ role: Roles.System, content: systemMessage },
|
||||||
return {
|
...messageList,
|
||||||
role: item.role,
|
...currentMessageRef.current
|
||||||
content: [
|
];
|
||||||
{
|
|
||||||
type: 'text',
|
const messages = generateMessages(messageParams);
|
||||||
text: item.content
|
|
||||||
},
|
|
||||||
..._.map(
|
|
||||||
item.imgs,
|
|
||||||
(img: { uid: string | number; dataUrl: string }) => {
|
|
||||||
return {
|
|
||||||
type: 'image_url',
|
|
||||||
image_url: {
|
|
||||||
url: img.dataUrl
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const chatParams = {
|
const chatParams = {
|
||||||
messages: systemMessage
|
messages: messages,
|
||||||
? [
|
|
||||||
{
|
|
||||||
role: Roles.System,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: systemMessage
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
...formatMessages
|
|
||||||
]
|
|
||||||
: [...formatMessages],
|
|
||||||
...parameters,
|
...parameters,
|
||||||
stream: true
|
stream: true,
|
||||||
|
stream_options: {
|
||||||
|
include_usage: true
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const result: any = await fetchChunkedData({
|
const result: any = await fetchChunkedData({
|
||||||
data: chatParams,
|
data: chatParams,
|
||||||
@@ -184,13 +167,21 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
setTokenResult({
|
setTokenResult({
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: result?.data?.message
|
errorMessage:
|
||||||
|
result?.data?.error?.message || result?.data?.message || ''
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setMessageId();
|
setMessageId();
|
||||||
const { reader, decoder } = result;
|
const { reader, decoder } = result;
|
||||||
await readStreamData(reader, decoder, (chunk: any) => {
|
await readStreamData(reader, decoder, (chunk: any) => {
|
||||||
|
if (chunk?.error) {
|
||||||
|
setTokenResult({
|
||||||
|
error: true,
|
||||||
|
errorMessage: chunk?.error?.message || chunk?.message || ''
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
joinMessage(chunk);
|
joinMessage(chunk);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -330,6 +321,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
<div className="box">
|
<div className="box">
|
||||||
<ParamsSettings
|
<ParamsSettings
|
||||||
setParams={setParams}
|
setParams={setParams}
|
||||||
|
params={parameters}
|
||||||
selectedModel={selectModel}
|
selectedModel={selectModel}
|
||||||
modelList={modelList}
|
modelList={modelList}
|
||||||
/>
|
/>
|
||||||
@@ -338,8 +330,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
<ViewCodeModal
|
<ViewCodeModal
|
||||||
open={show}
|
open={show}
|
||||||
systemMessage={systemMessage}
|
messageList={viewCodeMessage}
|
||||||
messageList={messageList}
|
|
||||||
parameters={parameters}
|
parameters={parameters}
|
||||||
onCancel={handleCloseViewCode}
|
onCancel={handleCloseViewCode}
|
||||||
title={intl.formatMessage({ id: 'playground.viewcode' })}
|
title={intl.formatMessage({ id: 'playground.viewcode' })}
|
||||||
|
|||||||
@@ -0,0 +1,355 @@
|
|||||||
|
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: 'Results',
|
||||||
|
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}
|
||||||
|
textListCount={textList.length || 0}
|
||||||
|
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,110 @@
|
|||||||
|
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={`${index + 1}.`}
|
||||||
|
value={text.text}
|
||||||
|
placeholder="Input your 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 IconFont from '@/components/icon-font';
|
||||||
import HotKeys, { KeyMap } from '@/config/hotkeys';
|
import HotKeys, { KeyMap } from '@/config/hotkeys';
|
||||||
import { platformCall } from '@/utils';
|
|
||||||
import { ClearOutlined, SendOutlined, SwapOutlined } from '@ant-design/icons';
|
import { ClearOutlined, SendOutlined, SwapOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Divider, Input, Select, Tooltip } from 'antd';
|
import { Button, Divider, Input, Select, Tooltip } from 'antd';
|
||||||
@@ -61,18 +60,19 @@ interface MessageInputProps {
|
|||||||
handleAbortFetch: () => void;
|
handleAbortFetch: () => void;
|
||||||
updateLayout?: (value: { span: number; count: number }) => void;
|
updateLayout?: (value: { span: number; count: number }) => void;
|
||||||
clearAll: () => void;
|
clearAll: () => void;
|
||||||
setModelSelections: (
|
setModelSelections?: (
|
||||||
modelList: (Global.BaseOption<string> & {
|
modelList: (Global.BaseOption<string> & {
|
||||||
instanceId: symbol;
|
instanceId: symbol;
|
||||||
})[]
|
})[]
|
||||||
) => void;
|
) => void;
|
||||||
presetPrompt: (list: CurrentMessage[]) => void;
|
presetPrompt?: (list: CurrentMessage[]) => void;
|
||||||
addMessage: (message: CurrentMessage) => void;
|
addMessage?: (message: CurrentMessage) => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
showModelSelection?: boolean;
|
showModelSelection?: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
isEmpty?: boolean;
|
isEmpty?: boolean;
|
||||||
scope: string;
|
scope: string;
|
||||||
|
placeholer?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageInput: React.FC<MessageInputProps> = ({
|
const MessageInput: React.FC<MessageInputProps> = ({
|
||||||
@@ -88,12 +88,11 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
showModelSelection,
|
showModelSelection,
|
||||||
disabled,
|
disabled,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
scope
|
scope,
|
||||||
|
placeholer
|
||||||
}) => {
|
}) => {
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const platform = platformCall();
|
|
||||||
// const [disabled, setDisabled] = useState(false);
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [focused, setFocused] = useState(false);
|
const [focused, setFocused] = useState(false);
|
||||||
const [message, setMessage] = useState<CurrentMessage>({
|
const [message, setMessage] = useState<CurrentMessage>({
|
||||||
@@ -130,11 +129,9 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
resetMessage();
|
resetMessage();
|
||||||
};
|
};
|
||||||
const onStop = () => {
|
const onStop = () => {
|
||||||
// setDisabled(false);
|
|
||||||
handleAbortFetch();
|
handleAbortFetch();
|
||||||
};
|
};
|
||||||
const handleLayoutChange = (value: { span: number; count: number }) => {
|
const handleLayoutChange = (value: { span: number; count: number }) => {
|
||||||
console.log('layout change:', value);
|
|
||||||
updateLayout?.(value);
|
updateLayout?.(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -151,7 +148,6 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateModelSelections = (value: string[]) => {
|
const handleUpdateModelSelections = (value: string[]) => {
|
||||||
console.log('update model selections:', value);
|
|
||||||
const list = value?.map?.((val) => {
|
const list = value?.map?.((val) => {
|
||||||
return {
|
return {
|
||||||
value: val,
|
value: val,
|
||||||
@@ -159,17 +155,21 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
instanceId: Symbol(val)
|
instanceId: Symbol(val)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setModelSelections(list);
|
setModelSelections?.(list);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenPrompt = () => {
|
const handleOpenPrompt = () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddMessage = () => {
|
const handleAddMessage = (e?: any) => {
|
||||||
console.log('add message=====1');
|
e?.preventDefault();
|
||||||
addMessage({ ...message });
|
addMessage?.({ ...message });
|
||||||
resetMessage();
|
resetMessage();
|
||||||
|
setFocused(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
inputRef.current?.focus?.();
|
||||||
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPasteContent = useCallback(
|
const getPasteContent = useCallback(
|
||||||
@@ -246,14 +246,26 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOnPaste = (e: any) => {
|
const handleOnPaste = (e: any) => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
|
|
||||||
const text = e.clipboardData.getData('text');
|
const text = e.clipboardData.getData('text');
|
||||||
if (text) {
|
if (text) {
|
||||||
setMessage?.({
|
// const startPos = e.target.selectionStart;
|
||||||
...message,
|
// const endPos = e.target.selectionEnd;
|
||||||
content: message.content + text
|
// setMessage?.({
|
||||||
});
|
// ...message,
|
||||||
|
// content:
|
||||||
|
// message.content.slice(0, startPos) +
|
||||||
|
// text +
|
||||||
|
// message.content.slice(endPos)
|
||||||
|
// });
|
||||||
|
// if (endPos !== startPos) {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// e.target.setSelectionRange(endPos, endPos);
|
||||||
|
// }, 0);
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
|
e.preventDefault();
|
||||||
getPasteContent(e);
|
getPasteContent(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -284,6 +296,10 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
[message, handleDeleteLastImage]
|
[message, handleDeleteLastImage]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleSelectPrompt = (list: CurrentMessage[]) => {
|
||||||
|
presetPrompt?.(list);
|
||||||
|
};
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
HotKeys.SUBMIT,
|
HotKeys.SUBMIT,
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
@@ -325,6 +341,8 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
<div className="messageInput">
|
<div className="messageInput">
|
||||||
<div className="tool-bar">
|
<div className="tool-bar">
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
|
{scope !== 'reranker' && (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
size="middle"
|
size="middle"
|
||||||
@@ -334,7 +352,14 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
{intl.formatMessage({ id: `playground.${message.role}` })}
|
{intl.formatMessage({ id: `playground.${message.role}` })}
|
||||||
</Button>
|
</Button>
|
||||||
<Divider type="vertical" style={{ margin: 0 }} />
|
<Divider type="vertical" style={{ margin: 0 }} />
|
||||||
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
|
{message.role === Roles.User && (
|
||||||
|
<UploadImg
|
||||||
|
handleUpdateImgList={handleUpdateImgList}
|
||||||
|
size="middle"
|
||||||
|
></UploadImg>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={intl.formatMessage({ id: 'playground.toolbar.clearmsg' })}
|
title={intl.formatMessage({ id: 'playground.toolbar.clearmsg' })}
|
||||||
>
|
>
|
||||||
@@ -342,20 +367,10 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
type="text"
|
type="text"
|
||||||
icon={<ClearOutlined />}
|
icon={<ClearOutlined />}
|
||||||
size="middle"
|
size="middle"
|
||||||
|
disabled={loading}
|
||||||
onClick={handleClearAll}
|
onClick={handleClearAll}
|
||||||
></Button>
|
></Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* <Tooltip
|
|
||||||
title={intl.formatMessage({ id: 'playground.toolbar.prompts' })}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<ControlOutlined />}
|
|
||||||
size="middle"
|
|
||||||
onClick={handleOpenPrompt}
|
|
||||||
></Button>
|
|
||||||
</Tooltip> */}
|
|
||||||
{updateLayout && (
|
{updateLayout && (
|
||||||
<>
|
<>
|
||||||
<Divider type="vertical" style={{ margin: 0 }} />
|
<Divider type="vertical" style={{ margin: 0 }} />
|
||||||
@@ -391,6 +406,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
></Select>
|
></Select>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{scope !== 'reranker' && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
<span>
|
<span>
|
||||||
@@ -403,6 +419,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
{intl.formatMessage({ id: 'common.button.add' })}
|
{intl.formatMessage({ id: 'common.button.add' })}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
)}
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
@@ -440,6 +457,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
onDelete={handleDeleteImg}
|
onDelete={handleDeleteImg}
|
||||||
></ThumbImg>
|
></ThumbImg>
|
||||||
<div className="input-box">
|
<div className="input-box">
|
||||||
|
{scope !== 'reranker' ? (
|
||||||
<TextArea
|
<TextArea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
autoSize={{ minRows: 3, maxRows: 8 }}
|
autoSize={{ minRows: 3, maxRows: 8 }}
|
||||||
@@ -452,11 +470,26 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onPaste={handleOnPaste}
|
onPaste={handleOnPaste}
|
||||||
></TextArea>
|
></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 && (
|
{!message.content && !focused && (
|
||||||
<span
|
<span
|
||||||
className="holder"
|
className="holder"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: intl.formatMessage({ id: 'playground.input.holder' })
|
__html:
|
||||||
|
placeholer ??
|
||||||
|
intl.formatMessage({ id: 'playground.input.holder' })
|
||||||
}}
|
}}
|
||||||
></span>
|
></span>
|
||||||
)}
|
)}
|
||||||
@@ -464,7 +497,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
<PromptModal
|
<PromptModal
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onSelect={presetPrompt}
|
onSelect={handleSelectPrompt}
|
||||||
></PromptModal>
|
></PromptModal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface MessageItemProps {
|
|||||||
data: MessageItem;
|
data: MessageItem;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
actions?: string[];
|
||||||
updateMessage?: (message: MessageItem) => void;
|
updateMessage?: (message: MessageItem) => void;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,8 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
|||||||
onDelete,
|
onDelete,
|
||||||
loading,
|
loading,
|
||||||
data,
|
data,
|
||||||
editable
|
editable,
|
||||||
|
actions = ['upload', 'delete', 'copy']
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
@@ -56,7 +58,6 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
|||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
let item = items[i];
|
let item = items[i];
|
||||||
console.log('item===========', item);
|
|
||||||
|
|
||||||
if (item.kind === 'file' && item.type.indexOf('image') !== -1) {
|
if (item.kind === 'file' && item.type.indexOf('image') !== -1) {
|
||||||
const file = item.getAsFile();
|
const file = item.getAsFile();
|
||||||
@@ -100,26 +101,34 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
|||||||
console.error('Error processing images:', error);
|
console.error('Error processing images:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[data]
|
[data, updateMessage]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOnPaste = (e: any) => {
|
const handleOnPaste = (e: any) => {
|
||||||
e.preventDefault();
|
// e.preventDefault();
|
||||||
const text = e.clipboardData.getData('text');
|
const text = e.clipboardData.getData('text');
|
||||||
if (text) {
|
if (text) {
|
||||||
updateMessage?.({
|
// const startPos = e.target.selectionStart;
|
||||||
role: data.role,
|
// const endPos = e.target.selectionEnd;
|
||||||
content: data.content + text,
|
// updateMessage?.({
|
||||||
uid: data.uid
|
// role: data.role,
|
||||||
});
|
// content:
|
||||||
|
// data.content.slice(0, startPos) + text + data.content.slice(endPos),
|
||||||
|
// uid: data.uid
|
||||||
|
// });
|
||||||
|
// if (endPos !== startPos) {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// e.target.setSelectionRange(endPos, endPos);
|
||||||
|
// }, 0);
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
|
e.preventDefault();
|
||||||
getPasteContent(e);
|
getPasteContent(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateImgList = useCallback(
|
const handleUpdateImgList = useCallback(
|
||||||
(list: { uid: number | string; dataUrl: string }[]) => {
|
(list: { uid: number | string; dataUrl: string }[]) => {
|
||||||
console.log('list===========', data.imgs, list);
|
|
||||||
updateMessage?.({
|
updateMessage?.({
|
||||||
role: data.role,
|
role: data.role,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
@@ -127,7 +136,7 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
|||||||
imgs: [...(data.imgs || []), ...list]
|
imgs: [...(data.imgs || []), ...list]
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[data]
|
[data, updateMessage]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDeleteImg = (uid: number | string) => {
|
const handleDeleteImg = (uid: number | string) => {
|
||||||
@@ -177,13 +186,13 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
|||||||
<div className="content-item">
|
<div className="content-item">
|
||||||
<div className="content-item-role">
|
<div className="content-item-role">
|
||||||
<div className="role">
|
<div className="role">
|
||||||
{intl.formatMessage({ id: `playground.${data.role}` })}
|
{data.title ?? intl.formatMessage({ id: `playground.${data.role}` })}
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
{editable && (
|
{actions.includes('upload') && data.role === Roles.User && (
|
||||||
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
|
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
|
||||||
)}
|
)}
|
||||||
{data.content && (
|
{data.content && actions.includes('copy') && (
|
||||||
<CopyButton
|
<CopyButton
|
||||||
text={data.content}
|
text={data.content}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -192,7 +201,7 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
|||||||
fontSize="12px"
|
fontSize="12px"
|
||||||
></CopyButton>
|
></CopyButton>
|
||||||
)}
|
)}
|
||||||
{editable && (
|
{actions.includes('delete') && (
|
||||||
<Tooltip title={intl.formatMessage({ id: 'common.button.delete' })}>
|
<Tooltip title={intl.formatMessage({ id: 'common.button.delete' })}>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -230,9 +239,7 @@ const ContentItem: React.FC<MessageItemProps> = ({
|
|||||||
></Input.TextArea>
|
></Input.TextArea>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="content-item-content">
|
<div className="content-item-content">{data.content}</div>
|
||||||
<span>{data.content}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import ContentItem from './content-item';
|
|||||||
|
|
||||||
interface MessageContentProps {
|
interface MessageContentProps {
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
spans: {
|
spans?: {
|
||||||
span: number;
|
span: number;
|
||||||
count: number;
|
count: number;
|
||||||
};
|
};
|
||||||
|
actions?: string[];
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
messageList: MessageItem[];
|
messageList: MessageItem[];
|
||||||
setMessageList?: (list: any) => void;
|
setMessageList?: (list: any) => void;
|
||||||
@@ -17,8 +18,8 @@ interface MessageContentProps {
|
|||||||
const MessageContent: React.FC<MessageContentProps> = ({
|
const MessageContent: React.FC<MessageContentProps> = ({
|
||||||
setMessageList,
|
setMessageList,
|
||||||
messageList,
|
messageList,
|
||||||
spans,
|
editable,
|
||||||
editable
|
actions = ['upload', 'delete', 'copy']
|
||||||
}) => {
|
}) => {
|
||||||
const updateMessage = (index: number, message: MessageItem) => {
|
const updateMessage = (index: number, message: MessageItem) => {
|
||||||
const newMessageList = [...messageList];
|
const newMessageList = [...messageList];
|
||||||
@@ -41,6 +42,7 @@ const MessageContent: React.FC<MessageContentProps> = ({
|
|||||||
key={item.uid}
|
key={item.uid}
|
||||||
data={item}
|
data={item}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
|
actions={actions}
|
||||||
onDelete={() => handleDelete(index)}
|
onDelete={() => handleDelete(index)}
|
||||||
updateMessage={(data) => updateMessage(index, data)}
|
updateMessage={(data) => updateMessage(index, data)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import _ from 'lodash';
|
|||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
@@ -23,7 +24,7 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import 'simplebar-react/dist/simplebar.min.css';
|
import 'simplebar-react/dist/simplebar.min.css';
|
||||||
import { CHAT_API } from '../../apis';
|
import { CHAT_API } from '../../apis';
|
||||||
import { Roles } from '../../config';
|
import { Roles, generateMessages } from '../../config';
|
||||||
import CompareContext from '../../config/compare-context';
|
import CompareContext from '../../config/compare-context';
|
||||||
import { MessageItem, ModelSelectionItem } from '../../config/types';
|
import { MessageItem, ModelSelectionItem } from '../../config/types';
|
||||||
import '../../style/model-item.less';
|
import '../../style/model-item.less';
|
||||||
@@ -69,6 +70,13 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
|
|
||||||
const { initialize, updateScrollerPosition } = useOverlayScroller();
|
const { initialize, updateScrollerPosition } = useOverlayScroller();
|
||||||
|
|
||||||
|
const viewCodeMessage = useMemo(() => {
|
||||||
|
return generateMessages([
|
||||||
|
{ role: Roles.System, content: systemMessage },
|
||||||
|
...messageList
|
||||||
|
]);
|
||||||
|
}, [messageList, systemMessage]);
|
||||||
|
|
||||||
const setMessageId = () => {
|
const setMessageId = () => {
|
||||||
messageId.current = messageId.current + 1;
|
messageId.current = messageId.current + 1;
|
||||||
};
|
};
|
||||||
@@ -121,58 +129,25 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
setMessageList((preList) => {
|
setMessageList((preList) => {
|
||||||
return [...preList, ...currentMessageRef.current];
|
return [...preList, ...currentMessageRef.current];
|
||||||
});
|
});
|
||||||
const messages = _.map(
|
|
||||||
[...messageList, ...currentMessageRef.current],
|
|
||||||
(item: MessageItem) => {
|
|
||||||
return {
|
|
||||||
role: item.role,
|
|
||||||
content: item.content,
|
|
||||||
imgs: item.imgs || []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
contentRef.current = '';
|
contentRef.current = '';
|
||||||
// ====== payload =================
|
// ====== payload =================
|
||||||
const formatMessages = _.map(messages, (item: MessageItem) => {
|
|
||||||
return {
|
const messageParams = [
|
||||||
role: item.role,
|
{ role: Roles.System, content: systemMessage },
|
||||||
content: [
|
...messageList,
|
||||||
{
|
...currentMessageRef.current
|
||||||
type: 'text',
|
];
|
||||||
text: item.content
|
|
||||||
},
|
const messages = generateMessages(messageParams);
|
||||||
..._.map(
|
|
||||||
item.imgs,
|
|
||||||
(img: { uid: string | number; dataUrl: string }) => {
|
|
||||||
return {
|
|
||||||
type: 'image_url',
|
|
||||||
image_url: {
|
|
||||||
url: img.dataUrl
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const chatParams = {
|
const chatParams = {
|
||||||
messages: systemMessage
|
messages: messages,
|
||||||
? [
|
|
||||||
{
|
|
||||||
role: Roles.System,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: systemMessage
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
...formatMessages
|
|
||||||
]
|
|
||||||
: [...formatMessages],
|
|
||||||
...params,
|
...params,
|
||||||
stream: true
|
stream: true,
|
||||||
|
stream_options: {
|
||||||
|
include_usage: true
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// ============== payload end ================
|
// ============== payload end ================
|
||||||
const result: any = await fetchChunkedData({
|
const result: any = await fetchChunkedData({
|
||||||
@@ -184,13 +159,21 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
setTokenResult({
|
setTokenResult({
|
||||||
error: true,
|
error: true,
|
||||||
errorMessage: result?.data?.message
|
errorMessage:
|
||||||
|
result?.data?.error?.message || result?.data?.message || ''
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setMessageId();
|
setMessageId();
|
||||||
const { reader, decoder } = result;
|
const { reader, decoder } = result;
|
||||||
await readStreamData(reader, decoder, (chunk: any) => {
|
await readStreamData(reader, decoder, (chunk: any) => {
|
||||||
|
if (chunk?.error) {
|
||||||
|
setTokenResult({
|
||||||
|
error: true,
|
||||||
|
errorMessage: chunk?.error?.message || chunk?.message || ''
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
joinMessage(chunk);
|
joinMessage(chunk);
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -213,7 +196,6 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleApplyToAllModels = (e: any) => {
|
const handleApplyToAllModels = (e: any) => {
|
||||||
console.log('checkbox change:', e.target.checked);
|
|
||||||
isApplyToAllModels.current = e.target.checked;
|
isApplyToAllModels.current = e.target.checked;
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setGlobalParams({
|
setGlobalParams({
|
||||||
@@ -222,10 +204,8 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnValuesChange = (
|
const handleOnValuesChange = useCallback(
|
||||||
changeValues: any,
|
(changeValues: any, allValues: Record<string, any>) => {
|
||||||
allValues: Record<string, any>
|
|
||||||
) => {
|
|
||||||
if (isApplyToAllModels.current) {
|
if (isApplyToAllModels.current) {
|
||||||
setParams({
|
setParams({
|
||||||
...params,
|
...params,
|
||||||
@@ -240,12 +220,13 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
...changeValues
|
...changeValues
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[params, isApplyToAllModels.current]
|
||||||
|
);
|
||||||
|
|
||||||
const handleClearMessage = () => {
|
const handleClearMessage = () => {
|
||||||
setMessageList([]);
|
setMessageList([]);
|
||||||
setTokenResult(null);
|
setTokenResult(null);
|
||||||
setSystemMessage('');
|
|
||||||
currentMessageRef.current = [];
|
currentMessageRef.current = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -331,7 +312,6 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
}, [modelList, intl]);
|
}, [modelList, intl]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('globalParams:', globalParams.model, globalParams);
|
|
||||||
setParams({
|
setParams({
|
||||||
...params,
|
...params,
|
||||||
model: model,
|
model: model,
|
||||||
@@ -381,19 +361,34 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
<div className="header">
|
<div className="header">
|
||||||
<span className="title">
|
<span className="title">
|
||||||
<Select
|
<Select
|
||||||
style={{ minWidth: '120px', maxWidth: '200px' }}
|
style={{ width: '100%' }}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
options={modelFullList}
|
options={modelFullList}
|
||||||
onChange={handleModelChange}
|
onChange={handleModelChange}
|
||||||
value={params.model}
|
value={params.model}
|
||||||
optionRender={(data) => {
|
labelRender={(data) => {
|
||||||
return (
|
return (
|
||||||
<AutoTooltip
|
<AutoTooltip
|
||||||
title={data.label}
|
|
||||||
ghost
|
ghost
|
||||||
tooltipProps={{
|
tooltipProps={{
|
||||||
placement: 'right'
|
placement: 'right'
|
||||||
}}
|
}}
|
||||||
|
minWidth={60}
|
||||||
|
maxWidth={180}
|
||||||
|
>
|
||||||
|
{data.label}
|
||||||
|
</AutoTooltip>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
optionRender={(data) => {
|
||||||
|
return (
|
||||||
|
<AutoTooltip
|
||||||
|
ghost
|
||||||
|
tooltipProps={{
|
||||||
|
placement: 'right'
|
||||||
|
}}
|
||||||
|
minWidth={60}
|
||||||
|
maxWidth={180}
|
||||||
>
|
>
|
||||||
{data.label}
|
{data.label}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
@@ -401,7 +396,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
}}
|
}}
|
||||||
></Select>
|
></Select>
|
||||||
</span>
|
</span>
|
||||||
<ReferenceParams usage={tokenResult}></ReferenceParams>
|
<ReferenceParams usage={tokenResult} scaleable></ReferenceParams>
|
||||||
<span className="action">
|
<span className="action">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
@@ -471,8 +466,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
</div>
|
</div>
|
||||||
<ViewCodeModal
|
<ViewCodeModal
|
||||||
open={show}
|
open={show}
|
||||||
systemMessage={systemMessage}
|
messageList={viewCodeMessage}
|
||||||
messageList={messageList}
|
|
||||||
parameters={params}
|
parameters={params}
|
||||||
onCancel={handleCloseViewCode}
|
onCancel={handleCloseViewCode}
|
||||||
title={intl.formatMessage({ id: 'playground.viewcode' })}
|
title={intl.formatMessage({ id: 'playground.viewcode' })}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { InfoCircleOutlined } from '@ant-design/icons';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form, InputNumber, Slider, Tooltip } from 'antd';
|
import { Form, InputNumber, Slider, Tooltip } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useEffect, useId, useState } from 'react';
|
import { memo, useCallback, useEffect, useId } from 'react';
|
||||||
import CustomLabelStyles from '../style/custom-label.less';
|
import CustomLabelStyles from '../style/custom-label.less';
|
||||||
|
|
||||||
type ParamsSettingsFormProps = {
|
type ParamsSettingsFormProps = {
|
||||||
@@ -34,12 +34,10 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
setParams,
|
setParams,
|
||||||
globalParams,
|
globalParams,
|
||||||
onValuesChange,
|
onValuesChange,
|
||||||
model,
|
|
||||||
modelList,
|
modelList,
|
||||||
showModelSelector = true
|
showModelSelector = true
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [ModelList, setModelList] = useState([]);
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
seed: null,
|
seed: null,
|
||||||
stop: null,
|
stop: null,
|
||||||
@@ -81,12 +79,15 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
console.log('handleOnFinishFailed', errorInfo);
|
console.log('handleOnFinishFailed', errorInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleValuesChange = (changedValues: any, allValues: any) => {
|
const handleValuesChange = useCallback(
|
||||||
console.log('changedValues===', changedValues);
|
(changedValues: any, allValues: any) => {
|
||||||
setParams?.(allValues);
|
setParams?.(allValues);
|
||||||
onValuesChange?.(changedValues, allValues);
|
onValuesChange?.(changedValues, allValues);
|
||||||
};
|
},
|
||||||
const handleFieldValueChange = (val: any, field: string) => {
|
[onValuesChange, setParams]
|
||||||
|
);
|
||||||
|
const handleFieldValueChange = useCallback(
|
||||||
|
(val: any, field: string) => {
|
||||||
const values = form.getFieldsValue();
|
const values = form.getFieldsValue();
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...values,
|
...values,
|
||||||
@@ -103,7 +104,9 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
[field]: val
|
[field]: val
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
[form, setParams, onValuesChange]
|
||||||
|
);
|
||||||
|
|
||||||
const handleResetParams = () => {
|
const handleResetParams = () => {
|
||||||
form.setFieldsValue(initialValues);
|
form.setFieldsValue(initialValues);
|
||||||
@@ -177,7 +180,7 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect showSearch options={modelList}></SealSelect>
|
<SealSelect showSearch={true} options={modelList}></SealSelect>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -296,4 +299,4 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ParamsSettings;
|
export default memo(ParamsSettings);
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { WarningOutlined } from '@ant-design/icons';
|
import { WarningOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Alert, Space, Tooltip } from 'antd';
|
import { Space, Tooltip, Typography } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import '../style/reference-params.less';
|
import '../style/reference-params.less';
|
||||||
|
|
||||||
interface ReferenceParamsProps {
|
interface ReferenceParamsProps {
|
||||||
|
showOutput?: boolean;
|
||||||
|
scaleable?: boolean;
|
||||||
usage: {
|
usage: {
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
@@ -19,29 +22,41 @@ interface ReferenceParamsProps {
|
|||||||
|
|
||||||
const ReferenceParams = (props: ReferenceParamsProps) => {
|
const ReferenceParams = (props: ReferenceParamsProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { usage } = props;
|
const { usage, showOutput = true, scaleable } = props;
|
||||||
if (!usage || _.isEmpty(usage)) {
|
if (!usage || _.isEmpty(usage)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (usage.error) {
|
if (usage.error) {
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Typography.Paragraph
|
||||||
type="error"
|
type="danger"
|
||||||
style={{ textAlign: 'center', paddingBlock: 0 }}
|
ellipsis={{
|
||||||
message={
|
rows: 2,
|
||||||
<span style={{ color: 'var(--ant-color-error)' }}>
|
tooltip: usage?.errorMessage
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
textAlign: 'center',
|
||||||
|
paddingBlock: 0,
|
||||||
|
margin: 0,
|
||||||
|
backgroundColor: 'var(--ant-color-error-bg)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<WarningOutlined className="m-r-8" />
|
<WarningOutlined className="m-r-8" />
|
||||||
{usage?.errorMessage}
|
{usage?.errorMessage}
|
||||||
</span>
|
</Typography.Paragraph>
|
||||||
}
|
|
||||||
banner
|
|
||||||
showIcon={false}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="reference-params">
|
<div
|
||||||
<span className="usage">
|
className={classNames('reference-params', {
|
||||||
|
scaleable: scaleable
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={classNames('usage', {
|
||||||
|
scaleable: scaleable
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
@@ -63,7 +78,12 @@ const ReferenceParams = (props: ReferenceParamsProps) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span className="usage">
|
{showOutput && (
|
||||||
|
<span
|
||||||
|
className={classNames('usage', {
|
||||||
|
scaleable: scaleable
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
@@ -82,6 +102,7 @@ const ReferenceParams = (props: ReferenceParamsProps) => {
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</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);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import AutoImage from '@/components/auto-image';
|
||||||
import { CloseCircleOutlined, EyeOutlined } from '@ant-design/icons';
|
import { CloseCircleOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
import { Image } from 'antd';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import '../style/thumb-img.less';
|
import '../style/thumb-img.less';
|
||||||
@@ -23,12 +23,18 @@ const ThumbImg: React.FC<{
|
|||||||
<div className="thumb-list-wrap">
|
<div className="thumb-list-wrap">
|
||||||
{_.map(dataList, (item: any) => {
|
{_.map(dataList, (item: any) => {
|
||||||
return (
|
return (
|
||||||
<span key={item.uid} className="thumb-img">
|
<span
|
||||||
|
key={item.uid}
|
||||||
|
className="thumb-img"
|
||||||
|
style={{
|
||||||
|
width: item.width,
|
||||||
|
height: item.height
|
||||||
|
}}
|
||||||
|
>
|
||||||
<span className="img">
|
<span className="img">
|
||||||
<Image
|
<AutoImage
|
||||||
src={item.dataUrl}
|
src={item.dataUrl}
|
||||||
width={56}
|
height={100}
|
||||||
height={56}
|
|
||||||
preview={{
|
preview={{
|
||||||
mask: <EyeOutlined />
|
mask: <EyeOutlined />
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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,23 +7,27 @@ import { debounce } from 'lodash';
|
|||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
interface UploadImgProps {
|
interface UploadImgProps {
|
||||||
|
size?: 'small' | 'middle' | 'large';
|
||||||
handleUpdateImgList: (
|
handleUpdateImgList: (
|
||||||
imgList: { dataUrl: string; uid: number | string }[]
|
imgList: { dataUrl: string; uid: number | string }[]
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UploadImg: React.FC<UploadImgProps> = ({ handleUpdateImgList }) => {
|
const UploadImg: React.FC<UploadImgProps> = ({
|
||||||
|
handleUpdateImgList,
|
||||||
|
size = 'small'
|
||||||
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const uploadRef = useRef<any>(null);
|
const uploadRef = useRef<any>(null);
|
||||||
|
|
||||||
const getBase64 = (file: RcFile): Promise<string> => {
|
const getBase64 = useCallback((file: RcFile): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
reader.onload = () => resolve(reader.result as string);
|
reader.onload = () => resolve(reader.result as string);
|
||||||
reader.onerror = (error) => reject(error);
|
reader.onerror = (error) => reject(error);
|
||||||
});
|
});
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const debouncedUpdate = useCallback(
|
const debouncedUpdate = useCallback(
|
||||||
debounce((base64List: { dataUrl: string; uid: number | string }[]) => {
|
debounce((base64List: { dataUrl: string; uid: number | string }[]) => {
|
||||||
@@ -32,13 +36,15 @@ const UploadImg: React.FC<UploadImgProps> = ({ handleUpdateImgList }) => {
|
|||||||
[handleUpdateImgList, intl]
|
[handleUpdateImgList, intl]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = async (info: any) => {
|
const handleChange = useCallback(
|
||||||
|
async (info: any) => {
|
||||||
const { fileList } = info;
|
const { fileList } = info;
|
||||||
|
|
||||||
const newFileList = await Promise.all(
|
const newFileList = await Promise.all(
|
||||||
fileList.map(async (item: UploadFile) => {
|
fileList.map(async (item: UploadFile) => {
|
||||||
if (item.originFileObj && !item.url) {
|
if (item.originFileObj && !item.url) {
|
||||||
const base64 = await getBase64(item.originFileObj as RcFile);
|
const base64 = await getBase64(item.originFileObj as RcFile);
|
||||||
|
|
||||||
item.url = base64;
|
item.url = base64;
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
@@ -57,7 +63,9 @@ const UploadImg: React.FC<UploadImgProps> = ({ handleUpdateImgList }) => {
|
|||||||
|
|
||||||
debouncedUpdate(base64List);
|
debouncedUpdate(base64List);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[debouncedUpdate, getBase64]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -71,7 +79,7 @@ const UploadImg: React.FC<UploadImgProps> = ({ handleUpdateImgList }) => {
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<Tooltip title={intl.formatMessage({ id: 'playground.img.upload' })}>
|
<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>
|
</Tooltip>
|
||||||
</Upload>
|
</Upload>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { BulbOutlined } from '@ant-design/icons';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Modal } from 'antd';
|
import { Button, Modal } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
|
|
||||||
type ViewModalProps = {
|
type ViewModalProps = {
|
||||||
systemMessage?: string;
|
systemMessage?: string;
|
||||||
@@ -33,16 +33,12 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
title,
|
title,
|
||||||
open,
|
open,
|
||||||
onCancel,
|
onCancel,
|
||||||
systemMessage,
|
|
||||||
messageList,
|
messageList,
|
||||||
parameters = {},
|
parameters = {},
|
||||||
apiType = 'chat'
|
apiType = 'chat'
|
||||||
} = props || {};
|
} = props || {};
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const editorRef = useRef(null);
|
|
||||||
const [loaded, setLoaded] = useState(false);
|
|
||||||
const [codeValue, setCodeValue] = useState('');
|
|
||||||
const [lang, setLang] = useState(langMap.shell);
|
const [lang, setLang] = useState(langMap.shell);
|
||||||
|
|
||||||
const BaseURL = `${window.location.origin}/v1-openai`;
|
const BaseURL = `${window.location.origin}/v1-openai`;
|
||||||
@@ -51,42 +47,9 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
const logcommand =
|
const logcommand =
|
||||||
apiType === 'chat' ? 'choices[0].message.content' : 'data[0].embedding';
|
apiType === 'chat' ? 'choices[0].message.content' : 'data[0].embedding';
|
||||||
|
|
||||||
const generateCode = () => {
|
const codeValue = useMemo(() => {
|
||||||
const systemList = systemMessage
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: systemMessage
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const formatMessageList = _.map(messageList, (item: any) => {
|
|
||||||
return {
|
|
||||||
role: item.role,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: item.content
|
|
||||||
},
|
|
||||||
..._.map(item.imgs, (img: any) => {
|
|
||||||
return {
|
|
||||||
type: 'image_url',
|
|
||||||
image_url: {
|
|
||||||
url: img.dataUrl
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})
|
|
||||||
]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (lang === langMap.shell) {
|
if (lang === langMap.shell) {
|
||||||
const messages = [...systemList, ...formatMessageList];
|
const messages = messageList;
|
||||||
const code = `curl ${window.location.origin}/v1-openai/${api} \\\n-H "Content-Type: application/json" \\\n-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\\n-d '${JSON.stringify(
|
const code = `curl ${window.location.origin}/v1-openai/${api} \\\n-H "Content-Type: application/json" \\\n-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\\n-d '${JSON.stringify(
|
||||||
{
|
{
|
||||||
...parameters,
|
...parameters,
|
||||||
@@ -95,9 +58,10 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
)}'`;
|
)}'`;
|
||||||
setCodeValue(code);
|
return code;
|
||||||
} else if (lang === langMap.javascript) {
|
}
|
||||||
const messages = [...systemList, ...formatMessageList];
|
if (lang === langMap.javascript) {
|
||||||
|
const messages = messageList;
|
||||||
const code = `const OpenAI = require("openai");\n\nconst openai = new OpenAI({\n "apiKey": "YOUR_GPUSTACK_API_KEY",\n "baseURL": "${BaseURL}"\n});\n\nasync function main(){\n const params = ${JSON.stringify(
|
const code = `const OpenAI = require("openai");\n\nconst openai = new OpenAI({\n "apiKey": "YOUR_GPUSTACK_API_KEY",\n "baseURL": "${BaseURL}"\n});\n\nasync function main(){\n const params = ${JSON.stringify(
|
||||||
{
|
{
|
||||||
...parameters,
|
...parameters,
|
||||||
@@ -106,8 +70,10 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
null,
|
null,
|
||||||
4
|
4
|
||||||
)};\nconst response = await openai.${ClientType}.create(params);\n console.log(response.${logcommand});\n}\nmain();`;
|
)};\nconst response = await openai.${ClientType}.create(params);\n console.log(response.${logcommand});\n}\nmain();`;
|
||||||
setCodeValue(code);
|
|
||||||
} else if (lang === langMap.python) {
|
return code;
|
||||||
|
}
|
||||||
|
if (lang === langMap.python) {
|
||||||
const formattedParams = _.keys(parameters).reduce(
|
const formattedParams = _.keys(parameters).reduce(
|
||||||
(acc: string, key: string) => {
|
(acc: string, key: string) => {
|
||||||
if (parameters[key] === null) {
|
if (parameters[key] === null) {
|
||||||
@@ -123,16 +89,13 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
);
|
);
|
||||||
const messages =
|
const messages =
|
||||||
apiType === 'chat'
|
apiType === 'chat'
|
||||||
? `messages=${JSON.stringify(
|
? `messages=${JSON.stringify(messageList, null, 2)}`
|
||||||
[...systemList, ...formatMessageList],
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}`
|
|
||||||
: '';
|
: '';
|
||||||
const code = `from openai import OpenAI\n\nclient = OpenAI(\n base_url="${BaseURL}", \n api_key="YOUR_GPUSTACK_API_KEY"\n)\n\nresponse = client.${ClientType}.create(\n${formattedParams} ${messages})\nprint(response.${logcommand})`;
|
const code = `from openai import OpenAI\n\nclient = OpenAI(\n base_url="${BaseURL}", \n api_key="YOUR_GPUSTACK_API_KEY"\n)\n\nresponse = client.${ClientType}.create(\n${formattedParams} ${messages})\nprint(response.${logcommand})`;
|
||||||
setCodeValue(code);
|
return code;
|
||||||
}
|
}
|
||||||
};
|
return '';
|
||||||
|
}, [lang, messageList, parameters]);
|
||||||
|
|
||||||
const handleOnChangeLang = (value: string) => {
|
const handleOnChangeLang = (value: string) => {
|
||||||
setLang(value);
|
setLang(value);
|
||||||
@@ -142,25 +105,6 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
setLang(langMap.shell);
|
setLang(langMap.shell);
|
||||||
onCancel();
|
onCancel();
|
||||||
};
|
};
|
||||||
const editorConfig = {
|
|
||||||
minimap: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
readOnly: true,
|
|
||||||
formatOnType: true,
|
|
||||||
formatOnPaste: true,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
scrollbar: {
|
|
||||||
verticalSliderSize: 8
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
generateCode();
|
|
||||||
}, [lang, systemMessage, messageList, parameters]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -191,6 +135,12 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
backgroundColor: 'var(--color-editor-dark)'
|
backgroundColor: 'var(--color-editor-dark)'
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingRight: 2,
|
||||||
|
paddingBottom: 2
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<HighlightCode
|
<HighlightCode
|
||||||
height={380}
|
height={380}
|
||||||
@@ -199,6 +149,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
lang={lang}
|
lang={lang}
|
||||||
copyable={false}
|
copyable={false}
|
||||||
></HighlightCode>
|
></HighlightCode>
|
||||||
|
</div>
|
||||||
</EditorWrap>
|
</EditorWrap>
|
||||||
<div
|
<div
|
||||||
style={{ marginTop: 10, display: 'flex', alignItems: 'baseline' }}
|
style={{ marginTop: 10, display: 'flex', alignItems: 'baseline' }}
|
||||||
@@ -234,4 +185,4 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ViewCodeModal;
|
export default React.memo(ViewCodeModal);
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { map } from 'lodash';
|
||||||
|
import { MessageItem } from './types';
|
||||||
|
|
||||||
export const Roles = {
|
export const Roles = {
|
||||||
User: 'user',
|
User: 'user',
|
||||||
Assistant: 'assistant',
|
Assistant: 'assistant',
|
||||||
@@ -13,3 +16,90 @@ export const playGroundRoles = [
|
|||||||
label: 'playground.assitant'
|
label: 'playground.assitant'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const formatMessageParams = (messageList: any[]) => {
|
||||||
|
const result: any[] = [];
|
||||||
|
|
||||||
|
messageList.forEach((item) => {
|
||||||
|
const { role, content, title, imgs, uid } = item;
|
||||||
|
content?.forEach((contentItem: any) => {
|
||||||
|
if (contentItem.type === 'text') {
|
||||||
|
result.push({
|
||||||
|
role,
|
||||||
|
content: contentItem.text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (contentItem.type === 'image_url') {
|
||||||
|
result.push({
|
||||||
|
role,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
...contentItem
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateMessagesByListContent = (messageList: any[]) => {
|
||||||
|
if (!messageList.length) return [];
|
||||||
|
|
||||||
|
return messageList.map((item: MessageItem) => {
|
||||||
|
const content = map(
|
||||||
|
item.imgs,
|
||||||
|
(img: { uid: string | number; dataUrl: string }) => {
|
||||||
|
return {
|
||||||
|
type: 'image_url',
|
||||||
|
image_url: {
|
||||||
|
url: img.dataUrl
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (item.content) {
|
||||||
|
content.push({
|
||||||
|
type: 'text',
|
||||||
|
text: item.content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
role: item.role,
|
||||||
|
content: content
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateMessages = (messageList: Omit<MessageItem, 'uid'>[]) => {
|
||||||
|
if (!messageList.length) return [];
|
||||||
|
|
||||||
|
const result: any[] = [];
|
||||||
|
|
||||||
|
messageList.forEach((item: Omit<MessageItem, 'uid'>) => {
|
||||||
|
if (item.imgs?.length) {
|
||||||
|
const imgList = item.imgs.map((img) => {
|
||||||
|
return {
|
||||||
|
type: 'image_url',
|
||||||
|
image_url: {
|
||||||
|
url: img.dataUrl
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
result.push({
|
||||||
|
role: item.role,
|
||||||
|
content: imgList
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (item.content) {
|
||||||
|
result.push({
|
||||||
|
role: item.role,
|
||||||
|
content: item.content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface ModelSelectionItem extends Global.BaseOption<string> {
|
|||||||
export interface MessageItem {
|
export interface MessageItem {
|
||||||
role: string;
|
role: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
title?: string;
|
||||||
imgs?: { uid: string | number; dataUrl: string }[];
|
imgs?: { uid: string | number; dataUrl: string }[];
|
||||||
uid: number;
|
uid: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
import { fetchChunkedData, readStreamData } from '@/utils/fetch-chunk-data';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { useRef, useState } from 'react';
|
|
||||||
import { CHAT_API } from '../apis';
|
|
||||||
import { Roles } from '../config';
|
|
||||||
|
|
||||||
interface MessageItemProps {
|
|
||||||
role: string | number;
|
|
||||||
content: string;
|
|
||||||
uid: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const useChatCompletion = () => {
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const messageId = useRef<number>(0);
|
|
||||||
const [messageList, setMessageList] = useState<MessageItemProps[]>([]);
|
|
||||||
const contentRef = useRef<any>('');
|
|
||||||
const controllerRef = useRef<any>(null);
|
|
||||||
|
|
||||||
const setMessageId = () => {
|
|
||||||
messageId.current = messageId.current + 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const abortFetch = () => {
|
|
||||||
controllerRef.current?.abort?.();
|
|
||||||
};
|
|
||||||
|
|
||||||
const joinMessage = (chunk: any) => {
|
|
||||||
if (!chunk) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_.get(chunk, 'choices.0.finish_reason')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
contentRef.current =
|
|
||||||
contentRef.current + _.get(chunk, 'choices.0.delta.content', '');
|
|
||||||
setMessageList([
|
|
||||||
...messageList,
|
|
||||||
{
|
|
||||||
role: Roles.Assistant,
|
|
||||||
content: contentRef.current,
|
|
||||||
uid: messageId.current
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitMessage = async (pramas: {
|
|
||||||
parameters: Record<string, any>;
|
|
||||||
systemMessage: string;
|
|
||||||
}) => {
|
|
||||||
const { parameters, systemMessage } = pramas;
|
|
||||||
if (!parameters.model) return;
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
setMessageId();
|
|
||||||
console.log('messagelist=========2=', messageList);
|
|
||||||
controllerRef.current?.abort?.();
|
|
||||||
controllerRef.current = new AbortController();
|
|
||||||
const signal = controllerRef.current.signal;
|
|
||||||
const messages = _.map(messageList, (item: MessageItemProps) => {
|
|
||||||
return {
|
|
||||||
role: item.role,
|
|
||||||
content: item.content
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
contentRef.current = '';
|
|
||||||
const chatParams = {
|
|
||||||
messages: systemMessage
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
role: Roles.System,
|
|
||||||
content: systemMessage
|
|
||||||
},
|
|
||||||
...messages
|
|
||||||
]
|
|
||||||
: [...messages],
|
|
||||||
...parameters,
|
|
||||||
stream: true
|
|
||||||
};
|
|
||||||
const result = await fetchChunkedData({
|
|
||||||
data: chatParams,
|
|
||||||
url: CHAT_API,
|
|
||||||
signal
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { reader, decoder } = result;
|
|
||||||
await readStreamData(reader, decoder, (chunk: any) => {
|
|
||||||
joinMessage(chunk);
|
|
||||||
});
|
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
|
||||||
console.log('error=====', error);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
loading,
|
|
||||||
messageList,
|
|
||||||
setMessageList,
|
|
||||||
submitMessage,
|
|
||||||
abortFetch
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useChatCompletion;
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
|
import breakpoints from '@/config/breakpoints';
|
||||||
import HotKeys from '@/config/hotkeys';
|
import HotKeys from '@/config/hotkeys';
|
||||||
|
import useWindowResize from '@/hooks/use-window-resize';
|
||||||
import { MessageOutlined, OneToOneOutlined } from '@ant-design/icons';
|
import { MessageOutlined, OneToOneOutlined } from '@ant-design/icons';
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
@@ -10,14 +12,20 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { queryModelsList } from './apis';
|
import { queryModelsList } from './apis';
|
||||||
import GroundLeft from './components/ground-left';
|
import GroundLeft from './components/ground-left';
|
||||||
|
import GroundReranker from './components/ground-reranker';
|
||||||
import MultipleChat from './components/multiple-chat';
|
import MultipleChat from './components/multiple-chat';
|
||||||
import './style/play-ground.less';
|
import './style/play-ground.less';
|
||||||
|
|
||||||
const Playground: React.FC = () => {
|
const Playground: React.FC = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { size } = useWindowResize();
|
||||||
const [activeKey, setActiveKey] = useState('chat');
|
const [activeKey, setActiveKey] = useState('chat');
|
||||||
const groundLeftRef = useRef<any>(null);
|
const groundLeftRef = useRef<any>(null);
|
||||||
|
const groundRerankerRef = useRef<any>(null);
|
||||||
const [modelList, setModelList] = useState<Global.BaseOption<string>[]>([]);
|
const [modelList, setModelList] = useState<Global.BaseOption<string>[]>([]);
|
||||||
|
const [rerankerModelList, setRerankerModelList] = useState<
|
||||||
|
Global.BaseOption<string>[]
|
||||||
|
>([]);
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
const optionsList = [
|
const optionsList = [
|
||||||
{
|
{
|
||||||
@@ -30,6 +38,11 @@ const Playground: React.FC = () => {
|
|||||||
value: 'compare',
|
value: 'compare',
|
||||||
icon: <OneToOneOutlined />
|
icon: <OneToOneOutlined />
|
||||||
}
|
}
|
||||||
|
// {
|
||||||
|
// label: 'Rerank',
|
||||||
|
// value: 'reranker',
|
||||||
|
// icon: <FileSearchOutlined />
|
||||||
|
// }
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleViewCode = useCallback(() => {
|
const handleViewCode = useCallback(() => {
|
||||||
@@ -37,8 +50,12 @@ const Playground: React.FC = () => {
|
|||||||
}, [groundLeftRef]);
|
}, [groundLeftRef]);
|
||||||
|
|
||||||
const handleToggleCollapse = useCallback(() => {
|
const handleToggleCollapse = useCallback(() => {
|
||||||
|
if (activeKey === 'reranker') {
|
||||||
|
groundRerankerRef.current?.setCollapse?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
groundLeftRef.current?.setCollapse?.();
|
groundLeftRef.current?.setCollapse?.();
|
||||||
}, [groundLeftRef]);
|
}, [groundLeftRef, groundRerankerRef, activeKey]);
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
const items: TabsProps['items'] = [
|
||||||
{
|
{
|
||||||
@@ -52,9 +69,28 @@ const Playground: React.FC = () => {
|
|||||||
key: 'compare',
|
key: 'compare',
|
||||||
label: 'Compare',
|
label: 'Compare',
|
||||||
children: <MultipleChat modelList={modelList} loaded={loaded} />
|
children: <MultipleChat modelList={modelList} loaded={loaded} />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'reranker',
|
||||||
|
label: 'Reranker',
|
||||||
|
children: (
|
||||||
|
<GroundReranker
|
||||||
|
ref={groundRerankerRef}
|
||||||
|
modelList={rerankerModelList}
|
||||||
|
loaded={loaded}
|
||||||
|
></GroundReranker>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (size.width < breakpoints.lg) {
|
||||||
|
if (!groundLeftRef.current?.collapse) {
|
||||||
|
groundLeftRef.current?.setCollapse?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [size.width]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getModelList = async () => {
|
const getModelList = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -68,14 +104,43 @@ const Playground: React.FC = () => {
|
|||||||
label: item.id
|
label: item.id
|
||||||
};
|
};
|
||||||
}) as Global.BaseOption<string>[];
|
}) as Global.BaseOption<string>[];
|
||||||
setModelList(list);
|
return list;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(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);
|
setLoaded(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
getModelList();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderExtra = () => {
|
const renderExtra = () => {
|
||||||
@@ -136,7 +201,7 @@ const Playground: React.FC = () => {
|
|||||||
extra={renderExtra()}
|
extra={renderExtra()}
|
||||||
className={classNames('playground-container', {
|
className={classNames('playground-container', {
|
||||||
compare: activeKey === 'compare',
|
compare: activeKey === 'compare',
|
||||||
chat: activeKey === 'chat'
|
chat: activeKey !== 'compare'
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="play-ground">
|
<div className="play-ground">
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
.role {
|
.role {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
border-radius: var(--border-radius-mini);
|
border-radius: var(--border-radius-mini);
|
||||||
}
|
}
|
||||||
@@ -32,7 +31,7 @@
|
|||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
padding: 8px;
|
padding: 8px 14px;
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
border-radius: var(--border-radius-mini);
|
border-radius: var(--border-radius-mini);
|
||||||
background-color: var(--ant-color-fill-tertiary);
|
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;
|
display: flex;
|
||||||
|
|
||||||
.params-wrapper {
|
.params-wrapper {
|
||||||
|
position: relative;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
width: 390px;
|
width: 390px;
|
||||||
border-left: 1px solid var(--ant-color-split);
|
border-left: 1px solid var(--ant-color-split);
|
||||||
@@ -10,6 +11,10 @@
|
|||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
width: 390px;
|
width: 390px;
|
||||||
padding-inline: var(--layout-content-inlinepadding);
|
padding-inline: var(--layout-content-inlinepadding);
|
||||||
@@ -50,4 +55,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);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--ant-color-fill-tertiary);
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrap {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
width: 60px;
|
||||||
|
margin-left: 8px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--ant-color-border);
|
||||||
border-radius: var(--border-radius-base);
|
border-radius: var(--border-radius-base);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background-color: var(--color-white-1);
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
padding-inline: 2px 16px;
|
padding-inline: 2px 16px;
|
||||||
@@ -12,6 +13,12 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
height: 46px;
|
height: 46px;
|
||||||
border-bottom: 1px solid var(--ant-color-border);
|
border-bottom: 1px solid var(--ant-color-border);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
max-width: min(100%, 180px);
|
||||||
|
min-width: min(120px, 32%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
|
|||||||
@@ -6,7 +6,16 @@
|
|||||||
color: var(--ant-orange);
|
color: var(--ant-orange);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
|
|
||||||
|
&.scaleable {
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.usage {
|
.usage {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
line-height: 16px;
|
||||||
|
|
||||||
|
&.scaleable {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +1,6 @@
|
|||||||
.thumb-img {
|
.thumb-img {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
|
|
||||||
.img {
|
.img {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user