refactor: playground add comparsion
This commit is contained in:
+1
-1
@@ -14,7 +14,7 @@ const isProduction = env === 'production';
|
||||
const t = Date.now();
|
||||
export default defineConfig({
|
||||
proxy: {
|
||||
...proxy('http://192.168.50.166:8080')
|
||||
...proxy()
|
||||
},
|
||||
history: {
|
||||
type: 'hash'
|
||||
|
||||
+5
-1
@@ -28,6 +28,10 @@ export default {
|
||||
Message: {
|
||||
contentPadding: '12px 16px'
|
||||
},
|
||||
Tooltip: {
|
||||
colorBgSpotlight: 'rgba(110,110,110,1)'
|
||||
// sizePopupArrow: 0
|
||||
},
|
||||
Slider: {
|
||||
handleSize: 8,
|
||||
handleSizeHover: 8,
|
||||
@@ -42,7 +46,7 @@ export default {
|
||||
},
|
||||
token: {
|
||||
colorPrimary: '#007BFF',
|
||||
borderRadius: 8,
|
||||
borderRadius: 6,
|
||||
fontSize: 12,
|
||||
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 (
|
||||
<div
|
||||
style={{ ...style }}
|
||||
style={{
|
||||
padding: '0 calc(var(--ant-padding-sm) - 5px)',
|
||||
width: '100%',
|
||||
...style
|
||||
}}
|
||||
className={classNames(
|
||||
wrapperStyle.wrapper,
|
||||
wrapperStyle[`validate-status-${status}`],
|
||||
@@ -51,8 +55,8 @@ const Wrapper: React.FC<WrapperProps> = ({
|
||||
></LabelInfo>
|
||||
</label>
|
||||
<div
|
||||
className="child-inner"
|
||||
style={{
|
||||
padding: '0 calc(var(--ant-padding-sm) - 5px)',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
|
||||
+34
-10
@@ -21,10 +21,10 @@ html {
|
||||
--color-logs-text: #d4d4d4;
|
||||
--layout-content-blockpadding: 32px;
|
||||
--layout-content-inlinepadding: 32px;
|
||||
--menu-border-radius-base: 8px;
|
||||
--border-radius-base: 8px;
|
||||
--menu-border-radius-base: 6px;
|
||||
--border-radius-base: 6px;
|
||||
--border-radius-middle: 20px;
|
||||
--border-radius-small: 8px;
|
||||
--border-radius-small: 6px;
|
||||
--border-radius-mdium: 6px;
|
||||
--border-radius-mini: 4px;
|
||||
--color-white-1: rgba(255, 255, 255, 100%);
|
||||
@@ -39,11 +39,11 @@ html {
|
||||
--font-size-base: 12px;
|
||||
--font-size-large: 16px;
|
||||
--font-size-middle: 14px;
|
||||
--table-td-radius: 8px;
|
||||
--table-td-radius: 6px;
|
||||
--checkbox-border-radius: 4px;
|
||||
--ant-table-cell-padding-inline: 16px;
|
||||
--ant-table-cell-padding-block: 8px;
|
||||
--ant-table-header-border-radius: 8px;
|
||||
--ant-table-cell-padding-block: 6px;
|
||||
--ant-table-header-border-radius: 6px;
|
||||
--ant-table-header-split-color: #f0f0f0;
|
||||
--ant-table-row-selected-bg: #e6f6ff;
|
||||
--ant-table-row-selected-hover-bg: #e6f6ff;
|
||||
@@ -59,7 +59,7 @@ html {
|
||||
--ant-input-active-border-color: #007bff;
|
||||
--ant-input-hover-border-color: #2997ff;
|
||||
--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);
|
||||
--color-selected-bg: rgba(230, 230, 230, 88%);
|
||||
// --box-shadow-base: none;
|
||||
@@ -75,7 +75,7 @@ html {
|
||||
|
||||
&.ant-menu-css-var {
|
||||
// --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-color: var(--color-text-1);
|
||||
}
|
||||
@@ -92,7 +92,7 @@ html {
|
||||
--ant-font-size-xl: 20px;
|
||||
--ant-font-size: var(--font-size-base);
|
||||
--ant-padding-sm: 14px;
|
||||
--ant-border-radius-lg: 8px;
|
||||
--ant-border-radius-lg: 6px;
|
||||
--ant-color-text: #000;
|
||||
--ant-color-error: #ff4d4f;
|
||||
--ant-color-bg-mask: rgba(0, 0, 0, 35%);
|
||||
@@ -126,7 +126,7 @@ html {
|
||||
.css-var-ri.ant-menu-css-var,
|
||||
.css-var-rh.ant-menu-css-var {
|
||||
--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-color: var(--color-text-1);
|
||||
--ant-menu-item-active-bg: rgba(0, 0, 0, 4%);
|
||||
@@ -293,12 +293,26 @@ body {
|
||||
}
|
||||
|
||||
.ant-pro-sider-logo {
|
||||
position: relative;
|
||||
padding-left: 18px;
|
||||
padding-block: 12px;
|
||||
border-block-end: none;
|
||||
}
|
||||
|
||||
.ant-pro-sider-logo-collapsed {
|
||||
padding-left: 12px;
|
||||
|
||||
.collapse-wrap {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.collapse-wrap {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pro-sider .ant-layout-sider-children {
|
||||
@@ -427,6 +441,10 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
+22
-6
@@ -8,6 +8,7 @@ import VersionInfo, { modalConfig } from '@/components/version-info';
|
||||
import { logout } from '@/pages/login/apis';
|
||||
import { useAccessMarkedRoutes } from '@@/plugin-access';
|
||||
import { useModel } from '@@/plugin-model';
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
|
||||
import { ProLayout } from '@ant-design/pro-components';
|
||||
import {
|
||||
Link,
|
||||
@@ -20,7 +21,7 @@ import {
|
||||
useNavigate,
|
||||
type IRoute
|
||||
} from '@umijs/max';
|
||||
import { Modal } from 'antd';
|
||||
import { Button, Modal } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useMemo, useState } from 'react';
|
||||
import Exception from './Exception';
|
||||
@@ -139,6 +140,11 @@ export default (props: any) => {
|
||||
notFound: <span>404 not found</span>
|
||||
};
|
||||
|
||||
const handleToggleCollapse = (e: any) => {
|
||||
e.stopPropagation();
|
||||
setCollapsed(!collapsed);
|
||||
};
|
||||
|
||||
const newRoutes = filterRoutes(
|
||||
clientRoutes.filter((route) => route.id === '@@/global-layout'),
|
||||
(route) => {
|
||||
@@ -151,7 +157,6 @@ export default (props: any) => {
|
||||
|
||||
const role = initialState?.currentUser?.is_admin ? 'admin' : 'user';
|
||||
const [route] = useAccessMarkedRoutes(mapRoutes(newRoutes, role));
|
||||
console.log('clientRoutes===========', route, clientRoutes, newRoutes);
|
||||
|
||||
patchRoutes({
|
||||
routes: route.children,
|
||||
@@ -162,7 +167,6 @@ export default (props: any) => {
|
||||
() => matchRoutes(route?.children || [], location.pathname)?.pop?.()?.route,
|
||||
[location.pathname]
|
||||
);
|
||||
console.log('route===========', matchedRoute, route);
|
||||
return (
|
||||
<div>
|
||||
<div className="background"></div>
|
||||
@@ -179,10 +183,22 @@ export default (props: any) => {
|
||||
e.preventDefault();
|
||||
navigate('/');
|
||||
}}
|
||||
collapsed={collapsed}
|
||||
onCollapse={(collapsed) => {
|
||||
setCollapsed(collapsed);
|
||||
menuHeaderRender={(logo, title) => {
|
||||
return (
|
||||
<>
|
||||
{logo}
|
||||
<div className="collapse-wrap">
|
||||
<Button
|
||||
type={collapsed ? 'default' : 'text'}
|
||||
onClick={handleToggleCollapse}
|
||||
>
|
||||
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
collapsed={collapsed}
|
||||
onPageChange={(route) => {
|
||||
const { location } = history;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import avatarImg from '@/assets/images/avatar.png';
|
||||
import externalLinks from '@/constants/external-links';
|
||||
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 _ from 'lodash';
|
||||
|
||||
export function getRightRenderContent(opts: {
|
||||
export const getRightRenderContent = (opts: {
|
||||
runtimeConfig: any;
|
||||
loading: boolean;
|
||||
initialState: any;
|
||||
@@ -26,7 +25,7 @@ export function getRightRenderContent(opts: {
|
||||
setInitialState: any;
|
||||
siderWidth: number;
|
||||
intl: any;
|
||||
}) {
|
||||
}) => {
|
||||
const { intl, collapsed, siderWidth } = opts;
|
||||
|
||||
const allLocals = getAllLocales();
|
||||
@@ -121,7 +120,9 @@ export function getRightRenderContent(opts: {
|
||||
key: 'help',
|
||||
icon: <QuestionCircleOutlined />,
|
||||
label: (
|
||||
<span>{intl?.formatMessage?.({ id: 'common.button.help' })}</span>
|
||||
<span className="sub-title">
|
||||
{intl?.formatMessage?.({ id: 'common.button.help' })}
|
||||
</span>
|
||||
),
|
||||
children: helpList.map((item) => ({
|
||||
key: item.key,
|
||||
@@ -169,7 +170,7 @@ export function getRightRenderContent(opts: {
|
||||
key: 'lang',
|
||||
icon: <GlobalOutlined />,
|
||||
label: (
|
||||
<span>
|
||||
<span className="sub-title">
|
||||
{intl?.formatMessage?.({ id: 'common.settings.language' })}
|
||||
</span>
|
||||
),
|
||||
@@ -280,4 +281,4 @@ export function getRightRenderContent(opts: {
|
||||
></Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,5 +33,11 @@ export default {
|
||||
'playground.delete.img': 'Delete Image',
|
||||
'playground.img.upload': 'Upload Image',
|
||||
'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.img.upload': '上传图片',
|
||||
'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)}
|
||||
{item.parts && item.parts.length > 1 && (
|
||||
<Tooltip
|
||||
color="var(--color-white-1)"
|
||||
overlayInnerStyle={{
|
||||
width: 150,
|
||||
padding: 0,
|
||||
color: 'var(--ant-color-text-secondary)'
|
||||
width: 180,
|
||||
padding: 0
|
||||
}}
|
||||
title={
|
||||
<FileParts fileList={item.parts}></FileParts>
|
||||
|
||||
@@ -153,7 +153,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
|
||||
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>
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import TransitionWrapper from '@/components/transition';
|
||||
import HotKeys from '@/config/hotkeys';
|
||||
import useContainerScroll from '@/hooks/use-container-scorll';
|
||||
import { fetchChunkedData, readStreamData } from '@/utils/fetch-chunk-data';
|
||||
import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Input, Spin, Tooltip } from 'antd';
|
||||
import { useIntl, useSearchParams } from '@umijs/max';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
forwardRef,
|
||||
@@ -15,38 +15,33 @@ import {
|
||||
useState
|
||||
} from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
import { CHAT_API } from '../apis';
|
||||
import { Roles } from '../config';
|
||||
import { MessageItem } from '../config/types';
|
||||
import '../style/ground-left.less';
|
||||
import '../style/system-message-wrap.less';
|
||||
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';
|
||||
|
||||
interface MessageProps {
|
||||
parameters: any;
|
||||
parameters?: any;
|
||||
modelList: Global.BaseOption<string>[];
|
||||
ref?: any;
|
||||
}
|
||||
|
||||
interface MessageItemProps {
|
||||
role: string;
|
||||
content: string;
|
||||
uid: number;
|
||||
}
|
||||
|
||||
const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const { parameters, modelList } = props;
|
||||
const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const { modelList } = props;
|
||||
const messageId = useRef<number>(0);
|
||||
const [messageList, setMessageList] = useState<MessageItemProps[]>([
|
||||
{
|
||||
role: 'user',
|
||||
content: '',
|
||||
uid: messageId.current
|
||||
}
|
||||
]);
|
||||
const [messageList, setMessageList] = useState<MessageItem[]>([]);
|
||||
|
||||
const intl = useIntl();
|
||||
const [searchParams] = useSearchParams();
|
||||
const selectModel = searchParams.get('model') || '';
|
||||
const [parameters, setParams] = useState<any>({});
|
||||
const [systemMessage, setSystemMessage] = useState('');
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const [show, setShow] = useState(false);
|
||||
@@ -54,10 +49,14 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const [activeIndex, setActiveIndex] = useState(-1);
|
||||
const [tokenResult, setTokenResult] = useState<any>(null);
|
||||
const [currentIsFocus, setCurrentIsFocus] = useState(false);
|
||||
const [collapse, setCollapse] = useState(false);
|
||||
const systemRef = useRef<any>(null);
|
||||
const contentRef = useRef<any>('');
|
||||
const controllerRef = 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(
|
||||
scroller,
|
||||
{ toBottom: true }
|
||||
@@ -67,10 +66,18 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
updateScrollerPosition();
|
||||
}, [messageList]);
|
||||
|
||||
useEffect(() => {
|
||||
paramsScroller.current?.recalculate();
|
||||
leftSimple.current?.recalculate();
|
||||
}, [collapse]);
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
viewCode() {
|
||||
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', '');
|
||||
setMessageList([
|
||||
...messageList,
|
||||
...currentMessageRef.current,
|
||||
{
|
||||
role: Roles.Assistant,
|
||||
content: contentRef.current,
|
||||
@@ -122,7 +130,7 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const submitMessage = async () => {
|
||||
const submitMessage = async (current?: { role: string; content: string }) => {
|
||||
if (!parameters.model) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -132,24 +140,60 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
controllerRef.current?.abort?.();
|
||||
controllerRef.current = new AbortController();
|
||||
const signal = controllerRef.current.signal;
|
||||
const messages = _.map(messageList, (item: MessageItemProps) => {
|
||||
return {
|
||||
role: item.role,
|
||||
content: item.content
|
||||
};
|
||||
currentMessageRef.current = current
|
||||
? [
|
||||
{
|
||||
...current,
|
||||
uid: messageId.current
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
setMessageList((pre) => {
|
||||
return [...pre, ...currentMessageRef.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 = {
|
||||
messages: systemMessage
|
||||
? [
|
||||
{
|
||||
role: Roles.System,
|
||||
content: systemMessage
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: systemMessage
|
||||
}
|
||||
]
|
||||
},
|
||||
...messages
|
||||
...formatMessages
|
||||
]
|
||||
: [...messages],
|
||||
: [...formatMessages],
|
||||
...parameters,
|
||||
stream: true
|
||||
};
|
||||
@@ -162,6 +206,7 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
setMessageId();
|
||||
const { reader, decoder } = result;
|
||||
await readStreamData(reader, decoder, (chunk: any) => {
|
||||
joinMessage(chunk);
|
||||
@@ -177,19 +222,19 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
return;
|
||||
}
|
||||
setMessageId();
|
||||
setMessageList([
|
||||
{
|
||||
role: Roles.User,
|
||||
content: '',
|
||||
uid: messageId.current
|
||||
}
|
||||
]);
|
||||
setMessageList([]);
|
||||
};
|
||||
|
||||
const handleView = () => {
|
||||
setShow(true);
|
||||
};
|
||||
|
||||
const handleSendMessage = (message: { role: string; content: string }) => {
|
||||
console.log('message:', message);
|
||||
const currentMessage = message.content ? message : undefined;
|
||||
submitMessage(currentMessage);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
submitMessage();
|
||||
};
|
||||
@@ -203,7 +248,7 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
setMessageList([...messageList]);
|
||||
};
|
||||
|
||||
const handleUpdateMessage = (index: number, message: MessageItemProps) => {
|
||||
const handleUpdateMessage = (index: number, message: MessageItem) => {
|
||||
messageList[index] = message;
|
||||
setMessageList([...messageList]);
|
||||
};
|
||||
@@ -239,7 +284,20 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
|
||||
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(
|
||||
HotKeys.SUBMIT,
|
||||
@@ -278,14 +336,21 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="ground-left">
|
||||
<div
|
||||
className="message-list-wrap"
|
||||
ref={scroller}
|
||||
onWheel={handleContentWheel}
|
||||
>
|
||||
<div style={{ marginBottom: 40 }}>
|
||||
<TransitionWrapper
|
||||
<div className="ground-left-wrapper">
|
||||
<div className="ground-left">
|
||||
<div className="message-list-wrap" onWheel={handleContentWheel}>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
borderRadius: 'var(--border-radius-mini)',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
>
|
||||
<SystemMessage
|
||||
systemMessage={systemMessage}
|
||||
setSystemMessage={setSystemMessage}
|
||||
></SystemMessage>
|
||||
{/* <TransitionWrapper
|
||||
header={renderLabel()}
|
||||
variant="filled"
|
||||
setCollapsed={setCollapsed}
|
||||
@@ -303,11 +368,11 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
})}
|
||||
onChange={handleSystemMessageChange}
|
||||
></Input.TextArea>
|
||||
</TransitionWrapper>
|
||||
</div>
|
||||
</TransitionWrapper> */}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{messageList.map((item, index) => {
|
||||
<div className="content">
|
||||
{/* {messageList.map((item, index) => {
|
||||
return (
|
||||
<MessageItem
|
||||
key={item.uid}
|
||||
@@ -315,44 +380,57 @@ const MessageList: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
islast={index === messageList.length - 1}
|
||||
loading={loading}
|
||||
onDelete={() => handleDelete(index)}
|
||||
updateMessage={(message: MessageItemProps) =>
|
||||
updateMessage={(message: MessageItem) =>
|
||||
handleUpdateMessage(index, message)
|
||||
}
|
||||
onSubmit={handleSubmit}
|
||||
message={item}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{loading && (
|
||||
})} */}
|
||||
<MessageContent
|
||||
spans={{
|
||||
span: 24,
|
||||
count: 1
|
||||
}}
|
||||
messageList={messageList}
|
||||
setMessageList={setMessageList}
|
||||
editable={true}
|
||||
loading={loading}
|
||||
/>
|
||||
{/* {loading && (
|
||||
<Spin>
|
||||
<div style={{ height: '46px' }}></div>
|
||||
</Spin>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ground-left-footer">
|
||||
<MessageInput
|
||||
loading={loading}
|
||||
disabled={!parameters.model}
|
||||
isEmpty={!messageList.length}
|
||||
handleSubmit={handleSendMessage}
|
||||
addMessage={handleNewMessage}
|
||||
handleAbortFetch={handleStopConversation}
|
||||
clearAll={handleClear}
|
||||
setModelSelections={handleSelectModel}
|
||||
presetPrompt={handlePresetPrompt}
|
||||
modelList={modelList}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ground-left-footer">
|
||||
<MessageInput
|
||||
loading={loading}
|
||||
handleSubmit={handleSubmit}
|
||||
addMessage={handleNewMessage}
|
||||
handleAbortFetch={handleStopConversation}
|
||||
clearAll={handleClear}
|
||||
setModelSelections={handleSelectModel}
|
||||
presetPrompt={handlePresetPrompt}
|
||||
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
|
||||
className={classNames('params-wrapper', {
|
||||
collapsed: collapse
|
||||
})}
|
||||
>
|
||||
<div className="box">
|
||||
<ParamsSettings setParams={setParams} selectedModel={selectModel} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ViewCodeModal
|
||||
open={show}
|
||||
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 {
|
||||
ClearOutlined,
|
||||
ControlOutlined,
|
||||
PictureOutlined,
|
||||
EnterOutlined,
|
||||
SwapOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Divider, Input, Select } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import { Button, Divider, Input, Select, Tooltip } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { Roles } from '../config';
|
||||
import { MessageItem } from '../config/types';
|
||||
import '../style/message-input.less';
|
||||
import PromptModal from './prompt-modal';
|
||||
import ThumbImg from './thumb-img';
|
||||
import UploadImg from './upload-img';
|
||||
|
||||
type CurrentMessage = Omit<MessageItem, 'uid'>;
|
||||
|
||||
const layoutOptions = [
|
||||
{
|
||||
@@ -23,7 +29,7 @@ const layoutOptions = [
|
||||
span: 12,
|
||||
count: 2
|
||||
},
|
||||
tips: 'two models compare'
|
||||
tips: 'playground.toolbar.compare2Model'
|
||||
},
|
||||
{
|
||||
label: '3 columns',
|
||||
@@ -32,7 +38,7 @@ const layoutOptions = [
|
||||
span: 8,
|
||||
count: 3
|
||||
},
|
||||
tips: 'three models compare'
|
||||
tips: 'playground.toolbar.compare3Model'
|
||||
},
|
||||
{
|
||||
label: '4 columns',
|
||||
@@ -41,7 +47,7 @@ const layoutOptions = [
|
||||
span: 12,
|
||||
count: 4
|
||||
},
|
||||
tips: 'four models compare'
|
||||
tips: 'playground.toolbar.compare4Model'
|
||||
},
|
||||
{
|
||||
label: '6 columns',
|
||||
@@ -50,13 +56,13 @@ const layoutOptions = [
|
||||
span: 8,
|
||||
count: 6
|
||||
},
|
||||
tips: 'six models compare'
|
||||
tips: 'playground.toolbar.compare6Model'
|
||||
}
|
||||
];
|
||||
|
||||
interface MessageInputProps {
|
||||
modelList: Global.BaseOption<string>[];
|
||||
handleSubmit: (params: { role: string; content: string }) => void;
|
||||
handleSubmit: (params: CurrentMessage) => void;
|
||||
handleAbortFetch: () => void;
|
||||
updateLayout?: (value: { span: number; count: number }) => void;
|
||||
clearAll: () => void;
|
||||
@@ -65,9 +71,12 @@ interface MessageInputProps {
|
||||
instanceId: symbol;
|
||||
})[]
|
||||
) => void;
|
||||
presetPrompt: (list: { role: string; content: string }[]) => void;
|
||||
addMessage: (message: { role: string; content: string }) => void;
|
||||
presetPrompt: (list: CurrentMessage[]) => void;
|
||||
addMessage: (message: CurrentMessage) => void;
|
||||
loading: boolean;
|
||||
showModelSelection?: boolean;
|
||||
disabled: boolean;
|
||||
isEmpty?: boolean;
|
||||
}
|
||||
|
||||
const MessageInput: React.FC<MessageInputProps> = ({
|
||||
@@ -75,21 +84,35 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
handleAbortFetch,
|
||||
setModelSelections,
|
||||
presetPrompt,
|
||||
loading,
|
||||
modelList,
|
||||
clearAll,
|
||||
updateLayout,
|
||||
addMessage
|
||||
addMessage,
|
||||
loading,
|
||||
modelList,
|
||||
showModelSelection,
|
||||
disabled,
|
||||
isEmpty
|
||||
}) => {
|
||||
const { TextArea } = Input;
|
||||
const intl = useIntl();
|
||||
const platform = platformCall();
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
// const [disabled, setDisabled] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [message, setMessage] = useState<{ role: string; content: string }>({
|
||||
const [message, setMessage] = useState<CurrentMessage>({
|
||||
role: Roles.User,
|
||||
content: ''
|
||||
content: '',
|
||||
imgs: []
|
||||
});
|
||||
const imgCountRef = useRef(0);
|
||||
|
||||
const resetMessage = () => {
|
||||
setMessage({
|
||||
role: message.role,
|
||||
content: '',
|
||||
imgs: []
|
||||
});
|
||||
};
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
console.log('input change:', value);
|
||||
setMessage({
|
||||
@@ -99,13 +122,10 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
};
|
||||
const handleSendMessage = () => {
|
||||
handleSubmit({ ...message });
|
||||
setMessage({
|
||||
...message,
|
||||
content: ''
|
||||
});
|
||||
resetMessage();
|
||||
};
|
||||
const onStop = () => {
|
||||
setDisabled(false);
|
||||
// setDisabled(false);
|
||||
handleAbortFetch();
|
||||
};
|
||||
const handleLayoutChange = (value: { span: number; count: number }) => {
|
||||
@@ -144,19 +164,129 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
const handleAddMessage = () => {
|
||||
console.log('add 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({
|
||||
...message,
|
||||
imgs: [...(message.imgs || []), ...list]
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing images:', error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// ========== upload image ==========
|
||||
const handleUpdateImgList = (
|
||||
list: { uid: number | string; dataUrl: string }[]
|
||||
) => {
|
||||
setMessage({
|
||||
...message,
|
||||
content: ''
|
||||
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(
|
||||
HotKeys.SUBMIT.join(','),
|
||||
() => {
|
||||
console.log('submit message', loading);
|
||||
handleSendMessage();
|
||||
},
|
||||
{ preventDefault: true }
|
||||
{ enabled: true }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="messageInput">
|
||||
<div className="tool-bar">
|
||||
@@ -170,52 +300,86 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
{intl.formatMessage({ id: `playground.${message.role}` })}
|
||||
</Button>
|
||||
<Divider type="vertical" style={{ margin: 0 }} />
|
||||
<Button type="text" icon={<PictureOutlined />} size="middle"></Button>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ClearOutlined />}
|
||||
size="middle"
|
||||
onClick={handleClearAll}
|
||||
></Button>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ControlOutlined />}
|
||||
size="middle"
|
||||
onClick={handleOpenPrompt}
|
||||
></Button>
|
||||
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: 'playground.toolbar.clearmsg' })}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ClearOutlined />}
|
||||
size="middle"
|
||||
onClick={handleClearAll}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: 'playground.toolbar.prompts' })}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<ControlOutlined />}
|
||||
size="middle"
|
||||
onClick={handleOpenPrompt}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
{updateLayout && (
|
||||
<>
|
||||
<Divider type="vertical" style={{ margin: 0 }} />
|
||||
{layoutOptions.map((option) => (
|
||||
<Button
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: option.tips })}
|
||||
key={option.icon}
|
||||
type="text"
|
||||
icon={<IconFont type={option.icon}></IconFont>}
|
||||
size="middle"
|
||||
onClick={() => handleLayoutChange(option.value)}
|
||||
></Button>
|
||||
>
|
||||
<Button
|
||||
key={option.icon}
|
||||
type="text"
|
||||
icon={<IconFont type={option.icon}></IconFont>}
|
||||
size="middle"
|
||||
onClick={() => handleLayoutChange(option.value)}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="actions">
|
||||
<Select
|
||||
variant="borderless"
|
||||
style={{ width: 180 }}
|
||||
placeholder="select models"
|
||||
options={modelList}
|
||||
mode="multiple"
|
||||
maxCount={6}
|
||||
maxTagCount={0}
|
||||
maxTagTextLength={15}
|
||||
onChange={handleUpdateModelSelections}
|
||||
></Select>
|
||||
{showModelSelection && (
|
||||
<Select
|
||||
variant="borderless"
|
||||
style={{ width: 180 }}
|
||||
placeholder="select models"
|
||||
options={modelList}
|
||||
mode="multiple"
|
||||
maxCount={6}
|
||||
maxTagCount={0}
|
||||
maxTagTextLength={15}
|
||||
onChange={handleUpdateModelSelections}
|
||||
></Select>
|
||||
)}
|
||||
|
||||
<Button type="default" size="middle" onClick={handleAddMessage}>
|
||||
{intl.formatMessage({ id: 'common.button.add' })}
|
||||
</Button>
|
||||
{!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' })}
|
||||
<span className="m-l-5 opct-7">
|
||||
{platform.isMac ? (
|
||||
<>
|
||||
<IconFont type="icon-command"></IconFont> +{' '}
|
||||
<EnterOutlined />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
CTRL + <EnterOutlined />
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -228,6 +392,10 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ThumbImg
|
||||
dataList={message.imgs || []}
|
||||
onDelete={handleDeleteImg}
|
||||
></ThumbImg>
|
||||
<TextArea
|
||||
placeholder="Type your message here"
|
||||
autoSize={{ minRows: 3, maxRows: 3 }}
|
||||
@@ -235,6 +403,8 @@ const MessageInput: React.FC<MessageInputProps> = ({
|
||||
value={message.content}
|
||||
size="large"
|
||||
variant="borderless"
|
||||
onKeyDown={handleKeyDown}
|
||||
onPaste={handleOnPaste}
|
||||
></TextArea>
|
||||
<PromptModal
|
||||
open={open}
|
||||
|
||||
@@ -216,7 +216,7 @@ const MessageItem: React.FC<{
|
||||
<ThumbImg dataList={imgList} onDelete={handleDeleteImg}></ThumbImg>
|
||||
<Input.TextArea
|
||||
ref={inputRef}
|
||||
style={{ paddingBlock: '12px', paddingTop: 20 }}
|
||||
style={{ paddingBlock: '12px' }}
|
||||
value={message.content}
|
||||
autoSize={true}
|
||||
variant="filled"
|
||||
|
||||
@@ -1,27 +1,256 @@
|
||||
import CopyButton from '@/components/copy-button';
|
||||
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 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 { MessageItem } from '../../config/types';
|
||||
import '../../style/content-item.less';
|
||||
import ThumbImg from '../thumb-img';
|
||||
import UploadImg from '../upload-img';
|
||||
|
||||
const ContentItem: React.FC<{ data: { role: string; content: string } }> = ({
|
||||
data
|
||||
interface MessageItemProps {
|
||||
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 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 (
|
||||
<div className="content-item">
|
||||
<div className="content-item-role">
|
||||
<span className="m-r-5">
|
||||
{Roles.User === data.role ? (
|
||||
<UserOutlined></UserOutlined>
|
||||
) : (
|
||||
<IconFont type="icon-AIzhineng"></IconFont>
|
||||
<div className="role" onClick={handleToggleRole}>
|
||||
<span className="m-r-5">
|
||||
{Roles.User === data.role ? (
|
||||
<UserOutlined></UserOutlined>
|
||||
) : (
|
||||
<IconFont type="icon-AIzhineng"></IconFont>
|
||||
)}
|
||||
</span>
|
||||
{intl.formatMessage({ id: `playground.${data.role}` })}
|
||||
<SwapOutlined rotate={90} className="m-l-5" />
|
||||
</div>
|
||||
<div className="actions">
|
||||
{editable && (
|
||||
<UploadImg handleUpdateImgList={handleUpdateImgList}></UploadImg>
|
||||
)}
|
||||
</span>
|
||||
{intl.formatMessage({ id: `playground.${data.role}` })}
|
||||
{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>
|
||||
<div className="content-item-content">{data.content}</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import _ from 'lodash';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import CompareContext from '../../config/compare-context';
|
||||
import { ModelSelectionItem } from '../../config/types';
|
||||
import { MessageItem, ModelSelectionItem } from '../../config/types';
|
||||
import '../../style/multiple-chat.less';
|
||||
import MessageInput from '../message-input';
|
||||
import ActiveModels from './active-models';
|
||||
|
||||
type CurrentMessage = Omit<MessageItem, 'uid'>;
|
||||
|
||||
interface MultiCompareProps {
|
||||
modelList: (Global.BaseOption<string> & { type?: string })[];
|
||||
spans?: number;
|
||||
@@ -61,7 +63,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
||||
loadingStatus[instanceId] = false;
|
||||
};
|
||||
|
||||
const handleSubmit = (currentMessage: { role: string; content: string }) => {
|
||||
const handleSubmit = (currentMessage: CurrentMessage) => {
|
||||
const modelRefList = Object.getOwnPropertySymbols(modelRefs.current);
|
||||
modelRefList.forEach((instanceId: symbol) => {
|
||||
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);
|
||||
modelRefList.forEach((instanceId: symbol) => {
|
||||
const ref = modelRefs.current[instanceId];
|
||||
@@ -218,13 +220,6 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
||||
setModelSelections(resultList);
|
||||
}, [modelList]);
|
||||
|
||||
// useEffect(() => {
|
||||
// modelRefs.current = {};
|
||||
// modelSelections.forEach((item) => {
|
||||
// modelRefs.current[item.instanceId] = null;
|
||||
// });
|
||||
// }, [modelSelections]);
|
||||
|
||||
return (
|
||||
<div className="multiple-chat" style={{ height: boxHeight }}>
|
||||
<div className="chat-list">
|
||||
@@ -248,6 +243,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
||||
<div>
|
||||
<MessageInput
|
||||
loading={isLoading}
|
||||
disabled={isLoading || modelSelections.length === 0}
|
||||
handleSubmit={handleSubmit}
|
||||
addMessage={handleAddMessage}
|
||||
handleAbortFetch={handleAbortFetch}
|
||||
@@ -256,6 +252,7 @@ const MultiCompare: React.FC<MultiCompareProps> = ({ modelList }) => {
|
||||
setModelSelections={handleUpdateModelSelections}
|
||||
presetPrompt={handlePresetPrompt}
|
||||
modelList={modelFullList}
|
||||
showModelSelection={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,48 +1,51 @@
|
||||
import { Spin } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import SimpleBar from 'simplebar-react';
|
||||
import React from 'react';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
import { MessageItem } from '../../config/types';
|
||||
import ContentItem from './content-item';
|
||||
|
||||
interface MessageContentProps {
|
||||
loading: boolean;
|
||||
loading?: boolean;
|
||||
spans: {
|
||||
span: number;
|
||||
count: number;
|
||||
};
|
||||
messageList: {
|
||||
role: string;
|
||||
uid?: any;
|
||||
content: string;
|
||||
}[];
|
||||
editable?: boolean;
|
||||
messageList: MessageItem[];
|
||||
setMessageList?: (list: any) => void;
|
||||
}
|
||||
|
||||
const MessageContent: React.FC<MessageContentProps> = ({
|
||||
setMessageList,
|
||||
messageList,
|
||||
spans,
|
||||
loading
|
||||
editable
|
||||
}) => {
|
||||
const maxHeight = useMemo(() => {
|
||||
const total = 72 + 110 + 46 + 16 + 32;
|
||||
if (spans.span < 4) {
|
||||
return `calc(100vh - ${total}px)`;
|
||||
}
|
||||
return `calc(100vh - ${total * 2 + 16}px)`;
|
||||
}, [spans.span]);
|
||||
const updateMessage = (index: number, message: MessageItem) => {
|
||||
const newMessageList = [...messageList];
|
||||
newMessageList[index] = message;
|
||||
setMessageList?.(newMessageList);
|
||||
};
|
||||
|
||||
const handleDelete = (index: number) => {
|
||||
const newMessageList = [...messageList];
|
||||
newMessageList.splice(index, 1);
|
||||
setMessageList?.(newMessageList);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{messageList.length ? (
|
||||
<SimpleBar style={{ maxHeight: 'calc(100% - 46px)' }}>
|
||||
<div className="message-content-list">
|
||||
{messageList.map((item, index) => (
|
||||
<ContentItem key={index} data={item} />
|
||||
))}
|
||||
</div>
|
||||
</SimpleBar>
|
||||
) : (
|
||||
<span>{loading}</span>
|
||||
{!!messageList.length && (
|
||||
<div className="message-content-list">
|
||||
{messageList.map((item, index) => (
|
||||
<ContentItem
|
||||
key={item.uid}
|
||||
data={item}
|
||||
editable={editable}
|
||||
onDelete={() => handleDelete(index)}
|
||||
updateMessage={(data) => updateMessage(index, data)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Spin spinning={!!loading} size="small" style={{ width: '100%' }} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,15 +7,7 @@ import {
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Input,
|
||||
Popover,
|
||||
Select
|
||||
} from 'antd';
|
||||
import { Button, Checkbox, Dropdown, Popover, Select, Spin } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, {
|
||||
forwardRef,
|
||||
@@ -27,16 +19,18 @@ import React, {
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import SimpleBar from 'simplebar-react';
|
||||
import 'simplebar-react/dist/simplebar.min.css';
|
||||
import { CHAT_API } from '../../apis';
|
||||
import { Roles } from '../../config';
|
||||
import CompareContext from '../../config/compare-context';
|
||||
import { ModelSelectionItem } from '../../config/types';
|
||||
import { MessageItem, ModelSelectionItem } from '../../config/types';
|
||||
import '../../style/model-item.less';
|
||||
import ParamsSettings from '../params-settings';
|
||||
import ReferenceParams from '../reference-params';
|
||||
import ViewCodeModal from '../view-code-modal';
|
||||
import MessageContent from './message-content';
|
||||
import SystemMessage from './system-message';
|
||||
|
||||
interface ModelItemProps {
|
||||
model: string;
|
||||
@@ -45,12 +39,6 @@ interface ModelItemProps {
|
||||
ref: any;
|
||||
}
|
||||
|
||||
interface MessageItemProps {
|
||||
role: string;
|
||||
content: string;
|
||||
uid: string | number;
|
||||
}
|
||||
|
||||
const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
({ model, modelList, instanceId }, ref) => {
|
||||
const {
|
||||
@@ -63,26 +51,28 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
} = useContext(CompareContext);
|
||||
const intl = useIntl();
|
||||
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 [params, setParams] = useState<Record<string, any>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const messageId = useRef<number>(0);
|
||||
const [messageList, setMessageList] = useState<MessageItemProps[]>([]);
|
||||
const [messageList, setMessageList] = useState<MessageItem[]>([]);
|
||||
const [tokenResult, setTokenResult] = useState<any>(null);
|
||||
const [show, setShow] = useState(false);
|
||||
const contentRef = useRef<any>('');
|
||||
const controllerRef = useRef<any>(null);
|
||||
const currentMessageRef = useRef<MessageItemProps>({} as MessageItemProps);
|
||||
const systemMessageRef = useRef<any>(null);
|
||||
const currentMessageRef = useRef<MessageItem>({} as MessageItem);
|
||||
|
||||
const setMessageId = () => {
|
||||
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 = () => {
|
||||
controllerRef.current?.abort?.();
|
||||
@@ -104,9 +94,6 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
console.log('currentMessage==========5', messageList);
|
||||
setMessageList([
|
||||
...messageList,
|
||||
{
|
||||
...currentMessageRef.current
|
||||
},
|
||||
{
|
||||
role: Roles.Assistant,
|
||||
content: contentRef.current,
|
||||
@@ -149,28 +136,58 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
...currentMessageRef.current
|
||||
}
|
||||
],
|
||||
(item: MessageItemProps) => {
|
||||
(item: MessageItem) => {
|
||||
return {
|
||||
role: item.role,
|
||||
content: item.content
|
||||
content: item.content,
|
||||
imgs: item.imgs || []
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
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 = {
|
||||
messages: systemMessage
|
||||
? [
|
||||
{
|
||||
role: Roles.System,
|
||||
content: systemMessage
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: systemMessage
|
||||
}
|
||||
]
|
||||
},
|
||||
...messages
|
||||
...formatMessages
|
||||
]
|
||||
: [...messages],
|
||||
: [...formatMessages],
|
||||
...parameters,
|
||||
stream: true
|
||||
};
|
||||
// ============== payload end ================
|
||||
const result = await fetchChunkedData({
|
||||
data: chatParams,
|
||||
url: CHAT_API,
|
||||
@@ -180,6 +197,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
setMessageId();
|
||||
const { reader, decoder } = result;
|
||||
await readStreamData(reader, decoder, (chunk: any) => {
|
||||
joinMessage(chunk);
|
||||
@@ -242,7 +260,7 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
setMessageList([]);
|
||||
setTokenResult(null);
|
||||
setSystemMessage('');
|
||||
currentMessageRef.current = {} as MessageItemProps;
|
||||
currentMessageRef.current = {} as MessageItem;
|
||||
console.log('clear message', systemMessage);
|
||||
};
|
||||
|
||||
@@ -258,8 +276,8 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
handleClearMessage();
|
||||
};
|
||||
|
||||
const handlePresetMessageList = (list: MessageItemProps[]) => {
|
||||
currentMessageRef.current = {} as MessageItemProps;
|
||||
const handlePresetMessageList = (list: MessageItem[]) => {
|
||||
currentMessageRef.current = {} as MessageItem;
|
||||
const messages = _.map(
|
||||
list,
|
||||
(item: { role: string; content: string }) => {
|
||||
@@ -279,31 +297,6 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
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(() => {
|
||||
return modelList.filter((item) => {
|
||||
return item.type !== 'empty';
|
||||
@@ -406,66 +399,35 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
|
||||
size="small"
|
||||
></Button>
|
||||
</Popover>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<CloseOutlined />}
|
||||
size="small"
|
||||
onClick={handleDelete}
|
||||
></Button>
|
||||
{modelList.length > 2 && (
|
||||
<Button
|
||||
type="text"
|
||||
icon={<CloseOutlined />}
|
||||
size="small"
|
||||
onClick={handleDelete}
|
||||
></Button>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<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>
|
||||
<div className="content">
|
||||
<MessageContent
|
||||
spans={spans}
|
||||
messageList={messageList}
|
||||
loading={loadingStatus[params.model]}
|
||||
/>
|
||||
</div>
|
||||
<SystemMessage
|
||||
systemMessage={systemMessage}
|
||||
setSystemMessage={setSystemMessage}
|
||||
></SystemMessage>
|
||||
<SimpleBar style={{ maxHeight: maxHeight }}>
|
||||
<div className="content">
|
||||
<MessageContent
|
||||
spans={spans}
|
||||
messageList={messageList}
|
||||
setMessageList={setMessageList}
|
||||
editable={true}
|
||||
/>
|
||||
<Spin
|
||||
spinning={!!loadingStatus[instanceId]}
|
||||
size="small"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</SimpleBar>
|
||||
<ViewCodeModal
|
||||
open={show}
|
||||
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'
|
||||
})
|
||||
})}
|
||||
style={{ paddingInline: 0 }}
|
||||
style={{ padding: '20px 2px 0' }}
|
||||
variant="borderless"
|
||||
>
|
||||
<Slider
|
||||
defaultValue={1}
|
||||
max={2}
|
||||
step={0.1}
|
||||
style={{ marginBottom: 0, marginTop: 16 }}
|
||||
style={{ marginBottom: 0, marginTop: 16, marginInline: 0 }}
|
||||
tooltip={{ open: false }}
|
||||
value={form.getFieldValue('temperature') || undefined}
|
||||
onChange={(val) => handleFieldValueChange(val, 'temperature')}
|
||||
@@ -240,14 +240,14 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
||||
id: 'playground.params.maxtokens.tips'
|
||||
})
|
||||
})}
|
||||
style={{ paddingInline: 0 }}
|
||||
style={{ padding: '20px 2px 0' }}
|
||||
variant="borderless"
|
||||
>
|
||||
<Slider
|
||||
defaultValue={1024}
|
||||
max={2048}
|
||||
step={1}
|
||||
style={{ marginBottom: 0, marginTop: 16 }}
|
||||
style={{ marginBottom: 0, marginTop: 16, marginInline: 0 }}
|
||||
tooltip={{ open: false }}
|
||||
value={form.getFieldValue('max_tokens') || undefined}
|
||||
onChange={(val) => handleFieldValueChange(val, 'max_tokens')}
|
||||
@@ -266,14 +266,14 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
||||
id: 'playground.params.topp.tips'
|
||||
})
|
||||
})}
|
||||
style={{ paddingInline: 0 }}
|
||||
style={{ padding: '20px 2px 0' }}
|
||||
variant="borderless"
|
||||
>
|
||||
<Slider
|
||||
defaultValue={1}
|
||||
max={1}
|
||||
step={0.1}
|
||||
style={{ marginBottom: 0, marginTop: 16 }}
|
||||
style={{ marginBottom: 0, marginTop: 16, marginInline: 0 }}
|
||||
tooltip={{ open: false }}
|
||||
value={form.getFieldValue('top_p') || undefined}
|
||||
onChange={(val) => handleFieldValueChange(val, 'top_p')}
|
||||
|
||||
@@ -57,7 +57,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
|
||||
type="default"
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
Use
|
||||
Apply
|
||||
</Button>
|
||||
</h3>
|
||||
{item.data.map((data, i) => {
|
||||
|
||||
@@ -58,16 +58,41 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
}
|
||||
};
|
||||
const generateCode = () => {
|
||||
const systemList = systemMessage
|
||||
? [
|
||||
{
|
||||
role: 'system',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: systemMessage
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
const formatMessageList = _.map(messageList, (item: any) => {
|
||||
return {
|
||||
role: item.role,
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: item.content
|
||||
},
|
||||
..._.map(item.imgs, (img: any) => {
|
||||
return {
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: img.dataUrl
|
||||
}
|
||||
};
|
||||
})
|
||||
]
|
||||
};
|
||||
});
|
||||
if (lang === 'shell') {
|
||||
const systemList = systemMessage
|
||||
? [{ role: 'system', content: systemMessage }]
|
||||
: [];
|
||||
const messages = [
|
||||
...systemList,
|
||||
..._.map(messageList, (item: any) => {
|
||||
return { role: item.role, content: item.content };
|
||||
})
|
||||
];
|
||||
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(
|
||||
{
|
||||
...parameters,
|
||||
@@ -78,15 +103,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
)}'`;
|
||||
setCodeValue(code);
|
||||
} else if (lang === 'javascript') {
|
||||
const systemList = systemMessage
|
||||
? [{ role: 'system', content: systemMessage }]
|
||||
: [];
|
||||
const messages = [
|
||||
...systemList,
|
||||
..._.map(messageList, (item: any) => {
|
||||
return { role: item.role, content: item.content };
|
||||
})
|
||||
];
|
||||
const messages = [...systemList, ...formatMessageList];
|
||||
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,
|
||||
@@ -110,18 +127,10 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
|
||||
},
|
||||
''
|
||||
);
|
||||
const systemList = systemMessage
|
||||
? [{ role: 'system', content: systemMessage }]
|
||||
: [];
|
||||
const messages =
|
||||
apiType === 'chat'
|
||||
? `messages=${JSON.stringify(
|
||||
[
|
||||
...systemList,
|
||||
..._.map(messageList, (item: any) => {
|
||||
return { role: item.role, content: item.content };
|
||||
})
|
||||
],
|
||||
[...systemList, ...formatMessageList],
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
|
||||
@@ -7,7 +7,7 @@ interface CompareContextProps {
|
||||
};
|
||||
systemMessage?: string;
|
||||
globalParams: Record<string, any>;
|
||||
loadingStatus: Record<string, boolean>;
|
||||
loadingStatus: Record<symbol, boolean>;
|
||||
handleDeleteModel: (instanceId: symbol) => void;
|
||||
setSystemMessage?: (message: string) => void;
|
||||
setGlobalParams: (value: Record<string, any>) => void;
|
||||
|
||||
@@ -3,3 +3,10 @@ export interface ModelSelectionItem extends Global.BaseOption<string> {
|
||||
instanceId: symbol;
|
||||
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 GroundLeft from './components/ground-left';
|
||||
import MultipleChat from './components/multiple-chat';
|
||||
import ParamsSettings from './components/params-settings';
|
||||
import './style/play-ground.less';
|
||||
|
||||
const Playground: React.FC = () => {
|
||||
@@ -40,16 +39,16 @@ const Playground: React.FC = () => {
|
||||
groundLeftRef.current?.viewCode?.();
|
||||
}, [groundLeftRef]);
|
||||
|
||||
const handleToggleCollapse = useCallback(() => {
|
||||
groundLeftRef.current?.setCollapse?.();
|
||||
}, [groundLeftRef]);
|
||||
|
||||
const items: TabsProps['items'] = [
|
||||
{
|
||||
key: 'chat',
|
||||
label: 'Chat',
|
||||
children: (
|
||||
<GroundLeft
|
||||
parameters={params}
|
||||
ref={groundLeftRef}
|
||||
modelList={modelList}
|
||||
></GroundLeft>
|
||||
<GroundLeft ref={groundLeftRef} modelList={modelList}></GroundLeft>
|
||||
)
|
||||
},
|
||||
{
|
||||
@@ -95,7 +94,7 @@ const Playground: React.FC = () => {
|
||||
</Button>
|
||||
<Button
|
||||
size="middle"
|
||||
onClick={() => setCollapse(!collapse)}
|
||||
onClick={handleToggleCollapse}
|
||||
icon={
|
||||
<IconFont
|
||||
type="icon-a-layout6-line"
|
||||
@@ -144,7 +143,7 @@ const Playground: React.FC = () => {
|
||||
<div className="chat">
|
||||
<Tabs items={items} activeKey={activeKey}></Tabs>
|
||||
</div>
|
||||
{activeKey === 'chat' && (
|
||||
{/* {activeKey === 'chat' && (
|
||||
<div
|
||||
className={classNames('left', {
|
||||
collapse: collapse
|
||||
@@ -163,7 +162,7 @@ const Playground: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
@@ -4,14 +4,62 @@
|
||||
&-role {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-weight: var(--font-weight-bold);
|
||||
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 {
|
||||
word-break: break-word;
|
||||
padding: 8px;
|
||||
min-height: 38px;
|
||||
border-radius: var(--border-radius-mini);
|
||||
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) {
|
||||
position: absolute;
|
||||
top: -14px;
|
||||
right: 2px;
|
||||
right: -14px;
|
||||
width: 80px;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--border-radius-base);
|
||||
text-align: center;
|
||||
border: 1px solid var(--ant-color-border) !important;
|
||||
|
||||
|
||||
@@ -1,13 +1,51 @@
|
||||
.ground-left {
|
||||
.ground-left-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
height: calc(100vh - 72px);
|
||||
|
||||
.message-list-wrap {
|
||||
max-height: calc(100vh - 152px);
|
||||
.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;
|
||||
padding-inline: var(--layout-content-inlinepadding);
|
||||
|
||||
.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;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
height: calc(100vh - 72px);
|
||||
|
||||
.message-list-wrap {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
padding-inline: var(--layout-content-inlinepadding);
|
||||
overflow-y: auto;
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
.messageInput {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
text-align: left;
|
||||
width: 100px;
|
||||
background-color: var(--ant-button-text-hover-bg);
|
||||
height: 54px;
|
||||
height: 46px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,42 +14,6 @@
|
||||
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 {
|
||||
flex: 1;
|
||||
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