From 6fa1fd6e7f030a9538b236e5a7ed89da78162897 Mon Sep 17 00:00:00 2001 From: "shen893514322@126.com" Date: Wed, 1 Apr 2026 12:50:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E8=A1=A5=E8=BE=93=E5=85=A5=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | Bin 30 -> 40 bytes .idea/codeStyles/codeStyleConfig.xml | 5 + backend/core/models/chat_history.py | 21 +- frontend-react/Dockerfile | 2 +- .../src/Store/Slices/ChatBoxSlice.jsx | 207 ++++++++----- .../src/components/ChatBox/ChatBox.jsx | 239 ++++++++------ .../Markdown2Html/Markdown2Html.jsx | 292 +++++++++--------- .../ceaa019ee659f86fc8e2a22217df30f7.jpg | Bin 508714 -> 0 bytes 8 files changed, 450 insertions(+), 316 deletions(-) create mode 100644 .idea/codeStyles/codeStyleConfig.xml diff --git a/.gitignore b/.gitignore index 6c24455e92541753408c1e99512a3df976b922d9..160f93616bd0787cdecb266d0ea02d4377073f73 100644 GIT binary patch delta 15 Wcmb2sm>|cc#h}Mfz);DM%m4rtg#x1h delta 4 LcmdOpn;-`O0z3fq diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/backend/core/models/chat_history.py b/backend/core/models/chat_history.py index 5f96ae9..793e1a5 100644 --- a/backend/core/models/chat_history.py +++ b/backend/core/models/chat_history.py @@ -15,6 +15,14 @@ class Message(BaseModel): description="消息发送时间戳" ) floor: int = Field(0, description="对话楼层数") + swipes: List[str] = Field( + default_factory=list, + description="历史版本列表。用户消息:存编辑过的不同版本。AI消息:存重roll生成的不同版本" + ) + swipe_id: int = Field( + 0, + description="当前指针。指示当前显示的是 swipes 数组中的第几个(从 0 开始)" + ) mes: str = Field(..., description="消息内容文本") extra: Dict[str, Any] = Field( default_factory=dict, @@ -211,17 +219,26 @@ class ChatHistory(BaseModel): "name": str, "is_user": bool, "floor": int, - "mes": str + "mes": str, + "swipes": List[str], + "swipe_id": int } """ # 创建消息字典列表 messages_list = [] for msg in self.messages: + # 获取当前消息内容:优先从swipes数组中获取,如果不存在则使用mes + current_mes = msg.mes + if msg.swipes and 0 <= msg.swipe_id < len(msg.swipes): + current_mes = msg.swipes[msg.swipe_id] + msg_dict = { "name": msg.name, "is_user": msg.is_user, "floor": msg.floor, - "mes": msg.mes + "mes": current_mes, + "swipes": msg.swipes, + "swipe_id": msg.swipe_id } messages_list.append(msg_dict) diff --git a/frontend-react/Dockerfile b/frontend-react/Dockerfile index 2e2ffb2..2402881 100644 --- a/frontend-react/Dockerfile +++ b/frontend-react/Dockerfile @@ -1,6 +1,6 @@ # 使用 Node.js 18 Alpine 镜像作为基础 # 必须明确指定 node 版本,否则可能默认为空或 python 镜像 -FROM node:18-alpine +FROM node:20-alpine # 设置工作目录 WORKDIR /app diff --git a/frontend-react/src/Store/Slices/ChatBoxSlice.jsx b/frontend-react/src/Store/Slices/ChatBoxSlice.jsx index 578acab..ca1337a 100644 --- a/frontend-react/src/Store/Slices/ChatBoxSlice.jsx +++ b/frontend-react/src/Store/Slices/ChatBoxSlice.jsx @@ -3,88 +3,147 @@ import { subscribeWithSelector } from 'zustand/middleware'; const useChatBoxStore = create( subscribeWithSelector((set, get) => ({ - // 聊天历史消息列表 - messages: [], + // 聊天历史消息列表 + messages: [], - // 用户名称 - userName: '', + // 用户名称 + userName: '', - // 角色名称 - characterName: '', + // 角色名称 + characterName: '', - // 当前选中的角色 - currentRole: null, + // 当前选中的角色 + currentRole: null, - // 当前选中的聊天 - currentChat: null, + // 当前选中的聊天 + currentChat: null, - // 是否正在加载 - isLoading: false, + // 是否正在加载 + isLoading: false, - // 错误信息 - error: null, + // 是否正在生成 + isGenerating: false, - // 设置消息列表 - setMessages: (messages) => set({ messages }), + // 错误信息 + error: null, - // 设置用户名称 - setUserName: (userName) => set({ userName }), + // 设置消息列表 + setMessages: (messages) => set({ messages }), - // 设置角色名称 - setCharacterName: (characterName) => set({ characterName }), + // 设置用户名称 + setUserName: (userName) => set({ userName }), - // 设置当前角色 - setCurrentRole: (role) => set({ currentRole: role }), + // 设置角色名称 + setCharacterName: (characterName) => set({ characterName }), - // 设置当前聊天 - setCurrentChat: (chat) => set({ currentChat: chat }), + // 设置当前角色 + setCurrentRole: (role) => set({ currentRole: role }), - // 同时设置角色和聊天 - setChatBoxRoleAndChat: (role, chat) => set({ - currentRole: role, - currentChat: chat - }), + // 设置当前聊天 + setCurrentChat: (chat) => set({ currentChat: chat }), - - // 加载聊天历史 - fetchChatHistory: async (roleName, chatName) => { - set({ isLoading: true, error: null }); - try { - const response = await fetch(`/api/chat_box/get_chat_history?role_name=${encodeURIComponent(roleName)}&chat_name=${encodeURIComponent(chatName)}`); - if (!response.ok) { - throw new Error('Failed to fetch chat history'); - } - const data = await response.json(); - // 修改数据处理逻辑,适配API返回的数据结构 - set({ - messages: data || [], // 直接使用返回的数组 - userName: 'User', // 固定用户名 - characterName: roleName || 'Assistant', // 使用角色名作为角色名称 - isLoading: false - }); - } catch (error) { - set({ - error: error.message, - isLoading: false - }); - } + // 同时设置角色和聊天 + setChatBoxRoleAndChat: (role, chat) => { + console.log('[ChatBoxStore] setChatBoxRoleAndChat called with:', { role, chat }); + set({ + currentRole: role, + currentChat: chat + }); }, - // 清空聊天历史 - clearChatHistory: () => set({ - messages: [], - userName: '', - characterName: '', - error: null - }), + // 设置生成状态 + setIsGenerating: (status) => set({ isGenerating: status }), - // 更新特定消息的内容 - updateMessage: (id, content) => set((state) => ({ - messages: state.messages.map((msg) => - msg.id === id ? { ...msg, content } : msg - ) - })), + sendMessage: async (content) => { + const { messages, userName, characterName, currentRole, currentChat } = get(); + + set({ + isGenerating: true, + messages: [...messages, { + id: Date.now(), + floor: messages.length + 1, + mes: content, + is_user: true + }] + }); + + try { + const response = await fetch('/api/chat_box/send_message', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + role_name: currentRole, + chat_name: currentChat, + message: content + }) + }); + + if (!response.ok) { + throw new Error('Failed to send message'); + } + + const data = await response.json(); + set((state) => ({ + messages: [...state.messages, { + id: Date.now(), + floor: state.messages.length + 1, + mes: data.response, + is_user: false + }], + isGenerating: false + })); + } catch (error) { + set({ + error: error.message, + isGenerating: false + }); + } + }, + + // 终止生成 + stopGeneration: () => set({ isGenerating: false }), + // 加载聊天历史 + fetchChatHistory: async (roleName, chatName) => { + set({ isLoading: true, error: null }); + try { + const response = await fetch(`/api/chat_box/get_chat_history?role_name=${encodeURIComponent(roleName)}&chat_name=${encodeURIComponent(chatName)}`); + if (!response.ok) { + throw new Error('Failed to fetch chat history'); + } + const data = await response.json(); + // 修改数据处理逻辑,适配API返回的数据结构 + set({ + messages: data || [], // 直接使用返回的数组 + userName: 'User', // 固定用户名 + characterName: roleName || 'Assistant', // 使用角色名作为角色名称 + isLoading: false + }); + } catch (error) { + set({ + error: error.message, + isLoading: false + }); + } + }, + + + // 清空聊天历史 + clearChatHistory: () => set({ + messages: [], + userName: '', + characterName: '', + error: null + }), + + // 更新特定消息的内容 + updateMessage: (id, content) => set((state) => ({ + messages: state.messages.map((msg) => + msg.id === id ? { ...msg, content } : msg + ) + })), })) ); @@ -92,15 +151,15 @@ const useChatBoxStore = create( useChatBoxStore.subscribe( (state) => ({ role: state.currentRole, chat: state.currentChat }), ({ role, chat }, prev) => { - // 只有当角色或聊天发生变化时才处理 - if (role !== prev.role || chat !== prev.chat) { - // 确保角色和聊天都存在且不为null - if (role && chat) { - useChatBoxStore.getState().fetchChatHistory(role, chat); - } else { - useChatBoxStore.getState().clearChatHistory(); - } - } + // 只有当角色或聊天发生变化时才处理 + if (role !== prev.role || chat !== prev.chat) { + // 确保角色和聊天都存在且不为null + if (role && chat) { + useChatBoxStore.getState().fetchChatHistory(role, chat); + } else { + useChatBoxStore.getState().clearChatHistory(); + } + } }, { equalityFn: (a, b) => a.role === b.role && a.chat === b.chat } ); diff --git a/frontend-react/src/components/ChatBox/ChatBox.jsx b/frontend-react/src/components/ChatBox/ChatBox.jsx index a75240a..4834951 100644 --- a/frontend-react/src/components/ChatBox/ChatBox.jsx +++ b/frontend-react/src/components/ChatBox/ChatBox.jsx @@ -6,128 +6,181 @@ const ChatBox = () => { const [editingId, setEditingId] = useState(null); const [editContent, setEditContent] = useState(''); const messagesEndRef = useRef(null); + const [inputValue, setInputValue] = useState(''); - // 从 ChatBoxStore 获取状态和方法 - const { - messages, - isLoading, - error, - userName, - characterName, - updateMessage - } = useChatBoxStore(); + // 从 ChatBoxStore 获取状态和方法 + const { + messages, + isLoading, + error, + userName, + characterName, + updateMessage, + isGenerating, + sendMessage, + stopGeneration + } = useChatBoxStore(); + + const [inputHeight, setInputHeight] = useState(42); + + // 添加输入框高度自适应处理 + const handleInputHeight = (e) => { + const textarea = e.target; + const newHeight = Math.min(Math.max(textarea.scrollHeight, 42), 300); + setInputHeight(newHeight); + }; + + // 处理发送或终止 + const handleSendOrStop = () => { + if (isGenerating) { + stopGeneration(); + } else { + sendMessage(inputValue); + setInputValue(''); + setInputHeight(42); + } + }; + + // 处理键盘事件 + const handleKeyDown = (e) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendOrStop(); + } + }; // 自动滚动到底部 const scrollToBottom = () => { - messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }; useEffect(() => { - scrollToBottom(); + scrollToBottom(); }, [messages]); // 处理编辑消息 const handleEdit = (message) => { - setEditingId(message.floor); - setEditContent(message.mes); + setEditingId(message.floor); + setEditContent(message.mes); }; // 保存编辑 const handleSaveEdit = (messageId) => { - // 调用 store 中的 updateMessage 方法 - updateMessage(messageId, editContent); - setEditingId(null); - setEditContent(''); + // 调用 store 中的 updateMessage 方法 + updateMessage(messageId, editContent); + setEditingId(null); + setEditContent(''); }; // 取消编辑 const handleCancelEdit = () => { - setEditingId(null); - setEditContent(''); + setEditingId(null); + setEditContent(''); }; // 渲染单条消息 const renderMessage = (message) => { - const isUser = message.is_user; - const isEditing = editingId === message.floor; + const isUser = message.is_user; + const isEditing = editingId === message.floor; - // 根据消息类型设置显示名称 - const displayName = isUser ? userName : characterName; + // 根据消息类型设置显示名称 + const displayName = isUser ? userName : characterName; - return ( -
-
-
- {displayName} - #{message.floor} -
-
- - -
-
-
-
- {isEditing ? ( -
-