🔧 fix(playground): resolve message state issues after page refresh and config reset
**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
This commit is contained in:
@@ -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]);
|
||||
|
||||
// 发送请求
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user