From c04b9342b8c528708cb02ead052e7c59b7dac20c Mon Sep 17 00:00:00 2001 From: jialin Date: Thu, 19 Sep 2024 15:21:51 +0800 Subject: [PATCH] refactor: playground add comparsion --- config/config.ts | 2 +- config/theme.ts | 6 +- src/components/editor/viewer.tsx | 89 ++++++ src/components/seal-form/field-wrapper.tsx | 8 +- src/global.less | 44 ++- src/layouts/index.tsx | 28 +- src/layouts/rightRender.tsx | 13 +- src/locales/en-US/playground.ts | 8 +- src/locales/zh-CN/playground.ts | 8 +- .../llmodels/components/hf-model-file.tsx | 6 +- .../llmodels/components/instance-item.tsx | 2 +- .../playground/components/ground-left.tsx | 232 ++++++++++----- .../playground/components/message-input.tsx | 278 ++++++++++++++---- .../playground/components/message-item.tsx | 2 +- .../components/multiple-chat/content-item.tsx | 253 +++++++++++++++- .../components/multiple-chat/index.tsx | 17 +- .../multiple-chat/message-content.tsx | 59 ++-- .../components/multiple-chat/model-item.tsx | 198 +++++-------- .../multiple-chat/system-message.tsx | 94 ++++++ .../playground/components/params-settings.tsx | 12 +- .../playground/components/prompt-modal.tsx | 2 +- .../playground/components/view-code-modal.tsx | 63 ++-- .../playground/config/compare-context.ts | 2 +- src/pages/playground/config/types.ts | 7 + src/pages/playground/index.tsx | 17 +- src/pages/playground/style/content-item.less | 48 +++ src/pages/playground/style/custom-label.less | 4 +- src/pages/playground/style/ground-left.less | 54 +++- src/pages/playground/style/message-input.less | 2 +- src/pages/playground/style/message-item.less | 2 +- src/pages/playground/style/model-item.less | 36 --- src/pages/playground/style/sys-message.less | 35 +++ 32 files changed, 1206 insertions(+), 425 deletions(-) create mode 100644 src/components/editor/viewer.tsx create mode 100644 src/pages/playground/components/multiple-chat/system-message.tsx create mode 100644 src/pages/playground/style/sys-message.less diff --git a/config/config.ts b/config/config.ts index 9dbc3ccd..36546891 100644 --- a/config/config.ts +++ b/config/config.ts @@ -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' diff --git a/config/theme.ts b/config/theme.ts index a05f2b1a..72829ab9 100644 --- a/config/theme.ts +++ b/config/theme.ts @@ -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 } diff --git a/src/components/editor/viewer.tsx b/src/components/editor/viewer.tsx new file mode 100644 index 00000000..20452dc5 --- /dev/null +++ b/src/components/editor/viewer.tsx @@ -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[]; + config?: any; + value: string; + height?: string | number; + theme?: string; + showHeader?: boolean; +} + +const ViewerEditor: React.FC = (props) => { + const editorRef = useRef(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 ( + + + + ); +}; + +export default React.memo(ViewerEditor); diff --git a/src/components/seal-form/field-wrapper.tsx b/src/components/seal-form/field-wrapper.tsx index 1d5ddfd8..c4db9aa0 100644 --- a/src/components/seal-form/field-wrapper.tsx +++ b/src/components/seal-form/field-wrapper.tsx @@ -28,7 +28,11 @@ const Wrapper: React.FC = ({ }) => { return (
= ({ >
diff --git a/src/global.less b/src/global.less index e3f6722f..cc985df9 100644 --- a/src/global.less +++ b/src/global.less @@ -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; diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 1f3d1722..eba2a100 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -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: 404 not found }; + 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 (
@@ -179,10 +183,22 @@ export default (props: any) => { e.preventDefault(); navigate('/'); }} - collapsed={collapsed} - onCollapse={(collapsed) => { - setCollapsed(collapsed); + menuHeaderRender={(logo, title) => { + return ( + <> + {logo} +
+ +
+ + ); }} + collapsed={collapsed} onPageChange={(route) => { const { location } = history; diff --git a/src/layouts/rightRender.tsx b/src/layouts/rightRender.tsx index 7ee849d4..ee73d91b 100644 --- a/src/layouts/rightRender.tsx +++ b/src/layouts/rightRender.tsx @@ -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: , label: ( - {intl?.formatMessage?.({ id: 'common.button.help' })} + + {intl?.formatMessage?.({ id: 'common.button.help' })} + ), children: helpList.map((item) => ({ key: item.key, @@ -169,7 +170,7 @@ export function getRightRenderContent(opts: { key: 'lang', icon: , label: ( - + {intl?.formatMessage?.({ id: 'common.settings.language' })} ), @@ -280,4 +281,4 @@ export function getRightRenderContent(opts: { >
); -} +}; diff --git a/src/locales/en-US/playground.ts b/src/locales/en-US/playground.ts index 3794c6ec..55390624 100644 --- a/src/locales/en-US/playground.ts +++ b/src/locales/en-US/playground.ts @@ -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' }; diff --git a/src/locales/zh-CN/playground.ts b/src/locales/zh-CN/playground.ts index fd86491d..89343d80 100644 --- a/src/locales/zh-CN/playground.ts +++ b/src/locales/zh-CN/playground.ts @@ -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 模型对比' }; diff --git a/src/pages/llmodels/components/hf-model-file.tsx b/src/pages/llmodels/components/hf-model-file.tsx index ae86bf87..611c6df5 100644 --- a/src/pages/llmodels/components/hf-model-file.tsx +++ b/src/pages/llmodels/components/hf-model-file.tsx @@ -252,11 +252,9 @@ const HFModelFile: React.FC = (props) => { {getModelQuantizationType(item)} {item.parts && item.parts.length > 1 && ( diff --git a/src/pages/llmodels/components/instance-item.tsx b/src/pages/llmodels/components/instance-item.tsx index 393f15d8..23d357df 100644 --- a/src/pages/llmodels/components/instance-item.tsx +++ b/src/pages/llmodels/components/instance-item.tsx @@ -153,7 +153,7 @@ const InstanceItem: React.FC = ({ paddingInline: 'var(--ant-table-cell-padding-inline)' }} > - + {item.name} diff --git a/src/pages/playground/components/ground-left.tsx b/src/pages/playground/components/ground-left.tsx index 6c60d008..a848d547 100644 --- a/src/pages/playground/components/ground-left.tsx +++ b/src/pages/playground/components/ground-left.tsx @@ -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[]; ref?: any; } -interface MessageItemProps { - role: string; - content: string; - uid: number; -} - -const MessageList: React.FC = forwardRef((props, ref) => { - const { parameters, modelList } = props; +const GroundLeft: React.FC = forwardRef((props, ref) => { + const { modelList } = props; const messageId = useRef(0); - const [messageList, setMessageList] = useState([ - { - role: 'user', - content: '', - uid: messageId.current - } - ]); + const [messageList, setMessageList] = useState([]); const intl = useIntl(); + const [searchParams] = useSearchParams(); + const selectModel = searchParams.get('model') || ''; + const [parameters, setParams] = useState({}); const [systemMessage, setSystemMessage] = useState(''); const [collapsed, setCollapsed] = useState(true); const [show, setShow] = useState(false); @@ -54,10 +49,14 @@ const MessageList: React.FC = forwardRef((props, ref) => { const [activeIndex, setActiveIndex] = useState(-1); const [tokenResult, setTokenResult] = useState(null); const [currentIsFocus, setCurrentIsFocus] = useState(false); + const [collapse, setCollapse] = useState(false); const systemRef = useRef(null); const contentRef = useRef(''); const controllerRef = useRef(null); const scroller = useRef(null); + const currentMessageRef = useRef(null); + const paramsScroller = useRef(null); + const leftSimple = useRef(null); const { updateScrollerPosition, handleContentWheel } = useContainerScroll( scroller, { toBottom: true } @@ -67,10 +66,18 @@ const MessageList: React.FC = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = forwardRef((props, ref) => { ); return ( -
-
-
- +
+
+
+ + {/* = forwardRef((props, ref) => { })} onChange={handleSystemMessageChange} > - -
+ */} +
-
- {messageList.map((item, index) => { +
+ {/* {messageList.map((item, index) => { return ( = 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 && ( + })} */} + + {/* {loading && (
- )} + )} */} +
+
+
+
-
- - {/* } - > */} + +
+
+ +
+ = forwardRef((props, ref) => { ); }); -export default memo(MessageList); +export default memo(GroundLeft); diff --git a/src/pages/playground/components/message-input.tsx b/src/pages/playground/components/message-input.tsx index 5dbed3c2..54812553 100644 --- a/src/pages/playground/components/message-input.tsx +++ b/src/pages/playground/components/message-input.tsx @@ -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; 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[]; - 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 = ({ @@ -75,21 +84,35 @@ const MessageInput: React.FC = ({ 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({ 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 = ({ }; 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 = ({ 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[] = []; + + 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((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 (
@@ -170,52 +300,86 @@ const MessageInput: React.FC = ({ {intl.formatMessage({ id: `playground.${message.role}` })} - - - + + + + + + + + {updateLayout && ( <> {layoutOptions.map((option) => ( - + > + + ))} )}
- + {showModelSelection && ( + + )} + {!loading ? ( - ) : (
+ = ({ - data +interface MessageItemProps { + data: MessageItem; + editable?: boolean; + loading?: boolean; + updateMessage?: (message: MessageItem) => void; + onDelete?: () => void; +} + +const ContentItem: React.FC = ({ + updateMessage, + onDelete, + loading, + data, + editable }) => { const intl = useIntl(); + const inputRef = useRef(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[] = []; + + 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((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 (
- - {Roles.User === data.role ? ( - - ) : ( - +
+ + {Roles.User === data.role ? ( + + ) : ( + + )} + + {intl.formatMessage({ id: `playground.${data.role}` })} + +
+
+ {editable && ( + )} - - {intl.formatMessage({ id: `playground.${data.role}` })} + {data.content && ( + + )} + {editable && ( + + + + )} +
-
{data.content}
+ {editable ? ( +
+ + +
+ ) : ( +
+ {data.content} +
+ )}
); }; diff --git a/src/pages/playground/components/multiple-chat/index.tsx b/src/pages/playground/components/multiple-chat/index.tsx index 2a8ac081..7ab38b98 100644 --- a/src/pages/playground/components/multiple-chat/index.tsx +++ b/src/pages/playground/components/multiple-chat/index.tsx @@ -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; + interface MultiCompareProps { modelList: (Global.BaseOption & { type?: string })[]; spans?: number; @@ -61,7 +63,7 @@ const MultiCompare: React.FC = ({ 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 = ({ 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 = ({ modelList }) => { setModelSelections(resultList); }, [modelList]); - // useEffect(() => { - // modelRefs.current = {}; - // modelSelections.forEach((item) => { - // modelRefs.current[item.instanceId] = null; - // }); - // }, [modelSelections]); - return (
@@ -248,6 +243,7 @@ const MultiCompare: React.FC = ({ modelList }) => {
= ({ modelList }) => { setModelSelections={handleUpdateModelSelections} presetPrompt={handlePresetPrompt} modelList={modelFullList} + showModelSelection={true} />
diff --git a/src/pages/playground/components/multiple-chat/message-content.tsx b/src/pages/playground/components/multiple-chat/message-content.tsx index 7e0c1d99..f6dc2111 100644 --- a/src/pages/playground/components/multiple-chat/message-content.tsx +++ b/src/pages/playground/components/multiple-chat/message-content.tsx @@ -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 = ({ + 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 ? ( - -
- {messageList.map((item, index) => ( - - ))} -
-
- ) : ( - {loading} + {!!messageList.length && ( +
+ {messageList.map((item, index) => ( + handleDelete(index)} + updateMessage={(data) => updateMessage(index, data)} + /> + ))} +
)} - ); }; diff --git a/src/pages/playground/components/multiple-chat/model-item.tsx b/src/pages/playground/components/multiple-chat/model-item.tsx index f7c515ba..b3938ae9 100644 --- a/src/pages/playground/components/multiple-chat/model-item.tsx +++ b/src/pages/playground/components/multiple-chat/model-item.tsx @@ -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 = forwardRef( ({ model, modelList, instanceId }, ref) => { const { @@ -63,26 +51,28 @@ const ModelItem: React.FC = 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(''); const [params, setParams] = useState>({}); const [loading, setLoading] = useState(false); const messageId = useRef(0); - const [messageList, setMessageList] = useState([]); + const [messageList, setMessageList] = useState([]); const [tokenResult, setTokenResult] = useState(null); const [show, setShow] = useState(false); const contentRef = useRef(''); const controllerRef = useRef(null); - const currentMessageRef = useRef({} as MessageItemProps); - const systemMessageRef = useRef(null); + const currentMessageRef = useRef({} 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 = forwardRef( console.log('currentMessage==========5', messageList); setMessageList([ ...messageList, - { - ...currentMessageRef.current - }, { role: Roles.Assistant, content: contentRef.current, @@ -149,28 +136,58 @@ const ModelItem: React.FC = 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 = 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 = 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 = 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 = 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 = forwardRef( size="small" > - + {modelList.length > 2 && ( + + )}
-
- { -
- setSystemMessage(e.target.value)} - > - -
- } - {!autoSize.focus && ( -
-
- {systemMessage || ( - - Type system message here - - )} -
- {systemMessage && ( - - )} -
- )} -
-
- -
+ + +
+ + +
+
void; +} + +const SystemMessage: React.FC = (props) => { + const { systemMessage, setSystemMessage } = props; + const systemMessageRef = React.useRef(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 ( +
+ { +
+ setSystemMessage(e.target.value)} + > + +
+ } + {!autoSize.focus && ( +
+
+ {systemMessage || ( + + Type system message here + + )} +
+ {systemMessage && ( + + )} +
+ )} +
+ ); +}; + +export default React.memo(SystemMessage); diff --git a/src/pages/playground/components/params-settings.tsx b/src/pages/playground/components/params-settings.tsx index 558b2797..04ab1f97 100644 --- a/src/pages/playground/components/params-settings.tsx +++ b/src/pages/playground/components/params-settings.tsx @@ -214,14 +214,14 @@ const ParamsSettings: React.FC = ({ id: 'playground.params.temperature.tips' }) })} - style={{ paddingInline: 0 }} + style={{ padding: '20px 2px 0' }} variant="borderless" > handleFieldValueChange(val, 'temperature')} @@ -240,14 +240,14 @@ const ParamsSettings: React.FC = ({ id: 'playground.params.maxtokens.tips' }) })} - style={{ paddingInline: 0 }} + style={{ padding: '20px 2px 0' }} variant="borderless" > handleFieldValueChange(val, 'max_tokens')} @@ -266,14 +266,14 @@ const ParamsSettings: React.FC = ({ id: 'playground.params.topp.tips' }) })} - style={{ paddingInline: 0 }} + style={{ padding: '20px 2px 0' }} variant="borderless" > handleFieldValueChange(val, 'top_p')} diff --git a/src/pages/playground/components/prompt-modal.tsx b/src/pages/playground/components/prompt-modal.tsx index 3c86bb23..fa75ba96 100644 --- a/src/pages/playground/components/prompt-modal.tsx +++ b/src/pages/playground/components/prompt-modal.tsx @@ -57,7 +57,7 @@ const AddWorker: React.FC = (props) => { type="default" onClick={() => handleSelect(item)} > - Use + Apply {item.data.map((data, i) => { diff --git a/src/pages/playground/components/view-code-modal.tsx b/src/pages/playground/components/view-code-modal.tsx index 9a1b7aca..3ab3bb23 100644 --- a/src/pages/playground/components/view-code-modal.tsx +++ b/src/pages/playground/components/view-code-modal.tsx @@ -58,16 +58,41 @@ const ViewCodeModal: React.FC = (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 = (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 = (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 )}` diff --git a/src/pages/playground/config/compare-context.ts b/src/pages/playground/config/compare-context.ts index 2dd6df9c..a322d8c8 100644 --- a/src/pages/playground/config/compare-context.ts +++ b/src/pages/playground/config/compare-context.ts @@ -7,7 +7,7 @@ interface CompareContextProps { }; systemMessage?: string; globalParams: Record; - loadingStatus: Record; + loadingStatus: Record; handleDeleteModel: (instanceId: symbol) => void; setSystemMessage?: (message: string) => void; setGlobalParams: (value: Record) => void; diff --git a/src/pages/playground/config/types.ts b/src/pages/playground/config/types.ts index 04730869..5e6ef0a1 100644 --- a/src/pages/playground/config/types.ts +++ b/src/pages/playground/config/types.ts @@ -3,3 +3,10 @@ export interface ModelSelectionItem extends Global.BaseOption { instanceId: symbol; type?: string; } + +export interface MessageItem { + role: string; + content: string; + imgs?: { uid: string | number; dataUrl: string }[]; + uid: number; +} diff --git a/src/pages/playground/index.tsx b/src/pages/playground/index.tsx index d790d90c..678f4d78 100644 --- a/src/pages/playground/index.tsx +++ b/src/pages/playground/index.tsx @@ -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: ( - + ) }, { @@ -95,7 +94,7 @@ const Playground: React.FC = () => {
- )} + )} */}
); diff --git a/src/pages/playground/style/content-item.less b/src/pages/playground/style/content-item.less index 842dd03a..7e9554e4 100644 --- a/src/pages/playground/style/content-item.less +++ b/src/pages/playground/style/content-item.less @@ -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); + } + } + } } diff --git a/src/pages/playground/style/custom-label.less b/src/pages/playground/style/custom-label.less index 9391ceb0..853c4d2d 100644 --- a/src/pages/playground/style/custom-label.less +++ b/src/pages/playground/style/custom-label.less @@ -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; diff --git a/src/pages/playground/style/ground-left.less b/src/pages/playground/style/ground-left.less index fc672099..a3fa17a3 100644 --- a/src/pages/playground/style/ground-left.less +++ b/src/pages/playground/style/ground-left.less @@ -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; + } + } } } diff --git a/src/pages/playground/style/message-input.less b/src/pages/playground/style/message-input.less index a7acf421..889ccffb 100644 --- a/src/pages/playground/style/message-input.less +++ b/src/pages/playground/style/message-input.less @@ -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%; diff --git a/src/pages/playground/style/message-item.less b/src/pages/playground/style/message-item.less index b70137fe..db7fa930 100644 --- a/src/pages/playground/style/message-item.less +++ b/src/pages/playground/style/message-item.less @@ -20,7 +20,7 @@ text-align: left; width: 100px; background-color: var(--ant-button-text-hover-bg); - height: 54px; + height: 46px; } } diff --git a/src/pages/playground/style/model-item.less b/src/pages/playground/style/model-item.less index f88ccfd8..2f3f4981 100644 --- a/src/pages/playground/style/model-item.less +++ b/src/pages/playground/style/model-item.less @@ -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; diff --git a/src/pages/playground/style/sys-message.less b/src/pages/playground/style/sys-message.less new file mode 100644 index 00000000..0482a738 --- /dev/null +++ b/src/pages/playground/style/sys-message.less @@ -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; + } +}