Files
SillyTavern_replica/frontend-react/src/components/RoleSelector/RoleSelector.jsx

340 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useRef } from 'react';
import useRoleSelectorStore from '../../store/Slices/RoleSelectorSlice';
import useChatBoxStore from '../../Store/Slices/ChatBoxSlice';
import './RoleSelector.css';
const RoleSelector = () => {
const panelRef = useRef(null);
// 从 Zustand store 中获取状态和操作
const {
roleData,
selectedRole,
selectedChat,
hoveredRole,
clickedRole,
isLoading,
searchTerm,
editingRole,
editingChat,
showDeleteConfirm,
deleteType,
fetchRoleData,
setSelectedRole,
setSelectedChat,
setHoveredRole,
setClickedRole,
setSearchTerm,
setEditingRole,
setEditingChat,
setShowDeleteConfirm,
setDeleteType,
handleRenameRole,
handleRenameChat,
confirmDelete,
cancelDelete,
handleAddRole,
handleAddChat,
resetPanel
} = useRoleSelectorStore();
// 从 ChatBoxStore 获取状态更新方法
const { setCurrentRole, setCurrentChat } = useChatBoxStore();
// 组件挂载时获取数据
useEffect(() => {
fetchRoleData();
}, [fetchRoleData]);
// 点击外部关闭面板
useEffect(() => {
const handleClickOutside = (event) => {
if (panelRef.current && !panelRef.current.contains(event.target)) {
resetPanel();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [resetPanel]);
// 处理角色选择
const handleRoleSelect = (role) => {
setSelectedRole(role);
// 更新 ChatBoxStore 中的当前角色
setCurrentRole(role);
// 如果该角色有聊天记录,默认选择第一个
if (roleData[role] && roleData[role].length > 0) {
const firstChat = roleData[role][0];
setSelectedChat(firstChat);
// 更新 ChatBoxStore 中的当前聊天
setCurrentChat(firstChat);
} else {
setSelectedChat(null);
// 清除 ChatBoxStore 中的当前聊天
setCurrentChat(null);
}
};
// 处理聊天选择
const handleChatSelect = (chat) => {
// 获取当前展开的角色(聊天所属的角色)
const currentRole = hoveredRole || clickedRole;
setSelectedChat(chat);
setSelectedRole(currentRole); // 使用当前展开的角色
setHoveredRole(null);
setClickedRole(null);
// 更新 ChatBoxStore 中的当前聊天和角色
setCurrentChat(chat);
setCurrentRole(currentRole);
};
// 处理角色卡片点击
const handleRoleCardClick = (role) => {
if (clickedRole === role) {
setClickedRole(null);
// 取消选择角色时,更新 selectedRole 和 selectedChat
setSelectedRole(null);
setSelectedChat(null);
// 同步更新 ChatBoxStore 中的状态
setCurrentRole(null);
setCurrentChat(null);
} else {
setClickedRole(role);
handleRoleSelect(role);
setSelectedRole(role);
setSelectedChat(chat);
// 同步更新 ChatBoxStore 中的状态
setSele
}
};
// 处理搜索
const handleSearchChange = (e) => {
setSearchTerm(e.target.value);
};
// 处理角色编辑
const handleEditRole = (e, role) => {
e.stopPropagation();
setEditingRole(role);
};
// 处理角色重命名
const handleRenameRoleWrapper = (e, oldName) => {
e.stopPropagation();
const newName = e.target.value;
handleRenameRole(oldName, newName);
if (selectedRole === oldName && newName && newName !== oldName) {
// 更新 ChatBoxStore 中的当前角色
setCurrentRole(newName);
}
};
// 处理聊天编辑
const handleEditChat = (e, chat) => {
e.stopPropagation();
setEditingChat(chat);
};
// 处理聊天重命名
const handleRenameChatWrapper = (e, oldName) => {
e.stopPropagation();
const newName = e.target.value;
handleRenameChat(oldName, newName);
if (selectedChat === oldName && newName && newName !== oldName) {
// 更新 ChatBoxStore 中的当前聊天
setCurrentChat(selectedRole, newName);
}
};
// 处理删除确认
const handleDeleteClick = (e, type, name) => {
e.stopPropagation();
setDeleteType(type);
setShowDeleteConfirm(name);
};
// 确认删除
const confirmDeleteWrapper = () => {
confirmDelete();
if (deleteType === 'role' && selectedRole === showDeleteConfirm) {
// 清除 ChatBoxStore 中的当前角色和聊天
setCurrentRole(null);
setCurrentChat(null);
} else if (deleteType === 'chat' && selectedChat === showDeleteConfirm) {
// 清除 ChatBoxStore 中的当前聊天
setCurrentChat(null);
}
};
// 过滤角色
const filteredRoles = Object.keys(roleData).filter(role =>
role.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div className="role-selector" ref={panelRef}>
<div className="selected-role-display">
{selectedRole ? (
<div className="role-badge">
<span className="role-label">当前角色/聊天:</span>
<span className="role-name">{selectedRole}</span>
{selectedChat && <span className="chat-name">/ {selectedChat}</span>}
</div>
) : (
<div className="role-badge empty">
<span>未选择角色</span>
</div>
)}
</div>
<div className="search-bar">
<input
type="text"
placeholder="搜索角色..."
value={searchTerm}
onChange={handleSearchChange}
/>
</div>
<div className="role-list">
{isLoading ? (
<div className="loading">加载中...</div>
) : filteredRoles.length === 0 ? (
<div className="empty-state">
<p>没有找到匹配的角色</p>
</div>
) : (
filteredRoles.map(role => (
<div
key={role}
className={`role-item ${selectedRole === role ? 'active' : ''}`}
onClick={() => handleRoleCardClick(role)}
onMouseEnter={() => setHoveredRole(role)}
onMouseLeave={() => setHoveredRole(null)}
>
<div className="role-header">
{editingRole === role ? (
<input
type="text"
defaultValue={role}
autoFocus
onBlur={(e) => handleRenameRoleWrapper(e, role)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleRenameRoleWrapper(e, role);
}
}}
onClick={(e) => e.stopPropagation()}
/>
) : (
<div className="role-name">{role}</div>
)}
<div className="role-actions">
<button
className="icon-btn"
title="编辑"
onClick={(e) => handleEditRole(e, role)}
>
</button>
<button
className="icon-btn delete"
title="删除"
onClick={(e) => handleDeleteClick(e, 'role', role)}
>
🗑
</button>
</div>
</div>
{/* 聊天列表 */}
{(hoveredRole === role || clickedRole === role) && roleData[role] && roleData[role].length > 0 && (
<div className="chat-list">
{roleData[role].map(chat => (
<div
key={chat}
className={`chat-item ${selectedChat === chat ? 'active' : ''}`}
onClick={(e) => {
e.stopPropagation();
handleChatSelect(chat);
}}
>
<div className="chat-content">
{editingChat === chat ? (
<input
type="text"
defaultValue={chat}
autoFocus
onBlur={(e) => handleRenameChatWrapper(e, chat)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleRenameChatWrapper(e, chat);
}
}}
onClick={(e) => e.stopPropagation()}
/>
) : (
<div className="chat-name">{chat}</div>
)}
</div>
<div className="chat-actions">
{editingChat !== chat && (
<>
<button
className="icon-btn"
title="编辑"
onClick={(e) => handleEditChat(e, chat)}
>
</button>
<button
className="icon-btn delete"
title="删除"
onClick={(e) => handleDeleteClick(e, 'chat', chat)}
>
🗑
</button>
</>
)}
</div>
</div>
))}
</div>
)}
</div>
))
)}
</div>
{/* 删除确认对话框 */}
{showDeleteConfirm && (
<div className="delete-confirm-modal">
<div className="modal-content">
<h3>确认删除</h3>
<p>确定要删除{deleteType === 'role' ? '角色' : '聊天'} "{showDeleteConfirm}" </p>
<div className="modal-actions">
<button className="modal-button cancel-button" onClick={cancelDelete}>
取消
</button>
<button className="modal-button danger" onClick={confirmDeleteWrapper}>
确认删除
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default RoleSelector;