- Store editing message object reference using useRef to avoid ID conflicts
- Use object reference comparison first before falling back to ID matching
- Apply fix to both save and cancel operations in message editing
- Clear reference after edit completion to prevent stale object issues
Previously, when editing imported messages that contained duplicate IDs,
the findIndex operation would match the first occurrence rather than the
intended message, causing conversation history truncation when saving
edits. This change stores and uses object references for accurate message
identification during the editing process.
- Use object reference comparison first before falling back to ID matching
- Prevent incorrect message index lookup when duplicate IDs exist
- Apply fix to both handleMessageReset and handleMessageDelete functions
- Maintain backward compatibility with ID-based message identification
Previously, when importing messages that contained duplicate IDs, the
findIndex operation would match the first occurrence rather than the
intended message, causing history truncation on retry. This change uses
object reference comparison as the primary method, ensuring accurate
message identification and preserving conversation history.
- Extend handleMessageReset condition to include 'system' role messages
- Allow system messages to trigger regeneration like assistant messages
- Fix disabled retry button issue when message role is switched to system
- Maintain consistent user experience across different message roles
Previously, when an assistant message was switched to system role,
the retry button became non-functional. This change ensures that
system messages can be regenerated by finding the previous user
message and resending it, maintaining feature parity with assistant
messages.
**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
When using the "reset messages simultaneously" option during config import,
the conversation messages in the chat interface were not being properly reset,
while normal conversation resets worked correctly.
**Changes:**
- Modified `handleConfigReset` in `usePlaygroundState.js` to clear messages
before setting default examples
- Added asynchronous message update to force Chat component re-render
- Ensures immediate UI refresh when resetting imported conversation data
**Root Cause:**
The direct assignment to DEFAULT_MESSAGES didn't trigger a complete
component refresh, causing the chat interface to display stale data.
**Solution:**
```javascript
// Before
setMessage(DEFAULT_MESSAGES);
// After
setMessage([]);
setTimeout(() => {
setMessage(DEFAULT_MESSAGES);
}, 0);
```
This two-step approach forces the Chat component to unmount and remount
with fresh data, resolving the display inconsistency.
- Modify saveMessagesImmediately to accept messages parameter
- Pass updated message list to all save calls instead of relying on closure
- Ensure complete message history is saved including the last message
- Fix timing issue where old message state was being saved
This fixes the issue where the last conversation was not being persisted to localStorage.
- Refactor message saving strategy from automatic to manual saving
- Save messages only on key operations: send, complete, edit, delete, role toggle, clear
- Prevent frequent localStorage writes during streaming responses
- Remove excessive console logging
- Remove all console.log statements from save/load operations
- Clean up debug logs to reduce console noise
- Optimize initial state loading with lazy initialization
- Replace useRef with useState lazy initialization for config and messages
- Ensure loadConfig and loadMessages are called only once on mount
- Prevent redundant localStorage reads during re-renders
- Update hooks to support new save strategy
- Pass saveMessages callback through component hierarchy
- Add saveMessagesImmediately to relevant hooks (useApiRequest, useMessageActions, useMessageEdit)
- Trigger saves at appropriate lifecycle points
This significantly improves performance by reducing localStorage I/O operations
from continuous writes during streaming to discrete saves at meaningful points.
Summary
This commit addresses two critical issues affecting the real-time chat experience in the Playground:
1. Optimized re-rendering of reasoning content
• Added `reasoningContent` to the comparison function of `OptimizedMessageContent` (`web/src/components/playground/OptimizedComponents.js`).
• Ensures the component re-renders while reasoning text streams, resolving the bug where only the first characters (“好,”) were shown until the stream finished.
2. Defensive checks for SSE message updates
• Added early-return guards in `streamMessageUpdate` (`web/src/hooks/useApiRequest.js`).
• Skips updates when `lastMessage` is undefined or the last message isn’t from the assistant, preventing `TypeError: Cannot read properties of undefined (reading 'status')` during rapid SSE responses.
Impact
• Real-time reasoning content now appears progressively, enhancing user feedback.
• Eliminates runtime crashes caused by undefined message references, improving overall stability.
- Remove duplicate onRoleToggle prop passing to ChatArea component in Playground/index.js
- Move Toast notification outside setMessage callback in useMessageActions hook
- Prevent multiple event bindings that caused repeated role switch notifications
- Add early return validation for role toggle eligibility
This fixes the issue where users would see multiple success toasts when switching
between Assistant and System roles in the chat interface.
Files changed:
- web/src/pages/Playground/index.js
- web/src/hooks/useMessageActions.js
- Add CustomRequestEditor component with JSON validation and real-time formatting
- Implement bidirectional sync between chat messages and custom request body
- Add persistent local storage for chat messages (separate from config)
- Remove redundant System Prompt field in custom mode
- Refactor configuration storage to separate messages and settings
New Features:
• Custom request body mode with JSON editor and syntax highlighting
• Real-time bidirectional synchronization between chat UI and custom request body
• Persistent message storage that survives page refresh
• Enhanced configuration export/import including message data
• Improved parameter organization with collapsible sections
Technical Changes:
• Add loadMessages/saveMessages functions in configStorage
• Update usePlaygroundState hook to handle message persistence
• Refactor SettingsPanel to remove System Prompt in custom mode
• Add STORAGE_KEYS constants for better storage key management
• Implement debounced auto-save for both config and messages
• Add hash-based change detection to prevent unnecessary updates
UI/UX Improvements:
• Disabled state styling for parameters in custom mode
• Warning banners and visual feedback for mode switching
• Mobile-responsive design for custom request editor
• Consistent styling with existing design system
- Extract common `applyAutoCollapseLogic` function for reasoning panel collapse behavior
- Consolidate duplicated auto-collapse logic across multiple functions
- Simplify conditional expressions using logical OR operator
- Replace repetitive property assignments with object spread syntax
- Update dependency arrays to include new shared function
- Ensure consistent behavior across stream/non-stream/error scenarios
This refactoring improves code maintainability by following DRY principles
and centralizing the auto-collapse logic in a single reusable function.
All message handling functions now use consistent logic for determining
when to auto-collapse the reasoning panel.
Benefits:
- Reduced code duplication from ~20 lines to 6 lines per function
- Single source of truth for auto-collapse behavior
- Improved readability and maintainability
- Easier to modify collapse logic in the future
Files changed:
- web/src/hooks/useApiRequest.js: Refactored message handling functions
- Add `hasAutoCollapsed` flag to track auto-collapse state
- Modify reasoning panel to auto-collapse only once after thinking completion
- Allow users to manually toggle reasoning panel after auto-collapse
- Update message creation, streaming updates, and completion handlers
- Ensure consistent behavior across stream/non-stream requests and error cases
Previously, the reasoning/thinking panel would auto-collapse every time
the AI completed its thinking process, preventing users from reopening
it to review the reasoning content. Now it auto-collapses only once
when thinking is complete, then allows full user control.
Files changed:
- web/src/hooks/useApiRequest.js: Updated all message handling functions
- web/src/utils/messageUtils.js: Added hasAutoCollapsed to initial state
Previously, the "thinking" indicator and loading icon would only disappear
after the entire message generation was complete, which created a poor user
experience where users had to wait for the full response to see that the
reasoning phase had finished.
Changes made:
- Add `isThinkingComplete` field to independently track reasoning state
- Update streaming logic to mark thinking complete when content starts flowing
- Detect closed `<think>` tags to mark reasoning completion
- Modify MessageContent component to use independent thinking state
- Update "思考中..." text and loading icon display conditions
- Ensure thinking state is properly set in all completion scenarios
(non-stream, errors, manual stop)
Now the thinking section immediately shows as complete when reasoning ends,
rather than waiting for the entire message to finish, providing much better
real-time feedback to users.
Files modified:
- web/src/hooks/useApiRequest.js
- web/src/components/playground/MessageContent.js
- web/src/utils/messageUtils.js
- Cache initial config using useRef to prevent repeated loadConfig() calls
- Fix useEffect dependencies to only trigger on actual config changes
- Modify debouncedSaveConfig dependency from function reference to actual config values
- Update handleConfigReset to use DEFAULT_CONFIG directly instead of reloading
- Prevent excessive console logging during chat interactions and frequent re-renders
This resolves the issue where console was flooded with:
"配置已从本地存储加载" and "配置已保存到本地存储" messages,
especially during active chat sessions where logs appeared every second.
Fixes: Frequent config load/save operations causing performance issues
Completely restructured the Playground component from a 1437-line monolith
into a maintainable, modular architecture with 62.4% code reduction (540 lines).
**Key Improvements:**
- **Modular Architecture**: Extracted business logic into separate utility files
- `utils/constants.js` - Centralized constant management
- `utils/messageUtils.js` - Message processing utilities
- `utils/apiUtils.js` - API-related helper functions
- **Custom Hooks**: Created specialized hooks for better state management
- `usePlaygroundState.js` - Centralized state management
- `useMessageActions.js` - Message operation handlers
- `useApiRequest.js` - API request management
- **Code Quality**: Applied SOLID principles and functional programming patterns
- **Performance**: Optimized re-renders with useCallback and proper dependency arrays
- **Maintainability**: Implemented single responsibility principle and separation of concerns
**Technical Achievements:**
- Eliminated code duplication and redundancy
- Replaced magic strings with typed constants
- Extracted complex inline logic into pure functions
- Improved error handling and API response processing
- Enhanced code readability and testability
**Breaking Changes:** None - All existing functionality preserved
This refactor transforms the codebase into enterprise-grade quality following
React best practices and modern development standards.