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.
This commit is contained in:
Apple\Apple
2025-06-02 21:21:46 +08:00
parent 3a5013b876
commit 07ffc36678
6 changed files with 85 additions and 59 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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