diff --git a/web/src/components/playground/configStorage.js b/web/src/components/playground/configStorage.js index 6e6e679d..a4e068cb 100644 --- a/web/src/components/playground/configStorage.js +++ b/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('配置文件格式无效')); diff --git a/web/src/hooks/useApiRequest.js b/web/src/hooks/useApiRequest.js index c58c43e2..1eaefea4 100644 --- a/web/src/hooks/useApiRequest.js +++ b/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) => { diff --git a/web/src/hooks/useMessageActions.js b/web/src/hooks/useMessageActions.js index aa43f8a5..3f9ddc09 100644 --- a/web/src/hooks/useMessageActions.js +++ b/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, diff --git a/web/src/hooks/useMessageEdit.js b/web/src/hooks/useMessageEdit.js index 7c154ccd..c41c735a 100644 --- a/web/src/hooks/useMessageEdit.js +++ b/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); diff --git a/web/src/hooks/usePlaygroundState.js b/web/src/hooks/usePlaygroundState.js index 86a5a10d..a974fde6 100644 --- a/web/src/hooks/usePlaygroundState.js +++ b/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, }; diff --git a/web/src/pages/Playground/index.js b/web/src/pages/Playground/index.js index fcb993c0..933651da 100644 --- a/web/src/pages/Playground/index.js +++ b/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 (