diff --git a/frontend-react/package-lock.json b/frontend-react/package-lock.json index 7dc51d6..fd6eea5 100644 --- a/frontend-react/package-lock.json +++ b/frontend-react/package-lock.json @@ -943,9 +943,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -960,9 +957,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -977,9 +971,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -994,9 +985,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1011,9 +999,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1028,9 +1013,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1045,9 +1027,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1062,9 +1041,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1079,9 +1055,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1096,9 +1069,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1113,9 +1083,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1130,9 +1097,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1147,9 +1111,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/frontend-react/src/App.jsx b/frontend-react/src/App.jsx index d2647ca..a5f6df2 100644 --- a/frontend-react/src/App.jsx +++ b/frontend-react/src/App.jsx @@ -1,86 +1,61 @@ import React, { useState } from 'react'; - -// 引入各区域组件 -import PresetPanel from './components/PresetPanel'; -import ChatBox from './components/ChatBox'; -import ImageDisplay from './components/ImageDisplay'; -import DicePanel from './components/DicePanel'; -import RoleSelector from './components/RoleSelector'; +import Toolbar from './components/Toolbar/Toolbar'; +import ChatBox from './components/ChatBox/ChatBox'; +import DicePanel from './components/DicePanel/DicePanel'; +import ImageDisplay from './components/ImageDisplay/ImageDisplay'; +import PresetPanel from './components/PresetPanel/PresetPanel'; +import './index.css'; function App() { const [isToolbarExpanded, setIsToolbarExpanded] = useState(false); const [selectedRole, setSelectedRole] = useState(null); const [selectedChat, setSelectedChat] = useState(null); - const toggleToolbar = () => { - setIsToolbarExpanded(!isToolbarExpanded); - }; - - // 处理角色选择变化 const handleRoleChange = (role) => { setSelectedRole(role); - console.log('选择的角色:', role); - // 这里可以添加其他逻辑,比如加载角色的聊天记录等 + console.log('角色已更改:', role); }; - // 处理聊天选择变化 const handleChatChange = (role, chat) => { setSelectedChat(chat); - console.log('选择的聊天:', role, chat); - // 这里可以添加其他逻辑,比如加载聊天内容等 + console.log('聊天已更改:', role, chat); }; return ( -
- {/* 顶部工具栏 */} -
- {/* 工具栏内容区域 */} - {isToolbarExpanded && ( -
- {/* 角色选择器 */} - -
- )} +
+ setIsToolbarExpanded(!isToolbarExpanded)} + onRoleChange={handleRoleChange} + onChatChange={handleChatChange} + selectedRole={selectedRole} + /> - {/* 工具栏切换按钮 */} - -
- - {/* 主体内容区域 */} -
- - {/* 左侧:预设栏 (仿AI酒馆) */} -
+ {/* 主内容容器 */} +
+ {/* 左侧栏 - 预设面板 */} +
-
+
- {/* 中间:历史对话框 + 输入框 */} -
- -
+ {/* 中间栏:聊天框 */} +
+ +
- {/* 右侧:上下布局 */} -
- {/* 上方:图片展示区 */} + {/* 右侧栏 */} +
- + {/* 图片展示放在顶部 */}
- {/* 下方:骰子区 */}
- + {/* 骰子面板放在底部 */}
-
- - + + ); } diff --git a/frontend-react/src/components/ChatBox/ChatBox.css b/frontend-react/src/components/ChatBox/ChatBox.css new file mode 100644 index 0000000..0d79dcf --- /dev/null +++ b/frontend-react/src/components/ChatBox/ChatBox.css @@ -0,0 +1,400 @@ +/* ==================== 聊天框区域 ==================== */ + +/* React 组件根容器 */ +.chat-box { + height: 95%; + width: 100%; + display: flex; + flex-direction: column; + background-color: #fafafa; +} + +/* 消息列表容器 */ +.chat-messages { + flex: 1; + min-height: 0; + max-height: 100%; + overflow-y: auto; + padding: 20px; + padding-top: 60px; + display: flex; + flex-direction: column; + gap: 15px; +} + +/* 设置面板 */ +.settings-panel { + position: absolute; + top: 0; + right: 0; + z-index: 20; + background-color: #fff; + border-bottom-left-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + overflow: hidden; + transition: all 0.3s ease; + max-width: 200px; +} + +.settings-panel.collapsed { + width: 40px; + height: 40px; + cursor: pointer; +} + +.settings-panel.expanded { + width: auto; + min-width: 150px; + padding: 10px; + border: 1px solid #eee; + border-top: none; + border-right: none; +} + +.settings-header { + height: 40px; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + background-color: #f0f0f0; + cursor: pointer; + font-size: 18px; + color: #666; +} + +.settings-panel.collapsed .settings-header:hover { + background-color: #e0e0e0; +} + +.settings-options { + display: none; + flex-direction: column; + gap: 10px; + padding-top: 10px; +} + +.settings-panel.expanded .settings-options { + display: flex; +} + +.setting-item { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 14px; + color: #333; +} + +.setting-item input[type="checkbox"] { + margin-right: 8px; + cursor: pointer; +} + +/* ==================== 聊天气泡样式 ==================== */ + +/* 消息行容器 */ +.message { + display: flex; + width: 100%; + margin-bottom: 10px; +} + +/* 区分左右布局 */ +.message.user { + justify-content: flex-end; /* 用户消息靠右 */ +} + +.message.ai { + justify-content: flex-start; /* AI 消息靠左 */ +} + +/* 消息容器 - 调整为垂直排列 */ +.message-container { + display: flex; + flex-direction: column; + max-width: 70%; +} + +/* 消息头部 - 包含名称和工具栏 */ +.message-header { + display: flex; + align-items: center; + margin-bottom: 5px; + order: 1; /* 确保头部显示在第一个位置 */ +} + +/* 消息名称 */ +.message-name { + font-size: 14px; + font-weight: 500; + color: #333; + padding: 2px 8px; + border-radius: 12px; + background-color: rgba(0, 0, 0, 0.05); + display: inline-block; +} + +/* 用户消息名称 */ +.message.user .message-name { + background-color: rgba(24, 144, 255, 0.1); + color: #1890ff; +} + +/* AI消息名称 */ +.message.ai .message-name { + background-color: rgba(0, 0, 0, 0.05); + color: #333; +} + +/* 消息工具栏 */ +.message-toolbar { + display: flex; + align-items: center; + padding: 0 5px; + opacity: 0; + transition: opacity 0.2s ease; +} + +.message:hover .message-toolbar { + opacity: 1; +} + +.message-id { + font-size: 12px; + color: #888; + padding: 2px 6px; + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.05); + display: flex; + align-items: center; +} + +.toolbar-buttons { + display: flex; + gap: 5px; + margin: 0 5px; +} + +.toolbar-button { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: none; + background-color: rgba(0, 0, 0, 0.05); + border-radius: 4px; + cursor: pointer; + color: #555; + transition: all 0.2s ease; +} + +.toolbar-button:hover { + background-color: rgba(0, 0, 0, 0.1); + color: #333; +} + +/* AI消息头部布局:AI助手 - ID - 编辑 - 更多 */ +.message.ai .message-header { + flex-direction: row; + justify-content: flex-start; +} + +.message.ai .message-name { + order: 1; +} + +.message.ai .message-id { + order: 2; + margin-left: 5px; +} + +.message.ai .toolbar-buttons { + order: 3; + margin-left: 5px; +} + +/* 用户消息头部布局:更多 - 编辑 - ID - 我 */ +.message.user .message-header { + flex-direction: row; + justify-content: flex-end; +} + +.message.user .message-name { + order: 4; +} + +.message.user .message-id { + order: 3; + margin-right: 5px; +} + +.message.user .toolbar-buttons { + order: 1; + margin-right: 5px; +} + +/* 消息内容 - 确保显示在名称下方 */ +.message-content { + display: flex; + flex-direction: column; + max-width: 100%; + order: 2; /* 确保内容显示在第二个位置 */ +} + +/* 气泡本体 */ +.message .bubble { + max-width: 100%; + padding: 10px 15px; + border-radius: 12px; + position: relative; + font-size: 14px; + line-height: 1.6; + word-wrap: break-word; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +/* AI 气泡样式 */ +.message.ai .bubble { + background-color: #fff; + color: #333; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + border: 1px solid #eee; +} + +/* 用户气泡样式 */ +.message.user .bubble { + background-color: #1890ff; + color: #fff; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +/* 针对 AI 消息中的 HTML 内容进行简单的样式重置 */ +.message.ai .bubble p { + margin: 0 0 8px 0; +} +.message.ai .bubble p:last-child { + margin-bottom: 0; +} +.message.ai .bubble ul, .message.ai .bubble ol { + margin: 0 0 8px 0; + padding-left: 20px; +} +.message.ai .bubble b { + font-weight: 600; + color: #000; +} + +/* 编辑模式 */ +.edit-container { + display: flex; + flex-direction: column; + gap: 10px; +} + +.edit-textarea { + width: 100%; + min-height: 80px; + padding: 10px; + border: 1px solid #ddd; + border-radius: 8px; + resize: vertical; + font-family: inherit; + font-size: 14px; + line-height: 1.5; +} + +.edit-textarea:focus { + outline: none; + border-color: #1890ff; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); +} + +.edit-buttons { + display: flex; + justify-content: flex-end; + gap: 8px; +} + +.save-button, .cancel-button { + display: flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + border: none; + border-radius: 4px; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; +} + +.save-button { + background-color: #1890ff; + color: white; +} + +.save-button:hover { + background-color: #40a9ff; +} + +.cancel-button { + background-color: #f0f0f0; + color: #333; +} + +.cancel-button:hover { + background-color: #e6e6e6; +} + +/* ==================== 输入区域 ==================== */ + +.chat-input-wrapper { + background-color: #fff; + border-top: 1px solid #ddd; + padding: 10px 20px; + flex: 0 0 auto; + display: flex; + align-items: flex-end; + gap: 10px; + width: 100%; +} + +.chat-input-area { + flex: 1; + display: flex; +} + +.chat-input-area textarea { + width: 100%; + height: 42px; + min-height: 42px; + max-height: 300px; + padding: 10px; + border: 1px solid #ddd; + border-radius: 4px; + resize: none; + outline: none; + font-family: inherit; + line-height: 1.5; + overflow-y: auto; + display: block; +} + +.send-button { + height: 42px; + padding: 0 20px; + background-color: #1890ff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + white-space: nowrap; + flex-shrink: 0; +} + +.send-button:hover { + background-color: #40a9ff; +} diff --git a/frontend-react/src/components/ChatBox.jsx b/frontend-react/src/components/ChatBox/ChatBox.jsx similarity index 78% rename from frontend-react/src/components/ChatBox.jsx rename to frontend-react/src/components/ChatBox/ChatBox.jsx index 2e71a21..857ace0 100644 --- a/frontend-react/src/components/ChatBox.jsx +++ b/frontend-react/src/components/ChatBox/ChatBox.jsx @@ -1,10 +1,10 @@ import React, { useState, useRef } from 'react'; +import './ChatBox.css'; const ChatBox = () => { const [isHtmlRender, setIsHtmlRender] = useState(false); const [isImageGen, setIsImageGen] = useState(false); const [isDynamicTable, setIsDynamicTable] = useState(false); - const [isSettingsExpanded, setIsSettingsExpanded] = useState(false); const [editingId, setEditingId] = useState(null); const [editContent, setEditContent] = useState(''); @@ -83,46 +83,6 @@ const ChatBox = () => {
{/* 上方:消息列表区域 */}
- - {/* 右上角:可折叠设置面板 */} -
-
setIsSettingsExpanded(!isSettingsExpanded)} - > - {isSettingsExpanded ? '▼' : '⚙'} -
- - {isSettingsExpanded && ( -
-
- - setIsHtmlRender(!isHtmlRender)} - /> -
-
- - setIsImageGen(!isImageGen)} - /> -
-
- - setIsDynamicTable(!isDynamicTable)} - /> -
-
- )} -
- {/* 消息列表 */} {messages.map((msg) => (
@@ -204,4 +164,4 @@ const ChatBox = () => { ); }; -export default ChatBox; +export default ChatBox; \ No newline at end of file diff --git a/frontend-react/src/components/DicePanel.jsx b/frontend-react/src/components/DicePanel/DicePanel.jsx similarity index 100% rename from frontend-react/src/components/DicePanel.jsx rename to frontend-react/src/components/DicePanel/DicePanel.jsx diff --git a/frontend-react/src/components/ImageDisplay.jsx b/frontend-react/src/components/ImageDisplay/ImageDisplay.jsx similarity index 100% rename from frontend-react/src/components/ImageDisplay.jsx rename to frontend-react/src/components/ImageDisplay/ImageDisplay.jsx diff --git a/frontend-react/src/components/PresetPanel/PresetPanel.css b/frontend-react/src/components/PresetPanel/PresetPanel.css new file mode 100644 index 0000000..58f50e5 --- /dev/null +++ b/frontend-react/src/components/PresetPanel/PresetPanel.css @@ -0,0 +1,57 @@ +/* ==================== 顶部工具栏区域 ==================== */ + +.toolbar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 50px; + background-color: #fff; + border-bottom: 1px solid #ddd; + padding: 0; + display: flex; + align-items: center; + justify-content: flex-end; + z-index: 100; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + transition: height 0.3s ease; +} + +.toolbar.expanded { + height: auto; + max-height: 300px; + overflow-y: auto; + padding-bottom: 10px; +} + +.toolbar-content { + display: none; + width: 100%; + padding: 10px 20px; +} + +.toolbar.expanded .toolbar-content { + display: block; +} + +.toolbar-toggle-btn { + position: absolute; + right: 0; + top: 0; + height: 50px; + width: 50px; + background: none; + border: none; + border-left: 1px solid #ddd; + cursor: pointer; + font-size: 0.8rem; + color: #666; + display: flex; + align-items: center; + justify-content: center; + z-index: 101; +} + +.toolbar-toggle-btn:hover { + background-color: #f5f5f5; +} diff --git a/frontend-react/src/components/PresetPanel.jsx b/frontend-react/src/components/PresetPanel/PresetPanel.jsx similarity index 96% rename from frontend-react/src/components/PresetPanel.jsx rename to frontend-react/src/components/PresetPanel/PresetPanel.jsx index 31a0303..10c068b 100644 --- a/frontend-react/src/components/PresetPanel.jsx +++ b/frontend-react/src/components/PresetPanel/PresetPanel.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import './PresetPanel.css'; const PresetPanel = () => { return ( diff --git a/frontend-react/src/components/RoleSelector/RoleSelector.css b/frontend-react/src/components/RoleSelector/RoleSelector.css new file mode 100644 index 0000000..e39d7bc --- /dev/null +++ b/frontend-react/src/components/RoleSelector/RoleSelector.css @@ -0,0 +1,95 @@ +/* ==================== 下拉框样式 ==================== */ + +/* 角色选择器容器 */ +.role-selector { + width: 100%; +} + +/* 下拉框容器 */ +.dropdown-container { + position: relative; + width: 100%; + max-width: 300px; +} + +/* 下拉框触发器 */ +.dropdown-trigger { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background-color: #f5f5f5; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +.dropdown-arrow { + margin-left: 8px; + font-size: 12px; +} + +/* 下拉菜单 */ +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + width: 100%; + background-color: white; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + z-index: 10; + max-height: 200px; + overflow-y: auto; + margin-top: 5px; +} + +/* 下拉菜单项 */ +.dropdown-item { + padding: 8px 12px; + cursor: pointer; + position: relative; + font-size: 14px; +} + +.dropdown-item:hover { + background-color: #f5f5f5; +} + +.dropdown-item.selected { + background-color: #e6f7ff; + color: #1890ff; +} + +/* 嵌套下拉框 */ +.nested-dropdown { + position: absolute; + left: 100%; + top: 0; + width: 100%; + background-color: white; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + z-index: 11; + max-height: 200px; + overflow-y: auto; +} + +.nested-dropdown .dropdown-item { + padding-left: 20px; +} + +/* 工具栏下拉框容器 */ +.toolbar-dropdown { + margin-bottom: 15px; +} + +.toolbar-dropdown label { + display: block; + margin-bottom: 5px; + font-size: 14px; + color: #333; +} diff --git a/frontend-react/src/components/RoleSelector.jsx b/frontend-react/src/components/RoleSelector/RoleSelector.jsx similarity index 98% rename from frontend-react/src/components/RoleSelector.jsx rename to frontend-react/src/components/RoleSelector/RoleSelector.jsx index 580dfbe..a520d6b 100644 --- a/frontend-react/src/components/RoleSelector.jsx +++ b/frontend-react/src/components/RoleSelector/RoleSelector.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import './RoleSelector.css'; const RoleSelector = ({ onRoleChange, onChatChange }) => { const [isDropdownOpen, setIsDropdownOpen] = useState(false); diff --git a/frontend-react/src/components/ToolBar/ToolBar.css b/frontend-react/src/components/ToolBar/ToolBar.css new file mode 100644 index 0000000..0f72aef --- /dev/null +++ b/frontend-react/src/components/ToolBar/ToolBar.css @@ -0,0 +1,148 @@ +/* ==================== 顶部工具栏区域 ==================== */ + +.toolbar { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 50px; + background-color: #fff; + border-bottom: 1px solid #ddd; + padding: 0; + display: flex; + align-items: center; + justify-content: flex-end; + z-index: 100; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + transition: height 0.3s ease; +} + +.toolbar.expanded { + height: auto; + max-height: 300px; + overflow-y: auto; + padding-bottom: 10px; +} + +.toolbar-content { + display: none; + width: 100%; + padding: 10px 20px; +} + +.toolbar.expanded .toolbar-content { + display: block; +} + +.toolbar-toggle-btn { + position: absolute; + right: 0; + top: 0; + height: 50px; + width: 50px; + background: none; + border: none; + border-left: 1px solid #ddd; + cursor: pointer; + font-size: 0.8rem; + color: #666; + display: flex; + align-items: center; + justify-content: center; + z-index: 101; +} + +.toolbar-toggle-btn:hover { + background-color: #f5f5f5; +} + +/* ==================== 下拉框样式 ==================== */ + +/* 工具栏下拉框容器 */ +.toolbar-dropdown { + margin-bottom: 15px; +} + +.toolbar-dropdown label { + display: block; + margin-bottom: 5px; + font-size: 14px; + color: #333; +} + +/* 下拉框容器 */ +.dropdown-container { + position: relative; + width: 100%; + max-width: 300px; +} + +/* 下拉框触发器 */ +.dropdown-trigger { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background-color: #f5f5f5; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + font-size: 14px; +} + +.dropdown-arrow { + margin-left: 8px; + font-size: 12px; +} + +/* 下拉菜单 */ +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + width: 100%; + background-color: white; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + z-index: 10; + max-height: 200px; + overflow-y: auto; + margin-top: 5px; +} + +/* 下拉菜单项 */ +.dropdown-item { + padding: 8px 12px; + cursor: pointer; + position: relative; + font-size: 14px; +} + +.dropdown-item:hover { + background-color: #f5f5f5; +} + +.dropdown-item.selected { + background-color: #e6f7ff; + color: #1890ff; +} + +/* 嵌套下拉框 */ +.nested-dropdown { + position: absolute; + left: 100%; + top: 0; + width: 100%; + background-color: white; + border: 1px solid #ddd; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + z-index: 11; + max-height: 200px; + overflow-y: auto; +} + +.nested-dropdown .dropdown-item { + padding-left: 20px; +} diff --git a/frontend-react/src/components/ToolBar/ToolBar.jsx b/frontend-react/src/components/ToolBar/ToolBar.jsx new file mode 100644 index 0000000..558a6b0 --- /dev/null +++ b/frontend-react/src/components/ToolBar/ToolBar.jsx @@ -0,0 +1,107 @@ +import React, { useState } from 'react'; +import './Toolbar.css'; + +const Toolbar = ({ + isExpanded, + onToggle, + onRoleChange, + onChatChange +}) => { + const [selectedRole, setSelectedRole] = useState(null); + const [selectedChat, setSelectedChat] = useState(null); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const [roleData, setRoleData] = useState({}); + + // 获取角色和聊天数据 + React.useEffect(() => { + const fetchRoleData = async () => { + try { + const response = await fetch('/api/get_all_role_and_chat'); + const data = await response.json(); + setRoleData(data); + } catch (error) { + console.error('获取角色数据失败:', error); + } + }; + + fetchRoleData(); + }, []); + + // 处理角色选择 + const handleRoleSelect = (role) => { + setSelectedRole(role); + setSelectedChat(null); + if (onRoleChange) { + onRoleChange(role); + } + }; + + // 处理聊天选择 + const handleChatSelect = (chat) => { + setSelectedChat(chat); + setIsDropdownOpen(false); + if (onChatChange) { + onChatChange(selectedRole, chat); + } + }; + + return ( +
+ + + {isExpanded && ( +
+
+ +
+
setIsDropdownOpen(!isDropdownOpen)} + > + {selectedRole || '选择角色'} + +
+ + {isDropdownOpen && ( +
+ {Object.keys(roleData).map(role => ( +
handleRoleSelect(role)} + > + {role} + {selectedRole === role && ( +
+ {roleData[role].map(chat => ( +
{ + e.stopPropagation(); + handleChatSelect(chat); + }} + > + {chat} +
+ ))} +
+ )} +
+ ))} +
+ )} +
+
+
+ )} +
+ ); +}; + +export default Toolbar; \ No newline at end of file diff --git a/frontend-react/src/index.css b/frontend-react/src/index.css index 5008c71..6a889cb 100644 --- a/frontend-react/src/index.css +++ b/frontend-react/src/index.css @@ -41,239 +41,6 @@ html, body, #root { background: #aaa; } -/* ==================== 顶部工具栏区域 ==================== */ - -.toolbar { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 50px; - background-color: #fff; - border-bottom: 1px solid #ddd; - padding: 0; - display: flex; - align-items: center; - justify-content: flex-end; - z-index: 100; - box-shadow: 0 2px 4px rgba(0,0,0,0.05); - transition: height 0.3s ease; -} - -.toolbar.expanded { - height: auto; - max-height: 300px; - overflow-y: auto; - padding-bottom: 10px; -} - -.toolbar-content { - display: none; - width: 100%; - padding: 10px 20px; -} - -.toolbar.expanded .toolbar-content { - display: block; -} - -.toolbar-toggle-btn { - position: absolute; - right: 0; - top: 0; - height: 50px; - width: 50px; - background: none; - border: none; - border-left: 1px solid #ddd; - cursor: pointer; - font-size: 0.8rem; - color: #666; - display: flex; - align-items: center; - justify-content: center; - z-index: 101; -} - -.toolbar-toggle-btn:hover { - background-color: #f5f5f5; -} - -/* ==================== 下拉框样式 ==================== */ - -/* 角色选择器容器 */ -.role-selector { - width: 100%; -} - -/* 下拉框容器 */ -.dropdown-container { - position: relative; - width: 100%; - max-width: 300px; -} - -/* 下拉框触发器 */ -.dropdown-trigger { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 12px; - background-color: #f5f5f5; - border: 1px solid #ddd; - border-radius: 4px; - cursor: pointer; - font-size: 14px; -} - -.dropdown-arrow { - margin-left: 8px; - font-size: 12px; -} - -/* 下拉菜单 */ -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - width: 100%; - background-color: white; - border: 1px solid #ddd; - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - z-index: 10; - max-height: 200px; - overflow-y: auto; - margin-top: 5px; -} - -/* 下拉菜单项 */ -.dropdown-item { - padding: 8px 12px; - cursor: pointer; - position: relative; - font-size: 14px; -} - -.dropdown-item:hover { - background-color: #f5f5f5; -} - -.dropdown-item.selected { - background-color: #e6f7ff; - color: #1890ff; -} - -/* 嵌套下拉框 */ -.nested-dropdown { - position: absolute; - left: 100%; - top: 0; - width: 100%; - background-color: white; - border: 1px solid #ddd; - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - z-index: 11; - max-height: 200px; - overflow-y: auto; -} - -.nested-dropdown .dropdown-item { - padding-left: 20px; -} - -/* ==================== 下拉框样式 ==================== */ - -/* 工具栏下拉框容器 */ -.toolbar-dropdown { - margin-bottom: 15px; -} - -.toolbar-dropdown label { - display: block; - margin-bottom: 5px; - font-size: 14px; - color: #333; -} - -/* 下拉框容器 */ -.dropdown-container { - position: relative; - width: 100%; - max-width: 300px; -} - -/* 下拉框触发器 */ -.dropdown-trigger { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 12px; - background-color: #f5f5f5; - border: 1px solid #ddd; - border-radius: 4px; - cursor: pointer; - font-size: 14px; -} - -.dropdown-arrow { - margin-left: 8px; - font-size: 12px; -} - -/* 下拉菜单 */ -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - width: 100%; - background-color: white; - border: 1px solid #ddd; - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - z-index: 10; - max-height: 200px; - overflow-y: auto; - margin-top: 5px; -} - -/* 下拉菜单项 */ -.dropdown-item { - padding: 8px 12px; - cursor: pointer; - position: relative; - font-size: 14px; -} - -.dropdown-item:hover { - background-color: #f5f5f5; -} - -.dropdown-item.selected { - background-color: #e6f7ff; - color: #1890ff; -} - -/* 嵌套下拉框 */ -.nested-dropdown { - position: absolute; - left: 100%; - top: 0; - width: 100%; - background-color: white; - border: 1px solid #ddd; - border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - z-index: 11; - max-height: 200px; - overflow-y: auto; -} - -.nested-dropdown .dropdown-item { - padding-left: 20px; -} - /* ==================== 主内容区域 ==================== */ .main-container { @@ -330,668 +97,3 @@ html, body, #root { .right-top { border-bottom: 1px solid #ddd; } - -/* ==================== 聊天框区域 ==================== */ - -/* React 组件根容器 */ -.chat-box { - height: 95%; - width: 100%; - display: flex; - flex-direction: column; - background-color: #fafafa; -} - -/* 消息列表容器 */ -.chat-messages { - flex: 1; - min-height: 0; - max-height: 100%; - overflow-y: auto; - padding: 20px; - padding-top: 60px; - display: flex; - flex-direction: column; - gap: 15px; -} - -/* 设置面板 */ -.settings-panel { - position: absolute; - top: 0; - right: 0; - z-index: 20; - background-color: #fff; - border-bottom-left-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - overflow: hidden; - transition: all 0.3s ease; - max-width: 200px; -} - -.settings-panel.collapsed { - width: 40px; - height: 40px; - cursor: pointer; -} - -.settings-panel.expanded { - width: auto; - min-width: 150px; - padding: 10px; - border: 1px solid #eee; - border-top: none; - border-right: none; -} - -.settings-header { - height: 40px; - width: 100%; - display: flex; - align-items: center; - justify-content: center; - background-color: #f0f0f0; - cursor: pointer; - font-size: 18px; - color: #666; -} - -.settings-panel.collapsed .settings-header:hover { - background-color: #e0e0e0; -} - -.settings-options { - display: none; - flex-direction: column; - gap: 10px; - padding-top: 10px; -} - -.settings-panel.expanded .settings-options { - display: flex; -} - -.setting-item { - display: flex; - align-items: center; - justify-content: space-between; - font-size: 14px; - color: #333; -} - -.setting-item input[type="checkbox"] { - margin-right: 8px; - cursor: pointer; -} - -/* ==================== 聊天气泡样式 ==================== */ - -/* 消息行容器 */ -.message { - display: flex; - width: 100%; - margin-bottom: 10px; -} - -/* 区分左右布局 */ -.message.user { - justify-content: flex-end; /* 用户消息靠右 */ -} - -.message.ai { - justify-content: flex-start; /* AI 消息靠左 */ -} - -/* 消息容器 - 调整为垂直排列 */ -.message-container { - display: flex; - flex-direction: column; - max-width: 70%; -} - -/* 消息头部 - 包含名称和工具栏 */ -.message-header { - display: flex; - align-items: center; - margin-bottom: 5px; - order: 1; /* 确保头部显示在第一个位置 */ -} - -/* 消息名称 */ -.message-name { - font-size: 14px; - font-weight: 500; - color: #333; - padding: 2px 8px; - border-radius: 12px; - background-color: rgba(0, 0, 0, 0.05); - display: inline-block; -} - -/* 用户消息名称 */ -.message.user .message-name { - background-color: rgba(24, 144, 255, 0.1); - color: #1890ff; -} - -/* AI消息名称 */ -.message.ai .message-name { - background-color: rgba(0, 0, 0, 0.05); - color: #333; -} - -/* 消息工具栏 */ -.message-toolbar { - display: flex; - align-items: center; - padding: 0 5px; - opacity: 0; - transition: opacity 0.2s ease; -} - -.message:hover .message-toolbar { - opacity: 1; -} - -.message-id { - font-size: 12px; - color: #888; - padding: 2px 6px; - border-radius: 4px; - background-color: rgba(0, 0, 0, 0.05); - display: flex; - align-items: center; -} - -.toolbar-buttons { - display: flex; - gap: 5px; - margin: 0 5px; -} - -.toolbar-button { - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border: none; - background-color: rgba(0, 0, 0, 0.05); - border-radius: 4px; - cursor: pointer; - color: #555; - transition: all 0.2s ease; -} - -.toolbar-button:hover { - background-color: rgba(0, 0, 0, 0.1); - color: #333; -} - -/* AI消息头部布局:AI助手 - ID - 编辑 - 更多 */ -.message.ai .message-header { - flex-direction: row; - justify-content: flex-start; -} - -.message.ai .message-name { - order: 1; -} - -.message.ai .message-id { - order: 2; - margin-left: 5px; -} - -.message.ai .toolbar-buttons { - order: 3; - margin-left: 5px; -} - -/* 用户消息头部布局:更多 - 编辑 - ID - 我 */ -.message.user .message-header { - flex-direction: row; - justify-content: flex-end; -} - -.message.user .message-name { - order: 4; -} - -.message.user .message-id { - order: 3; - margin-right: 5px; -} - -.message.user .toolbar-buttons { - order: 1; - margin-right: 5px; -} - -/* 消息内容 - 确保显示在名称下方 */ -.message-content { - display: flex; - flex-direction: column; - max-width: 100%; - order: 2; /* 确保内容显示在第二个位置 */ -} - -/* 气泡本体 */ -.message .bubble { - max-width: 100%; - padding: 10px 15px; - border-radius: 12px; - position: relative; - font-size: 14px; - line-height: 1.6; - word-wrap: break-word; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -/* AI 气泡样式 */ -.message.ai .bubble { - background-color: #fff; - color: #333; - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; - border: 1px solid #eee; -} - -/* 用户气泡样式 */ -.message.user .bubble { - background-color: #1890ff; - color: #fff; - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; -} - -/* 针对 AI 消息中的 HTML 内容进行简单的样式重置 */ -.message.ai .bubble p { - margin: 0 0 8px 0; -} -.message.ai .bubble p:last-child { - margin-bottom: 0; -} -.message.ai .bubble ul, .message.ai .bubble ol { - margin: 0 0 8px 0; - padding-left: 20px; -} -.message.ai .bubble b { - font-weight: 600; - color: #000; -} - -/* 编辑模式 */ -.edit-container { - display: flex; - flex-direction: column; - gap: 10px; -} - -.edit-textarea { - width: 100%; - min-height: 80px; - padding: 10px; - border: 1px solid #ddd; - border-radius: 8px; - resize: vertical; - font-family: inherit; - font-size: 14px; - line-height: 1.5; -} - -.edit-textarea:focus { - outline: none; - border-color: #1890ff; - box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); -} - -.edit-buttons { - display: flex; - justify-content: flex-end; - gap: 8px; -} - -.save-button, .cancel-button { - display: flex; - align-items: center; - gap: 4px; - padding: 6px 12px; - border: none; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.2s ease; -} - -.save-button { - background-color: #1890ff; - color: white; -} - -.save-button:hover { - background-color: #40a9ff; -} - -.cancel-button { - background-color: #f0f0f0; - color: #333; -} - -.cancel-button:hover { - background-color: #e6e6e6; -} - -/* ==================== 输入区域 ==================== */ - -.chat-input-wrapper { - background-color: #fff; - border-top: 1px solid #ddd; - padding: 10px 20px; - flex: 0 0 auto; - display: flex; - align-items: flex-end; - gap: 10px; - width: 100%; -} - -.chat-input-area { - flex: 1; - display: flex; -} - -.chat-input-area textarea { - width: 100%; - height: 42px; - min-height: 42px; - max-height: 300px; - padding: 10px; - border: 1px solid #ddd; - border-radius: 4px; - resize: none; - outline: none; - font-family: inherit; - line-height: 1.5; - overflow-y: auto; - display: block; -} - -.send-button { - height: 42px; - padding: 0 20px; - background-color: #1890ff; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - white-space: nowrap; - flex-shrink: 0; -} - -.send-button:hover { - background-color: #40a9ff;import React, { useState, useEffect } from 'react'; - -const RoleSelector = ({ onRoleChange, onChatChange }) => { - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const [roleData, setRoleData] = useState({}); - const [selectedRole, setSelectedRole] = useState(null); - const [selectedChat, setSelectedChat] = useState(null); - - // 获取角色和聊天数据 - useEffect(() => { - const fetchRoleData = async () => { - try { - const response = await fetch('/api/get_all_role_and_chat'); - const data = await response.json(); - setRoleData(data); - } catch (error) { - console.error('获取角色数据失败:', error); - } - }; - - fetchRoleData(); - }, []); - - // 处理角色选择 - const handleRoleSelect = (role) => { - setSelectedRole(role); - setSelectedChat(null); - if (onRoleChange) { - onRoleChange(role); - } - }; - - // 处理聊天选择 - const handleChatSelect = (chat) => { - setSelectedChat(chat); - setIsDropdownOpen(false); - if (onChatChange) { - onChatChange(selectedRole, chat); - } - }; - - return ( -
- -
-
setIsDropdownOpen(!isDropdownOpen)} - > - {selectedRole || '选择角色'} - -
- - {isDropdownOpen && ( -
- {Object.keys(roleData).map(role => ( -
handleRoleSelect(role)} - > - {role} - {selectedRole === role && ( -
- {roleData[role].map(chat => ( -
{ - e.stopPropagation(); - handleChatSelect(chat); - }} - > - {chat} -
- ))} -
- )} -
- ))} -
- )} -
-
- ); -}; - -export default RoleSelector; -import React, { useState, useEffect } from 'react'; - -const RoleSelector = ({ onRoleChange, onChatChange }) => { - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const [roleData, setRoleData] = useState({}); - const [selectedRole, setSelectedRole] = useState(null); - const [selectedChat, setSelectedChat] = useState(null); - - // 获取角色和聊天数据 - useEffect(() => { - const fetchRoleData = async () => { - try { - const response = await fetch('/api/get_all_role_and_chat'); - const data = await response.json(); - setRoleData(data); - } catch (error) { - console.error('获取角色数据失败:', error); - } - }; - - fetchRoleData(); - }, []); - - // 处理角色选择 - const handleRoleSelect = (role) => { - setSelectedRole(role); - setSelectedChat(null); - if (onRoleChange) { - onRoleChange(role); - } - }; - - // 处理聊天选择 - const handleChatSelect = (chat) => { - setSelectedChat(chat); - setIsDropdownOpen(false); - if (onChatChange) { - onChatChange(selectedRole, chat); - } - }; - - return ( -
- -
-
setIsDropdownOpen(!isDropdownOpen)} - > - {selectedRole || '选择角色'} - -
- - {isDropdownOpen && ( -
- {Object.keys(roleData).map(role => ( -
handleRoleSelect(role)} - > - {role} - {selectedRole === role && ( -
- {roleData[role].map(chat => ( -
{ - e.stopPropagation(); - handleChatSelect(chat); - }} - > - {chat} -
- ))} -
- )} -
- ))} -
- )} -
-
- ); -}; - -export default RoleSelector; -import React, { useState, useEffect } from 'react'; - -const RoleSelector = ({ onRoleChange, onChatChange }) => { - const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const [roleData, setRoleData] = useState({}); - const [selectedRole, setSelectedRole] = useState(null); - const [selectedChat, setSelectedChat] = useState(null); - - // 获取角色和聊天数据 - useEffect(() => { - const fetchRoleData = async () => { - try { - const response = await fetch('/api/get_all_role_and_chat'); - const data = await response.json(); - setRoleData(data); - } catch (error) { - console.error('获取角色数据失败:', error); - } - }; - - fetchRoleData(); - }, []); - - // 处理角色选择 - const handleRoleSelect = (role) => { - setSelectedRole(role); - setSelectedChat(null); - if (onRoleChange) { - onRoleChange(role); - } - }; - - // 处理聊天选择 - const handleChatSelect = (chat) => { - setSelectedChat(chat); - setIsDropdownOpen(false); - if (onChatChange) { - onChatChange(selectedRole, chat); - } - }; - - return ( -
- -
-
setIsDropdownOpen(!isDropdownOpen)} - > - {selectedRole || '选择角色'} - -
- - {isDropdownOpen && ( -
- {Object.keys(roleData).map(role => ( -
handleRoleSelect(role)} - > - {role} - {selectedRole === role && ( -
- {roleData[role].map(chat => ( -
{ - e.stopPropagation(); - handleChatSelect(chat); - }} - > - {chat} -
- ))} -
- )} -
- ))} -
- )} -
-
- ); -}; - -export default RoleSelector; - -}