From c5ed0753a66338e2a4e3e171f89ac040cdec5eb9 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Fri, 30 May 2025 19:24:17 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20refactor(playground):=20Refactor?= =?UTF-8?q?=20the=20structure=20of=20the=20playground=20and=20implement=20?= =?UTF-8?q?responsive=20design=20adaptation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/PageLayout.js | 22 +- web/src/components/playground/ChatArea.js | 112 ++ .../components/playground/ConfigManager.js | 234 ++++ .../playground/CustomInputRender.js | 27 + web/src/components/playground/DebugPanel.js | 120 ++ .../components/playground/FloatingButtons.js | 71 ++ .../components/playground/ImageUrlInput.js | 92 ++ .../components/playground/MessageActions.js | 69 ++ .../components/playground/MessageContent.js | 248 ++++ .../components/playground/ParameterControl.js | 234 ++++ .../components/playground/SettingsPanel.js | 195 +++ .../components/playground/configStorage.js | 178 +++ web/src/components/playground/index.js | 20 + web/src/pages/Playground/Playground.js | 1057 ++++------------- 14 files changed, 1867 insertions(+), 812 deletions(-) create mode 100644 web/src/components/playground/ChatArea.js create mode 100644 web/src/components/playground/ConfigManager.js create mode 100644 web/src/components/playground/CustomInputRender.js create mode 100644 web/src/components/playground/DebugPanel.js create mode 100644 web/src/components/playground/FloatingButtons.js create mode 100644 web/src/components/playground/ImageUrlInput.js create mode 100644 web/src/components/playground/MessageActions.js create mode 100644 web/src/components/playground/MessageContent.js create mode 100644 web/src/components/playground/ParameterControl.js create mode 100644 web/src/components/playground/SettingsPanel.js create mode 100644 web/src/components/playground/configStorage.js create mode 100644 web/src/components/playground/index.js diff --git a/web/src/components/PageLayout.js b/web/src/components/PageLayout.js index d9d5471e..88bf2b15 100644 --- a/web/src/components/PageLayout.js +++ b/web/src/components/PageLayout.js @@ -11,6 +11,7 @@ import { API, getLogo, getSystemName, showError } from '../helpers/index.js'; import { setStatusData } from '../helpers/data.js'; import { UserContext } from '../context/User/index.js'; import { StatusContext } from '../context/Status/index.js'; +import { useLocation } from 'react-router-dom'; const { Sider, Content, Header, Footer } = Layout; const PageLayout = () => { @@ -18,6 +19,9 @@ const PageLayout = () => { const [statusState, statusDispatch] = useContext(StatusContext); const [styleState, styleDispatch] = useContext(StyleContext); const { i18n } = useTranslation(); + const location = useLocation(); + + const isPlaygroundRoute = location.pathname === '/console/playground'; const loadUser = () => { let user = localStorage.getItem('user'); @@ -144,14 +148,16 @@ const PageLayout = () => { > - - - + {!isPlaygroundRoute && ( + + + + )} diff --git a/web/src/components/playground/ChatArea.js b/web/src/components/playground/ChatArea.js new file mode 100644 index 00000000..6b42040d --- /dev/null +++ b/web/src/components/playground/ChatArea.js @@ -0,0 +1,112 @@ +import React from 'react'; +import { + Card, + Chat, + Typography, + Button, +} from '@douyinfe/semi-ui'; +import { + MessageSquare, + Eye, + EyeOff, +} from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import CustomInputRender from './CustomInputRender'; + +const ChatArea = ({ + chatRef, + message, + inputs, + styleState, + showDebugPanel, + roleInfo, + onMessageSend, + onMessageCopy, + onMessageReset, + onMessageDelete, + onStopGenerator, + onClearMessages, + onToggleDebugPanel, + renderCustomChatContent, + renderChatBoxAction, +}) => { + const { t } = useTranslation(); + + const renderInputArea = React.useCallback((props) => { + return ; + }, []); + + return ( + + {/* 聊天头部 */} + {styleState.isMobile ? ( +
+ ) : ( +
+
+
+
+ +
+
+ + {t('AI 对话')} + + + {inputs.model || t('选择模型开始对话')} + +
+
+
+ +
+
+
+ )} + + {/* 聊天内容区域 */} +
+ null, + }} + renderInputArea={renderInputArea} + roleConfig={roleInfo} + style={{ + height: '100%', + maxWidth: '100%', + overflow: 'hidden' + }} + chats={message} + onMessageSend={onMessageSend} + onMessageCopy={onMessageCopy} + onMessageReset={onMessageReset} + onMessageDelete={onMessageDelete} + showClearContext + showStopGenerate + onStopGenerator={onStopGenerator} + onClear={onClearMessages} + className="h-full" + placeholder={t('请输入您的问题...')} + /> +
+
+ ); +}; + +export default ChatArea; \ No newline at end of file diff --git a/web/src/components/playground/ConfigManager.js b/web/src/components/playground/ConfigManager.js new file mode 100644 index 00000000..c5b9eea4 --- /dev/null +++ b/web/src/components/playground/ConfigManager.js @@ -0,0 +1,234 @@ +import React, { useRef } from 'react'; +import { + Button, + Typography, + Toast, + Modal, + Dropdown, +} from '@douyinfe/semi-ui'; +import { + Download, + Upload, + RotateCcw, + Settings2, +} from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { exportConfig, importConfig, clearConfig, hasStoredConfig, getConfigTimestamp } from './configStorage'; + +const ConfigManager = ({ + currentConfig, + onConfigImport, + onConfigReset, + styleState, +}) => { + const { t } = useTranslation(); + const fileInputRef = useRef(null); + + const handleExport = () => { + try { + exportConfig(currentConfig); + Toast.success({ + content: t('配置已导出到下载文件夹'), + duration: 3, + }); + } catch (error) { + Toast.error({ + content: t('导出配置失败: ') + error.message, + duration: 3, + }); + } + }; + + const handleImportClick = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = async (event) => { + const file = event.target.files[0]; + if (!file) return; + + try { + const importedConfig = await importConfig(file); + + Modal.confirm({ + title: t('确认导入配置'), + content: t('导入的配置将覆盖当前设置,是否继续?'), + okText: t('确定导入'), + cancelText: t('取消'), + onOk: () => { + onConfigImport(importedConfig); + Toast.success({ + content: t('配置导入成功'), + duration: 3, + }); + }, + }); + } catch (error) { + Toast.error({ + content: t('导入配置失败: ') + error.message, + duration: 3, + }); + } finally { + // 重置文件输入,允许重复选择同一文件 + event.target.value = ''; + } + }; + + const handleReset = () => { + Modal.confirm({ + title: t('重置配置'), + content: t('将清除所有保存的配置并恢复默认设置,此操作不可撤销。是否继续?'), + okText: t('确定重置'), + cancelText: t('取消'), + okButtonProps: { + type: 'danger', + }, + onOk: () => { + clearConfig(); + onConfigReset(); + Toast.success({ + content: t('配置已重置为默认值'), + duration: 3, + }); + }, + }); + }; + + const getConfigStatus = () => { + if (hasStoredConfig()) { + const timestamp = getConfigTimestamp(); + if (timestamp) { + const date = new Date(timestamp); + return t('上次保存: ') + date.toLocaleString(); + } + return t('已有保存的配置'); + } + return t('暂无保存的配置'); + }; + + const dropdownItems = [ + { + node: 'item', + name: 'export', + onClick: handleExport, + children: ( +
+ + {t('导出配置')} +
+ ), + }, + { + node: 'item', + name: 'import', + onClick: handleImportClick, + children: ( +
+ + {t('导入配置')} +
+ ), + }, + { + node: 'divider', + }, + { + node: 'item', + name: 'reset', + onClick: handleReset, + children: ( +
+ + {t('重置配置')} +
+ ), + }, + ]; + + if (styleState.isMobile) { + // 移动端显示简化的下拉菜单 + return ( + <> + + + + + +