From f422a0588b3fbe120ed06cc40c84d0805dc92eb7 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Mon, 2 Jun 2025 23:56:58 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20fix(playground):=20resolve=20mes?= =?UTF-8?q?sage=20state=20issues=20after=20page=20refresh=20and=20config?= =?UTF-8?q?=20reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem 1: Chat interface not refreshing when resetting imported messages** - The "reset messages simultaneously" option during config import failed to update the chat interface properly - Normal conversation resets worked correctly **Problem 2: Messages stuck in loading state after page refresh** - When AI was generating a response and user refreshed the page, the message remained in loading state indefinitely - Stop button had no effect on these orphaned loading messages **Changes Made:** 1. **Fixed config reset message refresh** (`usePlaygroundState.js`): ```javascript // Clear messages first, then set defaults to force component re-render setMessage([]); setTimeout(() => { setMessage(DEFAULT_MESSAGES); }, 0); ``` 2. **Enhanced stop generator functionality** (`useApiRequest.js`): ```javascript // Handle orphaned loading messages even without active SSE connection const onStopGenerator = useCallback(() => { // Close active SSE if exists if (sseSourceRef.current) { sseSourceRef.current.close(); sseSourceRef.current = null; } // Always attempt to complete any loading/incomplete messages // ... processing logic }, [setMessage, applyAutoCollapseLogic, saveMessages]); ``` 3. **Added automatic message state recovery** (`usePlaygroundState.js`): ```javascript // Auto-fix loading/incomplete messages on page load useEffect(() => { const lastMsg = message[message.length - 1]; if (lastMsg.status === MESSAGE_STATUS.LOADING || lastMsg.status === MESSAGE_STATUS.INCOMPLETE) { // Process incomplete content and mark as complete // Save corrected message state } }, []); ``` **Root Cause:** - Config reset: Direct state assignment didn't trigger component refresh - Loading state: No recovery mechanism for interrupted SSE connections after refresh **Impact:** - ✅ Config reset now properly refreshes chat interface - ✅ Stop button works on orphaned loading messages - ✅ Page refresh automatically recovers incomplete messages - ✅ No more permanently stuck loading states --- web/src/hooks/useApiRequest.js | 66 +++++++++++++++-------------- web/src/hooks/usePlaygroundState.js | 30 ++++++++++++- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/web/src/hooks/useApiRequest.js b/web/src/hooks/useApiRequest.js index bbe49eac..55688726 100644 --- a/web/src/hooks/useApiRequest.js +++ b/web/src/hooks/useApiRequest.js @@ -348,41 +348,45 @@ export const useApiRequest = ( // 停止生成 const onStopGenerator = useCallback(() => { + // 如果仍有活动的 SSE 连接,首先关闭 if (sseSourceRef.current) { sseSourceRef.current.close(); sseSourceRef.current = null; - - setMessage(prevMessage => { - const lastMessage = prevMessage[prevMessage.length - 1]; - if (lastMessage.status === MESSAGE_STATUS.LOADING || - lastMessage.status === MESSAGE_STATUS.INCOMPLETE) { - - const processed = processIncompleteThinkTags( - lastMessage.content || '', - lastMessage.reasoningContent || '' - ); - - const autoCollapseState = applyAutoCollapseLogic(lastMessage, true); - - const updatedMessages = [ - ...prevMessage.slice(0, -1), - { - ...lastMessage, - status: MESSAGE_STATUS.COMPLETE, - reasoningContent: processed.reasoningContent || null, - content: processed.content, - ...autoCollapseState, - } - ]; - - // 停止生成时也保存,传入更新后的消息列表 - setTimeout(() => saveMessages(updatedMessages), 0); - - return updatedMessages; - } - return prevMessage; - }); } + + // 无论是否存在 SSE 连接,都尝试处理最后一条正在生成的消息 + setMessage(prevMessage => { + if (prevMessage.length === 0) return prevMessage; + const lastMessage = prevMessage[prevMessage.length - 1]; + + if (lastMessage.status === MESSAGE_STATUS.LOADING || + lastMessage.status === MESSAGE_STATUS.INCOMPLETE) { + + const processed = processIncompleteThinkTags( + lastMessage.content || '', + lastMessage.reasoningContent || '' + ); + + const autoCollapseState = applyAutoCollapseLogic(lastMessage, true); + + const updatedMessages = [ + ...prevMessage.slice(0, -1), + { + ...lastMessage, + status: MESSAGE_STATUS.COMPLETE, + reasoningContent: processed.reasoningContent || null, + content: processed.content, + ...autoCollapseState, + } + ]; + + // 停止生成时也保存,传入更新后的消息列表 + setTimeout(() => saveMessages(updatedMessages), 0); + + return updatedMessages; + } + return prevMessage; + }); }, [setMessage, applyAutoCollapseLogic, saveMessages]); // 发送请求 diff --git a/web/src/hooks/usePlaygroundState.js b/web/src/hooks/usePlaygroundState.js index 39e68902..9e76aa2e 100644 --- a/web/src/hooks/usePlaygroundState.js +++ b/web/src/hooks/usePlaygroundState.js @@ -1,6 +1,7 @@ import { useState, useCallback, useRef, useEffect } from 'react'; -import { DEFAULT_MESSAGES, DEFAULT_CONFIG, DEBUG_TABS } from '../utils/constants'; +import { DEFAULT_MESSAGES, DEFAULT_CONFIG, DEBUG_TABS, MESSAGE_STATUS } from '../utils/constants'; import { loadConfig, saveConfig, loadMessages, saveMessages } from '../components/playground/configStorage'; +import { processIncompleteThinkTags } from '../utils/messageUtils'; export const usePlaygroundState = () => { // 使用惰性初始化,确保只在组件首次挂载时加载配置和消息 @@ -138,6 +139,33 @@ export const usePlaygroundState = () => { }; }, []); + // 页面首次加载时,若最后一条消息仍处于 LOADING/INCOMPLETE 状态,自动修复 + useEffect(() => { + if (!Array.isArray(message) || message.length === 0) return; + + const lastMsg = message[message.length - 1]; + if (lastMsg.status === MESSAGE_STATUS.LOADING || lastMsg.status === MESSAGE_STATUS.INCOMPLETE) { + const processed = processIncompleteThinkTags( + lastMsg.content || '', + lastMsg.reasoningContent || '' + ); + + const fixedLastMsg = { + ...lastMsg, + status: MESSAGE_STATUS.COMPLETE, + content: processed.content, + reasoningContent: processed.reasoningContent || null, + isThinkingComplete: true, + }; + + const updatedMessages = [...message.slice(0, -1), fixedLastMsg]; + setMessage(updatedMessages); + + // 保存修复后的消息列表 + setTimeout(() => saveMessagesImmediately(updatedMessages), 0); + } + }, []); + return { // 配置状态 inputs,