From 5cbbd78f38a266cf6f532b0fc24f7432474eafcc Mon Sep 17 00:00:00 2001 From: lmlm Date: Wed, 3 Jun 2026 12:40:48 +0800 Subject: [PATCH] refactor(web): migrate chat sidebar collapse storage (#36963) Co-authored-by: lmlm <7487674+popsiclelmlm@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- eslint-suppressions.json | 6 --- .../base/chat/chat-with-history/hooks.tsx | 40 ++++++++----------- 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 40255553fd..180a51f723 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -817,12 +817,6 @@ } }, "web/app/components/base/chat/chat-with-history/hooks.tsx": { - "no-restricted-globals": { - "count": 2 - }, - "react/set-state-in-effect": { - "count": 4 - }, "ts/no-explicit-any": { "count": 18 } diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index ae5692a56e..9f89b2e231 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -21,6 +21,9 @@ import { addFileInfos, sortAgentSorts } from '../../../tools/utils' import { CONVERSATION_ID_INFO } from '../constants' import { buildChatItemTree, getProcessedSystemVariablesFromUrlParams, getRawInputsFromUrlParams, getRawUserVariablesFromUrlParams } from '../utils' +const WEBAPP_SIDEBAR_COLLAPSE_STORAGE_KEY = 'webappSidebarCollapse' +const rawStorageOptions = { raw: true } as const + function getFormattedChatList(messages: any[]) { const newChatList: ChatItem[] = [] messages.forEach((item) => { @@ -116,31 +119,16 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { } setLocaleFromProps() }, [appData]) - const [sidebarCollapseState, setSidebarCollapseState] = useState(() => { - if (typeof window !== 'undefined') { - try { - const localState = localStorage.getItem('webappSidebarCollapse') - return localState === 'collapsed' - } - catch { - // localStorage may be disabled in private browsing mode or by security settings - // fallback to default value - return false - } - } - return false - }) + const [storedSidebarCollapseState, setStoredSidebarCollapseState] = useLocalStorage( + WEBAPP_SIDEBAR_COLLAPSE_STORAGE_KEY, + undefined, + rawStorageOptions, + ) + const sidebarCollapseState = storedSidebarCollapseState === 'collapsed' const handleSidebarCollapse = useCallback((state: boolean) => { - if (appId) { - setSidebarCollapseState(state) - try { - localStorage.setItem('webappSidebarCollapse', state ? 'collapsed' : 'expanded') - } - catch { - // localStorage may be disabled, continue without persisting state - } - } - }, [appId, setSidebarCollapseState]) + if (appId) + setStoredSidebarCollapseState(state ? 'collapsed' : 'expanded') + }, [appId, setStoredSidebarCollapseState]) const [conversationIdInfo, setConversationIdInfo] = useLocalStorage>>(CONVERSATION_ID_INFO, {}) const currentConversationId = useMemo(() => conversationIdInfo?.[appId || '']?.[userId || 'DEFAULT'] || '', [appId, conversationIdInfo, userId]) const handleConversationIdInfoChange = useCallback((changeConversationId: string) => { @@ -209,6 +197,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const [initUserVariables, setInitUserVariables] = useState>({}) const handleNewConversationInputsChange = useCallback((newInputs: Record) => { newConversationInputsRef.current = newInputs + // eslint-disable-next-line react/set-state-in-effect -- This handler intentionally syncs derived input defaults when called from the reset effect below. setNewConversationInputs(newInputs) }, []) const inputsForms = useMemo(() => { @@ -305,6 +294,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const [originConversationList, setOriginConversationList] = useState([]) useEffect(() => { if (appConversationData?.data && !appConversationDataLoading) + // eslint-disable-next-line react/set-state-in-effect -- Conversation query results intentionally replace the local editable list. setOriginConversationList(appConversationData?.data) }, [appConversationData, appConversationDataLoading]) const conversationList = useMemo(() => { @@ -321,6 +311,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { }, [originConversationList, showNewConversationItemInList, t]) useEffect(() => { if (newConversation) { + // eslint-disable-next-line react/set-state-in-effect -- Newly resolved conversation names intentionally patch the local list cache. setOriginConversationList(produce((draft) => { const index = draft.findIndex(item => item.id === newConversation.id) if (index > -1) @@ -344,6 +335,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => { const [currentConversationInputs, setCurrentConversationInputs] = useState>(currentConversationLatestInputs || {}) useEffect(() => { if (currentConversationItem) + // eslint-disable-next-line react/set-state-in-effect -- Selected conversation changes intentionally resync the editable input snapshot. setCurrentConversationInputs(currentConversationLatestInputs || {}) }, [currentConversationItem, currentConversationLatestInputs]) const checkInputsRequired = useCallback((silent?: boolean) => {