refactor: playground add comparsion
This commit is contained in:
+1
-1
@@ -14,7 +14,7 @@ const isProduction = env === 'production';
|
|||||||
const t = Date.now();
|
const t = Date.now();
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
proxy: {
|
proxy: {
|
||||||
...proxy('http://192.168.50.166:8080')
|
...proxy()
|
||||||
},
|
},
|
||||||
history: {
|
history: {
|
||||||
type: 'hash'
|
type: 'hash'
|
||||||
|
|||||||
+5
-1
@@ -28,6 +28,10 @@ export default {
|
|||||||
Message: {
|
Message: {
|
||||||
contentPadding: '12px 16px'
|
contentPadding: '12px 16px'
|
||||||
},
|
},
|
||||||
|
Tooltip: {
|
||||||
|
colorBgSpotlight: 'rgba(110,110,110,1)'
|
||||||
|
// sizePopupArrow: 0
|
||||||
|
},
|
||||||
Slider: {
|
Slider: {
|
||||||
handleSize: 8,
|
handleSize: 8,
|
||||||
handleSizeHover: 8,
|
handleSizeHover: 8,
|
||||||
@@ -42,7 +46,7 @@ export default {
|
|||||||
},
|
},
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: '#007BFF',
|
colorPrimary: '#007BFF',
|
||||||
borderRadius: 8,
|
borderRadius: 6,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
motion: true
|
motion: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import Editor from '@monaco-editor/react';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import EditorWrap from '../editor-wrap';
|
||||||
|
|
||||||
|
interface ViewerProps {
|
||||||
|
lang: string;
|
||||||
|
defaultLang?: string;
|
||||||
|
langOptions?: Global.BaseOption<string>[];
|
||||||
|
config?: any;
|
||||||
|
value: string;
|
||||||
|
height?: string | number;
|
||||||
|
theme?: string;
|
||||||
|
showHeader?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ViewerEditor: React.FC<ViewerProps> = (props) => {
|
||||||
|
const editorRef = useRef<any>(null);
|
||||||
|
const {
|
||||||
|
lang,
|
||||||
|
value,
|
||||||
|
config,
|
||||||
|
langOptions,
|
||||||
|
defaultLang,
|
||||||
|
height = 380,
|
||||||
|
showHeader
|
||||||
|
} = props;
|
||||||
|
const [langType, setLangType] = useState(defaultLang);
|
||||||
|
|
||||||
|
const handleBeforeMount = (monaco: any) => {
|
||||||
|
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
|
||||||
|
noSemanticValidation: false,
|
||||||
|
noSyntaxValidation: false,
|
||||||
|
diagnosticCodesToIgnore: [80001]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditorDidMount = (editor: any, monaco: any) => {
|
||||||
|
editorRef.current = editor;
|
||||||
|
console.log('loaded====', editor, monaco);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnChangeLang = (value: string) => {
|
||||||
|
setLangType(value);
|
||||||
|
};
|
||||||
|
const formatCode = () => {
|
||||||
|
if (editorRef.current) {
|
||||||
|
setTimeout(() => {
|
||||||
|
editorRef.current
|
||||||
|
?.getAction?.('editor.action.formatDocument')
|
||||||
|
?.run()
|
||||||
|
.then(() => {
|
||||||
|
console.log('format success');
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
formatCode();
|
||||||
|
setTimeout(() => {
|
||||||
|
const lineCount = editorRef.current?.getModel().getLineCount(); // 获取总行数
|
||||||
|
editorRef.current?.revealLine(lineCount); // 滚动到最后一行
|
||||||
|
}, 100); // 可以调整延时,确保编辑器完全加载
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditorWrap
|
||||||
|
copyText={value}
|
||||||
|
langOptions={langOptions}
|
||||||
|
defaultValue={lang}
|
||||||
|
showHeader={showHeader}
|
||||||
|
onChangeLang={handleOnChangeLang}
|
||||||
|
>
|
||||||
|
<Editor
|
||||||
|
height={height}
|
||||||
|
theme="vs-dark"
|
||||||
|
className="monaco-editor"
|
||||||
|
defaultLanguage={defaultLang}
|
||||||
|
language={langType}
|
||||||
|
value={value}
|
||||||
|
options={config}
|
||||||
|
beforeMount={handleBeforeMount}
|
||||||
|
onMount={handleEditorDidMount}
|
||||||
|
/>
|
||||||
|
</EditorWrap>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ViewerEditor);
|
||||||
@@ -28,7 +28,11 @@ const Wrapper: React.FC<WrapperProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{ ...style }}
|
style={{
|
||||||
|
padding: '0 calc(var(--ant-padding-sm) - 5px)',
|
||||||
|
width: '100%',
|
||||||
|
...style
|
||||||
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
wrapperStyle.wrapper,
|
wrapperStyle.wrapper,
|
||||||
wrapperStyle[`validate-status-${status}`],
|
wrapperStyle[`validate-status-${status}`],
|
||||||
@@ -51,8 +55,8 @@ const Wrapper: React.FC<WrapperProps> = ({
|
|||||||
></LabelInfo>
|
></LabelInfo>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
|
className="child-inner"
|
||||||
style={{
|
style={{
|
||||||
padding: '0 calc(var(--ant-padding-sm) - 5px)',
|
|
||||||
width: '100%'
|
width: '100%'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
+34
-10
@@ -21,10 +21,10 @@ html {
|
|||||||
--color-logs-text: #d4d4d4;
|
--color-logs-text: #d4d4d4;
|
||||||
--layout-content-blockpadding: 32px;
|
--layout-content-blockpadding: 32px;
|
||||||
--layout-content-inlinepadding: 32px;
|
--layout-content-inlinepadding: 32px;
|
||||||
--menu-border-radius-base: 8px;
|
--menu-border-radius-base: 6px;
|
||||||
--border-radius-base: 8px;
|
--border-radius-base: 6px;
|
||||||
--border-radius-middle: 20px;
|
--border-radius-middle: 20px;
|
||||||
--border-radius-small: 8px;
|
--border-radius-small: 6px;
|
||||||
--border-radius-mdium: 6px;
|
--border-radius-mdium: 6px;
|
||||||
--border-radius-mini: 4px;
|
--border-radius-mini: 4px;
|
||||||
--color-white-1: rgba(255, 255, 255, 100%);
|
--color-white-1: rgba(255, 255, 255, 100%);
|
||||||
@@ -39,11 +39,11 @@ html {
|
|||||||
--font-size-base: 12px;
|
--font-size-base: 12px;
|
||||||
--font-size-large: 16px;
|
--font-size-large: 16px;
|
||||||
--font-size-middle: 14px;
|
--font-size-middle: 14px;
|
||||||
--table-td-radius: 8px;
|
--table-td-radius: 6px;
|
||||||
--checkbox-border-radius: 4px;
|
--checkbox-border-radius: 4px;
|
||||||
--ant-table-cell-padding-inline: 16px;
|
--ant-table-cell-padding-inline: 16px;
|
||||||
--ant-table-cell-padding-block: 8px;
|
--ant-table-cell-padding-block: 6px;
|
||||||
--ant-table-header-border-radius: 8px;
|
--ant-table-header-border-radius: 6px;
|
||||||
--ant-table-header-split-color: #f0f0f0;
|
--ant-table-header-split-color: #f0f0f0;
|
||||||
--ant-table-row-selected-bg: #e6f6ff;
|
--ant-table-row-selected-bg: #e6f6ff;
|
||||||
--ant-table-row-selected-hover-bg: #e6f6ff;
|
--ant-table-row-selected-hover-bg: #e6f6ff;
|
||||||
@@ -59,7 +59,7 @@ html {
|
|||||||
--ant-input-active-border-color: #007bff;
|
--ant-input-active-border-color: #007bff;
|
||||||
--ant-input-hover-border-color: #2997ff;
|
--ant-input-hover-border-color: #2997ff;
|
||||||
--box-shadow-base: 0 4px 6px rgba(227, 232, 240, 70%);
|
--box-shadow-base: 0 4px 6px rgba(227, 232, 240, 70%);
|
||||||
--ant-border-radius-lg: 8px;
|
--ant-border-radius-lg: 6px;
|
||||||
--ant-menu-item-color: var(--color-text-1);
|
--ant-menu-item-color: var(--color-text-1);
|
||||||
--color-selected-bg: rgba(230, 230, 230, 88%);
|
--color-selected-bg: rgba(230, 230, 230, 88%);
|
||||||
// --box-shadow-base: none;
|
// --box-shadow-base: none;
|
||||||
@@ -75,7 +75,7 @@ html {
|
|||||||
|
|
||||||
&.ant-menu-css-var {
|
&.ant-menu-css-var {
|
||||||
// --ant-menu-item-selected-bg: var(--color-white-1);
|
// --ant-menu-item-selected-bg: var(--color-white-1);
|
||||||
--ant-menu-item-border-radius: 8px;
|
--ant-menu-item-border-radius: 6px;
|
||||||
--ant-menu-item-selected-color: var(--ant-color-primary);
|
--ant-menu-item-selected-color: var(--ant-color-primary);
|
||||||
--ant-menu-item-color: var(--color-text-1);
|
--ant-menu-item-color: var(--color-text-1);
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ html {
|
|||||||
--ant-font-size-xl: 20px;
|
--ant-font-size-xl: 20px;
|
||||||
--ant-font-size: var(--font-size-base);
|
--ant-font-size: var(--font-size-base);
|
||||||
--ant-padding-sm: 14px;
|
--ant-padding-sm: 14px;
|
||||||
--ant-border-radius-lg: 8px;
|
--ant-border-radius-lg: 6px;
|
||||||
--ant-color-text: #000;
|
--ant-color-text: #000;
|
||||||
--ant-color-error: #ff4d4f;
|
--ant-color-error: #ff4d4f;
|
||||||
--ant-color-bg-mask: rgba(0, 0, 0, 35%);
|
--ant-color-bg-mask: rgba(0, 0, 0, 35%);
|
||||||
@@ -126,7 +126,7 @@ html {
|
|||||||
.css-var-ri.ant-menu-css-var,
|
.css-var-ri.ant-menu-css-var,
|
||||||
.css-var-rh.ant-menu-css-var {
|
.css-var-rh.ant-menu-css-var {
|
||||||
--ant-menu-item-height: 46px;
|
--ant-menu-item-height: 46px;
|
||||||
--ant-menu-item-border-radius: 8px;
|
--ant-menu-item-border-radius: 6px;
|
||||||
--ant-menu-item-selected-color: var(--ant-color-primary);
|
--ant-menu-item-selected-color: var(--ant-color-primary);
|
||||||
--ant-menu-item-color: var(--color-text-1);
|
--ant-menu-item-color: var(--color-text-1);
|
||||||
--ant-menu-item-active-bg: rgba(0, 0, 0, 4%);
|
--ant-menu-item-active-bg: rgba(0, 0, 0, 4%);
|
||||||
@@ -293,12 +293,26 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider-logo {
|
.ant-pro-sider-logo {
|
||||||
|
position: relative;
|
||||||
padding-left: 18px;
|
padding-left: 18px;
|
||||||
|
padding-block: 12px;
|
||||||
border-block-end: none;
|
border-block-end: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider-logo-collapsed {
|
.ant-pro-sider-logo-collapsed {
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
|
|
||||||
|
.collapse-wrap {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.collapse-wrap {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider .ant-layout-sider-children {
|
.ant-pro-sider .ant-layout-sider-children {
|
||||||
@@ -427,6 +441,10 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-submenu-title {
|
.ant-menu-submenu-title {
|
||||||
@@ -500,6 +518,12 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============== segment start==============
|
||||||
|
.ant-segmented .ant-segmented-item::after {
|
||||||
|
border-radius: var(--border-radius-mini);
|
||||||
|
}
|
||||||
|
// =============== segment end ===============
|
||||||
|
|
||||||
.background {
|
.background {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
+22
-6
@@ -8,6 +8,7 @@ import VersionInfo, { modalConfig } from '@/components/version-info';
|
|||||||
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';
|
||||||
|
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
|
||||||
import { ProLayout } from '@ant-design/pro-components';
|
import { ProLayout } from '@ant-design/pro-components';
|
||||||
import {
|
import {
|
||||||
Link,
|
Link,
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
type IRoute
|
type IRoute
|
||||||
} from '@umijs/max';
|
} from '@umijs/max';
|
||||||
import { Modal } from 'antd';
|
import { Button, Modal } from 'antd';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import Exception from './Exception';
|
import Exception from './Exception';
|
||||||
@@ -139,6 +140,11 @@ export default (props: any) => {
|
|||||||
notFound: <span>404 not found</span>
|
notFound: <span>404 not found</span>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleToggleCollapse = (e: any) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setCollapsed(!collapsed);
|
||||||
|
};
|
||||||
|
|
||||||
const newRoutes = filterRoutes(
|
const newRoutes = filterRoutes(
|
||||||
clientRoutes.filter((route) => route.id === '@@/global-layout'),
|
clientRoutes.filter((route) => route.id === '@@/global-layout'),
|
||||||
(route) => {
|
(route) => {
|
||||||
@@ -151,7 +157,6 @@ export default (props: any) => {
|
|||||||
|
|
||||||
const role = initialState?.currentUser?.is_admin ? 'admin' : 'user';
|
const role = initialState?.currentUser?.is_admin ? 'admin' : 'user';
|
||||||
const [route] = useAccessMarkedRoutes(mapRoutes(newRoutes, role));
|
const [route] = useAccessMarkedRoutes(mapRoutes(newRoutes, role));
|
||||||
console.log('clientRoutes===========', route, clientRoutes, newRoutes);
|
|
||||||
|
|
||||||
patchRoutes({
|
patchRoutes({
|
||||||
routes: route.children,
|
routes: route.children,
|
||||||
@@ -162,7 +167,6 @@ export default (props: any) => {
|
|||||||
() => matchRoutes(route?.children || [], location.pathname)?.pop?.()?.route,
|
() => matchRoutes(route?.children || [], location.pathname)?.pop?.()?.route,
|
||||||
[location.pathname]
|
[location.pathname]
|
||||||
);
|
);
|
||||||
console.log('route===========', matchedRoute, route);
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="background"></div>
|
<div className="background"></div>
|
||||||
@@ -179,10 +183,22 @@ export default (props: any) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}}
|
}}
|
||||||
collapsed={collapsed}
|
menuHeaderRender={(logo, title) => {
|
||||||
onCollapse={(collapsed) => {
|
return (
|
||||||
setCollapsed(collapsed);
|
<>
|
||||||
|
{logo}
|
||||||
|
<div className="collapse-wrap">
|
||||||
|
<Button
|
||||||
|
type={collapsed ? 'default' : 'text'}
|
||||||
|
onClick={handleToggleCollapse}
|
||||||
|
>
|
||||||
|
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
|
collapsed={collapsed}
|
||||||
onPageChange={(route) => {
|
onPageChange={(route) => {
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import avatarImg from '@/assets/images/avatar.png';
|
import avatarImg from '@/assets/images/avatar.png';
|
||||||
import externalLinks from '@/constants/external-links';
|
import externalLinks from '@/constants/external-links';
|
||||||
import langConfigMap from '@/locales/lang-config-map';
|
import langConfigMap from '@/locales/lang-config-map';
|
||||||
@@ -18,7 +17,7 @@ import { getAllLocales, history, setLocale } from '@umijs/max';
|
|||||||
import { Avatar, Menu, Spin } from 'antd';
|
import { Avatar, Menu, Spin } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
export function getRightRenderContent(opts: {
|
export const getRightRenderContent = (opts: {
|
||||||
runtimeConfig: any;
|
runtimeConfig: any;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
initialState: any;
|
initialState: any;
|
||||||
@@ -26,7 +25,7 @@ export function getRightRenderContent(opts: {
|
|||||||
setInitialState: any;
|
setInitialState: any;
|
||||||
siderWidth: number;
|
siderWidth: number;
|
||||||
intl: any;
|
intl: any;
|
||||||
}) {
|
}) => {
|
||||||
const { intl, collapsed, siderWidth } = opts;
|
const { intl, collapsed, siderWidth } = opts;
|
||||||
|
|
||||||
const allLocals = getAllLocales();
|
const allLocals = getAllLocales();
|
||||||
@@ -121,7 +120,9 @@ export function getRightRenderContent(opts: {
|
|||||||
key: 'help',
|
key: 'help',
|
||||||
icon: <QuestionCircleOutlined />,
|
icon: <QuestionCircleOutlined />,
|
||||||
label: (
|
label: (
|
||||||
<span>{intl?.formatMessage?.({ id: 'common.button.help' })}</span>
|
<span className="sub-title">
|
||||||
|
{intl?.formatMessage?.({ id: 'common.button.help' })}
|
||||||
|
</span>
|
||||||
),
|
),
|
||||||
children: helpList.map((item) => ({
|
children: helpList.map((item) => ({
|
||||||
key: item.key,
|
key: item.key,
|
||||||
@@ -169,7 +170,7 @@ export function getRightRenderContent(opts: {
|
|||||||
key: 'lang',
|
key: 'lang',
|
||||||
icon: <GlobalOutlined />,
|
icon: <GlobalOutlined />,
|
||||||
label: (
|
label: (
|
||||||
<span>
|
<span className="sub-title">
|
||||||
{intl?.formatMessage?.({ id: 'common.settings.language' })}
|
{intl?.formatMessage?.({ id: 'common.settings.language' })}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
@@ -280,4 +281,4 @@ export function getRightRenderContent(opts: {
|
|||||||
></Menu>
|
></Menu>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -33,5 +33,11 @@ export default {
|
|||||||
'playground.delete.img': 'Delete Image',
|
'playground.delete.img': 'Delete Image',
|
||||||
'playground.img.upload': 'Upload Image',
|
'playground.img.upload': 'Upload Image',
|
||||||
'playground.img.upload.success': 'Upload Success',
|
'playground.img.upload.success': 'Upload Success',
|
||||||
'playground.img.upload.error': 'Upload Error'
|
'playground.img.upload.error': 'Upload Error',
|
||||||
|
'playground.toolbar.clearmsg': 'Clear Messages',
|
||||||
|
'playground.toolbar.prompts': 'Prompts',
|
||||||
|
'playground.toolbar.compare2Model': '2-Model Compare',
|
||||||
|
'playground.toolbar.compare3Model': '3-Model Compare',
|
||||||
|
'playground.toolbar.compare4Model': '4-Model Compare',
|
||||||
|
'playground.toolbar.compare6Model': '6-Model Compare'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,5 +33,11 @@ export default {
|
|||||||
'playground.delete.img': '删除图片',
|
'playground.delete.img': '删除图片',
|
||||||
'playground.img.upload': '上传图片',
|
'playground.img.upload': '上传图片',
|
||||||
'playground.img.upload.success': '上传成功',
|
'playground.img.upload.success': '上传成功',
|
||||||
'playground.img.upload.error': '上传失败'
|
'playground.img.upload.error': '上传失败',
|
||||||
|
'playground.toolbar.clearmsg': '清空消息',
|
||||||
|
'playground.toolbar.prompts': '提示词',
|
||||||
|
'playground.toolbar.compare2Model': '2 模型对比',
|
||||||
|
'playground.toolbar.compare3Model': '3 模型对比',
|
||||||
|
'playground.toolbar.compare4Model': '4 模型对比',
|
||||||
|
'playground.toolbar.compare6Model': '6 模型对比'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -252,11 +252,9 @@ const HFModelFile: React.FC<HFModelFileProps> = (props) => {
|
|||||||
{getModelQuantizationType(item)}
|
{getModelQuantizationType(item)}
|
||||||
{item.parts && item.parts.length > 1 && (
|
{item.parts && item.parts.length > 1 && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
color="var(--color-white-1)"
|
|
||||||
overlayInnerStyle={{
|
overlayInnerStyle={{
|
||||||
width: 150,
|
width: 180,
|
||||||
padding: 0,
|
padding: 0
|
||||||
color: 'var(--ant-color-text-secondary)'
|
|
||||||
}}
|
}}
|
||||||
title={
|
title={
|
||||||
<FileParts fileList={item.parts}></FileParts>
|
<FileParts fileList={item.parts}></FileParts>
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
|||||||
paddingInline: 'var(--ant-table-cell-padding-inline)'
|
paddingInline: 'var(--ant-table-cell-padding-inline)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title={renderWorkerInfo(item)}>
|
<Tooltip title={renderWorkerInfo(item)} trigger={['click']}>
|
||||||
<span className="m-r-5">{item.name}</span>
|
<span className="m-r-5">{item.name}</span>
|
||||||
<InfoCircleOutlined />
|
<InfoCircleOutlined />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import TransitionWrapper from '@/components/transition';
|
|
||||||
import HotKeys from '@/config/hotkeys';
|
import HotKeys from '@/config/hotkeys';
|
||||||
import useContainerScroll from '@/hooks/use-container-scorll';
|
import useContainerScroll from '@/hooks/use-container-scorll';
|
||||||
import { fetchChunkedData, readStreamData } from '@/utils/fetch-chunk-data';
|
import { fetchChunkedData, readStreamData } from '@/utils/fetch-chunk-data';
|
||||||
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
|
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl, useSearchParams } from '@umijs/max';
|
||||||
import { Button, Input, Spin, Tooltip } from 'antd';
|
import { Button, Tooltip } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
@@ -15,38 +15,33 @@ import {
|
|||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
import 'simplebar-react/dist/simplebar.min.css';
|
||||||
import { CHAT_API } from '../apis';
|
import { CHAT_API } from '../apis';
|
||||||
import { Roles } from '../config';
|
import { Roles } from '../config';
|
||||||
|
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';
|
||||||
import MessageInput from './message-input';
|
import MessageInput from './message-input';
|
||||||
import MessageItem from './message-item';
|
import MessageContent from './multiple-chat/message-content';
|
||||||
|
import SystemMessage from './multiple-chat/system-message';
|
||||||
|
import ParamsSettings from './params-settings';
|
||||||
import ViewCodeModal from './view-code-modal';
|
import ViewCodeModal from './view-code-modal';
|
||||||
|
|
||||||
interface MessageProps {
|
interface MessageProps {
|
||||||
parameters: any;
|
parameters?: any;
|
||||||
modelList: Global.BaseOption<string>[];
|
modelList: Global.BaseOption<string>[];
|
||||||
ref?: any;
|
ref?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MessageItemProps {
|
const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||||
role: string;
|
const { modelList } = props;
|
||||||
content: string;
|
|
||||||
uid: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|
||||||
const { parameters, modelList } = props;
|
|
||||||
const messageId = useRef<number>(0);
|
const messageId = useRef<number>(0);
|
||||||
const [messageList, setMessageList] = useState<MessageItemProps[]>([
|
const [messageList, setMessageList] = useState<MessageItem[]>([]);
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: '',
|
|
||||||
uid: messageId.current
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const selectModel = searchParams.get('model') || '';
|
||||||
|
const [parameters, setParams] = useState<any>({});
|
||||||
const [systemMessage, setSystemMessage] = useState('');
|
const [systemMessage, setSystemMessage] = useState('');
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
@@ -54,10 +49,14 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
const [activeIndex, setActiveIndex] = useState(-1);
|
const [activeIndex, setActiveIndex] = useState(-1);
|
||||||
const [tokenResult, setTokenResult] = useState<any>(null);
|
const [tokenResult, setTokenResult] = useState<any>(null);
|
||||||
const [currentIsFocus, setCurrentIsFocus] = useState(false);
|
const [currentIsFocus, setCurrentIsFocus] = useState(false);
|
||||||
|
const [collapse, setCollapse] = useState(false);
|
||||||
const systemRef = useRef<any>(null);
|
const systemRef = useRef<any>(null);
|
||||||
const contentRef = useRef<any>('');
|
const contentRef = useRef<any>('');
|
||||||
const controllerRef = useRef<any>(null);
|
const controllerRef = useRef<any>(null);
|
||||||
const scroller = useRef<any>(null);
|
const scroller = useRef<any>(null);
|
||||||
|
const currentMessageRef = useRef<any>(null);
|
||||||
|
const paramsScroller = useRef<any>(null);
|
||||||
|
const leftSimple = useRef<any>(null);
|
||||||
const { updateScrollerPosition, handleContentWheel } = useContainerScroll(
|
const { updateScrollerPosition, handleContentWheel } = useContainerScroll(
|
||||||
scroller,
|
scroller,
|
||||||
{ toBottom: true }
|
{ toBottom: true }
|
||||||
@@ -67,10 +66,18 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
updateScrollerPosition();
|
updateScrollerPosition();
|
||||||
}, [messageList]);
|
}, [messageList]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
paramsScroller.current?.recalculate();
|
||||||
|
leftSimple.current?.recalculate();
|
||||||
|
}, [collapse]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
return {
|
return {
|
||||||
viewCode() {
|
viewCode() {
|
||||||
setShow(true);
|
setShow(true);
|
||||||
|
},
|
||||||
|
setCollapse() {
|
||||||
|
setCollapse(!collapse);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -110,6 +117,7 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
contentRef.current + _.get(chunk, 'choices.0.delta.content', '');
|
contentRef.current + _.get(chunk, 'choices.0.delta.content', '');
|
||||||
setMessageList([
|
setMessageList([
|
||||||
...messageList,
|
...messageList,
|
||||||
|
...currentMessageRef.current,
|
||||||
{
|
{
|
||||||
role: Roles.Assistant,
|
role: Roles.Assistant,
|
||||||
content: contentRef.current,
|
content: contentRef.current,
|
||||||
@@ -122,7 +130,7 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitMessage = async () => {
|
const submitMessage = async (current?: { role: string; content: string }) => {
|
||||||
if (!parameters.model) return;
|
if (!parameters.model) return;
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -132,24 +140,60 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
controllerRef.current?.abort?.();
|
controllerRef.current?.abort?.();
|
||||||
controllerRef.current = new AbortController();
|
controllerRef.current = new AbortController();
|
||||||
const signal = controllerRef.current.signal;
|
const signal = controllerRef.current.signal;
|
||||||
const messages = _.map(messageList, (item: MessageItemProps) => {
|
currentMessageRef.current = current
|
||||||
return {
|
? [
|
||||||
role: item.role,
|
{
|
||||||
content: item.content
|
...current,
|
||||||
};
|
uid: messageId.current
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
setMessageList((pre) => {
|
||||||
|
return [...pre, ...currentMessageRef.current];
|
||||||
});
|
});
|
||||||
|
|
||||||
contentRef.current = '';
|
contentRef.current = '';
|
||||||
|
const formatMessages = _.map(
|
||||||
|
[...messageList, ...currentMessageRef.current],
|
||||||
|
(item: MessageItem) => {
|
||||||
|
return {
|
||||||
|
role: item.role,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
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: systemMessage
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
role: Roles.System,
|
role: Roles.System,
|
||||||
content: systemMessage
|
content: [
|
||||||
},
|
{
|
||||||
...messages
|
type: 'text',
|
||||||
|
text: systemMessage
|
||||||
|
}
|
||||||
]
|
]
|
||||||
: [...messages],
|
},
|
||||||
|
...formatMessages
|
||||||
|
]
|
||||||
|
: [...formatMessages],
|
||||||
...parameters,
|
...parameters,
|
||||||
stream: true
|
stream: true
|
||||||
};
|
};
|
||||||
@@ -162,6 +206,7 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setMessageId();
|
||||||
const { reader, decoder } = result;
|
const { reader, decoder } = result;
|
||||||
await readStreamData(reader, decoder, (chunk: any) => {
|
await readStreamData(reader, decoder, (chunk: any) => {
|
||||||
joinMessage(chunk);
|
joinMessage(chunk);
|
||||||
@@ -177,19 +222,19 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setMessageId();
|
setMessageId();
|
||||||
setMessageList([
|
setMessageList([]);
|
||||||
{
|
|
||||||
role: Roles.User,
|
|
||||||
content: '',
|
|
||||||
uid: messageId.current
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleView = () => {
|
const handleView = () => {
|
||||||
setShow(true);
|
setShow(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSendMessage = (message: { role: string; content: string }) => {
|
||||||
|
console.log('message:', message);
|
||||||
|
const currentMessage = message.content ? message : undefined;
|
||||||
|
submitMessage(currentMessage);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
submitMessage();
|
submitMessage();
|
||||||
};
|
};
|
||||||
@@ -203,7 +248,7 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
setMessageList([...messageList]);
|
setMessageList([...messageList]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateMessage = (index: number, message: MessageItemProps) => {
|
const handleUpdateMessage = (index: number, message: MessageItem) => {
|
||||||
messageList[index] = message;
|
messageList[index] = message;
|
||||||
setMessageList([...messageList]);
|
setMessageList([...messageList]);
|
||||||
};
|
};
|
||||||
@@ -239,7 +284,20 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const handleSelectModel = () => {};
|
const handleSelectModel = () => {};
|
||||||
|
|
||||||
const handlePresetPrompt = () => {};
|
const handlePresetPrompt = (list: { role: string; content: string }[]) => {
|
||||||
|
const sysMsg = list.filter((item) => item.role === 'system');
|
||||||
|
const userMsg = list
|
||||||
|
.filter((item) => item.role === 'user')
|
||||||
|
.map((item) => {
|
||||||
|
setMessageId();
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
uid: messageId.current
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setSystemMessage(sysMsg[0]?.content || '');
|
||||||
|
setMessageList(userMsg);
|
||||||
|
};
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
HotKeys.SUBMIT,
|
HotKeys.SUBMIT,
|
||||||
@@ -278,14 +336,21 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="ground-left-wrapper">
|
||||||
<div className="ground-left">
|
<div className="ground-left">
|
||||||
|
<div className="message-list-wrap" onWheel={handleContentWheel}>
|
||||||
<div
|
<div
|
||||||
className="message-list-wrap"
|
style={{
|
||||||
ref={scroller}
|
marginBottom: 20,
|
||||||
onWheel={handleContentWheel}
|
borderRadius: 'var(--border-radius-mini)',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ marginBottom: 40 }}>
|
<SystemMessage
|
||||||
<TransitionWrapper
|
systemMessage={systemMessage}
|
||||||
|
setSystemMessage={setSystemMessage}
|
||||||
|
></SystemMessage>
|
||||||
|
{/* <TransitionWrapper
|
||||||
header={renderLabel()}
|
header={renderLabel()}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
setCollapsed={setCollapsed}
|
setCollapsed={setCollapsed}
|
||||||
@@ -303,11 +368,11 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
})}
|
})}
|
||||||
onChange={handleSystemMessageChange}
|
onChange={handleSystemMessageChange}
|
||||||
></Input.TextArea>
|
></Input.TextArea>
|
||||||
</TransitionWrapper>
|
</TransitionWrapper> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div className="content">
|
||||||
{messageList.map((item, index) => {
|
{/* {messageList.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<MessageItem
|
<MessageItem
|
||||||
key={item.uid}
|
key={item.uid}
|
||||||
@@ -315,25 +380,37 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
islast={index === messageList.length - 1}
|
islast={index === messageList.length - 1}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onDelete={() => handleDelete(index)}
|
onDelete={() => handleDelete(index)}
|
||||||
updateMessage={(message: MessageItemProps) =>
|
updateMessage={(message: MessageItem) =>
|
||||||
handleUpdateMessage(index, message)
|
handleUpdateMessage(index, message)
|
||||||
}
|
}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
message={item}
|
message={item}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})} */}
|
||||||
{loading && (
|
<MessageContent
|
||||||
|
spans={{
|
||||||
|
span: 24,
|
||||||
|
count: 1
|
||||||
|
}}
|
||||||
|
messageList={messageList}
|
||||||
|
setMessageList={setMessageList}
|
||||||
|
editable={true}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
{/* {loading && (
|
||||||
<Spin>
|
<Spin>
|
||||||
<div style={{ height: '46px' }}></div>
|
<div style={{ height: '46px' }}></div>
|
||||||
</Spin>
|
</Spin>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ground-left-footer">
|
<div className="ground-left-footer">
|
||||||
<MessageInput
|
<MessageInput
|
||||||
loading={loading}
|
loading={loading}
|
||||||
handleSubmit={handleSubmit}
|
disabled={!parameters.model}
|
||||||
|
isEmpty={!messageList.length}
|
||||||
|
handleSubmit={handleSendMessage}
|
||||||
addMessage={handleNewMessage}
|
addMessage={handleNewMessage}
|
||||||
handleAbortFetch={handleStopConversation}
|
handleAbortFetch={handleStopConversation}
|
||||||
clearAll={handleClear}
|
clearAll={handleClear}
|
||||||
@@ -341,18 +418,19 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
presetPrompt={handlePresetPrompt}
|
presetPrompt={handlePresetPrompt}
|
||||||
modelList={modelList}
|
modelList={modelList}
|
||||||
/>
|
/>
|
||||||
{/* <ChatFooter
|
|
||||||
onClear={handleClear}
|
|
||||||
onNewMessage={handleNewMessage}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
onView={handleView}
|
|
||||||
onStop={handleStopConversation}
|
|
||||||
disabled={loading}
|
|
||||||
selectedModel={parameters.model}
|
|
||||||
hasTokenResult={!!tokenResult}
|
|
||||||
feedback={<ReferenceParams usage={tokenResult}></ReferenceParams>}
|
|
||||||
></ChatFooter> */}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classNames('params-wrapper', {
|
||||||
|
collapsed: collapse
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className="box">
|
||||||
|
<ParamsSettings setParams={setParams} selectedModel={selectModel} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ViewCodeModal
|
<ViewCodeModal
|
||||||
open={show}
|
open={show}
|
||||||
systemMessage={systemMessage}
|
systemMessage={systemMessage}
|
||||||
@@ -365,4 +443,4 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default memo(MessageList);
|
export default memo(GroundLeft);
|
||||||
|
|||||||
@@ -4,16 +4,22 @@ import { platformCall } from '@/utils';
|
|||||||
import {
|
import {
|
||||||
ClearOutlined,
|
ClearOutlined,
|
||||||
ControlOutlined,
|
ControlOutlined,
|
||||||
PictureOutlined,
|
EnterOutlined,
|
||||||
SwapOutlined
|
SwapOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Divider, Input, Select } from 'antd';
|
import { Button, Divider, Input, Select, Tooltip } from 'antd';
|
||||||
import { useState } from 'react';
|
import _ from 'lodash';
|
||||||
|
import { useCallback, useRef, useState } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { Roles } from '../config';
|
import { Roles } from '../config';
|
||||||
|
import { MessageItem } from '../config/types';
|
||||||
import '../style/message-input.less';
|
import '../style/message-input.less';
|
||||||
import PromptModal from './prompt-modal';
|
import PromptModal from './prompt-modal';
|
||||||
|
import ThumbImg from './thumb-img';
|
||||||
|
import UploadImg from './upload-img';
|
||||||
|
|
||||||
|
type CurrentMessage = Omit<MessageItem, 'uid'>;
|
||||||
|
|
||||||
const layoutOptions = [
|
const layoutOptions = [
|
||||||
{
|
{
|
||||||
@@ -23,7 +29,7 @@ const layoutOptions = [
|
|||||||
span: 12,
|
span: 12,
|
||||||
count: 2
|
count: 2
|
||||||
},
|
},
|
||||||
tips: 'two models compare'
|
tips: 'playground.toolbar.compare2Model'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '3 columns',
|
label: '3 columns',
|
||||||
@@ -32,7 +38,7 @@ const layoutOptions = [
|
|||||||
span: 8,
|
span: 8,
|
||||||
count: 3
|
count: 3
|
||||||
},
|
},
|
||||||
tips: 'three models compare'
|
tips: 'playground.toolbar.compare3Model'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '4 columns',
|
label: '4 columns',
|
||||||
@@ -41,7 +47,7 @@ const layoutOptions = [
|
|||||||
span: 12,
|
span: 12,
|
||||||
count: 4
|
count: 4
|
||||||
},
|
},
|
||||||
tips: 'four models compare'
|
tips: 'playground.toolbar.compare4Model'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '6 columns',
|
label: '6 columns',
|
||||||
@@ -50,13 +56,13 @@ const layoutOptions = [
|
|||||||
span: 8,
|
span: 8,
|
||||||
count: 6
|
count: 6
|
||||||
},
|
},
|
||||||
tips: 'six models compare'
|
tips: 'playground.toolbar.compare6Model'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
interface MessageInputProps {
|
interface MessageInputProps {
|
||||||
modelList: Global.BaseOption<string>[];
|
modelList: Global.BaseOption<string>[];
|
||||||
handleSubmit: (params: { role: string; content: string }) => void;
|
handleSubmit: (params: CurrentMessage) => void;
|
||||||
handleAbortFetch: () => void;
|
handleAbortFetch: () => void;
|
||||||
updateLayout?: (value: { span: number; count: number }) => void;
|
updateLayout?: (value: { span: number; count: number }) => void;
|
||||||
clearAll: () => void;
|
clearAll: () => void;
|
||||||
@@ -65,9 +71,12 @@ interface MessageInputProps {
|
|||||||
instanceId: symbol;
|
instanceId: symbol;
|
||||||
})[]
|
})[]
|
||||||
) => void;
|
) => void;
|
||||||
presetPrompt: (list: { role: string; content: string }[]) => void;
|
presetPrompt: (list: CurrentMessage[]) => void;
|
||||||
addMessage: (message: { role: string; content: string }) => void;
|
addMessage: (message: CurrentMessage) => void;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
showModelSelection?: boolean;
|
||||||
|
disabled: boolean;
|
||||||
|
isEmpty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageInput: React.FC<MessageInputProps> = ({
|
const MessageInput: React.FC<MessageInputProps> = ({
|
||||||
@@ -75,21 +84,35 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
handleAbortFetch,
|
handleAbortFetch,
|
||||||
setModelSelections,
|
setModelSelections,
|
||||||
presetPrompt,
|
presetPrompt,
|
||||||
loading,
|
|
||||||
modelList,
|
|
||||||
clearAll,
|
clearAll,
|
||||||
updateLayout,
|
updateLayout,
|
||||||
addMessage
|
addMessage,
|
||||||
|
loading,
|
||||||
|
modelList,
|
||||||
|
showModelSelection,
|
||||||
|
disabled,
|
||||||
|
isEmpty
|
||||||
}) => {
|
}) => {
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const platform = platformCall();
|
const platform = platformCall();
|
||||||
const [disabled, setDisabled] = useState(false);
|
// const [disabled, setDisabled] = useState(false);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [message, setMessage] = useState<{ role: string; content: string }>({
|
const [message, setMessage] = useState<CurrentMessage>({
|
||||||
role: Roles.User,
|
role: Roles.User,
|
||||||
content: ''
|
content: '',
|
||||||
|
imgs: []
|
||||||
});
|
});
|
||||||
|
const imgCountRef = useRef(0);
|
||||||
|
|
||||||
|
const resetMessage = () => {
|
||||||
|
setMessage({
|
||||||
|
role: message.role,
|
||||||
|
content: '',
|
||||||
|
imgs: []
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleInputChange = (value: string) => {
|
const handleInputChange = (value: string) => {
|
||||||
console.log('input change:', value);
|
console.log('input change:', value);
|
||||||
setMessage({
|
setMessage({
|
||||||
@@ -99,13 +122,10 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
};
|
};
|
||||||
const handleSendMessage = () => {
|
const handleSendMessage = () => {
|
||||||
handleSubmit({ ...message });
|
handleSubmit({ ...message });
|
||||||
setMessage({
|
resetMessage();
|
||||||
...message,
|
|
||||||
content: ''
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
const onStop = () => {
|
const onStop = () => {
|
||||||
setDisabled(false);
|
// setDisabled(false);
|
||||||
handleAbortFetch();
|
handleAbortFetch();
|
||||||
};
|
};
|
||||||
const handleLayoutChange = (value: { span: number; count: number }) => {
|
const handleLayoutChange = (value: { span: number; count: number }) => {
|
||||||
@@ -144,19 +164,129 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
const handleAddMessage = () => {
|
const handleAddMessage = () => {
|
||||||
console.log('add message');
|
console.log('add message');
|
||||||
addMessage({ ...message });
|
addMessage({ ...message });
|
||||||
|
resetMessage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPasteContent = useCallback(async (event: any) => {
|
||||||
|
const clipboardData = event.clipboardData || window.clipboardData;
|
||||||
|
const items = clipboardData.items;
|
||||||
|
const imgPromises: Promise<string>[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let item = items[i];
|
||||||
|
console.log('item===========', item);
|
||||||
|
|
||||||
|
if (item.kind === 'file' && item.type.indexOf('image') !== -1) {
|
||||||
|
const file = item.getAsFile();
|
||||||
|
const imgPromise = new Promise<string>((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function (event) {
|
||||||
|
const base64String = event.target?.result as string;
|
||||||
|
if (base64String) {
|
||||||
|
resolve(base64String);
|
||||||
|
} else {
|
||||||
|
reject('Failed to convert image to base64');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
imgPromises.push(imgPromise);
|
||||||
|
} else if (item.kind === 'string') {
|
||||||
|
// string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const imgs = await Promise.all(imgPromises);
|
||||||
|
if (imgs.length) {
|
||||||
|
const list = _.map(imgs, (img: string) => {
|
||||||
|
imgCountRef.current += 1;
|
||||||
|
return {
|
||||||
|
uid: imgCountRef.current,
|
||||||
|
dataUrl: img
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// setImgList((pre) => {
|
||||||
|
// return [...pre, ...list];
|
||||||
|
// });
|
||||||
setMessage({
|
setMessage({
|
||||||
...message,
|
...message,
|
||||||
content: ''
|
imgs: [...(message.imgs || []), ...list]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing images:', error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// ========== upload image ==========
|
||||||
|
const handleUpdateImgList = (
|
||||||
|
list: { uid: number | string; dataUrl: string }[]
|
||||||
|
) => {
|
||||||
|
setMessage({
|
||||||
|
...message,
|
||||||
|
imgs: [...(message.imgs || []), ...list]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteImg = (uid: number | string) => {
|
||||||
|
const list = _.filter(
|
||||||
|
message.imgs,
|
||||||
|
(item: MessageItem) => item.uid !== uid
|
||||||
|
);
|
||||||
|
setMessage({
|
||||||
|
...message,
|
||||||
|
imgs: list
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnPaste = (e: any) => {
|
||||||
|
const text = e.clipboardData.getData('text');
|
||||||
|
if (text) {
|
||||||
|
setMessage?.({
|
||||||
|
...message,
|
||||||
|
content: text
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getPasteContent(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteLastImage = useCallback(() => {
|
||||||
|
if (message.imgs && message.imgs?.length > 0) {
|
||||||
|
const newImgList = [...(message.imgs || [])];
|
||||||
|
const lastImage = newImgList.pop();
|
||||||
|
if (lastImage) {
|
||||||
|
handleDeleteImg(lastImage.uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [message.imgs, handleDeleteImg]);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(event: any) => {
|
||||||
|
if (
|
||||||
|
event.key === 'Backspace' &&
|
||||||
|
message.content === '' &&
|
||||||
|
message.imgs &&
|
||||||
|
message.imgs?.length > 0
|
||||||
|
) {
|
||||||
|
// inputref blur
|
||||||
|
event.preventDefault();
|
||||||
|
handleDeleteLastImage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[message, handleDeleteLastImage]
|
||||||
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
HotKeys.SUBMIT.join(','),
|
HotKeys.SUBMIT.join(','),
|
||||||
() => {
|
() => {
|
||||||
|
console.log('submit message', loading);
|
||||||
handleSendMessage();
|
handleSendMessage();
|
||||||
},
|
},
|
||||||
{ preventDefault: true }
|
{ enabled: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="messageInput">
|
<div className="messageInput">
|
||||||
<div className="tool-bar">
|
<div className="tool-bar">
|
||||||
@@ -170,23 +300,36 @@ 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 }} />
|
||||||
<Button type="text" icon={<PictureOutlined />} size="middle"></Button>
|
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
|
||||||
|
<Tooltip
|
||||||
|
title={intl.formatMessage({ id: 'playground.toolbar.clearmsg' })}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<ClearOutlined />}
|
icon={<ClearOutlined />}
|
||||||
size="middle"
|
size="middle"
|
||||||
onClick={handleClearAll}
|
onClick={handleClearAll}
|
||||||
></Button>
|
></Button>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
title={intl.formatMessage({ id: 'playground.toolbar.prompts' })}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<ControlOutlined />}
|
icon={<ControlOutlined />}
|
||||||
size="middle"
|
size="middle"
|
||||||
onClick={handleOpenPrompt}
|
onClick={handleOpenPrompt}
|
||||||
></Button>
|
></Button>
|
||||||
|
</Tooltip>
|
||||||
{updateLayout && (
|
{updateLayout && (
|
||||||
<>
|
<>
|
||||||
<Divider type="vertical" style={{ margin: 0 }} />
|
<Divider type="vertical" style={{ margin: 0 }} />
|
||||||
{layoutOptions.map((option) => (
|
{layoutOptions.map((option) => (
|
||||||
|
<Tooltip
|
||||||
|
title={intl.formatMessage({ id: option.tips })}
|
||||||
|
key={option.icon}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
key={option.icon}
|
key={option.icon}
|
||||||
type="text"
|
type="text"
|
||||||
@@ -194,11 +337,13 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
size="middle"
|
size="middle"
|
||||||
onClick={() => handleLayoutChange(option.value)}
|
onClick={() => handleLayoutChange(option.value)}
|
||||||
></Button>
|
></Button>
|
||||||
|
</Tooltip>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
|
{showModelSelection && (
|
||||||
<Select
|
<Select
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
style={{ width: 180 }}
|
style={{ width: 180 }}
|
||||||
@@ -210,12 +355,31 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
maxTagTextLength={15}
|
maxTagTextLength={15}
|
||||||
onChange={handleUpdateModelSelections}
|
onChange={handleUpdateModelSelections}
|
||||||
></Select>
|
></Select>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button type="default" size="middle" onClick={handleAddMessage}>
|
<Button type="default" size="middle" onClick={handleAddMessage}>
|
||||||
{intl.formatMessage({ id: 'common.button.add' })}
|
{intl.formatMessage({ id: 'common.button.add' })}
|
||||||
</Button>
|
</Button>
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
<Button type="primary" onClick={handleSendMessage} size="middle">
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
size="middle"
|
||||||
|
disabled={disabled ? true : !message.content && isEmpty}
|
||||||
|
>
|
||||||
{intl.formatMessage({ id: 'common.button.submit' })}
|
{intl.formatMessage({ id: 'common.button.submit' })}
|
||||||
|
<span className="m-l-5 opct-7">
|
||||||
|
{platform.isMac ? (
|
||||||
|
<>
|
||||||
|
<IconFont type="icon-command"></IconFont> +{' '}
|
||||||
|
<EnterOutlined />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
CTRL + <EnterOutlined />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
@@ -228,6 +392,10 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ThumbImg
|
||||||
|
dataList={message.imgs || []}
|
||||||
|
onDelete={handleDeleteImg}
|
||||||
|
></ThumbImg>
|
||||||
<TextArea
|
<TextArea
|
||||||
placeholder="Type your message here"
|
placeholder="Type your message here"
|
||||||
autoSize={{ minRows: 3, maxRows: 3 }}
|
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||||
@@ -235,6 +403,8 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
|||||||
value={message.content}
|
value={message.content}
|
||||||
size="large"
|
size="large"
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onPaste={handleOnPaste}
|
||||||
></TextArea>
|
></TextArea>
|
||||||
<PromptModal
|
<PromptModal
|
||||||
open={open}
|
open={open}
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ const MessageItem: React.FC<{
|
|||||||
<ThumbImg dataList={imgList} onDelete={handleDeleteImg}></ThumbImg>
|
<ThumbImg dataList={imgList} onDelete={handleDeleteImg}></ThumbImg>
|
||||||
<Input.TextArea
|
<Input.TextArea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
style={{ paddingBlock: '12px', paddingTop: 20 }}
|
style={{ paddingBlock: '12px' }}
|
||||||
value={message.content}
|
value={message.content}
|
||||||
autoSize={true}
|
autoSize={true}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
|
|||||||
@@ -1,17 +1,191 @@
|
|||||||
|
import CopyButton from '@/components/copy-button';
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import { UserOutlined } from '@ant-design/icons';
|
import { CloseOutlined, SwapOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import React from 'react';
|
import { Button, Input, Tooltip } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
import { Roles } from '../../config';
|
import { Roles } from '../../config';
|
||||||
|
import { MessageItem } from '../../config/types';
|
||||||
import '../../style/content-item.less';
|
import '../../style/content-item.less';
|
||||||
|
import ThumbImg from '../thumb-img';
|
||||||
|
import UploadImg from '../upload-img';
|
||||||
|
|
||||||
const ContentItem: React.FC<{ data: { role: string; content: string } }> = ({
|
interface MessageItemProps {
|
||||||
data
|
data: MessageItem;
|
||||||
|
editable?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
updateMessage?: (message: MessageItem) => void;
|
||||||
|
onDelete?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContentItem: React.FC<MessageItemProps> = ({
|
||||||
|
updateMessage,
|
||||||
|
onDelete,
|
||||||
|
loading,
|
||||||
|
data,
|
||||||
|
editable
|
||||||
}) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const inputRef = useRef<any>(null);
|
||||||
|
const imgCountRef = useRef(0);
|
||||||
|
const [imgList, setImgList] = useState<
|
||||||
|
{ uid: number | string; dataUrl: string }[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const handleMessageChange = (e: any) => {
|
||||||
|
updateMessage?.({
|
||||||
|
imgs: data.imgs || [],
|
||||||
|
role: data.role,
|
||||||
|
content: e.target.value,
|
||||||
|
uid: data.uid
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleRole = () => {
|
||||||
|
updateMessage?.({
|
||||||
|
imgs: data.imgs || [],
|
||||||
|
role: data.role === Roles.User ? Roles.Assistant : Roles.User,
|
||||||
|
content: data.content,
|
||||||
|
uid: data.uid
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPasteContent = useCallback(async (event: any) => {
|
||||||
|
const clipboardData = event.clipboardData || window.clipboardData;
|
||||||
|
const items = clipboardData.items;
|
||||||
|
const imgPromises: Promise<string>[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let item = items[i];
|
||||||
|
console.log('item===========', item);
|
||||||
|
|
||||||
|
if (item.kind === 'file' && item.type.indexOf('image') !== -1) {
|
||||||
|
const file = item.getAsFile();
|
||||||
|
const imgPromise = new Promise<string>((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function (event) {
|
||||||
|
const base64String = event.target?.result as string;
|
||||||
|
if (base64String) {
|
||||||
|
resolve(base64String);
|
||||||
|
} else {
|
||||||
|
reject('Failed to convert image to base64');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
imgPromises.push(imgPromise);
|
||||||
|
} else if (item.kind === 'string') {
|
||||||
|
// string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const imgs = await Promise.all(imgPromises);
|
||||||
|
if (imgs.length) {
|
||||||
|
const list = _.map(imgs, (img: string) => {
|
||||||
|
imgCountRef.current += 1;
|
||||||
|
return {
|
||||||
|
uid: imgCountRef.current,
|
||||||
|
dataUrl: img
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// setImgList((pre) => {
|
||||||
|
// return [...pre, ...list];
|
||||||
|
// });
|
||||||
|
updateMessage?.({
|
||||||
|
role: data.role,
|
||||||
|
content: data.content,
|
||||||
|
uid: data.uid,
|
||||||
|
imgs: [...(data.imgs || []), ...list]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing images:', error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOnPaste = useCallback(
|
||||||
|
(e: any) => {
|
||||||
|
const text = e.clipboardData.getData('text');
|
||||||
|
if (text) {
|
||||||
|
updateMessage?.({
|
||||||
|
role: data.role,
|
||||||
|
content: inputRef.current?.resizableTextArea?.textArea?.value || '',
|
||||||
|
uid: data.uid
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getPasteContent(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[getPasteContent, data, updateMessage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleUpdateImgList = useCallback(
|
||||||
|
(list: { uid: number | string; dataUrl: string }[]) => {
|
||||||
|
// setImgList((preList) => {
|
||||||
|
// return [...preList, ...list];
|
||||||
|
// });
|
||||||
|
console.log('list===========', data.imgs, list);
|
||||||
|
updateMessage?.({
|
||||||
|
role: data.role,
|
||||||
|
content: data.content,
|
||||||
|
uid: data.uid,
|
||||||
|
imgs: [...(data.imgs || []), ...list]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteImg = (uid: number | string) => {
|
||||||
|
const list = _.filter(data.imgs, (item: MessageItem) => item.uid !== uid);
|
||||||
|
// setImgList(list);
|
||||||
|
updateMessage?.({
|
||||||
|
role: data.role,
|
||||||
|
content: data.content,
|
||||||
|
uid: data.uid,
|
||||||
|
imgs: list
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteLastImage = useCallback(() => {
|
||||||
|
if (data.imgs && data.imgs?.length > 0) {
|
||||||
|
const newImgList = [...(data.imgs || [])];
|
||||||
|
const lastImage = newImgList.pop();
|
||||||
|
if (lastImage) {
|
||||||
|
handleDeleteImg(lastImage.uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [data.imgs, handleDeleteImg]);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(event: any) => {
|
||||||
|
if (
|
||||||
|
event.key === 'Backspace' &&
|
||||||
|
data.content === '' &&
|
||||||
|
data.imgs &&
|
||||||
|
data.imgs?.length > 0
|
||||||
|
) {
|
||||||
|
// inputref blur
|
||||||
|
event.preventDefault();
|
||||||
|
handleDeleteLastImage();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[data, handleDeleteLastImage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClickWrapper = (e: any) => {
|
||||||
|
console.log('e===========', e);
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
inputRef.current.focus();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="content-item">
|
<div className="content-item">
|
||||||
<div className="content-item-role">
|
<div className="content-item-role">
|
||||||
|
<div className="role" onClick={handleToggleRole}>
|
||||||
<span className="m-r-5">
|
<span className="m-r-5">
|
||||||
{Roles.User === data.role ? (
|
{Roles.User === data.role ? (
|
||||||
<UserOutlined></UserOutlined>
|
<UserOutlined></UserOutlined>
|
||||||
@@ -20,8 +194,63 @@ const ContentItem: React.FC<{ data: { role: string; content: string } }> = ({
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{intl.formatMessage({ id: `playground.${data.role}` })}
|
{intl.formatMessage({ id: `playground.${data.role}` })}
|
||||||
|
<SwapOutlined rotate={90} className="m-l-5" />
|
||||||
</div>
|
</div>
|
||||||
<div className="content-item-content">{data.content}</div>
|
<div className="actions">
|
||||||
|
{editable && (
|
||||||
|
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
|
||||||
|
)}
|
||||||
|
{data.content && (
|
||||||
|
<CopyButton
|
||||||
|
text={data.content}
|
||||||
|
size="small"
|
||||||
|
shape="default"
|
||||||
|
type="text"
|
||||||
|
fontSize="12px"
|
||||||
|
></CopyButton>
|
||||||
|
)}
|
||||||
|
{editable && (
|
||||||
|
<Tooltip title={intl.formatMessage({ id: 'common.button.delete' })}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="text"
|
||||||
|
onClick={onDelete}
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
></Button>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{editable ? (
|
||||||
|
<div
|
||||||
|
className={classNames('message-content-input', {
|
||||||
|
'has-img': data.imgs?.length
|
||||||
|
})}
|
||||||
|
onClick={handleClickWrapper}
|
||||||
|
>
|
||||||
|
<ThumbImg
|
||||||
|
dataList={data.imgs || []}
|
||||||
|
onDelete={handleDeleteImg}
|
||||||
|
></ThumbImg>
|
||||||
|
<Input.TextArea
|
||||||
|
ref={inputRef}
|
||||||
|
value={data.content}
|
||||||
|
variant="filled"
|
||||||
|
autoSize={{ minRows: 1 }}
|
||||||
|
style={{
|
||||||
|
borderRadius: 'var(--border-radius-mini)'
|
||||||
|
}}
|
||||||
|
readOnly={loading}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onChange={handleMessageChange}
|
||||||
|
onPaste={handleOnPaste}
|
||||||
|
></Input.TextArea>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="content-item-content">
|
||||||
|
<span>{data.content}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import CompareContext from '../../config/compare-context';
|
import CompareContext from '../../config/compare-context';
|
||||||
import { ModelSelectionItem } from '../../config/types';
|
import { MessageItem, ModelSelectionItem } from '../../config/types';
|
||||||
import '../../style/multiple-chat.less';
|
import '../../style/multiple-chat.less';
|
||||||
import MessageInput from '../message-input';
|
import MessageInput from '../message-input';
|
||||||
import ActiveModels from './active-models';
|
import ActiveModels from './active-models';
|
||||||
|
|
||||||
|
type CurrentMessage = Omit<MessageItem, 'uid'>;
|
||||||
|
|
||||||
interface MultiCompareProps {
|
interface MultiCompareProps {
|
||||||
modelList: (Global.BaseOption<string> & { type?: string })[];
|
modelList: (Global.BaseOption<string> & { type?: string })[];
|
||||||
spans?: number;
|
spans?: number;
|
||||||
@@ -61,7 +63,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
|||||||
loadingStatus[instanceId] = false;
|
loadingStatus[instanceId] = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (currentMessage: { role: string; content: string }) => {
|
const handleSubmit = (currentMessage: CurrentMessage) => {
|
||||||
const modelRefList = Object.getOwnPropertySymbols(modelRefs.current);
|
const modelRefList = Object.getOwnPropertySymbols(modelRefs.current);
|
||||||
modelRefList.forEach((instanceId: symbol) => {
|
modelRefList.forEach((instanceId: symbol) => {
|
||||||
const ref = modelRefs.current[instanceId];
|
const ref = modelRefs.current[instanceId];
|
||||||
@@ -69,7 +71,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddMessage = (message: { role: string; content: string }) => {
|
const handleAddMessage = (message: CurrentMessage) => {
|
||||||
const modelRefList = Object.getOwnPropertySymbols(modelRefs.current);
|
const modelRefList = Object.getOwnPropertySymbols(modelRefs.current);
|
||||||
modelRefList.forEach((instanceId: symbol) => {
|
modelRefList.forEach((instanceId: symbol) => {
|
||||||
const ref = modelRefs.current[instanceId];
|
const ref = modelRefs.current[instanceId];
|
||||||
@@ -218,13 +220,6 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
|||||||
setModelSelections(resultList);
|
setModelSelections(resultList);
|
||||||
}, [modelList]);
|
}, [modelList]);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// modelRefs.current = {};
|
|
||||||
// modelSelections.forEach((item) => {
|
|
||||||
// modelRefs.current[item.instanceId] = null;
|
|
||||||
// });
|
|
||||||
// }, [modelSelections]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="multiple-chat" style={{ height: boxHeight }}>
|
<div className="multiple-chat" style={{ height: boxHeight }}>
|
||||||
<div className="chat-list">
|
<div className="chat-list">
|
||||||
@@ -248,6 +243,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
|||||||
<div>
|
<div>
|
||||||
<MessageInput
|
<MessageInput
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
|
disabled={isLoading || modelSelections.length === 0}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
addMessage={handleAddMessage}
|
addMessage={handleAddMessage}
|
||||||
handleAbortFetch={handleAbortFetch}
|
handleAbortFetch={handleAbortFetch}
|
||||||
@@ -256,6 +252,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
|||||||
setModelSelections={handleUpdateModelSelections}
|
setModelSelections={handleUpdateModelSelections}
|
||||||
presetPrompt={handlePresetPrompt}
|
presetPrompt={handlePresetPrompt}
|
||||||
modelList={modelFullList}
|
modelList={modelFullList}
|
||||||
|
showModelSelection={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,48 +1,51 @@
|
|||||||
import { Spin } from 'antd';
|
import React from 'react';
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import SimpleBar from 'simplebar-react';
|
|
||||||
import 'simplebar-react/dist/simplebar.min.css';
|
import 'simplebar-react/dist/simplebar.min.css';
|
||||||
|
import { MessageItem } from '../../config/types';
|
||||||
import ContentItem from './content-item';
|
import ContentItem from './content-item';
|
||||||
|
|
||||||
interface MessageContentProps {
|
interface MessageContentProps {
|
||||||
loading: boolean;
|
loading?: boolean;
|
||||||
spans: {
|
spans: {
|
||||||
span: number;
|
span: number;
|
||||||
count: number;
|
count: number;
|
||||||
};
|
};
|
||||||
messageList: {
|
editable?: boolean;
|
||||||
role: string;
|
messageList: MessageItem[];
|
||||||
uid?: any;
|
setMessageList?: (list: any) => void;
|
||||||
content: string;
|
|
||||||
}[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageContent: React.FC<MessageContentProps> = ({
|
const MessageContent: React.FC<MessageContentProps> = ({
|
||||||
|
setMessageList,
|
||||||
messageList,
|
messageList,
|
||||||
spans,
|
spans,
|
||||||
loading
|
editable
|
||||||
}) => {
|
}) => {
|
||||||
const maxHeight = useMemo(() => {
|
const updateMessage = (index: number, message: MessageItem) => {
|
||||||
const total = 72 + 110 + 46 + 16 + 32;
|
const newMessageList = [...messageList];
|
||||||
if (spans.span < 4) {
|
newMessageList[index] = message;
|
||||||
return `calc(100vh - ${total}px)`;
|
setMessageList?.(newMessageList);
|
||||||
}
|
};
|
||||||
return `calc(100vh - ${total * 2 + 16}px)`;
|
|
||||||
}, [spans.span]);
|
const handleDelete = (index: number) => {
|
||||||
|
const newMessageList = [...messageList];
|
||||||
|
newMessageList.splice(index, 1);
|
||||||
|
setMessageList?.(newMessageList);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{messageList.length ? (
|
{!!messageList.length && (
|
||||||
<SimpleBar style={{ maxHeight: 'calc(100% - 46px)' }}>
|
|
||||||
<div className="message-content-list">
|
<div className="message-content-list">
|
||||||
{messageList.map((item, index) => (
|
{messageList.map((item, index) => (
|
||||||
<ContentItem key={index} data={item} />
|
<ContentItem
|
||||||
|
key={item.uid}
|
||||||
|
data={item}
|
||||||
|
editable={editable}
|
||||||
|
onDelete={() => handleDelete(index)}
|
||||||
|
updateMessage={(data) => updateMessage(index, data)}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</SimpleBar>
|
|
||||||
) : (
|
|
||||||
<span>{loading}</span>
|
|
||||||
)}
|
)}
|
||||||
<Spin spinning={!!loading} size="small" style={{ width: '100%' }} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,15 +7,7 @@ import {
|
|||||||
SettingOutlined
|
SettingOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import {
|
import { Button, Checkbox, Dropdown, Popover, Select, Spin } from 'antd';
|
||||||
Button,
|
|
||||||
Checkbox,
|
|
||||||
Divider,
|
|
||||||
Dropdown,
|
|
||||||
Input,
|
|
||||||
Popover,
|
|
||||||
Select
|
|
||||||
} from 'antd';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
@@ -27,16 +19,18 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState
|
useState
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import SimpleBar from 'simplebar-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 } from '../../config';
|
||||||
import CompareContext from '../../config/compare-context';
|
import CompareContext from '../../config/compare-context';
|
||||||
import { ModelSelectionItem } from '../../config/types';
|
import { MessageItem, ModelSelectionItem } from '../../config/types';
|
||||||
import '../../style/model-item.less';
|
import '../../style/model-item.less';
|
||||||
import ParamsSettings from '../params-settings';
|
import ParamsSettings from '../params-settings';
|
||||||
import ReferenceParams from '../reference-params';
|
import ReferenceParams from '../reference-params';
|
||||||
import ViewCodeModal from '../view-code-modal';
|
import ViewCodeModal from '../view-code-modal';
|
||||||
import MessageContent from './message-content';
|
import MessageContent from './message-content';
|
||||||
|
import SystemMessage from './system-message';
|
||||||
|
|
||||||
interface ModelItemProps {
|
interface ModelItemProps {
|
||||||
model: string;
|
model: string;
|
||||||
@@ -45,12 +39,6 @@ interface ModelItemProps {
|
|||||||
ref: any;
|
ref: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MessageItemProps {
|
|
||||||
role: string;
|
|
||||||
content: string;
|
|
||||||
uid: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||||
({ model, modelList, instanceId }, ref) => {
|
({ model, modelList, instanceId }, ref) => {
|
||||||
const {
|
const {
|
||||||
@@ -63,26 +51,28 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
} = useContext(CompareContext);
|
} = useContext(CompareContext);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const isApplyToAllModels = useRef(false);
|
const isApplyToAllModels = useRef(false);
|
||||||
const [autoSize, setAutoSize] = useState<{
|
|
||||||
minRows: number;
|
|
||||||
maxRows: number;
|
|
||||||
focus: boolean;
|
|
||||||
}>({ minRows: 1, maxRows: 1, focus: false });
|
|
||||||
const [systemMessage, setSystemMessage] = useState<string>('');
|
const [systemMessage, setSystemMessage] = useState<string>('');
|
||||||
const [params, setParams] = useState<Record<string, any>>({});
|
const [params, setParams] = useState<Record<string, any>>({});
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const messageId = useRef<number>(0);
|
const messageId = useRef<number>(0);
|
||||||
const [messageList, setMessageList] = useState<MessageItemProps[]>([]);
|
const [messageList, setMessageList] = useState<MessageItem[]>([]);
|
||||||
const [tokenResult, setTokenResult] = useState<any>(null);
|
const [tokenResult, setTokenResult] = useState<any>(null);
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const contentRef = useRef<any>('');
|
const contentRef = useRef<any>('');
|
||||||
const controllerRef = useRef<any>(null);
|
const controllerRef = useRef<any>(null);
|
||||||
const currentMessageRef = useRef<MessageItemProps>({} as MessageItemProps);
|
const currentMessageRef = useRef<MessageItem>({} as MessageItem);
|
||||||
const systemMessageRef = useRef<any>(null);
|
|
||||||
|
|
||||||
const setMessageId = () => {
|
const setMessageId = () => {
|
||||||
messageId.current = messageId.current + 1;
|
messageId.current = messageId.current + 1;
|
||||||
};
|
};
|
||||||
|
const maxHeight = useMemo(() => {
|
||||||
|
console.log('spans==========', spans);
|
||||||
|
const total = 72 + 110 + 46 + 16 + 32;
|
||||||
|
if (spans.count < 4) {
|
||||||
|
return `calc(100vh - ${total}px)`;
|
||||||
|
}
|
||||||
|
return `calc(100vh - ${total * 2 + 16}px)`;
|
||||||
|
}, [spans.count]);
|
||||||
|
|
||||||
const abortFetch = () => {
|
const abortFetch = () => {
|
||||||
controllerRef.current?.abort?.();
|
controllerRef.current?.abort?.();
|
||||||
@@ -104,9 +94,6 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
console.log('currentMessage==========5', messageList);
|
console.log('currentMessage==========5', messageList);
|
||||||
setMessageList([
|
setMessageList([
|
||||||
...messageList,
|
...messageList,
|
||||||
{
|
|
||||||
...currentMessageRef.current
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
role: Roles.Assistant,
|
role: Roles.Assistant,
|
||||||
content: contentRef.current,
|
content: contentRef.current,
|
||||||
@@ -149,28 +136,58 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
...currentMessageRef.current
|
...currentMessageRef.current
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
(item: MessageItemProps) => {
|
(item: MessageItem) => {
|
||||||
return {
|
return {
|
||||||
role: item.role,
|
role: item.role,
|
||||||
content: item.content
|
content: item.content,
|
||||||
|
imgs: item.imgs || []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
contentRef.current = '';
|
contentRef.current = '';
|
||||||
|
// ====== payload =================
|
||||||
|
const formatMessages = _.map(messages, (item: MessageItem) => {
|
||||||
|
return {
|
||||||
|
role: item.role,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
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: systemMessage
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
role: Roles.System,
|
role: Roles.System,
|
||||||
content: systemMessage
|
content: [
|
||||||
},
|
{
|
||||||
...messages
|
type: 'text',
|
||||||
|
text: systemMessage
|
||||||
|
}
|
||||||
]
|
]
|
||||||
: [...messages],
|
},
|
||||||
|
...formatMessages
|
||||||
|
]
|
||||||
|
: [...formatMessages],
|
||||||
...parameters,
|
...parameters,
|
||||||
stream: true
|
stream: true
|
||||||
};
|
};
|
||||||
|
// ============== payload end ================
|
||||||
const result = await fetchChunkedData({
|
const result = await fetchChunkedData({
|
||||||
data: chatParams,
|
data: chatParams,
|
||||||
url: CHAT_API,
|
url: CHAT_API,
|
||||||
@@ -180,6 +197,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setMessageId();
|
||||||
const { reader, decoder } = result;
|
const { reader, decoder } = result;
|
||||||
await readStreamData(reader, decoder, (chunk: any) => {
|
await readStreamData(reader, decoder, (chunk: any) => {
|
||||||
joinMessage(chunk);
|
joinMessage(chunk);
|
||||||
@@ -242,7 +260,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
setMessageList([]);
|
setMessageList([]);
|
||||||
setTokenResult(null);
|
setTokenResult(null);
|
||||||
setSystemMessage('');
|
setSystemMessage('');
|
||||||
currentMessageRef.current = {} as MessageItemProps;
|
currentMessageRef.current = {} as MessageItem;
|
||||||
console.log('clear message', systemMessage);
|
console.log('clear message', systemMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -258,8 +276,8 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
handleClearMessage();
|
handleClearMessage();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePresetMessageList = (list: MessageItemProps[]) => {
|
const handlePresetMessageList = (list: MessageItem[]) => {
|
||||||
currentMessageRef.current = {} as MessageItemProps;
|
currentMessageRef.current = {} as MessageItem;
|
||||||
const messages = _.map(
|
const messages = _.map(
|
||||||
list,
|
list,
|
||||||
(item: { role: string; content: string }) => {
|
(item: { role: string; content: string }) => {
|
||||||
@@ -279,31 +297,6 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
handleDeleteModel(instanceId);
|
handleDeleteModel(instanceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFocus = () => {
|
|
||||||
setAutoSize({
|
|
||||||
minRows: 4,
|
|
||||||
maxRows: 4,
|
|
||||||
focus: true
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
systemMessageRef.current?.focus?.({
|
|
||||||
cursor: 'end'
|
|
||||||
});
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBlur = () => {
|
|
||||||
setAutoSize({
|
|
||||||
minRows: 1,
|
|
||||||
maxRows: 1,
|
|
||||||
focus: false
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClearSystemMessage = () => {
|
|
||||||
setSystemMessage('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const modelOptions = useMemo(() => {
|
const modelOptions = useMemo(() => {
|
||||||
return modelList.filter((item) => {
|
return modelList.filter((item) => {
|
||||||
return item.type !== 'empty';
|
return item.type !== 'empty';
|
||||||
@@ -406,66 +399,35 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
|||||||
size="small"
|
size="small"
|
||||||
></Button>
|
></Button>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
{modelList.length > 2 && (
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={<CloseOutlined />}
|
icon={<CloseOutlined />}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
></Button>
|
></Button>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="sys-message">
|
<SystemMessage
|
||||||
{
|
systemMessage={systemMessage}
|
||||||
<div style={{ display: autoSize.focus ? 'block' : 'none' }}>
|
setSystemMessage={setSystemMessage}
|
||||||
<Input.TextArea
|
></SystemMessage>
|
||||||
ref={systemMessageRef}
|
<SimpleBar style={{ maxHeight: maxHeight }}>
|
||||||
variant="filled"
|
|
||||||
placeholder="Type system message here"
|
|
||||||
style={{
|
|
||||||
borderRadius: '0',
|
|
||||||
border: 'none'
|
|
||||||
}}
|
|
||||||
value={systemMessage}
|
|
||||||
autoSize={{
|
|
||||||
minRows: autoSize.minRows,
|
|
||||||
maxRows: autoSize.maxRows
|
|
||||||
}}
|
|
||||||
onFocus={handleFocus}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
allowClear={false}
|
|
||||||
onChange={(e) => setSystemMessage(e.target.value)}
|
|
||||||
></Input.TextArea>
|
|
||||||
<Divider style={{ margin: '0' }}></Divider>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{!autoSize.focus && (
|
|
||||||
<div className="sys-content-wrap" onClick={handleFocus}>
|
|
||||||
<div className="sys-content">
|
|
||||||
{systemMessage || (
|
|
||||||
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
|
|
||||||
Type system message here
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{systemMessage && (
|
|
||||||
<Button
|
|
||||||
className="clear-btn"
|
|
||||||
type="text"
|
|
||||||
icon={<CloseOutlined />}
|
|
||||||
size="small"
|
|
||||||
onClick={handleClearSystemMessage}
|
|
||||||
></Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<MessageContent
|
<MessageContent
|
||||||
spans={spans}
|
spans={spans}
|
||||||
messageList={messageList}
|
messageList={messageList}
|
||||||
loading={loadingStatus[params.model]}
|
setMessageList={setMessageList}
|
||||||
|
editable={true}
|
||||||
|
/>
|
||||||
|
<Spin
|
||||||
|
spinning={!!loadingStatus[instanceId]}
|
||||||
|
size="small"
|
||||||
|
style={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</SimpleBar>
|
||||||
<ViewCodeModal
|
<ViewCodeModal
|
||||||
open={show}
|
open={show}
|
||||||
systemMessage={systemMessage}
|
systemMessage={systemMessage}
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Divider, Input } from 'antd';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import '../../style/sys-message.less';
|
||||||
|
|
||||||
|
interface SystemMessageProps {
|
||||||
|
systemMessage: string;
|
||||||
|
setSystemMessage: (value: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SystemMessage: React.FC<SystemMessageProps> = (props) => {
|
||||||
|
const { systemMessage, setSystemMessage } = props;
|
||||||
|
const systemMessageRef = React.useRef<any>(null);
|
||||||
|
const [autoSize, setAutoSize] = useState<{
|
||||||
|
minRows: number;
|
||||||
|
maxRows: number;
|
||||||
|
focus: boolean;
|
||||||
|
}>({ minRows: 1, maxRows: 1, focus: false });
|
||||||
|
|
||||||
|
const handleFocus = () => {
|
||||||
|
setAutoSize({
|
||||||
|
minRows: 4,
|
||||||
|
maxRows: 4,
|
||||||
|
focus: true
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
systemMessageRef.current?.focus?.({
|
||||||
|
cursor: 'end'
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
setAutoSize({
|
||||||
|
minRows: 1,
|
||||||
|
maxRows: 1,
|
||||||
|
focus: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearSystemMessage = () => {
|
||||||
|
setSystemMessage('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sys-message">
|
||||||
|
{
|
||||||
|
<div style={{ display: autoSize.focus ? 'block' : 'none' }}>
|
||||||
|
<Input.TextArea
|
||||||
|
ref={systemMessageRef}
|
||||||
|
variant="filled"
|
||||||
|
placeholder="Type system message here"
|
||||||
|
style={{
|
||||||
|
borderRadius: '0',
|
||||||
|
border: 'none'
|
||||||
|
}}
|
||||||
|
value={systemMessage}
|
||||||
|
autoSize={{
|
||||||
|
minRows: autoSize.minRows,
|
||||||
|
maxRows: autoSize.maxRows
|
||||||
|
}}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
allowClear={false}
|
||||||
|
onChange={(e) => setSystemMessage(e.target.value)}
|
||||||
|
></Input.TextArea>
|
||||||
|
<Divider style={{ margin: '0' }}></Divider>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{!autoSize.focus && (
|
||||||
|
<div className="sys-content-wrap" onClick={handleFocus}>
|
||||||
|
<div className="sys-content">
|
||||||
|
{systemMessage || (
|
||||||
|
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
|
||||||
|
Type system message here
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{systemMessage && (
|
||||||
|
<Button
|
||||||
|
className="clear-btn"
|
||||||
|
type="text"
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
size="small"
|
||||||
|
onClick={handleClearSystemMessage}
|
||||||
|
></Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(SystemMessage);
|
||||||
@@ -214,14 +214,14 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
id: 'playground.params.temperature.tips'
|
id: 'playground.params.temperature.tips'
|
||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
style={{ paddingInline: 0 }}
|
style={{ padding: '20px 2px 0' }}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
>
|
>
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={1}
|
defaultValue={1}
|
||||||
max={2}
|
max={2}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
style={{ marginBottom: 0, marginTop: 16 }}
|
style={{ marginBottom: 0, marginTop: 16, marginInline: 0 }}
|
||||||
tooltip={{ open: false }}
|
tooltip={{ open: false }}
|
||||||
value={form.getFieldValue('temperature') || undefined}
|
value={form.getFieldValue('temperature') || undefined}
|
||||||
onChange={(val) => handleFieldValueChange(val, 'temperature')}
|
onChange={(val) => handleFieldValueChange(val, 'temperature')}
|
||||||
@@ -240,14 +240,14 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
id: 'playground.params.maxtokens.tips'
|
id: 'playground.params.maxtokens.tips'
|
||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
style={{ paddingInline: 0 }}
|
style={{ padding: '20px 2px 0' }}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
>
|
>
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={1024}
|
defaultValue={1024}
|
||||||
max={2048}
|
max={2048}
|
||||||
step={1}
|
step={1}
|
||||||
style={{ marginBottom: 0, marginTop: 16 }}
|
style={{ marginBottom: 0, marginTop: 16, marginInline: 0 }}
|
||||||
tooltip={{ open: false }}
|
tooltip={{ open: false }}
|
||||||
value={form.getFieldValue('max_tokens') || undefined}
|
value={form.getFieldValue('max_tokens') || undefined}
|
||||||
onChange={(val) => handleFieldValueChange(val, 'max_tokens')}
|
onChange={(val) => handleFieldValueChange(val, 'max_tokens')}
|
||||||
@@ -266,14 +266,14 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
|||||||
id: 'playground.params.topp.tips'
|
id: 'playground.params.topp.tips'
|
||||||
})
|
})
|
||||||
})}
|
})}
|
||||||
style={{ paddingInline: 0 }}
|
style={{ padding: '20px 2px 0' }}
|
||||||
variant="borderless"
|
variant="borderless"
|
||||||
>
|
>
|
||||||
<Slider
|
<Slider
|
||||||
defaultValue={1}
|
defaultValue={1}
|
||||||
max={1}
|
max={1}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
style={{ marginBottom: 0, marginTop: 16 }}
|
style={{ marginBottom: 0, marginTop: 16, marginInline: 0 }}
|
||||||
tooltip={{ open: false }}
|
tooltip={{ open: false }}
|
||||||
value={form.getFieldValue('top_p') || undefined}
|
value={form.getFieldValue('top_p') || undefined}
|
||||||
onChange={(val) => handleFieldValueChange(val, 'top_p')}
|
onChange={(val) => handleFieldValueChange(val, 'top_p')}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
|||||||
type="default"
|
type="default"
|
||||||
onClick={() => handleSelect(item)}
|
onClick={() => handleSelect(item)}
|
||||||
>
|
>
|
||||||
Use
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
</h3>
|
</h3>
|
||||||
{item.data.map((data, i) => {
|
{item.data.map((data, i) => {
|
||||||
|
|||||||
@@ -58,16 +58,41 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const generateCode = () => {
|
const generateCode = () => {
|
||||||
if (lang === 'shell') {
|
|
||||||
const systemList = systemMessage
|
const systemList = systemMessage
|
||||||
? [{ role: 'system', content: systemMessage }]
|
? [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: systemMessage
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
: [];
|
: [];
|
||||||
const messages = [
|
|
||||||
...systemList,
|
const formatMessageList = _.map(messageList, (item: any) => {
|
||||||
..._.map(messageList, (item: any) => {
|
return {
|
||||||
return { role: item.role, content: item.content };
|
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 === 'shell') {
|
||||||
|
const messages = [...systemList, ...formatMessageList];
|
||||||
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,
|
||||||
@@ -78,15 +103,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
)}'`;
|
)}'`;
|
||||||
setCodeValue(code);
|
setCodeValue(code);
|
||||||
} else if (lang === 'javascript') {
|
} else if (lang === 'javascript') {
|
||||||
const systemList = systemMessage
|
const messages = [...systemList, ...formatMessageList];
|
||||||
? [{ role: 'system', content: systemMessage }]
|
|
||||||
: [];
|
|
||||||
const messages = [
|
|
||||||
...systemList,
|
|
||||||
..._.map(messageList, (item: any) => {
|
|
||||||
return { role: item.role, content: item.content };
|
|
||||||
})
|
|
||||||
];
|
|
||||||
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,
|
||||||
@@ -110,18 +127,10 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
|||||||
},
|
},
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
const systemList = systemMessage
|
|
||||||
? [{ role: 'system', content: systemMessage }]
|
|
||||||
: [];
|
|
||||||
const messages =
|
const messages =
|
||||||
apiType === 'chat'
|
apiType === 'chat'
|
||||||
? `messages=${JSON.stringify(
|
? `messages=${JSON.stringify(
|
||||||
[
|
[...systemList, ...formatMessageList],
|
||||||
...systemList,
|
|
||||||
..._.map(messageList, (item: any) => {
|
|
||||||
return { role: item.role, content: item.content };
|
|
||||||
})
|
|
||||||
],
|
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
)}`
|
)}`
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ interface CompareContextProps {
|
|||||||
};
|
};
|
||||||
systemMessage?: string;
|
systemMessage?: string;
|
||||||
globalParams: Record<string, any>;
|
globalParams: Record<string, any>;
|
||||||
loadingStatus: Record<string, boolean>;
|
loadingStatus: Record<symbol, boolean>;
|
||||||
handleDeleteModel: (instanceId: symbol) => void;
|
handleDeleteModel: (instanceId: symbol) => void;
|
||||||
setSystemMessage?: (message: string) => void;
|
setSystemMessage?: (message: string) => void;
|
||||||
setGlobalParams: (value: Record<string, any>) => void;
|
setGlobalParams: (value: Record<string, any>) => void;
|
||||||
|
|||||||
@@ -3,3 +3,10 @@ export interface ModelSelectionItem extends Global.BaseOption<string> {
|
|||||||
instanceId: symbol;
|
instanceId: symbol;
|
||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MessageItem {
|
||||||
|
role: string;
|
||||||
|
content: string;
|
||||||
|
imgs?: { uid: string | number; dataUrl: string }[];
|
||||||
|
uid: number;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ 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 MultipleChat from './components/multiple-chat';
|
import MultipleChat from './components/multiple-chat';
|
||||||
import ParamsSettings from './components/params-settings';
|
|
||||||
import './style/play-ground.less';
|
import './style/play-ground.less';
|
||||||
|
|
||||||
const Playground: React.FC = () => {
|
const Playground: React.FC = () => {
|
||||||
@@ -40,16 +39,16 @@ const Playground: React.FC = () => {
|
|||||||
groundLeftRef.current?.viewCode?.();
|
groundLeftRef.current?.viewCode?.();
|
||||||
}, [groundLeftRef]);
|
}, [groundLeftRef]);
|
||||||
|
|
||||||
|
const handleToggleCollapse = useCallback(() => {
|
||||||
|
groundLeftRef.current?.setCollapse?.();
|
||||||
|
}, [groundLeftRef]);
|
||||||
|
|
||||||
const items: TabsProps['items'] = [
|
const items: TabsProps['items'] = [
|
||||||
{
|
{
|
||||||
key: 'chat',
|
key: 'chat',
|
||||||
label: 'Chat',
|
label: 'Chat',
|
||||||
children: (
|
children: (
|
||||||
<GroundLeft
|
<GroundLeft ref={groundLeftRef} modelList={modelList}></GroundLeft>
|
||||||
parameters={params}
|
|
||||||
ref={groundLeftRef}
|
|
||||||
modelList={modelList}
|
|
||||||
></GroundLeft>
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -95,7 +94,7 @@ const Playground: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="middle"
|
size="middle"
|
||||||
onClick={() => setCollapse(!collapse)}
|
onClick={handleToggleCollapse}
|
||||||
icon={
|
icon={
|
||||||
<IconFont
|
<IconFont
|
||||||
type="icon-a-layout6-line"
|
type="icon-a-layout6-line"
|
||||||
@@ -144,7 +143,7 @@ const Playground: React.FC = () => {
|
|||||||
<div className="chat">
|
<div className="chat">
|
||||||
<Tabs items={items} activeKey={activeKey}></Tabs>
|
<Tabs items={items} activeKey={activeKey}></Tabs>
|
||||||
</div>
|
</div>
|
||||||
{activeKey === 'chat' && (
|
{/* {activeKey === 'chat' && (
|
||||||
<div
|
<div
|
||||||
className={classNames('left', {
|
className={classNames('left', {
|
||||||
collapse: collapse
|
collapse: collapse
|
||||||
@@ -163,7 +162,7 @@ const Playground: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,14 +4,62 @@
|
|||||||
&-role {
|
&-role {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: var(--border-radius-mini);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--ant-color-fill-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-content-input {
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.has-img {
|
||||||
|
border: 1px solid var(--ant-color-fill-secondary);
|
||||||
|
border-radius: var(--border-radius-base);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.ant-input {
|
||||||
|
border-radius: 0 0 var(--border-radius-base) var(--border-radius-base);
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-color: var(--ant-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
:global(.label-val) {
|
:global(.label-val) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -14px;
|
top: -14px;
|
||||||
right: 2px;
|
right: -14px;
|
||||||
width: 80px;
|
width: 80px;
|
||||||
border-radius: 8px;
|
border-radius: var(--border-radius-base);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: 1px solid var(--ant-color-border) !important;
|
border: 1px solid var(--ant-color-border) !important;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,35 @@
|
|||||||
.ground-left {
|
.ground-left-wrapper {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.params-wrapper {
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 390px;
|
||||||
|
border-left: 1px solid var(--ant-color-split);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
height: calc(100vh - 72px);
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 390px;
|
||||||
|
padding-inline: var(--layout-content-inlinepadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
border-left: none;
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ground-left {
|
||||||
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -6,8 +37,15 @@
|
|||||||
height: calc(100vh - 72px);
|
height: calc(100vh - 72px);
|
||||||
|
|
||||||
.message-list-wrap {
|
.message-list-wrap {
|
||||||
max-height: calc(100vh - 152px);
|
display: flex;
|
||||||
overflow-y: auto;
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
padding-inline: var(--layout-content-inlinepadding);
|
padding-inline: var(--layout-content-inlinepadding);
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
.messageInput {
|
.messageInput {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
background-color: var(--ant-button-text-hover-bg);
|
background-color: var(--ant-button-text-hover-bg);
|
||||||
height: 54px;
|
height: 46px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,42 +14,6 @@
|
|||||||
border-bottom: 1px solid var(--ant-color-border);
|
border-bottom: 1px solid var(--ant-color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sys-message {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sys-content-wrap {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
background-color: var(--ant-color-fill-tertiary);
|
|
||||||
padding-right: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.clear-btn {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.clear-btn {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
right: 6px;
|
|
||||||
top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sys-content {
|
|
||||||
height: 36px;
|
|
||||||
line-height: 20px;
|
|
||||||
padding: 8px 14px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
.sys-message {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.sys-content-wrap {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background-color: var(--ant-color-fill-tertiary);
|
||||||
|
padding-right: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.clear-btn {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 6px;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sys-content {
|
||||||
|
height: 38px;
|
||||||
|
line-height: 18px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user