⚡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:
@@ -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('配置文件格式无效'));
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user