Explorar o código

⚡perf: Optimize message persistence and reduce localStorage operations

- 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.
Apple\Apple hai 9 meses
pai
achega
07ffc36678

+ 0 - 10
web/src/components/playground/configStorage.js

@@ -13,7 +13,6 @@ export const saveConfig = (config) => {
       timestamp: new Date().toISOString(),
     };
     localStorage.setItem(STORAGE_KEYS.CONFIG, JSON.stringify(configToSave));
-    console.log('配置已保存到本地存储');
   } catch (error) {
     console.error('保存配置失败:', error);
   }
@@ -30,7 +29,6 @@ export const saveMessages = (messages) => {
       timestamp: new Date().toISOString(),
     };
     localStorage.setItem(STORAGE_KEYS.MESSAGES, JSON.stringify(messagesToSave));
-    console.log('消息已保存到本地存储');
   } catch (error) {
     console.error('保存消息失败:', error);
   }
@@ -60,14 +58,12 @@ export const loadConfig = () => {
         customRequestBody: parsedConfig.customRequestBody || DEFAULT_CONFIG.customRequestBody,
       };
 
-      console.log('配置已从本地存储加载');
       return mergedConfig;
     }
   } catch (error) {
     console.error('加载配置失败:', error);
   }
 
-  console.log('使用默认配置');
   return DEFAULT_CONFIG;
 };
 
@@ -80,14 +76,12 @@ export const loadMessages = () => {
     const savedMessages = localStorage.getItem(STORAGE_KEYS.MESSAGES);
     if (savedMessages) {
       const parsedMessages = JSON.parse(savedMessages);
-      console.log('消息已从本地存储加载');
       return parsedMessages.messages || null;
     }
   } catch (error) {
     console.error('加载消息失败:', error);
   }
 
-  console.log('没有找到保存的消息');
   return null;
 };
 
@@ -98,7 +92,6 @@ export const clearConfig = () => {
   try {
     localStorage.removeItem(STORAGE_KEYS.CONFIG);
     localStorage.removeItem(STORAGE_KEYS.MESSAGES); // 同时清除消息
-    console.log('配置和消息已清除');
   } catch (error) {
     console.error('清除配置失败:', error);
   }
@@ -110,7 +103,6 @@ export const clearConfig = () => {
 export const clearMessages = () => {
   try {
     localStorage.removeItem(STORAGE_KEYS.MESSAGES);
-    console.log('消息已清除');
   } catch (error) {
     console.error('清除消息失败:', error);
   }
@@ -170,7 +162,6 @@ export const exportConfig = (config, messages = null) => {
 
     URL.revokeObjectURL(link.href);
 
-    console.log('配置已导出');
   } catch (error) {
     console.error('导出配置失败:', error);
   }
@@ -195,7 +186,6 @@ export const importConfig = (file) => {
               saveMessages(importedConfig.messages);
             }
 
-            console.log('配置已从文件导入');
             resolve(importedConfig);
           } else {
             reject(new Error('配置文件格式无效'));

+ 18 - 5
web/src/hooks/useApiRequest.js

@@ -20,7 +20,8 @@ export const useApiRequest = (
   setMessage,
   setDebugData,
   setActiveDebugTab,
-  sseSourceRef
+  sseSourceRef,
+  saveMessages
 ) => {
   const { t } = useTranslation();
 
@@ -105,7 +106,7 @@ export const useApiRequest = (
 
       const autoCollapseState = applyAutoCollapseLogic(lastMessage, true);
 
-      return [
+      const updatedMessages = [
         ...prevMessage.slice(0, -1),
         {
           ...lastMessage,
@@ -113,8 +114,15 @@ export const useApiRequest = (
           ...autoCollapseState,
         }
       ];
+
+      // 在消息完成时保存
+      if (status === MESSAGE_STATUS.COMPLETE || status === MESSAGE_STATUS.ERROR) {
+        setTimeout(() => saveMessages(), 0);
+      }
+
+      return updatedMessages;
     });
-  }, [setMessage, applyAutoCollapseLogic]);
+  }, [setMessage, applyAutoCollapseLogic, saveMessages]);
 
   // 非流式请求
   const handleNonStreamRequest = useCallback(async (payload) => {
@@ -356,7 +364,7 @@ export const useApiRequest = (
 
           const autoCollapseState = applyAutoCollapseLogic(lastMessage, true);
 
-          return [
+          const updatedMessages = [
             ...prevMessage.slice(0, -1),
             {
               ...lastMessage,
@@ -366,11 +374,16 @@ export const useApiRequest = (
               ...autoCollapseState,
             }
           ];
+
+          // 停止生成时也保存
+          setTimeout(() => saveMessages(), 0);
+
+          return updatedMessages;
         }
         return prevMessage;
       });
     }
-  }, [setMessage, applyAutoCollapseLogic]);
+  }, [setMessage, applyAutoCollapseLogic, saveMessages]);
 
   // 发送请求
   const sendRequest = useCallback((payload, isStream) => {

+ 25 - 10
web/src/hooks/useMessageActions.js

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
 import { getTextContent } from '../utils/messageUtils';
 import { ERROR_MESSAGES } from '../utils/constants';
 
-export const useMessageActions = (message, setMessage, onMessageSend) => {
+export const useMessageActions = (message, setMessage, onMessageSend, saveMessages) => {
   const { t } = useTranslation();
 
   // 复制消息
@@ -138,6 +138,7 @@ export const useMessageActions = (message, setMessage, onMessageSend) => {
           const messageIndex = prevMessages.findIndex(msg => msg.id === targetMessage.id);
           if (messageIndex === -1) return prevMessages;
 
+          let updatedMessages;
           if (targetMessage.role === 'user' && messageIndex < prevMessages.length - 1) {
             const nextMessage = prevMessages[messageIndex + 1];
             if (nextMessage.role === 'assistant') {
@@ -145,21 +146,31 @@ export const useMessageActions = (message, setMessage, onMessageSend) => {
                 content: t('已删除消息及其回复'),
                 duration: 2,
               });
-              return prevMessages.filter((_, index) =>
+              updatedMessages = prevMessages.filter((_, index) =>
                 index !== messageIndex && index !== messageIndex + 1
               );
+            } else {
+              Toast.success({
+                content: t('消息已删除'),
+                duration: 2,
+              });
+              updatedMessages = prevMessages.filter(msg => msg.id !== targetMessage.id);
             }
+          } else {
+            Toast.success({
+              content: t('消息已删除'),
+              duration: 2,
+            });
+            updatedMessages = prevMessages.filter(msg => msg.id !== targetMessage.id);
           }
 
-          Toast.success({
-            content: t('消息已删除'),
-            duration: 2,
-          });
-          return prevMessages.filter(msg => msg.id !== targetMessage.id);
+          // 删除消息后保存
+          setTimeout(() => saveMessages(), 0);
+          return updatedMessages;
         });
       },
     });
-  }, [setMessage, t]);
+  }, [setMessage, t, saveMessages]);
 
   // 切换角色
   const handleRoleToggle = useCallback((targetMessage) => {
@@ -170,20 +181,24 @@ export const useMessageActions = (message, setMessage, onMessageSend) => {
     const newRole = targetMessage.role === 'assistant' ? 'system' : 'assistant';
 
     setMessage(prevMessages => {
-      return prevMessages.map(msg => {
+      const updatedMessages = prevMessages.map(msg => {
         if (msg.id === targetMessage.id &&
           (msg.role === 'assistant' || msg.role === 'system')) {
           return { ...msg, role: newRole };
         }
         return msg;
       });
+
+      // 切换角色后保存
+      setTimeout(() => saveMessages(), 0);
+      return updatedMessages;
     });
 
     Toast.success({
       content: t(`已切换为${newRole === 'system' ? 'System' : 'Assistant'}角色`),
       duration: 2,
     });
-  }, [setMessage, t]);
+  }, [setMessage, t, saveMessages]);
 
   return {
     handleMessageCopy,

+ 12 - 3
web/src/hooks/useMessageEdit.js

@@ -8,7 +8,8 @@ export const useMessageEdit = (
   setMessage,
   inputs,
   parameterEnabled,
-  sendRequest
+  sendRequest,
+  saveMessages
 ) => {
   const { t } = useTranslation();
   const [editingMessageId, setEditingMessageId] = useState(null);
@@ -56,6 +57,8 @@ export const useMessageEdit = (
             onOk: () => {
               const messagesUntilUser = updatedMessages.slice(0, messageIndex + 1);
               setMessage(messagesUntilUser);
+              // 编辑后保存(重新生成的情况)
+              setTimeout(() => saveMessages(), 0);
 
               setTimeout(() => {
                 const payload = buildApiPayload(messagesUntilUser, null, inputs, parameterEnabled);
@@ -63,19 +66,25 @@ export const useMessageEdit = (
                 sendRequest(payload, inputs.stream);
               }, 100);
             },
-            onCancel: () => setMessage(updatedMessages)
+            onCancel: () => {
+              setMessage(updatedMessages);
+              // 编辑后保存(仅保存的情况)
+              setTimeout(() => saveMessages(), 0);
+            }
           });
           return prevMessages;
         }
       }
 
+      // 编辑后保存(普通情况)
+      setTimeout(() => saveMessages(), 0);
       return updatedMessages;
     });
 
     setEditingMessageId(null);
     setEditValue('');
     Toast.success({ content: t('消息已更新'), duration: 2 });
-  }, [editingMessageId, editValue, t, inputs, parameterEnabled, sendRequest, setMessage]);
+  }, [editingMessageId, editValue, t, inputs, parameterEnabled, sendRequest, setMessage, saveMessages]);
 
   const handleEditCancel = useCallback(() => {
     setEditingMessageId(null);

+ 11 - 27
web/src/hooks/usePlaygroundState.js

@@ -3,15 +3,9 @@ import { DEFAULT_MESSAGES, DEFAULT_CONFIG, DEBUG_TABS } from '../utils/constants
 import { loadConfig, saveConfig, loadMessages, saveMessages } from '../components/playground/configStorage';
 
 export const usePlaygroundState = () => {
-  // 使用 ref 缓存初始配置,只加载一次
-  const initialConfigRef = useRef(null);
-  if (!initialConfigRef.current) {
-    initialConfigRef.current = loadConfig();
-  }
-  const savedConfig = initialConfigRef.current;
-
-  // 加载保存的消息,如果没有则使用默认消息
-  const initialMessages = loadMessages() || DEFAULT_MESSAGES;
+  // 使用惰性初始化,确保只在组件首次挂载时加载配置和消息
+  const [savedConfig] = useState(() => loadConfig());
+  const [initialMessages] = useState(() => loadMessages() || DEFAULT_MESSAGES);
 
   // 基础配置状态
   const [inputs, setInputs] = useState(savedConfig.inputs || DEFAULT_CONFIG.inputs);
@@ -70,15 +64,9 @@ export const usePlaygroundState = () => {
     }));
   }, []);
 
-  // 消息保存函数
-  const debouncedSaveMessages = useCallback(() => {
-    if (saveMessagesTimeoutRef.current) {
-      clearTimeout(saveMessagesTimeoutRef.current);
-    }
-
-    saveMessagesTimeoutRef.current = setTimeout(() => {
-      saveMessages(message);
-    }, 1000);
+  // 消息保存函数 - 改为立即保存
+  const saveMessagesImmediately = useCallback(() => {
+    saveMessages(message);
   }, [message]);
 
   // 配置保存
@@ -137,20 +125,16 @@ export const usePlaygroundState = () => {
     }
   }, []);
 
-  // 监听消息变化并自动保存
-  useEffect(() => {
-    debouncedSaveMessages();
-  }, [debouncedSaveMessages]);
-
   // 清理定时器
   useEffect(() => {
     return () => {
       if (saveConfigTimeoutRef.current) {
         clearTimeout(saveConfigTimeoutRef.current);
       }
-      if (saveMessagesTimeoutRef.current) {
-        clearTimeout(saveMessagesTimeoutRef.current);
-      }
+      // 移除消息保存定时器的清理
+      // if (saveMessagesTimeoutRef.current) {
+      //   clearTimeout(saveMessagesTimeoutRef.current);
+      // }
     };
   }, []);
 
@@ -206,7 +190,7 @@ export const usePlaygroundState = () => {
     handleInputChange,
     handleParameterToggle,
     debouncedSaveConfig,
-    debouncedSaveMessages,
+    saveMessagesImmediately,  // 改为导出立即保存函数
     handleConfigImport,
     handleConfigReset,
   };

+ 19 - 4
web/src/pages/Playground/index.js

@@ -83,6 +83,7 @@ const Playground = () => {
     handleInputChange,
     handleParameterToggle,
     debouncedSaveConfig,
+    saveMessagesImmediately,
     handleConfigImport,
     handleConfigReset,
     setShowSettings,
@@ -103,7 +104,8 @@ const Playground = () => {
     setMessage,
     setDebugData,
     setActiveDebugTab,
-    sseSourceRef
+    sseSourceRef,
+    saveMessagesImmediately
   );
 
   // 数据加载
@@ -117,7 +119,7 @@ const Playground = () => {
     handleMessageEdit,
     handleEditSave,
     handleEditCancel
-  } = useMessageEdit(setMessage, inputs, parameterEnabled, sendRequest);
+  } = useMessageEdit(setMessage, inputs, parameterEnabled, sendRequest, saveMessagesImmediately);
 
   // 消息和自定义请求体同步
   const { syncMessageToCustomBody, syncCustomBodyToMessage } = useSyncMessageAndCustomBody(
@@ -147,7 +149,7 @@ const Playground = () => {
   };
 
   // 消息操作
-  const messageActions = useMessageActions(message, setMessage, onMessageSend);
+  const messageActions = useMessageActions(message, setMessage, onMessageSend, saveMessagesImmediately);
 
   // 构建预览请求体
   const constructPreviewPayload = useCallback(() => {
@@ -212,6 +214,9 @@ const Playground = () => {
           // 发送自定义请求体
           sendRequest(customPayload, customPayload.stream !== false);
 
+          // 发送消息后保存
+          setTimeout(() => saveMessagesImmediately(), 0);
+
           return newMessages;
         });
         return;
@@ -240,6 +245,9 @@ const Playground = () => {
         }, 100);
       }
 
+      // 发送消息后保存
+      setTimeout(() => saveMessagesImmediately(), 0);
+
       return [...newMessages, loadingMessage];
     });
   }
@@ -351,6 +359,13 @@ const Playground = () => {
     debouncedSaveConfig();
   }, [inputs, parameterEnabled, showDebugPanel, customRequestMode, customRequestBody, debouncedSaveConfig]);
 
+  // 清空对话的处理函数
+  const handleClearMessages = useCallback(() => {
+    setMessage([]);
+    // 清空对话后保存
+    setTimeout(() => saveMessagesImmediately(), 0);
+  }, [setMessage, saveMessagesImmediately]);
+
   return (
     <div className="h-full bg-gray-50">
       <Layout style={{ height: '100%', background: 'transparent' }} className="flex flex-col md:flex-row">
@@ -413,7 +428,7 @@ const Playground = () => {
                 onMessageReset={messageActions.handleMessageReset}
                 onMessageDelete={messageActions.handleMessageDelete}
                 onStopGenerator={onStopGenerator}
-                onClearMessages={() => setMessage([])}
+                onClearMessages={handleClearMessages}
                 onToggleDebugPanel={() => setShowDebugPanel(!showDebugPanel)}
                 renderCustomChatContent={renderCustomChatContent}
                 renderChatBoxAction={renderChatBoxAction}