读取本地chatandrole完成并优化排列

This commit is contained in:
2026-03-19 23:01:38 +08:00
parent 91d11abe90
commit a371039ee6
7 changed files with 302 additions and 214 deletions

View File

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import Toolbar from './components/Toolbar/Toolbar'; import Toolbar from './components/ToolBar/ToolBar';
import ChatBox from './components/ChatBox/ChatBox'; import ChatBox from './components/ChatBox/ChatBox';
import DicePanel from './components/DicePanel/DicePanel'; import DicePanel from './components/DicePanel/DicePanel';
import ImageDisplay from './components/ImageDisplay/ImageDisplay'; import ImageDisplay from './components/ImageDisplay/ImageDisplay';
@@ -7,7 +7,6 @@ import PresetPanel from './components/PresetPanel/PresetPanel';
import './index.css'; import './index.css';
function App() { function App() {
const [isToolbarExpanded, setIsToolbarExpanded] = useState(false);
const [selectedRole, setSelectedRole] = useState(null); const [selectedRole, setSelectedRole] = useState(null);
const [selectedChat, setSelectedChat] = useState(null); const [selectedChat, setSelectedChat] = useState(null);
@@ -24,11 +23,8 @@ function App() {
return ( return (
<div className="app"> <div className="app">
<Toolbar <Toolbar
isExpanded={isToolbarExpanded}
onToggle={() => setIsToolbarExpanded(!isToolbarExpanded)}
onRoleChange={handleRoleChange} onRoleChange={handleRoleChange}
onChatChange={handleChatChange} onChatChange={handleChatChange}
selectedRole={selectedRole}
/> />
{/* 主内容容器 */} {/* 主内容容器 */}

View File

@@ -2,18 +2,19 @@
/* React 组件根容器 */ /* React 组件根容器 */
.chat-box { .chat-box {
height: 95%; height: 100%; /* 修改为100%,填满父容器 */
width: 100%; width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: #fafafa; background-color: #fafafa;
overflow: hidden; /* 防止内容溢出 */
} }
/* 消息列表容器 */ /* 消息列表容器 */
.chat-messages { .chat-messages {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
max-height: 100%; max-height: calc(95vh - 62px); /* 减去输入区域的高度 */
overflow-y: auto; overflow-y: auto;
padding: 20px; padding: 20px;
padding-top: 60px; padding-top: 60px;
@@ -147,7 +148,7 @@
color: #333; color: #333;
} }
/* 消息工具栏 */ /* 消息工具栏 - 优化布局使图标更紧凑 */
.message-toolbar { .message-toolbar {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -172,7 +173,7 @@
.toolbar-buttons { .toolbar-buttons {
display: flex; display: flex;
gap: 5px; gap: 3px; /* 减少图标之间的间距 */
margin: 0 5px; margin: 0 5px;
} }
@@ -355,13 +356,17 @@
background-color: #fff; background-color: #fff;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
padding: 10px 20px; padding: 10px 20px;
flex: 0 0 auto; height: 62px; /* 明确设置高度 */
flex-shrink: 0; /* 防止被压缩 */
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
gap: 10px; gap: 10px;
width: 100%; width: 100%;
position: relative;
z-index: 10;
} }
.chat-input-area { .chat-input-area {
flex: 1; flex: 1;
display: flex; display: flex;

View File

@@ -1,80 +1,69 @@
/* ==================== 顶部工具栏区域 ==================== */ /* ==================== 顶部工具栏区域 ==================== */
.toolbar { .toolbar {
position: absolute; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
height: 50px; height: 50px;
background-color: #fff; background-color: #fff;
border-bottom: 1px solid #ddd; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 0; z-index: 1000;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; padding: 0 20px;
z-index: 100; justify-content: flex-start; /* 确保内容从左到右排列 */
box-shadow: 0 2px 4px rgba(0,0,0,0.05); transition: all 0.3s ease;
transition: height 0.3s ease;
} }
.toolbar.expanded { /* 工具栏图标容器 */
height: auto; .toolbar-icons {
max-height: 300px; display: flex;
overflow-y: auto; align-items: center;
padding-bottom: 10px; gap: 15px;
margin-right: auto; /* 添加这行,确保图标靠左 */
} }
.toolbar-content { /* 工具栏图标 */
display: none; .toolbar-icon {
width: 100%; width: 36px;
padding: 10px 20px; height: 36px;
} border-radius: 6px;
.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; display: flex;
align-items: center; align-items: center;
justify-content: 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 { .toolbar-icon:hover {
background-color: #f5f5f5; background-color: #e6f7ff;
color: #1890ff;
transform: translateY(-2px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
} }
/* ==================== 下拉框样式 ==================== */ /* ==================== 下拉框样式 ==================== */
/* 工具栏下拉框容器 */ /* 工具栏下拉框容器 */
.toolbar-dropdown { .toolbar-dropdown {
margin-bottom: 15px; margin-bottom: 10px;
} }
.toolbar-dropdown label { .toolbar-dropdown label {
display: block; display: block;
margin-bottom: 5px; margin-bottom: 5px;
font-size: 14px; font-size: 13px;
color: #333; color: #333;
font-weight: 500;
} }
/* 下拉框容器 */ /* 下拉框容器 */
.dropdown-container { .dropdown-container {
position: relative; position: relative;
width: 100%;
max-width: 300px;
} }
/* 下拉框触发器 */ /* 下拉框触发器 */
@@ -83,16 +72,28 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 8px 12px; padding: 8px 12px;
background-color: #f5f5f5; background-color: #fff;
border: 1px solid #ddd; border: 1px solid #e0e0e0;
border-radius: 4px; border-radius: 6px;
cursor: pointer; 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 { .dropdown-arrow {
margin-left: 8px; 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; position: absolute;
top: 100%; top: 100%;
left: 0; left: 0;
width: 100%; min-width: 180px;
background-color: white; background-color: white;
border: 1px solid #ddd; border: 1px solid #e0e0e0;
border-radius: 4px; border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 10; z-index: 1001;
max-height: 200px; margin-top: 8px;
overflow-y: auto; padding: 5px 0;
margin-top: 5px; 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 { .dropdown-item {
padding: 8px 12px; padding: 10px 15px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
font-size: 14px; font-size: 14px;
transition: background-color 0.2s ease;
display: flex;
justify-content: space-between;
align-items: center;
} }
.dropdown-item:hover { .dropdown-item:hover {
@@ -126,6 +140,7 @@
.dropdown-item.selected { .dropdown-item.selected {
background-color: #e6f7ff; background-color: #e6f7ff;
color: #1890ff; color: #1890ff;
font-weight: 500;
} }
/* 嵌套下拉框 */ /* 嵌套下拉框 */
@@ -133,16 +148,60 @@
position: absolute; position: absolute;
left: 100%; left: 100%;
top: 0; top: 0;
width: 100%; min-width: 180px;
background-color: white; background-color: white;
border: 1px solid #ddd; border: 1px solid #e0e0e0;
border-radius: 4px; border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 11; z-index: 1002;
max-height: 200px; margin-left: 5px;
overflow-y: auto; padding: 5px 0;
opacity: 0;
transform: translateX(-10px);
transition: opacity 0.2s ease, transform 0.2s ease;
pointer-events: none;
/* 移除滚动条相关设置,让下拉框根据内容自动扩展 */
} }
.nested-dropdown .dropdown-item { .nested-dropdown.visible {
padding-left: 20px; 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); }
} }

View File

@@ -1,9 +1,7 @@
import React, { useState } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import './Toolbar.css'; import './ToolBar.css';
const Toolbar = ({ const Toolbar = ({
isExpanded,
onToggle,
onRoleChange, onRoleChange,
onChatChange onChatChange
}) => { }) => {
@@ -11,107 +9,175 @@ const Toolbar = ({
const [selectedChat, setSelectedChat] = useState(null); const [selectedChat, setSelectedChat] = useState(null);
const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [roleData, setRoleData] = useState({}); const [roleData, setRoleData] = useState({});
const [hoveredRole, setHoveredRole] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const dropdownRef = useRef(null);
// 获取角色和聊天数据 // 获取角色和聊天数据
const fetchRoleData = async () => { const fetchRoleData = async () => {
console.log('开始获取角色数据...'); setIsLoading(true);
try { console.log('开始获取角色数据...');
const response = await fetch('/api/tool_bar/get_all_role_and_chat', { try {
method: 'GET', const response = await fetch('/api/tool_bar/get_all_role_and_chat', {
headers: { method: 'GET',
'Cache-Control': 'no-cache', headers: {
'Pragma': 'no-cache', 'Cache-Control': 'no-cache',
'Expires': '0' '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(); document.addEventListener('mousedown', handleClickOutside);
console.log('获取到的数据:', data); return () => {
setRoleData(data); document.removeEventListener('mousedown', handleClickOutside);
} catch (error) { };
console.error('获取角色数据失败:', error); }, []);
}
};
// 处理下拉框展开/收起 // 处理下拉框展开/收起
const handleDropdownToggle = async () => { const handleDropdownToggle = () => {
await fetchRoleData(); // 每次点击都重新获取数据
setIsDropdownOpen(!isDropdownOpen); fetchRoleData();
}; setIsDropdownOpen(!isDropdownOpen);
};
// 处理角色选择 // 处理角色选择
const handleRoleSelect = (role) => { const handleRoleSelect = (role) => {
setSelectedRole(role); setSelectedRole(role);
setSelectedChat(null);
if (onRoleChange) { if (onRoleChange) {
onRoleChange(role); 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) => { const handleChatSelect = (chat) => {
setSelectedChat(chat); setSelectedChat(chat);
setIsDropdownOpen(false); setIsDropdownOpen(false);
setHoveredRole(null);
if (onChatChange) { if (onChatChange) {
onChatChange(selectedRole, chat); onChatChange(selectedRole, chat);
} }
}; };
// 处理角色项悬停
const handleRoleHover = (role) => {
setHoveredRole(role);
};
// 处理角色项离开
const handleRoleLeave = () => {
// 不立即清除hoveredRole以便用户可以移动到二级菜单
};
// 处理二级菜单进入
const handleNestedMenuEnter = () => {
// 防止鼠标移动时菜单消失
};
// 处理二级菜单离开
const handleNestedMenuLeave = () => {
setHoveredRole(null);
};
return ( return (
<div className={`toolbar ${isExpanded ? 'expanded' : ''}`}> <div className="toolbar">
<button <div className="toolbar-icons">
className="toolbar-toggle-btn" {/* Logo图标 */}
onClick={onToggle} <div className="toolbar-icon" title="首页">
> 🤖
{isExpanded ? '▼' : '▲'} </div>
</button>
{isExpanded && ( {/* 角色选择下拉框 */}
<div className="toolbar-content"> <div className="dropdown-container" ref={dropdownRef}>
<div className="toolbar-dropdown"> <div
<label>选择角色</label> className="toolbar-icon"
<div className="dropdown-container"> onClick={handleDropdownToggle}
<div title="选择角色"
className="dropdown-trigger" >
onClick={handleDropdownToggle} 👤
> </div>
{selectedRole || '选择角色'}
<span className="dropdown-arrow"></span>
</div>
{isDropdownOpen && ( {isDropdownOpen && (
<div className="dropdown-menu"> <div className="dropdown-menu visible">
{Object.keys(roleData).map(role => ( {isLoading ? (
<div <div className="dropdown-item">
key={role} 加载中...
className={`dropdown-item ${selectedRole === role ? 'selected' : ''}`}
onClick={() => handleRoleSelect(role)}
>
{role}
{selectedRole === role && (
<div className="nested-dropdown">
{roleData[role].map(chat => (
<div
key={chat}
className={`dropdown-item ${selectedChat === chat ? 'selected' : ''}`}
onClick={(e) => {
e.stopPropagation();
handleChatSelect(chat);
}}
>
{chat}
</div>
))}
</div>
)}
</div>
))}
</div> </div>
) : (
Object.keys(roleData).map(role => (
<div
key={role}
className={`dropdown-item ${selectedRole === role ? 'selected' : ''} ${roleData[role] && roleData[role].length > 0 ? 'has-children' : ''}`}
onClick={() => handleRoleSelect(role)}
onMouseEnter={() => handleRoleHover(role)}
onMouseLeave={handleRoleLeave}
>
{role}
{/* 当悬停在角色上时显示二级菜单 */}
{hoveredRole === role && roleData[role] && roleData[role].length > 0 && (
<div
className={`nested-dropdown visible`}
onMouseEnter={handleNestedMenuEnter}
onMouseLeave={handleNestedMenuLeave}
>
{roleData[role].map(chat => (
<div
key={chat}
className={`dropdown-item ${selectedChat === chat ? 'selected' : ''}`}
onClick={(e) => {
e.stopPropagation();
handleChatSelect(chat);
}}
>
{chat}
</div>
))}
</div>
)}
</div>
))
)} )}
</div> </div>
</div> )}
</div> </div>
)}
{/* 其他工具栏图标 */}
<div className="toolbar-icon" title="设置">
</div>
<div className="toolbar-icon" title="帮助">
</div>
</div>
</div> </div>
); );
}; };

View File

@@ -1,99 +1,61 @@
/* ==================== 全局样式 ==================== */ /* 重置样式 */
/* 全局重置与基础设置 */
* { * {
box-sizing: border-box;
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box;
} }
html, body, #root { html, body, .app {
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: hidden; /* 禁止全局滚动 */ overflow: hidden; /* 防止出现滚动 */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f5f5f5;
color: #333;
} }
/* 布局容器 */ .app {
#root {
display: flex; display: flex;
flex-direction: column; 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 { .main-container {
position: absolute; flex: 1; /* 这会让主容器占据剩余的所有空间 */
top: 50px;
bottom: 0;
left: 0;
right: 0;
height: 100%;
width: 100%;
display: flex; display: flex;
overflow: hidden; overflow: hidden; /* 防止内容溢出 */
height: 100%; /* 确保高度填满 */
} }
/* 左侧栏 */ /* 左侧栏 */
.sidebar-left { .sidebar-left {
width: 20%; width: 250px; /* 或者你想要的宽度 */
background-color: #fff; height: ; /* 确保高度填满 */
border-right: 1px solid #ddd; overflow-y: auto; /* 内容过多时显示滚动条 */
overflow-y: auto;
height: 100%;
} }
/* 中间栏:聊天框 */ /* 中间聊天区域 */
.chat-area { .chat-area {
flex: 3; flex: 1; /* 占据剩余空间 */
background-color: #fafafa; height: 100%; /* 确保高度填满 */
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden; /* 防止内容溢出 */
position: relative;
} }
/* 右侧栏 */ /* 右侧栏 */
.sidebar-right { .sidebar-right {
width: 20%; width: 300px; /* 或者你想要的宽度 */
background-color: #fff; height: 100%; /* 确保高度填满 */
border-left: 1px solid #ddd;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%;
overflow: hidden;
}
/* 右侧上下分割 */
.right-top, .right-bottom {
flex: 1;
overflow-y: auto;
padding: 10px;
min-height: 0;
} }
/* 右侧栏顶部 */
.right-top { .right-top {
border-bottom: 1px solid #ddd; flex: 1; /* 占据剩余空间 */
overflow-y: auto;
}
/* 右侧栏底部 */
.right-bottom {
height: 200px; /* 或者你想要的高度 */
overflow-y: auto;
} }