diff --git a/frontend-react/src/App.jsx b/frontend-react/src/App.jsx
index edeaf58..d2647ca 100644
--- a/frontend-react/src/App.jsx
+++ b/frontend-react/src/App.jsx
@@ -1,36 +1,58 @@
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';
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);
+ // 这里可以添加其他逻辑,比如加载角色的聊天记录等
+ };
+
+ // 处理聊天选择变化
+ const handleChatChange = (role, chat) => {
+ setSelectedChat(chat);
+ console.log('选择的聊天:', role, chat);
+ // 这里可以添加其他逻辑,比如加载聊天内容等
+ };
+
return (
{/* 顶部工具栏 */}
- AI Chat Studio
+ {/* 工具栏内容区域 */}
+ {isToolbarExpanded && (
+
+ {/* 角色选择器 */}
+
+
+ )}
+
+ {/* 工具栏切换按钮 */}
- {/* 展开后的内容区域(预留) */}
- {isToolbarExpanded && (
-
- {/* 工具栏内容待定 */}
-
- )}
{/* 主体内容区域 */}
diff --git a/frontend-react/src/components/ChatBox.jsx b/frontend-react/src/components/ChatBox.jsx
index 6a3196b..2e71a21 100644
--- a/frontend-react/src/components/ChatBox.jsx
+++ b/frontend-react/src/components/ChatBox.jsx
@@ -5,16 +5,26 @@ const ChatBox = () => {
const [isImageGen, setIsImageGen] = useState(false);
const [isDynamicTable, setIsDynamicTable] = useState(false);
const [isSettingsExpanded, setIsSettingsExpanded] = useState(false);
+ const [editingId, setEditingId] = useState(null);
+ const [editContent, setEditContent] = useState('');
const textareaRef = useRef(null);
// 自动调整 Textarea 高度
const adjustHeight = () => {
const textarea = textareaRef.current;
- if (textarea) {
- textarea.style.height = 'auto'; // 重置高度以获取正确的 scrollHeight
- // 重新设置高度,限制最大高度由 CSS max-height 控制
- textarea.style.height = textarea.scrollHeight + 'px';
+ const wrapper = textarea?.closest('.chat-input-wrapper');
+
+ if (textarea && wrapper) {
+ textarea.style.height = '42px';
+ wrapper.style.height = 'auto';
+ const newHeight = textarea.scrollHeight;
+
+ if (newHeight > 42) {
+ textarea.style.height = newHeight + 'px';
+ } else {
+ textarea.style.height = '42px';
+ }
}
};
@@ -22,6 +32,34 @@ const ChatBox = () => {
adjustHeight();
};
+ // 开始编辑消息
+ const startEdit = (id, content) => {
+ setEditingId(id);
+ setEditContent(content);
+ };
+
+ // 保存编辑的消息
+ const saveEdit = (id) => {
+ // 这里可以添加向后端发送请求的代码
+ console.log(`保存消息 ${id} 的编辑内容: ${editContent}`);
+
+ // 更新前端显示
+ const messageIndex = messages.findIndex(msg => msg.id === id);
+ if (messageIndex !== -1) {
+ messages[messageIndex].content = editContent;
+ }
+
+ // 退出编辑模式
+ setEditingId(null);
+ setEditContent('');
+ };
+
+ // 取消编辑
+ const cancelEdit = () => {
+ setEditingId(null);
+ setEditContent('');
+ };
+
// 生成示例数据
const generateMessages = () => {
const messages = [];
@@ -30,6 +68,7 @@ const ChatBox = () => {
messages.push({
id: i,
role: isUser ? 'user' : 'ai',
+ name: isUser ? '我' : 'AI助手',
content: isUser
? `这是第 ${i} 条用户消息。这是一段比较长的文本,用来测试气泡的换行效果以及滚动条的表现。`
: `这是第 ${i} 条 AI 回复。
包含 HTML 标签的内容。如果渲染开关开启,这里应该显示粗体字。如果不开启,应该显示原始标签。`
@@ -87,12 +126,62 @@ const ChatBox = () => {
{/* 消息列表 */}
{messages.map((msg) => (
-
- {isHtmlRender ? (
-
- ) : (
-
{msg.content}
- )}
+
+ {/* 消息名称和工具栏在同一行 */}
+
+
{msg.name}
+
+ {/* 消息工具栏 */}
+
+
ID: {msg.id}
+
+
+
+
+
+
+
+ {/* 消息内容 */}
+
+
+ {editingId === msg.id ? (
+
+ ) : (
+ isHtmlRender ? (
+
+ ) : (
+
{msg.content}
+ )
+ )}
+
+
))}
@@ -106,7 +195,6 @@ const ChatBox = () => {
placeholder="输入消息..."
onInput={handleInput}
rows="1"
- // 关键修改:显式设置初始样式高度,确保可见
style={{ height: '42px' }}
/>
diff --git a/frontend-react/src/components/RoleSelector.jsx b/frontend-react/src/components/RoleSelector.jsx
new file mode 100644
index 0000000..580dfbe
--- /dev/null
+++ b/frontend-react/src/components/RoleSelector.jsx
@@ -0,0 +1,95 @@
+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 {
+ console.log('开始获取角色数据...');
+ const response = await fetch('/api/get_all_role_and_chat');
+ console.log('响应状态:', response.status);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ console.log('获取到的数据:', data);
+ 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;
diff --git a/frontend-react/src/index.css b/frontend-react/src/index.css
index 016b561..5008c71 100644
--- a/frontend-react/src/index.css
+++ b/frontend-react/src/index.css
@@ -1,3 +1,5 @@
+/* ==================== 全局样式 ==================== */
+
/* 全局重置与基础设置 */
* {
box-sizing: border-box;
@@ -14,103 +16,299 @@ html, body, #root {
color: #333;
}
-/* 布局容器:相对定位,作为绝对定位子元素的参考系 */
+/* 布局容器 */
#root {
display: flex;
flex-direction: column;
- position: relative; /* 让绝对定位的工具栏相对于此容器定位 */
- height: 100%; /* 确保高度占满 */
+ 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;
+}
+
+/* ==================== 顶部工具栏区域 ==================== */
+
.toolbar {
- position: absolute; /* 脱离文档流,实现覆盖效果 */
+ position: absolute;
top: 0;
left: 0;
right: 0;
- height: 50px; /* 默认折叠高度 */
+ height: 50px;
background-color: #fff;
border-bottom: 1px solid #ddd;
- padding: 0 20px;
+ padding: 0;
display: flex;
align-items: center;
- justify-content: space-between;
- z-index: 100; /* 确保层级最高,覆盖在内容之上 */
+ justify-content: flex-end;
+ z-index: 100;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
- transition: height 0.3s ease; /* 平滑过渡动画 */
+ transition: height 0.3s ease;
}
-/* 工具栏展开状态 */
.toolbar.expanded {
- height: auto; /* 或者设置一个具体的高度,如 200px */
- max-height: 300px; /* 防止无限撑开 */
- overflow-y: auto; /* 如果内容太多,工具栏内部也可以滚动 */
+ height: auto;
+ max-height: 300px;
+ overflow-y: auto;
padding-bottom: 10px;
}
-/* 工具栏内容容器 */
.toolbar-content {
- display: none; /* 默认隐藏 */
+ display: none;
width: 100%;
- padding-top: 10px;
+ padding: 10px 20px;
}
.toolbar.expanded .toolbar-content {
- display: block; /* 展开时显示 */
+ 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;
- padding: 4px 8px;
- font-size: 0.8rem;
- color: #666;
+ font-size: 14px;
}
-/* --- 主内容区域 --- */
-.main-container {
- /* 占据除工具栏外的所有空间 */
+.dropdown-arrow {
+ margin-left: 8px;
+ font-size: 12px;
+}
+
+/* 下拉菜单 */
+.dropdown-menu {
position: absolute;
- top: 50px; /* 对应 .toolbar 的默认高度 */
+ 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 {
+ position: absolute;
+ top: 50px;
bottom: 0;
left: 0;
right: 0;
-
- /* 【关键修复 1】显式设置高度为 100%,而不是 auto */
- /* 这确保了容器高度由视口决定,而不是由内容撑开 */
height: 100%;
width: 100%;
-
display: flex;
- overflow: hidden; /* 防止主容器本身滚动 */
+ overflow: hidden;
}
-/* --- 左中右三栏 --- */
+/* 左侧栏 */
.sidebar-left {
width: 20%;
background-color: #fff;
border-right: 1px solid #ddd;
- overflow-y: auto; /* 【独立滚动 1】左侧栏滚动 */
- height: 100%; /* 填满父容器高度 */
+ overflow-y: auto;
+ height: 100%;
}
-/* --- 中间栏:聊天框 --- */
+/* 中间栏:聊天框 */
.chat-area {
flex: 3;
background-color: #fafafa;
- height: 100%; /* 填满父容器高度 */
-
+ height: 100%;
display: flex;
- flex-direction: column; /* 垂直排列:消息列表 + 输入框 */
-
- overflow: hidden; /* 禁止外层滚动,强制内部滚动 */
- position: relative; /* 为绝对定位元素提供参考 */
+ flex-direction: column;
+ overflow: hidden;
+ position: relative;
}
+/* 右侧栏 */
.sidebar-right {
width: 20%;
background-color: #fff;
@@ -124,38 +322,40 @@ html, body, #root {
/* 右侧上下分割 */
.right-top, .right-bottom {
flex: 1;
- overflow-y: auto; /* 【独立滚动 2 & 3】右侧上下部分独立滚动 */
+ overflow-y: auto;
padding: 10px;
- min-height: 0; /* 关键:允许 flex 子项收缩 */
+ min-height: 0;
}
+
.right-top {
border-bottom: 1px solid #ddd;
}
-/* --- 消息列表容器 --- */
+/* ==================== 聊天框区域 ==================== */
+
+/* React 组件根容器 */
+.chat-box {
+ height: 95%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ background-color: #fafafa;
+}
+
+/* 消息列表容器 */
.chat-messages {
- /* 【关键修改 1】不再自动占据剩余空间,高度由内容决定 */
- flex: 0 0 auto;
-
- /* 【关键修改 2】设置最大高度,例如视口高度的 80% */
- max-height: calc(100vh - 110px);
-
- /* 强制约束高度,防止内容撑开 */
+ flex: 1;
min-height: 0;
-
- /* 内部独立滚动 */
- overflow-y: auto; /* 内容溢出时显示滚动条 */
-
+ max-height: 100%;
+ overflow-y: auto;
padding: 20px;
- /* 增加顶部 padding,防止内容被绝对定位的设置面板遮挡 */
padding-top: 60px;
-
display: flex;
flex-direction: column;
gap: 15px;
}
-/* --- 设置面板 (右上角) --- */
+/* 设置面板 */
.settings-panel {
position: absolute;
top: 0;
@@ -224,82 +424,7 @@ html, body, #root {
cursor: pointer;
}
-/* --- 输入区域容器 --- */
-.chat-input-wrapper {
- background-color: #fff;
- border-top: 1px solid #ddd;
- padding: 10px 20px;
-
- /* 使用 flex 属性,不伸缩,高度由内容决定 */
- flex: 0 0 auto;
-
- /* 保持 flex 布局以便内部对齐 */
- display: flex;
- align-items: flex-end; /* 底部对齐,适应多行输入框 */
- gap: 10px;
-
- width: 100%;
-}
-
-.chat-input-area {
- flex: 1; /* 占据除按钮外的所有宽度 */
- display: flex;
- /* 移除 height: 100%,让高度由内容决定 */
-}
-
-.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;
- /* 【独立滚动 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;
- margin-bottom: 0; /* 确保与输入框底部对齐 */
-}
-
-.send-button:hover {
- background-color: #40a9ff;
-}
-
-/* 通用滚动条美化 */
-::-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;
-}
-
-/* --- 聊天气泡样式美化 --- */
+/* ==================== 聊天气泡样式 ==================== */
/* 消息行容器 */
.message {
@@ -317,32 +442,166 @@ html, body, #root {
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: 70%; /* 气泡最大宽度,防止太长 */
+ max-width: 100%;
padding: 10px 15px;
- border-radius: 12px; /* 圆角 */
+ 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); /* 极淡的阴影 */
+ 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-top-left-radius: 2px;
border-bottom-left-radius: 2px;
- border: 1px solid #eee; /* 极淡的边框 */
+ border: 1px solid #eee;
}
/* 用户气泡样式 */
.message.user .bubble {
- background-color: #1890ff; /* 亮蓝色 */
+ background-color: #1890ff;
color: #fff;
- border-top-right-radius: 2px; /* 右上角直角或小圆角 */
+ border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}
@@ -361,3 +620,378 @@ html, body, #root {
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;
+
+}