diff --git a/data/chat/test/12312.jsonl b/data/chat/test/12312.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/data/chat/test/3111.jsonl b/data/chat/test/33211.jsonl similarity index 100% rename from data/chat/test/3111.jsonl rename to data/chat/test/33211.jsonl diff --git a/frontend-react/src/App.jsx b/frontend-react/src/App.jsx index a5f6df2..e7aec7f 100644 --- a/frontend-react/src/App.jsx +++ b/frontend-react/src/App.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import Toolbar from './components/Toolbar/Toolbar'; +import Toolbar from './components/ToolBar/ToolBar'; import ChatBox from './components/ChatBox/ChatBox'; import DicePanel from './components/DicePanel/DicePanel'; import ImageDisplay from './components/ImageDisplay/ImageDisplay'; @@ -7,7 +7,6 @@ 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); @@ -24,11 +23,8 @@ function App() { return (
setIsToolbarExpanded(!isToolbarExpanded)} onRoleChange={handleRoleChange} onChatChange={handleChatChange} - selectedRole={selectedRole} /> {/* 主内容容器 */} diff --git a/frontend-react/src/components/ChatBox/ChatBox.css b/frontend-react/src/components/ChatBox/ChatBox.css index 0d79dcf..2f7d178 100644 --- a/frontend-react/src/components/ChatBox/ChatBox.css +++ b/frontend-react/src/components/ChatBox/ChatBox.css @@ -2,18 +2,19 @@ /* React 组件根容器 */ .chat-box { - height: 95%; + height: 100%; /* 修改为100%,填满父容器 */ width: 100%; display: flex; flex-direction: column; background-color: #fafafa; + overflow: hidden; /* 防止内容溢出 */ } /* 消息列表容器 */ .chat-messages { flex: 1; min-height: 0; - max-height: 100%; + max-height: calc(95vh - 62px); /* 减去输入区域的高度 */ overflow-y: auto; padding: 20px; padding-top: 60px; @@ -147,7 +148,7 @@ color: #333; } -/* 消息工具栏 */ +/* 消息工具栏 - 优化布局使图标更紧凑 */ .message-toolbar { display: flex; align-items: center; @@ -172,7 +173,7 @@ .toolbar-buttons { display: flex; - gap: 5px; + gap: 3px; /* 减少图标之间的间距 */ margin: 0 5px; } @@ -355,13 +356,17 @@ background-color: #fff; border-top: 1px solid #ddd; padding: 10px 20px; - flex: 0 0 auto; + height: 62px; /* 明确设置高度 */ + flex-shrink: 0; /* 防止被压缩 */ display: flex; align-items: flex-end; gap: 10px; width: 100%; + position: relative; + z-index: 10; } + .chat-input-area { flex: 1; display: flex; diff --git a/frontend-react/src/components/ToolBar/ToolBar.css b/frontend-react/src/components/ToolBar/ToolBar.css index 0f72aef..d23497f 100644 --- a/frontend-react/src/components/ToolBar/ToolBar.css +++ b/frontend-react/src/components/ToolBar/ToolBar.css @@ -1,80 +1,69 @@ /* ==================== 顶部工具栏区域 ==================== */ .toolbar { - position: absolute; + position: fixed; top: 0; left: 0; right: 0; height: 50px; background-color: #fff; - border-bottom: 1px solid #ddd; - padding: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + z-index: 1000; 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; + padding: 0 20px; + justify-content: flex-start; /* 确保内容从左到右排列 */ + transition: all 0.3s ease; } -.toolbar.expanded { - height: auto; - max-height: 300px; - overflow-y: auto; - padding-bottom: 10px; +/* 工具栏图标容器 */ +.toolbar-icons { + display: flex; + align-items: center; + gap: 15px; + margin-right: auto; /* 添加这行,确保图标靠左 */ } -.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; +/* 工具栏图标 */ +.toolbar-icon { + width: 36px; + height: 36px; + border-radius: 6px; display: flex; align-items: center; justify-content: center; - z-index: 101; + cursor: pointer; + transition: all 0.2s ease; + font-size: 18px; + color: #555; + background-color: #f5f5f5; } -.toolbar-toggle-btn:hover { - background-color: #f5f5f5; +.toolbar-icon:hover { + background-color: #e6f7ff; + color: #1890ff; + transform: translateY(-2px); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); } /* ==================== 下拉框样式 ==================== */ /* 工具栏下拉框容器 */ .toolbar-dropdown { - margin-bottom: 15px; + margin-bottom: 10px; } .toolbar-dropdown label { display: block; margin-bottom: 5px; - font-size: 14px; + font-size: 13px; color: #333; + font-weight: 500; } /* 下拉框容器 */ .dropdown-container { position: relative; - width: 100%; - max-width: 300px; } /* 下拉框触发器 */ @@ -83,16 +72,28 @@ justify-content: space-between; align-items: center; padding: 8px 12px; - background-color: #f5f5f5; - border: 1px solid #ddd; - border-radius: 4px; + background-color: #fff; + border: 1px solid #e0e0e0; + border-radius: 6px; cursor: pointer; - font-size: 14px; + font-size: 13px; + transition: all 0.2s ease; + box-shadow: 0 1px 3px rgba(0,0,0,0.05); +} + +.dropdown-trigger:hover { + border-color: #1890ff; + box-shadow: 0 2px 6px rgba(0,0,0,0.1); } .dropdown-arrow { margin-left: 8px; - font-size: 12px; + font-size: 10px; + transition: transform 0.2s ease; +} + +.dropdown-trigger.open .dropdown-arrow { + transform: rotate(180deg); } /* 下拉菜单 */ @@ -100,23 +101,36 @@ position: absolute; top: 100%; left: 0; - width: 100%; + min-width: 180px; 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; + border: 1px solid #e0e0e0; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + z-index: 1001; + margin-top: 8px; + padding: 5px 0; + opacity: 0; + transform: translateY(-10px); + transition: opacity 0.2s ease, transform 0.2s ease; + pointer-events: none; +} + +.dropdown-menu.visible { + opacity: 1; + transform: translateY(0); + pointer-events: auto; } /* 下拉菜单项 */ .dropdown-item { - padding: 8px 12px; + padding: 10px 15px; cursor: pointer; position: relative; font-size: 14px; + transition: background-color 0.2s ease; + display: flex; + justify-content: space-between; + align-items: center; } .dropdown-item:hover { @@ -126,6 +140,7 @@ .dropdown-item.selected { background-color: #e6f7ff; color: #1890ff; + font-weight: 500; } /* 嵌套下拉框 */ @@ -133,16 +148,60 @@ position: absolute; left: 100%; top: 0; - width: 100%; + min-width: 180px; 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; + border: 1px solid #e0e0e0; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + z-index: 1002; + margin-left: 5px; + padding: 5px 0; + opacity: 0; + transform: translateX(-10px); + transition: opacity 0.2s ease, transform 0.2s ease; + pointer-events: none; + /* 移除滚动条相关设置,让下拉框根据内容自动扩展 */ } -.nested-dropdown .dropdown-item { - padding-left: 20px; +.nested-dropdown.visible { + opacity: 1; + transform: translateX(0); + pointer-events: auto; +} + +/* 添加指示箭头 */ +.dropdown-item.has-children::after { + content: '›'; + font-size: 18px; + color: #999; + margin-left: 8px; + font-weight: 300; +} + +.dropdown-item.selected.has-children::after { + color: #1890ff; +} + +/* 主内容区域 */ +.main-container { + margin-top: 50px; /* 为固定工具栏留出空间 */ + height: calc(100vh - 50px); + display: flex; + overflow: hidden; +} + +/* 加载动画 */ +.loading-spinner { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid rgba(24, 144, 255, 0.2); + border-radius: 50%; + border-top-color: #1890ff; + animation: spin 1s ease-in-out infinite; + margin-right: 8px; +} + +@keyframes spin { + to { transform: rotate(360deg); } } diff --git a/frontend-react/src/components/ToolBar/ToolBar.jsx b/frontend-react/src/components/ToolBar/ToolBar.jsx index 0479b15..7fae928 100644 --- a/frontend-react/src/components/ToolBar/ToolBar.jsx +++ b/frontend-react/src/components/ToolBar/ToolBar.jsx @@ -1,9 +1,7 @@ -import React, { useState } from 'react'; -import './Toolbar.css'; +import React, { useState, useEffect, useRef } from 'react'; +import './ToolBar.css'; const Toolbar = ({ - isExpanded, - onToggle, onRoleChange, onChatChange }) => { @@ -11,107 +9,175 @@ const Toolbar = ({ const [selectedChat, setSelectedChat] = useState(null); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [roleData, setRoleData] = useState({}); + const [hoveredRole, setHoveredRole] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const dropdownRef = useRef(null); // 获取角色和聊天数据 -const fetchRoleData = async () => { - console.log('开始获取角色数据...'); - try { - const response = await fetch('/api/tool_bar/get_all_role_and_chat', { - method: 'GET', - headers: { - 'Cache-Control': 'no-cache', - 'Pragma': 'no-cache', - 'Expires': '0' + const fetchRoleData = async () => { + setIsLoading(true); + console.log('开始获取角色数据...'); + try { + const response = await fetch('/api/tool_bar/get_all_role_and_chat', { + method: 'GET', + headers: { + 'Cache-Control': 'no-cache', + 'Pragma': 'no-cache', + 'Expires': '0' + } + }); + console.log('响应状态:', response.status); + const data = await response.json(); + console.log('获取到的数据:', data); + setRoleData(data); + } catch (error) { + console.error('获取角色数据失败:', error); + } finally { + setIsLoading(false); + } + }; + + // 点击外部关闭下拉菜单 + useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsDropdownOpen(false); + setHoveredRole(null); } - }); - console.log('响应状态:', response.status); - const data = await response.json(); - console.log('获取到的数据:', data); - setRoleData(data); - } catch (error) { - console.error('获取角色数据失败:', error); - } -}; + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); // 处理下拉框展开/收起 - const handleDropdownToggle = async () => { - await fetchRoleData(); - setIsDropdownOpen(!isDropdownOpen); - }; + const handleDropdownToggle = () => { + // 每次点击都重新获取数据 + fetchRoleData(); + setIsDropdownOpen(!isDropdownOpen); + }; // 处理角色选择 const handleRoleSelect = (role) => { setSelectedRole(role); - setSelectedChat(null); if (onRoleChange) { onRoleChange(role); } + + // 如果该角色有聊天记录,默认选择第一个 + if (roleData[role] && roleData[role].length > 0) { + const firstChat = roleData[role][0]; + setSelectedChat(firstChat); + if (onChatChange) { + onChatChange(role, firstChat); + } + } else { + setSelectedChat(null); + } }; // 处理聊天选择 const handleChatSelect = (chat) => { setSelectedChat(chat); setIsDropdownOpen(false); + setHoveredRole(null); if (onChatChange) { onChatChange(selectedRole, chat); } }; + // 处理角色项悬停 + const handleRoleHover = (role) => { + setHoveredRole(role); + }; + + // 处理角色项离开 + const handleRoleLeave = () => { + // 不立即清除hoveredRole,以便用户可以移动到二级菜单 + }; + + // 处理二级菜单进入 + const handleNestedMenuEnter = () => { + // 防止鼠标移动时菜单消失 + }; + + // 处理二级菜单离开 + const handleNestedMenuLeave = () => { + setHoveredRole(null); + }; + return ( -
- +
+
+ {/* Logo图标 */} +
+ 🤖 +
- {isExpanded && ( -
-
- -
-
- {selectedRole || '选择角色'} - -
+ {/* 角色选择下拉框 */} +
+
+ 👤 +
- {isDropdownOpen && ( -
- {Object.keys(roleData).map(role => ( -
handleRoleSelect(role)} - > - {role} - {selectedRole === role && ( -
- {roleData[role].map(chat => ( -
{ - e.stopPropagation(); - handleChatSelect(chat); - }} - > - {chat} -
- ))} -
- )} -
- ))} + {isDropdownOpen && ( +
+ {isLoading ? ( +
+ 加载中...
+ ) : ( + Object.keys(roleData).map(role => ( +
0 ? 'has-children' : ''}`} + onClick={() => handleRoleSelect(role)} + onMouseEnter={() => handleRoleHover(role)} + onMouseLeave={handleRoleLeave} + > + {role} + {/* 当悬停在角色上时显示二级菜单 */} + {hoveredRole === role && roleData[role] && roleData[role].length > 0 && ( +
+ {roleData[role].map(chat => ( +
{ + e.stopPropagation(); + handleChatSelect(chat); + }} + > + {chat} +
+ ))} +
+ )} +
+ )) )}
-
+ )}
- )} + + {/* 其他工具栏图标 */} +
+ ⚙️ +
+
+ ❓ +
+
); }; diff --git a/frontend-react/src/index.css b/frontend-react/src/index.css index 6a889cb..e025004 100644 --- a/frontend-react/src/index.css +++ b/frontend-react/src/index.css @@ -1,99 +1,61 @@ -/* ==================== 全局样式 ==================== */ - -/* 全局重置与基础设置 */ +/* 重置样式 */ * { - box-sizing: border-box; margin: 0; padding: 0; + box-sizing: border-box; } -html, body, #root { +html, body, .app { height: 100%; width: 100%; - overflow: hidden; /* 禁止全局滚动 */ - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - background-color: #f5f5f5; - color: #333; + overflow: hidden; /* 防止出现滚动条 */ } -/* 布局容器 */ -#root { +.app { display: flex; flex-direction: column; - position: relative; - height: 100%; - width: 100%; } -/* 滚动条美化 */ -::-webkit-scrollbar { - width: 6px; - height: 6px; -} -::-webkit-scrollbar-track { - background: transparent; -} -::-webkit-scrollbar-thumb { - background: #ccc; - border-radius: 3px; -} -::-webkit-scrollbar-thumb:hover { - background: #aaa; -} - -/* ==================== 主内容区域 ==================== */ - +/* 主内容区域 */ .main-container { - position: absolute; - top: 50px; - bottom: 0; - left: 0; - right: 0; - height: 100%; - width: 100%; + flex: 1; /* 这会让主容器占据剩余的所有空间 */ display: flex; - overflow: hidden; + overflow: hidden; /* 防止内容溢出 */ + height: 100%; /* 确保高度填满 */ } /* 左侧栏 */ .sidebar-left { - width: 20%; - background-color: #fff; - border-right: 1px solid #ddd; - overflow-y: auto; - height: 100%; + width: 250px; /* 或者你想要的宽度 */ + height: 继承父元素高度; /* 确保高度填满 */ + overflow-y: auto; /* 内容过多时显示滚动条 */ } -/* 中间栏:聊天框 */ +/* 中间聊天区域 */ .chat-area { - flex: 3; - background-color: #fafafa; - height: 100%; + flex: 1; /* 占据剩余空间 */ + height: 100%; /* 确保高度填满 */ display: flex; flex-direction: column; - overflow: hidden; - position: relative; + overflow: hidden; /* 防止内容溢出 */ } /* 右侧栏 */ .sidebar-right { - width: 20%; - background-color: #fff; - border-left: 1px solid #ddd; + width: 300px; /* 或者你想要的宽度 */ + height: 100%; /* 确保高度填满 */ display: flex; flex-direction: column; - height: 100%; - overflow: hidden; -} - -/* 右侧上下分割 */ -.right-top, .right-bottom { - flex: 1; - overflow-y: auto; - padding: 10px; - min-height: 0; } +/* 右侧栏顶部 */ .right-top { - border-bottom: 1px solid #ddd; + flex: 1; /* 占据剩余空间 */ + overflow-y: auto; +} + +/* 右侧栏底部 */ +.right-bottom { + height: 200px; /* 或者你想要的高度 */ + overflow-y: auto; }