完成聊天框功能开发

This commit is contained in:
2026-03-30 20:28:50 +08:00
parent f9bc77d392
commit 80237463ef
14 changed files with 263 additions and 191 deletions

View File

@@ -14,7 +14,6 @@
.chat-messages {
flex: 1;
min-height: 0;
max-height: calc(95vh - 62px); /* 减去输入区域的高度 */
overflow-y: auto;
padding: 20px;
padding-top: 60px;
@@ -427,3 +426,55 @@
margin: 10px 0;
}
/* 确保消息名称和工具栏在移动设备上也能正常显示 */
@media (max-width: 768px) {
.message-container {
max-width: 85%;
}
.message-header {
flex-wrap: wrap;
}
.message-toolbar {
opacity: 1; /* 在移动设备上始终显示工具栏 */
}
}
/* 添加滚动条样式,使其更美观 */
.chat-messages::-webkit-scrollbar {
width: 6px;
}
.chat-messages::-webkit-scrollbar-track {
background: #f1f1f1;
}
.chat-messages::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 确保消息内容中的链接样式 */
.bubble a {
color: inherit;
text-decoration: underline;
}
/* 代码块样式 */
.bubble pre {
background-color: #f6f8fa;
padding: 10px;
border-radius: 6px;
overflow-x: auto;
margin: 8px 0;
}
.bubble code {
font-family: 'Courier New', Courier, monospace;
font-size: 0.9em;
}

View File

@@ -1,156 +1,133 @@
import React, { useState, useRef } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import useChatBoxStore from '../../Store/Slices/ChatBoxSlice';
import './ChatBox.css';
const ChatBox = () => {
const [isHtmlRender, setIsHtmlRender] = useState(false);
const [isImageGen, setIsImageGen] = useState(false);
const [isDynamicTable, setIsDynamicTable] = useState(false);
const [editingId, setEditingId] = useState(null);
const [editContent, setEditContent] = useState('');
const messagesEndRef = useRef(null);
const textareaRef = useRef(null);
// 从 ChatBoxStore 获取状态和方法
const {
messages,
isLoading,
error,
userName,
characterName,
updateMessage
} = useChatBoxStore();
// 从 store 获取状态
const { messages, userName, characterName, isLoading, error } = useChatBoxStore();
// 自动调整 Textarea 高度
const adjustHeight = () => {
const textarea = textareaRef.current;
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';
}
}
// 自动滚动到底部
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
const handleInput = () => {
adjustHeight();
useEffect(() => {
scrollToBottom();
}, [messages]);
// 处理编辑消息
const handleEdit = (message) => {
setEditingId(message.floor);
setEditContent(message.mes);
};
// 开始编辑消息
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;
}
// 退出编辑模式
// 保存编辑
const handleSaveEdit = (messageId) => {
// 调用 store 中的 updateMessage 方法
updateMessage(messageId, editContent);
setEditingId(null);
setEditContent('');
};
// 取消编辑
const cancelEdit = () => {
const handleCancelEdit = () => {
setEditingId(null);
setEditContent('');
};
return (
<div className="chat-box">
{/* 上方:消息列表区域 */}
<div className="chat-messages">
{/* 加载状态和错误信息 */}
{isLoading && <div className="loading">加载中...</div>}
{error && <div className="error">{error}</div>}
// 渲染单条消息
const renderMessage = (message) => {
const isUser = message.is_user;
const isEditing = editingId === message.floor;
{/* 消息列表 */}
{messages.map((msg) => (
<div key={msg.id} className={`message ${msg.role}`}>
<div className="message-container">
{/* 消息名称和工具栏在同一行 */}
<div className="message-header">
<div className="message-name">{msg.role === 'user' ? userName : characterName}</div>
// 根据消息类型设置显示名称
const displayName = isUser ? userName : characterName;
{/* 消息工具栏 */}
<div className="message-toolbar">
<span className="message-id">ID: {msg.id}</span>
<div className="toolbar-buttons">
<button
className="toolbar-button edit-button"
onClick={() => startEdit(msg.id, msg.content)}
>
编辑
</button>
<button className="toolbar-button expand-button">
</button>
</div>
</div>
</div>
{/* 消息内容 */}
<div className="message-content">
<div className="bubble">
{editingId === msg.id ? (
<div className="edit-container">
<textarea
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
className="edit-textarea"
/>
<div className="edit-buttons">
<button
className="save-button"
onClick={() => saveEdit(msg.id)}
>
保存
</button>
<button
className="cancel-button"
onClick={cancelEdit}
>
取消
</button>
</div>
</div>
) : (
isHtmlRender ? (
<div dangerouslySetInnerHTML={{ __html: msg.content }} />
) : (
<div>{msg.content}</div>
)
)}
</div>
return (
<div key={message.floor} className={`message ${isUser ? 'user' : 'ai'}`}>
<div className="message-container">
<div className="message-header">
<span className="message-name">{displayName}</span>
<span className="message-id">#{message.floor}</span>
<div className="message-toolbar">
<div className="toolbar-buttons">
<button
className="toolbar-button"
onClick={() => handleEdit(message)}
title="编辑"
>
</button>
<button
className="toolbar-button"
title="更多"
>
</button>
</div>
</div>
</div>
))}
</div>
{/* 下方:输入框区域 */}
<div className="chat-input-wrapper">
<div className="chat-input-area">
<textarea
ref={textareaRef}
placeholder="输入消息..."
onInput={handleInput}
rows="1"
style={{ height: '42px' }}
/>
<div className="message-content">
{isEditing ? (
<div className="edit-container">
<textarea
className="edit-textarea"
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
/>
<div className="edit-buttons">
<button
className="cancel-button"
onClick={handleCancelEdit}
>
取消
</button>
<button
className="save-button"
onClick={() => handleSaveEdit(message.floor)}
>
保存
</button>
</div>
</div>
) : (
<div className="bubble">
{message.mes}
</div>
)}
</div>
</div>
<button className="send-button">发送</button>
</div>
);
};
return (
<div className="chat-box">
<div className="chat-messages">
{isLoading ? (
<div className="loading">加载中...</div>
) : error ? (
<div className="error">{error}</div>
) : messages.length === 0 ? (
<div className="loading">暂无消息</div>
) : (
messages.map(renderMessage)
)}
<div ref={messagesEndRef} />
</div>
</div>
);
};
export default ChatBox;
export default ChatBox;

View File

@@ -1,6 +1,7 @@
import React, { useEffect, useRef } from 'react';
import useRoleSelectorStore from '../../store/Slices/RoleSelectorSlice';
import useChatBoxStore from '../../Store/Slices/ChatBoxSlice';
import './RoleSelector.css';
const RoleSelector = () => {
@@ -22,6 +23,7 @@ const RoleSelector = () => {
fetchRoleData,
setSelectedRole,
setSelectedChat,
setSelectedRoleAndChat,
setHoveredRole,
setClickedRole,
setSearchTerm,
@@ -39,7 +41,10 @@ const RoleSelector = () => {
} = useRoleSelectorStore();
// 从 ChatBoxStore 获取状态更新方法
const { setCurrentRole, setCurrentChat } = useChatBoxStore();
const chatBoxStore = useChatBoxStore();
const { setCurrentRole, setCurrentChat } = chatBoxStore;
const setChatBoxRoleAndChat = chatBoxStore.setChatBoxRoleAndChat;
// 组件挂载时获取数据
useEffect(() => {
@@ -62,57 +67,47 @@ const RoleSelector = () => {
// 处理角色选择
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);
// 使用新的原子操作方法同时更新角色和聊天
setSelectedRoleAndChat(role, firstChat);
// 同步更新 ChatBoxStore 中的状态
setChatBoxRoleAndChat(role, firstChat);
} else {
setSelectedChat(null);
// 清除 ChatBoxStore 中的当前聊天
setCurrentChat(null);
// 清空角色和聊天
setSelectedRoleAndChat(null, null);
// 同步更新 ChatBoxStore 中的状态
setChatBoxRoleAndChat(null, null);
}
};
// 处理聊天选择
const handleChatSelect = (chat) => {
// 获取当前展开的角色(聊天所属的角色)
const currentRole = hoveredRole || clickedRole;
// 获取当前展开的角色(聊天所属的角色)
const currentRole = hoveredRole || clickedRole;
setSelectedChat(chat);
setSelectedRole(currentRole); // 使用当前展开的角色
setHoveredRole(null);
setClickedRole(null);
// 更新 ChatBoxStore 中的当前聊天和角色
setCurrentChat(chat);
setCurrentRole(currentRole);
// 使用新的原子操作方法同时更新角色和聊天
setSelectedRoleAndChat(currentRole, chat);
// 同步更新 ChatBoxStore 中的状态
setChatBoxRoleAndChat(currentRole, chat);
setHoveredRole(null);
setClickedRole(null);
};
// 处理角色卡片点击
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 handleRoleCardClick = (role) => {
if (clickedRole === role) {
setClickedRole(null);
// 取消选择角色时,更新 selectedRole 和 selectedChat
setSelectedRoleAndChat(null, null);
// 同步更新 ChatBoxStore 中的状态
setChatBoxRoleAndChat(null, null);
} else {
setClickedRole(role);
handleRoleSelect(role);
}
};
// 处理搜索
const handleSearchChange = (e) => {
@@ -165,11 +160,10 @@ const RoleSelector = () => {
confirmDelete();
if (deleteType === 'role' && selectedRole === showDeleteConfirm) {
// 清除 ChatBoxStore 中的当前角色和聊天
setCurrentRole(null);
setCurrentChat(null);
setChatBoxRoleAndChat(null, null);
} else if (deleteType === 'chat' && selectedChat === showDeleteConfirm) {
// 清除 ChatBoxStore 中的当前聊天
setCurrentChat(null);
setChatBoxRoleAndChat(selectedRole, null);
}
};