// frontend-react/src/components/ChatBox/ChatBox.jsx import React, { useEffect, useRef } from 'react'; import useChatBoxStore from '../../../Store/Mid/ChatBoxSlice'; import useChatBoxUIStore from '../../../Store/Mid/ChatBoxUISlice'; // ✅ 新增 import MarkdownRenderer from '../../shared/MarkdownRenderer'; import './ChatBox.css'; const ChatBox = () => { const messagesEndRef = useRef(null); const optionsRef = useRef(null); const chatSelectorRef = useRef(null); // ✅ 从 ChatBoxUIStore 获取 UI 状态 const { editingId, editContent, inputValue, showOptions, showChatSelector, characterChats, currentSwipeId, inputHeight, startEditing, cancelEditing, updateEditContent, setInputValue, clearInput, toggleOptions, setShowOptions, toggleChatSelector, setShowChatSelector, setCharacterChats, setCurrentSwipeId, setInputHeight } = useChatBoxUIStore(); // 从 ChatBoxStore 获取状态和方法 const { messages, isLoading, error, userName, characterName, updateMessage, isGenerating, sendMessage, stopGeneration, options, toggleOption, cycleRenderMode // ✅ 新增:切换渲染模式 } = useChatBoxStore(); // 点击外部关闭选项面板 useEffect(() => { const handleClickOutside = (event) => { if (optionsRef.current && !optionsRef.current.contains(event.target) && !event.target.closest('.options-button')) { setShowOptions(false); } }; if (showOptions) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [showOptions]); // 点击外部关闭聊天选择器 useEffect(() => { const handleClickOutside = (event) => { if (chatSelectorRef.current && !chatSelectorRef.current.contains(event.target) && !event.target.closest('.chat-selector-button')) { setShowChatSelector(false); } }; if (showChatSelector) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [showChatSelector]); const handleInputHeight = (e) => { const textarea = e.target; // 标准方案:先重置为 auto,再设置为 scrollHeight // 这是业界公认的最佳实践,确保高度计算准确 textarea.style.height = 'auto'; textarea.style.height = textarea.scrollHeight + 'px'; }; // 处理发送或终止 const handleSendOrStop = () => { if (isGenerating) { stopGeneration(); } else { sendMessage(inputValue); clearInput(); // ✅ 使用 store 方法 setInputHeight(42); // ✅ 重置为一行高度 } }; // 处理键盘事件 const handleKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSendOrStop(); } }; // 自动滚动到底部 const scrollToBottom = () => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { scrollToBottom(); }, [messages]); // 处理编辑消息 const handleEdit = (message) => { startEditing(message.floor, message.mes); // ✅ 使用 store 方法 }; // 保存编辑 const handleSaveEdit = (messageId) => { // 调用 store 中的 updateMessage 方法 updateMessage(messageId, editContent); cancelEditing(); // ✅ 使用 store 方法 }; // 取消编辑 const handleCancelEdit = () => { cancelEditing(); // ✅ 使用 store 方法 }; // 新增:处理swipe切换 const handleSwipeChange = (messageId, direction) => { const message = messages.find(m => m.floor === messageId); if (message && message.swipes && message.swipes.length > 0) { const currentIndex = currentSwipeId[messageId] !== undefined ? currentSwipeId[messageId] : message.swipe_id; const newIndex = currentIndex + direction; if (newIndex >= 0 && newIndex < message.swipes.length) { setCurrentSwipeId({ [messageId]: newIndex }); // ✅ 使用 store 方法 } } }; // 切换选项显示 const toggleOptionsPanel = () => { toggleOptions(); // ✅ 使用 store 方法 }; // 打开聊天选择器 const handleOpenChatSelector = async () => { const { currentRole } = useChatBoxStore.getState(); // 如果没有当前角色,提示用户 if (!currentRole) { alert('请先在左侧选择一个角色'); return; } try { // 获取当前角色的聊天列表 const response = await fetch(`/api/chat/${encodeURIComponent(currentRole)}`); if (!response.ok) throw new Error('获取聊天列表失败'); const chats = await response.json(); setCharacterChats(chats); // ✅ 使用 store 方法 // 显示聊天选择器弹窗,让用户手动选择 toggleChatSelector(); // ✅ 使用 store 方法 } catch (error) { console.error('获取聊天列表失败:', error); alert('获取聊天列表失败: ' + error.message); } }; // 选择聊天并加载 const handleSelectChat = async (chatName) => { try { const { setChatBoxRoleAndChat, currentRole } = useChatBoxStore.getState(); // 设置当前角色和聊天,这会触发 ChatBoxStore 的监听器自动加载聊天历史 setChatBoxRoleAndChat(currentRole, chatName); // 关闭选择器 setShowChatSelector(false); // ✅ 使用 store 方法 console.log(`已切换到聊天: ${chatName}`); } catch (error) { console.error('切换聊天失败:', error); alert('切换聊天失败: ' + error.message); } }; // 新建聊天 const handleCreateNewChat = async () => { try { const { currentRole, createChat, setChatBoxRoleAndChat } = useChatBoxStore.getState(); if (!currentRole) { alert('请先选择一个角色'); return; } // 使用当前时间戳作为聊天名称 const chatName = `chat_${Date.now()}`; // 创建新聊天 await createChat(currentRole, chatName); // 切换到新创建的聊天 setChatBoxRoleAndChat(currentRole, chatName); // 关闭选择器 setShowChatSelector(false); console.log(`已创建并切换到新聊天: ${chatName}`); } catch (error) { console.error('创建聊天失败:', error); alert('创建聊天失败: ' + error.message); } }; // 渲染单条消息 const renderMessage = (message, index) => { const isUser = message.is_user; const isEditing = editingId === message.floor; // 判断是否为最新消息 const isLatestMessage = messages.length > 0 && message.floor === messages[messages.length - 1].floor; // 根据消息类型设置显示名称 const displayName = isUser ? userName : characterName; // 确定当前显示的消息内容 let currentMes = message.mes; let hasSwipes = message.swipes && message.swipes.length > 0; let currentSwipeIndex = message.swipe_id; if (hasSwipes) { // 如果有swipes数组 if (currentSwipeId[message.floor] !== undefined) { // 如果用户已经切换过版本,使用用户选择的版本 currentSwipeIndex = currentSwipeId[message.floor]; } else { // 否则使用默认的swipe_id currentSwipeIndex = message.swipe_id; } if (currentSwipeIndex >= 0 && currentSwipeIndex < message.swipes.length) { currentMes = message.swipes[currentSwipeIndex]; } } // ✅ 使用 id 或组合 key 确保唯一性 const uniqueKey = message.id || `${message.floor}-${index}`; return (
暂无聊天记录