diff --git a/backend/__pycache__/__init__.cpython-311.pyc b/backend/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..c9a6da1 Binary files /dev/null and b/backend/__pycache__/__init__.cpython-311.pyc differ diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000..4432136 Binary files /dev/null and b/backend/__pycache__/main.cpython-311.pyc differ diff --git a/backend/api/__pycache__/__init__.cpython-311.pyc b/backend/api/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..f71c610 Binary files /dev/null and b/backend/api/__pycache__/__init__.cpython-311.pyc differ diff --git a/backend/api/__pycache__/route.cpython-311.pyc b/backend/api/__pycache__/route.cpython-311.pyc new file mode 100644 index 0000000..3de0bff Binary files /dev/null and b/backend/api/__pycache__/route.cpython-311.pyc differ diff --git a/backend/core/__pycache__/__init__.cpython-311.pyc b/backend/core/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..0d3b742 Binary files /dev/null and b/backend/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/backend/core/__pycache__/config.cpython-311.pyc b/backend/core/__pycache__/config.cpython-311.pyc new file mode 100644 index 0000000..356e5ff Binary files /dev/null and b/backend/core/__pycache__/config.cpython-311.pyc differ diff --git a/backend/core/__pycache__/items.cpython-311.pyc b/backend/core/__pycache__/items.cpython-311.pyc new file mode 100644 index 0000000..fd1b335 Binary files /dev/null and b/backend/core/__pycache__/items.cpython-311.pyc differ diff --git a/backend/core/models/chat_history.py b/backend/core/models/chat_history.py new file mode 100644 index 0000000..dddb861 --- /dev/null +++ b/backend/core/models/chat_history.py @@ -0,0 +1,158 @@ +from typing import List, Dict, Any, Optional +from datetime import datetime +from pathlib import Path +from pydantic import BaseModel, Field +import json + + +class Message(BaseModel): + """消息类,代表JSONL文件中的一行消息内容""" + name: str = Field(..., description="发言者名称") + is_user: bool = Field(..., description="是否为用户消息(true=用户,false=AI/角色)") + is_system: bool = Field(False, description="是否为系统消息(系统消息在文本导出时会被排除)") + send_date: str = Field(default_factory=lambda: str(int(datetime.now().timestamp() * 1000)), + description="发送时间戳(Unix毫秒数)") + mes: str = Field(..., description="消息正文内容") + extra: Dict[str, Any] = Field(default_factory=dict, description="额外信息,包含推理内容、API、模型等") + swipes: List[str] = Field(default_factory=list, description="备选回复列表") + swipe_id: int = Field(0, description="当前选中的备选索引(0=第一条)") + swipe_info: List[Dict[str, Any]] = Field(default_factory=list, description="每个备选回复的生成信息") + title: str = Field("", description="消息标题,用于消息摘要或分支标记") + force_avatar: Optional[str] = Field(None, description="强制头像路径") + variables: List[Any] = Field(default_factory=list, description="变量值数组") + variables_initialized: List[bool] = Field(default_factory=list, description="变量初始化状态数组") + is_ejs_processed: List[bool] = Field(default_factory=list, description="EJS模板处理状态数组") + gen_started: Optional[str] = Field(None, description="生成开始时间戳(Unix毫秒数)") + gen_finished: Optional[str] = Field(None, description="生成结束时间戳(Unix毫秒数)") + + +class ChatMetadata(BaseModel): + """聊天元数据模型,代表JSONL文件的第一行内容""" + integrity: str = Field("", description="完整性校验哈希值(UUID格式)") + chat_id_hash: str = Field("", description="聊天ID哈希值") + note_prompt: str = Field("", description="笔记提示词") + note_interval: int = Field(0, description="笔记间隔(整数)") + note_position: int = Field(0, description="笔记位置(整数)") + note_depth: int = Field(0, description="笔记深度(整数)") + note_role: int = Field(0, description="笔记角色(整数,0=用户,1=助手)") + extensions: Dict[str, Any] = Field(default_factory=dict, description="扩展信息,如LittleWhiteBox等") + timedWorldInfo: Dict[str, Any] = Field(default_factory=dict, description="定时世界信息") + variables: Dict[str, Any] = Field(default_factory=dict, description="变量字典") + tainted: bool = Field(False, description="是否被污染") + lastInContextMessageId: int = Field(-1, description="上下文中最后一条消息的ID") + + +class ChatFile(BaseModel): + """聊天文件类,包含元数据和消息列表""" + user_name: str = Field("User", description="用户名") + character_name: str = Field("Assistant", description="角色名") + create_date: str = Field(default_factory=lambda: datetime.now().isoformat(), description="创建日期(ISO 8601格式)") + chat_metadata: ChatMetadata = Field(default_factory=ChatMetadata, description="聊天元数据") + messages: List[Message] = Field(default_factory=list, description="消息列表") + + class Config: + arbitrary_types_allowed = True + + +def load_chat_file_data(chat_name: str, role_name: str, base_path: Path = None) -> Dict[str, Any]: + """ + 从文件系统加载聊天原始数据 + + 参数: + chat_name: 聊天名称 + role_name: 角色名称 + base_path: 基础路径,默认为项目数据目录 + + 返回: + dict: 包含元数据和消息列表的原始数据字典 + """ + # 设置默认基础路径 + if base_path is None: + from backend.core.config import settings + base_path = settings.DATA_PATH / "chat" + + # 构建文件路径 + file_path = base_path / role_name / f"{chat_name}.jsonl" + + # 检查文件是否存在 + if not file_path.exists(): + raise FileNotFoundError(f"聊天文件不存在: {file_path}") + + # 读取文件内容 + result = { + "user_name": "User", + "character_name": role_name, + "create_date": datetime.now().isoformat(), + "chat_metadata": {}, + "messages": [] + } + + with open(file_path, 'r', encoding='utf-8') as f: + for line in f: + try: + # 解析JSON行 + line_data = json.loads(line.strip()) + + # 添加到消息列表 + result["messages"].append(line_data) + except json.JSONDecodeError: + continue + + return result + + +def create_chat_file_from_data(data: Dict[str, Any]) -> ChatFile: + """ + 从原始数据创建ChatFile对象 + + 参数: + data: 包含元数据和消息列表的原始数据字典 + + 返回: + ChatFile: 创建的聊天文件对象 + """ + # 提取元数据 + metadata = data.get("chat_metadata", {}) + chat_metadata = ChatMetadata(**metadata) + + # 处理消息列表 + messages = [] + for msg_data in data.get("messages", []): + # 转换为Message对象 + message = Message( + name=msg_data.get('name', ''), + is_user=msg_data.get('is_user', False), + send_date=msg_data.get('send_date', ''), + mes=msg_data.get('content', ''), + swipes=msg_data.get('swipes', []), + swipe_id=msg_data.get('swipes_id', 0) + ) + messages.append(message) + + # 创建并返回ChatFile对象 + return ChatFile( + user_name=data.get("user_name", "User"), + character_name=data.get("character_name", "Assistant"), + create_date=data.get("create_date", datetime.now().isoformat()), + chat_metadata=chat_metadata, + messages=messages + ) + + +def load_chat_file(chat_name: str, role_name: str, base_path: Path = None) -> ChatFile: + """ + 从文件系统加载聊天数据并创建ChatFile对象 + + 参数: + chat_name: 聊天名称 + role_name: 角色名称 + base_path: 基础路径,默认为项目数据目录 + + 返回: + ChatFile: 加载的聊天文件对象 + """ + # 加载原始数据 + data = load_chat_file_data(chat_name, role_name, base_path) + + # 创建ChatFile对象 + return create_chat_file_from_data(data) diff --git a/backend/tools/__pycache__/__init__.cpython-311.pyc b/backend/tools/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..221097a Binary files /dev/null and b/backend/tools/__pycache__/__init__.cpython-311.pyc differ diff --git a/backend/tools/__pycache__/get_all_role_and_chat.cpython-311.pyc b/backend/tools/__pycache__/get_all_role_and_chat.cpython-311.pyc new file mode 100644 index 0000000..3e701c2 Binary files /dev/null and b/backend/tools/__pycache__/get_all_role_and_chat.cpython-311.pyc differ diff --git a/backend/tools/__pycache__/save_input_to_json.cpython-311.pyc b/backend/tools/__pycache__/save_input_to_json.cpython-311.pyc new file mode 100644 index 0000000..e7aba5e Binary files /dev/null and b/backend/tools/__pycache__/save_input_to_json.cpython-311.pyc differ diff --git a/backend/tools/get_all_role_and_chat.py b/backend/tools/get_all_role_and_chat.py index 48aec80..5fc805d 100644 --- a/backend/tools/get_all_role_and_chat.py +++ b/backend/tools/get_all_role_and_chat.py @@ -9,7 +9,7 @@ def get_all_role_and_chat() -> Dict[str, List[str]]: 读取配置目录下的所有子文件夹,并收集每个子文件夹中的 JSONL 文件 返回: - dict: 字典结构,键是文件夹名称,值是该文件夹中的 JSONL 文件列表 + dict: 字典结构,键是文件夹名称,值是该文件夹中的 JSONL 文件列表(仅文件名,无路径和后缀) """ result = {} @@ -33,7 +33,8 @@ def get_all_role_and_chat() -> Dict[str, List[str]]: # 遍历子文件夹中的所有文件 for file in entry.iterdir(): if file.is_file() and file.suffix == '.jsonl': - jsonl_files.append(str(file)) + # 使用 file.stem 获取不带后缀的文件名 + jsonl_files.append(file.stem) print(f" 找到文件: {file.name}") # 调试信息 # 如果该文件夹中有 JSONL 文件,则添加到结果中 diff --git a/data/chat/test/12312.jsonl b/data/chat/test/12331212.jsonl similarity index 100% rename from data/chat/test/12312.jsonl rename to data/chat/test/12331212.jsonl diff --git a/frontend-react/src/components/RoleSelector/RoleSelector.css b/frontend-react/src/components/RoleSelector/RoleSelector.css index e39d7bc..520e3ed 100644 --- a/frontend-react/src/components/RoleSelector/RoleSelector.css +++ b/frontend-react/src/components/RoleSelector/RoleSelector.css @@ -1,95 +1,419 @@ -/* ==================== 下拉框样式 ==================== */ +/* ==================== 角色选择器容器 ==================== */ -/* 角色选择器容器 */ .role-selector { width: 100%; + height: 100%; } -/* 下拉框容器 */ -.dropdown-container { - position: relative; +/* ==================== 角色面板样式 ==================== */ + +.role-panel { width: 100%; - max-width: 300px; + height: 100%; } -/* 下拉框触发器 */ -.dropdown-trigger { +.panel-controls { display: flex; justify-content: space-between; align-items: center; - padding: 8px 12px; - background-color: #f5f5f5; - border: 1px solid #ddd; - border-radius: 4px; + margin-bottom: 20px; +} + +.search-box { + flex: 1; + margin-right: 16px; +} + +.search-box input { + width: 100%; + padding: 10px 16px; + border: 1px solid #e0e0e0; + border-radius: 6px; + font-size: 14px; + transition: border-color 0.2s ease; +} + +.search-box input:focus { + outline: none; + border-color: #1890ff; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1); +} + +.add-button { + display: flex; + align-items: center; + gap: 6px; + padding: 10px 20px; + background-color: #1890ff; + color: white; + border: none; + border-radius: 6px; cursor: pointer; font-size: 14px; + transition: all 0.2s ease; } -.dropdown-arrow { - margin-left: 8px; - font-size: 12px; +.add-button:hover { + background-color: #40a9ff; + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(24, 144, 255, 0.2); } -/* 下拉菜单 */ -.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; +.add-button .icon { + font-size: 16px; + font-weight: bold; } -/* 下拉菜单项 */ -.dropdown-item { - padding: 8px 12px; +.panel-list-container { + display: flex; + flex-direction: column; + height: calc(100% - 80px); +} + +.loading-container { + display: flex; + align-items: center; + justify-content: center; + padding: 40px; + gap: 12px; + color: #666; +} + +.loading-spinner { + display: inline-block; + width: 20px; + height: 20px; + border: 2px solid rgba(24, 144, 255, 0.2); + border-radius: 50%; + border-top-color: #1890ff; + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.empty-state { + display: flex; + align-items: center; + justify-content: center; + padding: 40px; + color: #999; +} + +/* ==================== 角色卡片网格布局 ==================== */ + +.role-cards-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; +} + +/* ==================== 角色卡片样式 ==================== */ + +.role-card { + border-radius: 8px; cursor: pointer; position: relative; - font-size: 14px; + transition: all 0.2s ease; + background-color: #fff; + border: 1px solid #e0e0e0; + display: flex; + flex-direction: column; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); } -.dropdown-item:hover { +.role-card:hover { background-color: #f5f5f5; + border-color: #1890ff; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); } -.dropdown-item.selected { +.role-card.selected { + background-color: #e6f7ff; + border-color: #1890ff; + box-shadow: 0 4px 12px rgba(24, 144, 255, 0.2); +} + +.role-card-header { + padding: 16px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #f0f0f0; +} + +.role-name { + font-size: 15px; + color: #333; + font-weight: 500; +} + +.role-card.selected .role-name { + color: #1890ff; +} + +.role-chat-count { + font-size: 12px; + color: #999; + background-color: #f0f0f0; + padding: 4px 10px; + border-radius: 12px; +} + +.role-card-actions { + display: flex; + justify-content: flex-end; + padding: 8px 12px; + opacity: 0; + transition: opacity 0.2s ease; +} + +.role-card:hover .role-card-actions, +.role-card.clicked .role-card-actions { + opacity: 1; +} + +.action-button { + width: 28px; + height: 28px; + border-radius: 4px; + border: none; + background-color: #f5f5f5; + color: #666; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + transition: all 0.2s ease; +} + +.action-button:hover { background-color: #e6f7ff; color: #1890ff; } -/* 嵌套下拉框 */ -.nested-dropdown { - position: absolute; - left: 100%; - top: 0; - width: 100%; - background-color: white; - border: 1px solid #ddd; +.edit-button:hover { + background-color: #e6f7ff; + color: #1890ff; +} + +.delete-button:hover { + background-color: #fff1f0; + color: #ff4d4f; +} + +.role-card-header input { + padding: 6px 10px; + border: 1px solid #d9d9d9; border-radius: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - z-index: 11; - max-height: 200px; + font-size: 14px; + width: 150px; +} + +.role-card-header input:focus { + outline: none; + border-color: #1890ff; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1); +} + +/* ==================== 聊天列表样式 ==================== */ + +.chat-list { + margin-top: 0; + border-top: 1px solid #e0e0e0; + padding: 12px; + background-color: #fafafa; + animation: slideDown 0.2s ease; + max-height: 300px; overflow-y: auto; } -.nested-dropdown .dropdown-item { - padding-left: 20px; +.chat-list-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 0; + border-bottom: 1px solid #e0e0e0; + margin-bottom: 8px; } -/* 工具栏下拉框容器 */ -.toolbar-dropdown { - margin-bottom: 15px; -} - -.toolbar-dropdown label { - display: block; - margin-bottom: 5px; - font-size: 14px; +.chat-list-header span { + font-size: 13px; + font-weight: 500; color: #333; } + +.add-chat-button { + display: flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + background-color: #1890ff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + transition: all 0.2s ease; +} + +.add-chat-button:hover { + background-color: #40a9ff; +} + +.add-chat-button .icon { + font-size: 14px; + font-weight: bold; +} + +.chat-item { + padding: 10px 12px; + cursor: pointer; + transition: background-color 0.2s ease; + display: flex; + justify-content: space-between; + align-items: center; + border-radius: 4px; + margin-bottom: 4px; +} + +.chat-item:hover { + background-color: #f0f0f0; +} + +.chat-item.selected { + background-color: #e6f7ff; + color: #1890ff; +} + +.chat-name { + font-size: 13px; + color: #333; +} + +.chat-item.selected .chat-name { + color: #1890ff; + font-weight: 500; +} + +.chat-actions { + display: flex; + gap: 6px; + opacity: 0; + transition: opacity 0.2s ease; +} + +.chat-item:hover .chat-actions { + opacity: 1; +} + +.chat-content input { + padding: 6px 10px; + border: 1px solid #d9d9d9; + border-radius: 4px; + font-size: 13px; + width: 180px; +} + +.chat-content input:focus { + outline: none; + border-color: #1890ff; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1); +} + +/* ==================== 模态框样式 ==================== */ + +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 2000; + display: flex; + align-items: center; + justify-content: center; + animation: fadeIn 0.2s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.modal-content { + background-color: #fff; + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); + width: 90%; + max-width: 400px; + padding: 24px; + animation: slideDown 0.3s ease; +} + +@keyframes slideDown { + from { + transform: translateY(-20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.modal-content h3 { + margin: 0 0 16px 0; + font-size: 16px; + font-weight: 500; + color: #333; +} + +.modal-content p { + margin: 0 0 24px 0; + font-size: 14px; + color: #666; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 12px; +} + +.modal-button { + padding: 8px 16px; + border-radius: 4px; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; + border: none; +} + +.cancel-button { + background-color: #f5f5f5; + color: #666; +} + +.cancel-button:hover { + background-color: #e6e6e6; + color: #333; +} + +.confirm-button { + background-color: #ff4d4f; + color: white; +} + +.confirm-button:hover { + background-color: #ff7875; +} diff --git a/frontend-react/src/components/RoleSelector/RoleSelector.jsx b/frontend-react/src/components/RoleSelector/RoleSelector.jsx index a520d6b..9ad3ceb 100644 --- a/frontend-react/src/components/RoleSelector/RoleSelector.jsx +++ b/frontend-react/src/components/RoleSelector/RoleSelector.jsx @@ -1,94 +1,409 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import './RoleSelector.css'; const RoleSelector = ({ onRoleChange, onChatChange }) => { - const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [roleData, setRoleData] = useState({}); const [selectedRole, setSelectedRole] = useState(null); const [selectedChat, setSelectedChat] = useState(null); + const [hoveredRole, setHoveredRole] = useState(null); + const [clickedRole, setClickedRole] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + const [editingRole, setEditingRole] = useState(null); + const [editingChat, setEditingChat] = useState(null); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(null); + const [deleteType, setDeleteType] = useState(null); + const panelRef = useRef(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 fetchRoleData = async () => { + setIsLoading(true); + 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 data = await response.json(); + setRoleData(data); + } catch (error) { + console.error('获取角色数据失败:', error); + } finally { + setIsLoading(false); + } + }; - const data = await response.json(); - console.log('获取到的数据:', data); - setRoleData(data); - } catch (error) { - console.error('获取角色数据失败:', error); + // 组件挂载时获取数据 + useEffect(() => { + fetchRoleData(); + }, []); + + // 点击外部关闭面板 + useEffect(() => { + const handleClickOutside = (event) => { + if (panelRef.current && !panelRef.current.contains(event.target)) { + setHoveredRole(null); + setClickedRole(null); + setEditingRole(null); + setEditingChat(null); + setShowDeleteConfirm(null); } }; - fetchRoleData(); + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; }, []); // 处理角色选择 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); + setClickedRole(null); if (onChatChange) { onChatChange(selectedRole, chat); } }; + // 处理角色卡片点击 + const handleRoleCardClick = (role) => { + if (clickedRole === role) { + setClickedRole(null); + } else { + setClickedRole(role); + handleRoleSelect(role); + } + }; + + // 处理搜索 + const handleSearchChange = (e) => { + setSearchTerm(e.target.value); + }; + + // 处理角色编辑 + const handleEditRole = (e, role) => { + e.stopPropagation(); + setEditingRole(role); + }; + + // 处理角色重命名 + const handleRenameRole = (e, oldName) => { + e.stopPropagation(); + const newName = e.target.value; + if (newName && newName !== oldName) { + const newRoleData = { ...roleData }; + const chats = newRoleData[oldName]; + delete newRoleData[oldName]; + newRoleData[newName] = chats; + setRoleData(newRoleData); + + if (selectedRole === oldName) { + setSelectedRole(newName); + if (onRoleChange) { + onRoleChange(newName); + } + } + } + setEditingRole(null); + }; + + // 处理聊天编辑 + const handleEditChat = (e, chat) => { + e.stopPropagation(); + setEditingChat(chat); + }; + + // 处理聊天重命名 + const handleRenameChat = (e, oldName) => { + e.stopPropagation(); + const newName = e.target.value; + if (newName && newName !== oldName) { + const newRoleData = { ...roleData }; + const chatIndex = newRoleData[selectedRole].indexOf(oldName); + if (chatIndex !== -1) { + newRoleData[selectedRole][chatIndex] = newName; + setRoleData(newRoleData); + + if (selectedChat === oldName) { + setSelectedChat(newName); + if (onChatChange) { + onChatChange(selectedRole, newName); + } + } + } + } + setEditingChat(null); + }; + + // 处理删除确认 + const handleDeleteClick = (e, type, name) => { + e.stopPropagation(); + setDeleteType(type); + setShowDeleteConfirm(name); + }; + + // 确认删除 + const confirmDelete = () => { + if (deleteType === 'role') { + const newRoleData = { ...roleData }; + delete newRoleData[showDeleteConfirm]; + setRoleData(newRoleData); + + if (selectedRole === showDeleteConfirm) { + setSelectedRole(null); + setSelectedChat(null); + if (onRoleChange) { + onRoleChange(null); + } + } + } else if (deleteType === 'chat') { + const newRoleData = { ...roleData }; + const chatIndex = newRoleData[selectedRole].indexOf(showDeleteConfirm); + if (chatIndex !== -1) { + newRoleData[selectedRole].splice(chatIndex, 1); + setRoleData(newRoleData); + + if (selectedChat === showDeleteConfirm) { + setSelectedChat(null); + if (onChatChange) { + onChatChange(selectedRole, null); + } + } + } + } + setShowDeleteConfirm(null); + setDeleteType(null); + }; + + // 取消删除 + const cancelDelete = () => { + setShowDeleteConfirm(null); + setDeleteType(null); + }; + + // 处理新增角色 + const handleAddRole = () => { + const newRole = '新角色'; + const newRoleData = { ...roleData }; + newRoleData[newRole] = []; + setRoleData(newRoleData); + setEditingRole(newRole); + }; + + // 处理新增聊天 + const handleAddChat = () => { + if (!selectedRole) return; + + const newChat = '新聊天'; + const newRoleData = { ...roleData }; + newRoleData[selectedRole].push(newChat); + setRoleData(newRoleData); + setEditingChat(newChat); + }; + + // 过滤角色 + const filteredRoles = Object.keys(roleData).filter(role => + role.toLowerCase().includes(searchTerm.toLowerCase()) + ); + return ( -
没有找到匹配的角色
+确定要删除{deleteType === 'role' ? '角色' : '聊天'} "{showDeleteConfirm}" 吗?
+没有找到匹配的角色
-确定要删除{deleteType === 'role' ? '角色' : '聊天'} "{showDeleteConfirm}" 吗?
-设置内容...
@@ -536,6 +124,9 @@ const Toolbar = ({帮助内容...
diff --git a/frontend/__pycache__/app.cpython-39.pyc b/frontend/__pycache__/app.cpython-39.pyc new file mode 100644 index 0000000..8481713 Binary files /dev/null and b/frontend/__pycache__/app.cpython-39.pyc differ