diff --git a/packages/shared/data/cache/cacheSchemas.ts b/packages/shared/data/cache/cacheSchemas.ts index 57446da95d..32aabb2ff9 100644 --- a/packages/shared/data/cache/cacheSchemas.ts +++ b/packages/shared/data/cache/cacheSchemas.ts @@ -41,6 +41,7 @@ export type UseCacheSchema = { 'chat.generating': boolean 'chat.websearch.searching': boolean 'chat.websearch.active_searches': CacheValueTypes.CacheActiveSearches + 'chat.active_view': 'topic' | 'session' // Minapp management 'minapp.opened_keep_alive': CacheValueTypes.CacheMinAppType[] @@ -52,6 +53,11 @@ export type UseCacheSchema = { 'topic.active': CacheValueTypes.CacheTopic | null 'topic.renaming': string[] 'topic.newly_renamed': string[] + + // Agent management + 'agent.active_id': string | null + 'agent.session.active_id_map': Record + 'agent.session.waiting_id_map': Record } export const DefaultUseCache: UseCacheSchema = { @@ -74,6 +80,7 @@ export const DefaultUseCache: UseCacheSchema = { 'chat.generating': false, 'chat.websearch.searching': false, 'chat.websearch.active_searches': {}, + 'chat.active_view': 'topic', // Minapp management 'minapp.opened_keep_alive': [], @@ -84,7 +91,12 @@ export const DefaultUseCache: UseCacheSchema = { // Topic management 'topic.active': null, 'topic.renaming': [], - 'topic.newly_renamed': [] + 'topic.newly_renamed': [], + + // Agent management + 'agent.active_id': null, + 'agent.session.active_id_map': {}, + 'agent.session.waiting_id_map': {} } /** diff --git a/src/renderer/src/data/hooks/useDataApi.ts b/src/renderer/src/data/hooks/useDataApi.ts index e13b0c37c5..de30473ab0 100644 --- a/src/renderer/src/data/hooks/useDataApi.ts +++ b/src/renderer/src/data/hooks/useDataApi.ts @@ -2,6 +2,7 @@ import type { BodyForPath, QueryParamsForPath, ResponseForPath } from '@shared/d import type { ConcreteApiPaths } from '@shared/data/api/apiSchemas' import type { PaginatedResponse } from '@shared/data/api/apiTypes' import { useState } from 'react' +import type { KeyedMutator } from 'swr' import useSWR, { useSWRConfig } from 'swr' import useSWRMutation from 'swr/mutation' @@ -118,6 +119,18 @@ function getFetcher([path, query]: [TPath, Recor * revalidateOnFocus: true * } * }) + * + * // Optimistic updates with mutate (bound to current key, no key needed) + * const { data, mutate } = useQuery(`/topics/${id}`) + * + * // Update cache immediately without revalidation + * await mutate({ ...data, title: 'New Title' }, false) + * + * // Update cache with function and revalidate + * await mutate(prev => ({ ...prev, count: prev.count + 1 })) + * + * // Just revalidate (refetch from server) + * await mutate() * ``` */ export function useQuery( @@ -139,6 +152,8 @@ export function useQuery( error?: Error /** Function to manually refetch data */ refetch: () => void + /** SWR mutate function for optimistic updates */ + mutate: KeyedMutator> } { // Internal type conversion for SWR compatibility const key = options?.enabled !== false ? buildSWRKey(path, options?.query as Record) : null @@ -160,7 +175,8 @@ export function useQuery( data, loading: isLoading || isValidating, error: error as Error | undefined, - refetch + refetch, + mutate } } diff --git a/src/renderer/src/hooks/agents/useActiveAgent.ts b/src/renderer/src/hooks/agents/useActiveAgent.ts index f522ac7f28..44a50717aa 100644 --- a/src/renderer/src/hooks/agents/useActiveAgent.ts +++ b/src/renderer/src/hooks/agents/useActiveAgent.ts @@ -1,8 +1,8 @@ -import { useRuntime } from '../useRuntime' +import { useCache } from '@renderer/data/hooks/useCache' + import { useAgent } from './useAgent' export const useActiveAgent = () => { - const { chat } = useRuntime() - const { activeAgentId } = chat + const [activeAgentId] = useCache('agent.active_id') return useAgent(activeAgentId) } diff --git a/src/renderer/src/hooks/agents/useActiveSession.ts b/src/renderer/src/hooks/agents/useActiveSession.ts index dd744b6a50..9ac514f4a1 100644 --- a/src/renderer/src/hooks/agents/useActiveSession.ts +++ b/src/renderer/src/hooks/agents/useActiveSession.ts @@ -1,9 +1,10 @@ -import { useRuntime } from '../useRuntime' +import { useCache } from '@renderer/data/hooks/useCache' + import { useSession } from './useSession' export const useActiveSession = () => { - const { chat } = useRuntime() - const { activeSessionIdMap, activeAgentId } = chat + const [activeAgentId] = useCache('agent.active_id') + const [activeSessionIdMap] = useCache('agent.session.active_id_map') const activeSessionId = activeAgentId ? activeSessionIdMap[activeAgentId] : null return useSession(activeAgentId, activeSessionId) } diff --git a/src/renderer/src/hooks/agents/useAgentSessionInitializer.ts b/src/renderer/src/hooks/agents/useAgentSessionInitializer.ts index 98609f5b29..2a826ee373 100644 --- a/src/renderer/src/hooks/agents/useAgentSessionInitializer.ts +++ b/src/renderer/src/hooks/agents/useAgentSessionInitializer.ts @@ -1,7 +1,6 @@ import { loggerService } from '@logger' -import { useRuntime } from '@renderer/hooks/useRuntime' -import { useAppDispatch } from '@renderer/store' -import { setActiveSessionIdAction, setActiveTopicOrSessionAction } from '@renderer/store/runtime' +import { cacheService } from '@renderer/data/CacheService' +import { useCache } from '@renderer/data/hooks/useCache' import { useCallback, useEffect } from 'react' import { useAgentClient } from './useAgentClient' @@ -14,10 +13,9 @@ const logger = loggerService.withContext('useAgentSessionInitializer') * its most recent session is automatically selected. */ export const useAgentSessionInitializer = () => { - const dispatch = useAppDispatch() const client = useAgentClient() - const { chat } = useRuntime() - const { activeAgentId, activeSessionIdMap } = chat + const [activeAgentId] = useCache('agent.active_id') + const [activeSessionIdMap] = useCache('agent.session.active_id_map') /** * Initialize session for the given agent by loading its sessions @@ -32,7 +30,7 @@ export const useAgentSessionInitializer = () => { const currentSessionId = activeSessionIdMap[agentId] if (currentSessionId) { // Session already exists, just switch to session view - dispatch(setActiveTopicOrSessionAction('session')) + cacheService.set('chat.active_view', 'session') return } @@ -45,20 +43,21 @@ export const useAgentSessionInitializer = () => { const latestSession = sessions[0] // Set the latest session as active - dispatch(setActiveSessionIdAction({ agentId, sessionId: latestSession.id })) - dispatch(setActiveTopicOrSessionAction('session')) + const currentMap = cacheService.get('agent.session.active_id_map') ?? {} + cacheService.set('agent.session.active_id_map', { ...currentMap, [agentId]: latestSession.id }) + cacheService.set('chat.active_view', 'session') } else { // No sessions exist, we might want to create one // But for now, just switch to session view and let the Sessions component handle it - dispatch(setActiveTopicOrSessionAction('session')) + cacheService.set('chat.active_view', 'session') } } catch (error) { logger.error('Failed to initialize agent session:', error as Error) // Even if loading fails, switch to session view - dispatch(setActiveTopicOrSessionAction('session')) + cacheService.set('chat.active_view', 'session') } }, - [client, dispatch, activeSessionIdMap] + [client, activeSessionIdMap] ) /** diff --git a/src/renderer/src/hooks/agents/useAgents.ts b/src/renderer/src/hooks/agents/useAgents.ts index 5c60ef1041..f4f0a2526f 100644 --- a/src/renderer/src/hooks/agents/useAgents.ts +++ b/src/renderer/src/hooks/agents/useAgents.ts @@ -1,5 +1,5 @@ -import { useAppDispatch } from '@renderer/store' -import { setActiveAgentId, setActiveSessionIdAction } from '@renderer/store/runtime' +import { cacheService } from '@renderer/data/CacheService' +import { useCache } from '@renderer/data/hooks/useCache' import type { AddAgentForm, CreateAgentResponse } from '@renderer/types' import { formatErrorMessageWithPrefix } from '@renderer/utils/error' import { useCallback } from 'react' @@ -7,7 +7,6 @@ import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { useApiServer } from '../useApiServer' -import { useRuntime } from '../useRuntime' import { useAgentClient } from './useAgentClient' type Result = @@ -43,9 +42,7 @@ export const useAgents = () => { }, [apiServerConfig.enabled, apiServerRunning, client, t]) const { data, error, isLoading, mutate } = useSWR(swrKey, fetcher) - const { chat } = useRuntime() - const { activeAgentId } = chat - const dispatch = useAppDispatch() + const [activeAgentId] = useCache('agent.active_id') const addAgent = useCallback( async (form: AddAgentForm): Promise> => { @@ -71,14 +68,11 @@ export const useAgents = () => { async (id: string) => { try { await client.deleteAgent(id) - dispatch(setActiveSessionIdAction({ agentId: id, sessionId: null })) + const currentMap = cacheService.get('agent.session.active_id_map') ?? {} + cacheService.set('agent.session.active_id_map', { ...currentMap, [id]: null }) if (activeAgentId === id) { const newId = data?.filter((a) => a.id !== id).find(() => true)?.id - if (newId) { - dispatch(setActiveAgentId(newId)) - } else { - dispatch(setActiveAgentId(null)) - } + cacheService.set('agent.active_id', newId ?? null) } mutate((prev) => prev?.filter((a) => a.id !== id) ?? []) window.toast.success(t('common.delete_success')) @@ -86,7 +80,7 @@ export const useAgents = () => { window.toast.error(formatErrorMessageWithPrefix(error, t('agent.delete.error.failed'))) } }, - [activeAgentId, client, data, dispatch, mutate, t] + [activeAgentId, client, data, mutate, t] ) const getAgent = useCallback( diff --git a/src/renderer/src/hooks/agents/useCreateDefaultSession.ts b/src/renderer/src/hooks/agents/useCreateDefaultSession.ts index 7f1a9bcc58..a5342e7f00 100644 --- a/src/renderer/src/hooks/agents/useCreateDefaultSession.ts +++ b/src/renderer/src/hooks/agents/useCreateDefaultSession.ts @@ -1,8 +1,7 @@ import { loggerService } from '@logger' +import { cacheService } from '@renderer/data/CacheService' import { useAgent } from '@renderer/hooks/agents/useAgent' import { useSessions } from '@renderer/hooks/agents/useSessions' -import { useAppDispatch } from '@renderer/store' -import { setActiveSessionIdAction, setActiveTopicOrSessionAction } from '@renderer/store/runtime' import type { CreateSessionForm } from '@renderer/types' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -15,7 +14,6 @@ const logger = loggerService.withContext('useCreateDefaultSession') export const useCreateDefaultSession = (agentId: string | null) => { const { agent } = useAgent(agentId) const { createSession } = useSessions(agentId) - const dispatch = useAppDispatch() const { t } = useTranslation() const [creatingSession, setCreatingSession] = useState(false) @@ -35,8 +33,9 @@ export const useCreateDefaultSession = (agentId: string | null) => { const created = await createSession(session) if (created) { - dispatch(setActiveSessionIdAction({ agentId, sessionId: created.id })) - dispatch(setActiveTopicOrSessionAction('session')) + const currentMap = cacheService.get('agent.session.active_id_map') ?? {} + cacheService.set('agent.session.active_id_map', { ...currentMap, [agentId]: created.id }) + cacheService.set('chat.active_view', 'session') } return created @@ -46,7 +45,7 @@ export const useCreateDefaultSession = (agentId: string | null) => { } finally { setCreatingSession(false) } - }, [agentId, agent, createSession, creatingSession, dispatch, t]) + }, [agentId, agent, createSession, creatingSession, t]) return { createDefaultSession, diff --git a/src/renderer/src/hooks/useRuntime.ts b/src/renderer/src/hooks/useRuntime.ts deleted file mode 100644 index cb4159c840..0000000000 --- a/src/renderer/src/hooks/useRuntime.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Data Refactor, notes by fullex - * //TODO @deprecated this file will be removed - */ -import { useAppSelector } from '@renderer/store' - -export function useRuntime() { - return useAppSelector((state) => state.runtime) -} diff --git a/src/renderer/src/pages/home/Chat.tsx b/src/renderer/src/pages/home/Chat.tsx index a274123dc5..6a82794453 100644 --- a/src/renderer/src/pages/home/Chat.tsx +++ b/src/renderer/src/pages/home/Chat.tsx @@ -6,10 +6,10 @@ import { ContentSearch } from '@renderer/components/ContentSearch' import MultiSelectActionPopup from '@renderer/components/Popups/MultiSelectionPopup' import PromptPopup from '@renderer/components/Popups/PromptPopup' import { QuickPanelProvider } from '@renderer/components/QuickPanel' +import { useCache } from '@renderer/data/hooks/useCache' import { useCreateDefaultSession } from '@renderer/hooks/agents/useCreateDefaultSession' import { useAssistant } from '@renderer/hooks/useAssistant' import { useChatContext } from '@renderer/hooks/useChatContext' -import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' import { useShortcut } from '@renderer/hooks/useShortcuts' import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore' @@ -54,8 +54,9 @@ const Chat: FC = (props) => { const { isMultiSelectMode } = useChatContext(props.activeTopic) const [isTopNavbar] = usePreference('ui.navbar.position') const chatMaxWidth = useChatMaxWidth() - const { chat } = useRuntime() - const { activeTopicOrSession, activeAgentId, activeSessionIdMap } = chat + const [activeAgentId] = useCache('agent.active_id') + const [activeTopicOrSession] = useCache('chat.active_view') + const [activeSessionIdMap] = useCache('agent.session.active_id_map') const activeSessionId = activeAgentId ? activeSessionIdMap[activeAgentId] : null const sessionAgentId = activeTopicOrSession === 'session' ? activeAgentId : null const { createDefaultSession } = useCreateDefaultSession(sessionAgentId) diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index 6c5d31e738..488c1cbccf 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -1,13 +1,13 @@ import { usePreference } from '@data/hooks/usePreference' import { ErrorBoundary } from '@renderer/components/ErrorBoundary' +import { cacheService } from '@renderer/data/CacheService' +import { useCache } from '@renderer/data/hooks/useCache' import { useAgentSessionInitializer } from '@renderer/hooks/agents/useAgentSessionInitializer' import { useAssistants } from '@renderer/hooks/useAssistant' import { useNavbarPosition } from '@renderer/hooks/useNavbar' -import { useRuntime } from '@renderer/hooks/useRuntime' import { useActiveTopic } from '@renderer/hooks/useTopic' import NavigationService from '@renderer/services/NavigationService' import { newMessagesActions } from '@renderer/store/newMessage' -import { setActiveAgentId, setActiveTopicOrSessionAction } from '@renderer/store/runtime' import type { Assistant, Topic } from '@renderer/types' import { MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, SECOND_MIN_WINDOW_WIDTH } from '@shared/config/constant' import { useNavigate, useSearch } from '@tanstack/react-router' @@ -52,8 +52,7 @@ const HomePage: FC = () => { const [showTopics] = usePreference('topic.tab.show') const [topicPosition] = usePreference('topic.position') const dispatch = useDispatch() - const { chat } = useRuntime() - const { activeTopicOrSession } = chat + const [activeTopicOrSession, setActiveTopicOrSession] = useCache('chat.active_view') _activeAssistant = activeAssistant @@ -64,14 +63,14 @@ const HomePage: FC = () => { startTransition(() => { _setActiveAssistant(newAssistant) if (newAssistant.id !== 'fake') { - dispatch(setActiveAgentId(null)) + cacheService.set('agent.active_id', null) } // 同步更新 active topic,避免不必要的重新渲染 const newTopic = newAssistant.topics[0] _setActiveTopic((prev) => (newTopic?.id === prev.id ? prev : newTopic)) }) }, - [_setActiveTopic, activeAssistant?.id, dispatch] + [_setActiveTopic, activeAssistant?.id] ) const setActiveTopic = useCallback( @@ -79,10 +78,10 @@ const HomePage: FC = () => { startTransition(() => { _setActiveTopic((prev) => (newTopic?.id === prev.id ? prev : newTopic)) dispatch(newMessagesActions.setTopicFulfilled({ topicId: newTopic.id, fulfilled: false })) - dispatch(setActiveTopicOrSessionAction('topic')) + setActiveTopicOrSession('topic') }) }, - [_setActiveTopic, dispatch] + [_setActiveTopic, dispatch, setActiveTopicOrSession] ) useEffect(() => { diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index e223a64e96..29f4f334d8 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -10,6 +10,7 @@ import { isVisionModels, isWebSearchModel } from '@renderer/config/models' +import { useCache } from '@renderer/data/hooks/useCache' import db from '@renderer/databases' import { useAssistant } from '@renderer/hooks/useAssistant' import { useInputText } from '@renderer/hooks/useInputText' @@ -30,7 +31,7 @@ import { checkRateLimit, getUserMessage } from '@renderer/services/MessagesServi import { spanManagerService } from '@renderer/services/SpanManagerService' import { estimateTextTokens as estimateTxtTokens, estimateUserPromptUsage } from '@renderer/services/TokenService' import WebSearchService from '@renderer/services/WebSearchService' -import { useAppDispatch, useAppSelector } from '@renderer/store' +import { useAppDispatch } from '@renderer/store' import { sendMessage as _sendMessage } from '@renderer/store/thunk/messageThunk' import { type Assistant, type FileType, type KnowledgeBase, type Model, type Topic, TopicType } from '@renderer/types' import type { MessageInputBaseParams } from '@renderer/types/newMessage' @@ -165,7 +166,7 @@ const InputbarInner: FC = ({ assistant: initialAssistant, se const isVisionAssistant = useMemo(() => isVisionModel(model), [model]) const isGenerateImageAssistant = useMemo(() => isGenerateImageModel(model), [model]) const { setTimeoutTimer } = useTimer() - const isMultiSelectMode = useAppSelector((state) => state.runtime.chat.isMultiSelectMode) + const [isMultiSelectMode] = useCache('chat.multi_select_mode') const isVisionSupported = useMemo( () => diff --git a/src/renderer/src/pages/home/Messages/MessageHeader.tsx b/src/renderer/src/pages/home/Messages/MessageHeader.tsx index 69c38b842a..1176d2b5f3 100644 --- a/src/renderer/src/pages/home/Messages/MessageHeader.tsx +++ b/src/renderer/src/pages/home/Messages/MessageHeader.tsx @@ -5,11 +5,11 @@ import UserPopup from '@renderer/components/Popups/UserPopup' import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env' import { getModelLogoById } from '@renderer/config/models' import { useTheme } from '@renderer/context/ThemeProvider' +import { useCache } from '@renderer/data/hooks/useCache' import { useAgent } from '@renderer/hooks/agents/useAgent' import useAvatar from '@renderer/hooks/useAvatar' import { useChatContext } from '@renderer/hooks/useChatContext' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' -import { useRuntime } from '@renderer/hooks/useRuntime' import { useMessageStyle } from '@renderer/hooks/useSettings' import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon' import { getMessageModelId } from '@renderer/services/MessagesService' @@ -43,8 +43,8 @@ const MessageHeader: FC = memo(({ assistant, model, message, topic, isGro const { theme } = useTheme() const [userName] = usePreference('app.user.name') const showMinappIcon = useSidebarIconShow('minapp') - const { chat } = useRuntime() - const { activeTopicOrSession, activeAgentId } = chat + const [activeAgentId] = useCache('agent.active_id') + const [activeTopicOrSession] = useCache('chat.active_view') const { agent } = useAgent(activeAgentId) const isAgentView = activeTopicOrSession === 'session' const { t } = useTranslation() diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 1a369f4771..471d6921a0 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -1,9 +1,9 @@ import Scrollbar from '@renderer/components/Scrollbar' +import { useCache } from '@renderer/data/hooks/useCache' import { useAgents } from '@renderer/hooks/agents/useAgents' import { useApiServer } from '@renderer/hooks/useApiServer' import { useAssistants } from '@renderer/hooks/useAssistant' import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets' -import { useRuntime } from '@renderer/hooks/useRuntime' import { useAssistantsTabSortType } from '@renderer/hooks/useStore' import { useTags } from '@renderer/hooks/useTags' import type { Assistant, Topic } from '@renderer/types' @@ -32,11 +32,10 @@ const AssistantsTab: FC = (props) => { const containerRef = useRef(null) const { apiServerConfig } = useApiServer() const apiServerEnabled = apiServerConfig.enabled - const { chat } = useRuntime() // Agent related hooks const { agents, deleteAgent, isLoading: agentsLoading, error: agentsError } = useAgents() - const { activeAgentId } = chat + const [activeAgentId] = useCache('agent.active_id') const { setActiveAgentId } = useActiveAgent() // Assistant related hooks diff --git a/src/renderer/src/pages/home/Tabs/SessionsTab.tsx b/src/renderer/src/pages/home/Tabs/SessionsTab.tsx index b104b6d1d4..d86475a255 100644 --- a/src/renderer/src/pages/home/Tabs/SessionsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/SessionsTab.tsx @@ -1,4 +1,4 @@ -import { useRuntime } from '@renderer/hooks/useRuntime' +import { useCache } from '@renderer/data/hooks/useCache' import { useSettings } from '@renderer/hooks/useSettings' import { cn } from '@renderer/utils' import { Alert } from 'antd' @@ -11,8 +11,7 @@ import Sessions from './components/Sessions' interface SessionsTabProps {} const SessionsTab: FC = () => { - const { chat } = useRuntime() - const { activeAgentId } = chat + const [activeAgentId] = useCache('agent.active_id') const { t } = useTranslation() const { apiServer } = useSettings() diff --git a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx index 6400630357..5e2e58e17b 100644 --- a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx @@ -1,4 +1,4 @@ -import { useRuntime } from '@renderer/hooks/useRuntime' +import { useCache } from '@renderer/data/hooks/useCache' import type { Assistant, Topic } from '@renderer/types' import type { FC } from 'react' @@ -15,8 +15,7 @@ interface Props { } const TopicsTab: FC = (props) => { - const { chat } = useRuntime() - const { activeTopicOrSession } = chat + const [activeTopicOrSession] = useCache('chat.active_view') if (activeTopicOrSession === 'topic') { return } diff --git a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx index 86fcf23bb5..aded421b48 100644 --- a/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AssistantItem.tsx @@ -3,13 +3,12 @@ import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' import EmojiIcon from '@renderer/components/EmojiIcon' import { CopyIcon, DeleteIcon, EditIcon } from '@renderer/components/Icons' import PromptPopup from '@renderer/components/Popups/PromptPopup' +import { cacheService } from '@renderer/data/CacheService' import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant' import { useTags } from '@renderer/hooks/useTags' import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings' import { getDefaultModel } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' -import { useAppDispatch } from '@renderer/store' -import { setActiveTopicOrSessionAction } from '@renderer/store/runtime' import type { Assistant } from '@renderer/types' import { cn, getLeadingEmoji, uuid } from '@renderer/utils' import { hasTopicPendingRequests } from '@renderer/utils/queue' @@ -75,7 +74,6 @@ const AssistantItem: FC = ({ const { assistants, updateAssistants } = useAssistants() const [isPending, setIsPending] = useState(false) - const dispatch = useAppDispatch() useEffect(() => { if (isActive) { @@ -145,8 +143,8 @@ const AssistantItem: FC = ({ } } onSwitch(assistant) - dispatch(setActiveTopicOrSessionAction('topic')) - }, [clickAssistantToShowTopic, onSwitch, assistant, dispatch, topicPosition]) + cacheService.set('chat.active_view', 'topic') + }, [clickAssistantToShowTopic, onSwitch, assistant, topicPosition]) const assistantName = useMemo(() => assistant.name || t('chat.default.name'), [assistant.name, t]) const fullAssistantName = useMemo( diff --git a/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx b/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx index a9ae67d767..ac45ff8033 100644 --- a/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/SessionItem.tsx @@ -2,9 +2,9 @@ import { Tooltip } from '@cherrystudio/ui' import { usePreference } from '@data/hooks/usePreference' import { DeleteIcon, EditIcon } from '@renderer/components/Icons' import { isMac } from '@renderer/config/constant' +import { useCache } from '@renderer/data/hooks/useCache' import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import { useInPlaceEdit } from '@renderer/hooks/useInPlaceEdit' -import { useRuntime } from '@renderer/hooks/useRuntime' import { useTimer } from '@renderer/hooks/useTimer' import { SessionSettingsPopup } from '@renderer/pages/settings/AgentSettings' import { SessionLabel } from '@renderer/pages/settings/AgentSettings/shared' @@ -34,9 +34,9 @@ interface SessionItemProps { const SessionItem: FC = ({ session, agentId, onDelete, onPress }) => { const { t } = useTranslation() - const { chat } = useRuntime() + const [activeSessionIdMap] = useCache('agent.session.active_id_map') const { updateSession } = useUpdateSession(agentId) - const activeSessionId = chat.activeSessionIdMap[agentId] + const activeSessionId = activeSessionIdMap[agentId] const [isConfirmingDeletion, setIsConfirmingDeletion] = useState(false) const { setTimeoutTimer } = useTimer() const [_targetSession, setTargetSession] = useState(session) diff --git a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx index af667b942f..2b73bc0fcf 100644 --- a/src/renderer/src/pages/home/Tabs/components/Sessions.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Sessions.tsx @@ -1,14 +1,10 @@ import { DynamicVirtualList } from '@renderer/components/VirtualList' +import { cacheService } from '@renderer/data/CacheService' +import { useCache } from '@renderer/data/hooks/useCache' import { useCreateDefaultSession } from '@renderer/hooks/agents/useCreateDefaultSession' import { useSessions } from '@renderer/hooks/agents/useSessions' -import { useRuntime } from '@renderer/hooks/useRuntime' import { useAppDispatch } from '@renderer/store' import { newMessagesActions } from '@renderer/store/newMessage' -import { - setActiveSessionIdAction, - setActiveTopicOrSessionAction, - setSessionWaitingAction -} from '@renderer/store/runtime' import { buildAgentSessionTopicId } from '@renderer/utils/agentSession' import { Alert, Spin } from 'antd' import { motion } from 'framer-motion' @@ -28,18 +24,15 @@ interface SessionsProps { const Sessions: React.FC = ({ agentId }) => { const { t } = useTranslation() const { sessions, isLoading, error, deleteSession } = useSessions(agentId) - const { chat } = useRuntime() - const { activeSessionIdMap } = chat + const [activeSessionIdMap] = useCache('agent.session.active_id_map') const dispatch = useAppDispatch() const { createDefaultSession, creatingSession } = useCreateDefaultSession(agentId) - const setActiveSessionId = useCallback( - (agentId: string, sessionId: string | null) => { - dispatch(setActiveSessionIdAction({ agentId, sessionId })) - dispatch(setActiveTopicOrSessionAction('session')) - }, - [dispatch] - ) + const setActiveSessionId = useCallback((agentId: string, sessionId: string | null) => { + const currentMap = cacheService.get('agent.session.active_id_map') ?? {} + cacheService.set('agent.session.active_id_map', { ...currentMap, [agentId]: sessionId }) + cacheService.set('chat.active_view', 'session') + }, []) const handleDeleteSession = useCallback( async (id: string) => { @@ -47,19 +40,22 @@ const Sessions: React.FC = ({ agentId }) => { window.toast.error(t('agent.session.delete.error.last')) return } - dispatch(setSessionWaitingAction({ id, value: true })) + const waitingMap = cacheService.get('agent.session.waiting_id_map') ?? {} + cacheService.set('agent.session.waiting_id_map', { ...waitingMap, [id]: true }) const success = await deleteSession(id) if (success) { const newSessionId = sessions.find((s) => s.id !== id)?.id if (newSessionId) { - dispatch(setActiveSessionIdAction({ agentId, sessionId: newSessionId })) + const currentMap = cacheService.get('agent.session.active_id_map') ?? {} + cacheService.set('agent.session.active_id_map', { ...currentMap, [agentId]: newSessionId }) } else { // may clear messages instead of forbidden deletion } } - dispatch(setSessionWaitingAction({ id, value: false })) + const updatedMap = cacheService.get('agent.session.waiting_id_map') ?? {} + cacheService.set('agent.session.waiting_id_map', { ...updatedMap, [id]: false }) }, - [agentId, deleteSession, dispatch, sessions, t] + [agentId, deleteSession, sessions, t] ) const activeSessionId = activeSessionIdMap[agentId] diff --git a/src/renderer/src/pages/home/Tabs/components/Topics.tsx b/src/renderer/src/pages/home/Tabs/components/Topics.tsx index 323fa5a8d7..82ac433cbf 100644 --- a/src/renderer/src/pages/home/Tabs/components/Topics.tsx +++ b/src/renderer/src/pages/home/Tabs/components/Topics.tsx @@ -74,10 +74,10 @@ export const Topics: React.FC = ({ assistant: _assistant, activeTopic, se const [, setGenerating] = useCache('chat.generating') - const renamingTopics = useSelector((state: RootState) => state.runtime.chat.renamingTopics) + const [renamingTopics] = useCache('topic.renaming') const topicLoadingQuery = useSelector((state: RootState) => state.messages.loadingByTopic) const topicFulfilledQuery = useSelector((state: RootState) => state.messages.fulfilledByTopic) - const newlyRenamedTopics = useSelector((state: RootState) => state.runtime.chat.newlyRenamedTopics) + const [newlyRenamedTopics] = useCache('topic.newly_renamed') const borderRadius = showTopicTime ? 12 : 'var(--list-item-border-radius)' diff --git a/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx b/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx index dad657094a..ba4bc3fcee 100644 --- a/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx +++ b/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx @@ -1,8 +1,7 @@ import AddAssistantOrAgentPopup from '@renderer/components/Popups/AddAssistantOrAgentPopup' import AgentModalPopup from '@renderer/components/Popups/agent/AgentModal' +import { cacheService } from '@renderer/data/CacheService' import { useApiServer } from '@renderer/hooks/useApiServer' -import { useAppDispatch } from '@renderer/store' -import { setActiveTopicOrSessionAction } from '@renderer/store/runtime' import type { AgentEntity, Assistant, Topic } from '@renderer/types' import type { FC } from 'react' import { useCallback } from 'react' @@ -18,7 +17,6 @@ interface UnifiedAddButtonProps { const UnifiedAddButton: FC = ({ onCreateAssistant, setActiveAssistant, setActiveAgentId }) => { const { t } = useTranslation() - const dispatch = useAppDispatch() const { apiServerRunning, startApiServer } = useApiServer() const afterCreate = useCallback( @@ -41,9 +39,9 @@ const UnifiedAddButton: FC = ({ onCreateAssistant, setAct type: 'chat' }) setActiveAgentId(a.id) - dispatch(setActiveTopicOrSessionAction('session')) + cacheService.set('chat.active_view', 'session') }, - [dispatch, setActiveAgentId, setActiveAssistant] + [setActiveAgentId, setActiveAssistant] ) const handleAddButtonClick = async () => { diff --git a/src/renderer/src/pages/home/Tabs/hooks/useActiveAgent.ts b/src/renderer/src/pages/home/Tabs/hooks/useActiveAgent.ts index 8f65af437d..f8f902206b 100644 --- a/src/renderer/src/pages/home/Tabs/hooks/useActiveAgent.ts +++ b/src/renderer/src/pages/home/Tabs/hooks/useActiveAgent.ts @@ -1,18 +1,16 @@ +import { cacheService } from '@renderer/data/CacheService' import { useAgentSessionInitializer } from '@renderer/hooks/agents/useAgentSessionInitializer' -import { useAppDispatch } from '@renderer/store' -import { setActiveAgentId as setActiveAgentIdAction } from '@renderer/store/runtime' import { useCallback } from 'react' export const useActiveAgent = () => { - const dispatch = useAppDispatch() const { initializeAgentSession } = useAgentSessionInitializer() const setActiveAgentId = useCallback( async (id: string) => { - dispatch(setActiveAgentIdAction(id)) + cacheService.set('agent.active_id', id) await initializeAgentSession(id) }, - [dispatch, initializeAgentSession] + [initializeAgentSession] ) return { setActiveAgentId } diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx index 03106ce0f8..abcfd97fcc 100644 --- a/src/renderer/src/pages/home/Tabs/index.tsx +++ b/src/renderer/src/pages/home/Tabs/index.tsx @@ -1,14 +1,12 @@ import { usePreference } from '@data/hooks/usePreference' import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup' +import { useCache } from '@renderer/data/hooks/useCache' import { useActiveSession } from '@renderer/hooks/agents/useActiveSession' import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant' import { useNavbarPosition } from '@renderer/hooks/useNavbar' -import { useRuntime } from '@renderer/hooks/useRuntime' import { useShowTopics } from '@renderer/hooks/useStore' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' -import { useAppDispatch } from '@renderer/store' -import { setActiveAgentId, setActiveTopicOrSessionAction } from '@renderer/store/runtime' import type { Assistant, Topic } from '@renderer/types' import type { Tab } from '@renderer/types/chat' import { classNames, getErrorMessage, uuid } from '@renderer/utils' @@ -50,11 +48,10 @@ const HomeTabs: FC = ({ const { toggleShowTopics } = useShowTopics() const { isLeftNavbar } = useNavbarPosition() const { t } = useTranslation() - const { chat } = useRuntime() - const { activeTopicOrSession, activeAgentId } = chat + const [activeAgentId, setActiveAgentId] = useCache('agent.active_id') + const [activeTopicOrSession, setActiveTopicOrSession] = useCache('chat.active_view') const { session, isLoading: isSessionLoading, error: sessionError } = useActiveSession() const { updateSession } = useUpdateSession(activeAgentId) - const dispatch = useAppDispatch() const isSessionView = activeTopicOrSession === 'session' const isTopicView = activeTopicOrSession === 'topic' @@ -76,8 +73,8 @@ const HomeTabs: FC = ({ const assistant = await AddAssistantPopup.show() if (assistant) { setActiveAssistant(assistant) - dispatch(setActiveAgentId(null)) - dispatch(setActiveTopicOrSessionAction('topic')) + setActiveAgentId(null) + setActiveTopicOrSession('topic') } } @@ -85,8 +82,8 @@ const HomeTabs: FC = ({ const assistant = { ...defaultAssistant, id: uuid() } addAssistant(assistant) setActiveAssistant(assistant) - dispatch(setActiveAgentId(null)) - dispatch(setActiveTopicOrSessionAction('topic')) + setActiveAgentId(null) + setActiveTopicOrSession('topic') } useEffect(() => { diff --git a/src/renderer/src/pages/home/components/ChatNavbarContent.tsx b/src/renderer/src/pages/home/components/ChatNavbarContent.tsx index 5f9d30bd6d..92bed924e9 100644 --- a/src/renderer/src/pages/home/components/ChatNavbarContent.tsx +++ b/src/renderer/src/pages/home/components/ChatNavbarContent.tsx @@ -1,8 +1,8 @@ import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer' +import { useCache } from '@renderer/data/hooks/useCache' import { useActiveAgent } from '@renderer/hooks/agents/useActiveAgent' import { useActiveSession } from '@renderer/hooks/agents/useActiveSession' import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession' -import { useRuntime } from '@renderer/hooks/useRuntime' import type { AgentEntity, AgentSessionEntity, ApiModel, Assistant } from '@renderer/types' import { formatErrorMessageWithPrefix } from '@renderer/utils/error' import { t } from 'i18next' @@ -23,8 +23,7 @@ interface Props { } const ChatNavbarContent: FC = ({ assistant }) => { - const { chat } = useRuntime() - const { activeTopicOrSession } = chat + const [activeTopicOrSession] = useCache('chat.active_view') const { agent: activeAgent } = useActiveAgent() const { session: activeSession } = useActiveSession() const { updateModel } = useUpdateSession(activeAgent?.id ?? null) diff --git a/src/renderer/src/store/runtime.ts b/src/renderer/src/store/runtime.ts index 4411322556..125761f641 100644 --- a/src/renderer/src/store/runtime.ts +++ b/src/renderer/src/store/runtime.ts @@ -3,43 +3,41 @@ * //TODO @deprecated this file will be removed */ -import type { PayloadAction } from '@reduxjs/toolkit' import { createSlice } from '@reduxjs/toolkit' -import type { Topic, WebSearchStatus } from '@renderer/types' -import type { UpdateInfo } from 'builder-util-runtime' +// import type { Topic, WebSearchStatus } from '@renderer/types' -export interface ChatState { - isMultiSelectMode: boolean - selectedMessageIds: string[] - activeTopic: Topic | null - /** UI state. null represents no active agent */ - activeAgentId: string | null - /** UI state. Map agent id to active session id. - * null represents no active session */ - activeSessionIdMap: Record - /** meanwhile active Assistants or Agents */ - activeTopicOrSession: 'topic' | 'session' - /** topic ids that are currently being renamed */ - renamingTopics: string[] - /** topic ids that are newly renamed */ - newlyRenamedTopics: string[] - /** is a session waiting for updating/deleting. undefined and false share same semantics. */ - sessionWaiting: Record -} +// export interface ChatState { +// isMultiSelectMode: boolean +// selectedMessageIds: string[] +// activeTopic: Topic | null +// /** UI state. null represents no active agent */ +// activeAgentId: string | null +// /** UI state. Map agent id to active session id. +// * null represents no active session */ +// activeSessionIdMap: Record +// /** meanwhile active Assistants or Agents */ +// activeTopicOrSession: 'topic' | 'session' +// /** topic ids that are currently being renamed */ +// renamingTopics: string[] +// /** topic ids that are newly renamed */ +// newlyRenamedTopics: string[] +// /** is a session waiting for updating/deleting. undefined and false share same semantics. */ +// sessionWaiting: Record +// } -export interface WebSearchState { - activeSearches: Record -} +// export interface WebSearchState { +// activeSearches: Record +// } -export interface UpdateState { - info: UpdateInfo | null - checking: boolean - downloading: boolean - downloaded: boolean - downloadProgress: number - available: boolean - ignore: boolean -} +// export interface UpdateState { +// info: UpdateInfo | null +// checking: boolean +// downloading: boolean +// downloaded: boolean +// downloadProgress: number +// available: boolean +// ignore: boolean +// } export interface RuntimeState { // avatar: string @@ -59,13 +57,14 @@ export interface RuntimeState { // resourcesPath: string // update: UpdateState // export: ExportState - chat: ChatState + // chat: ChatState // websearch: WebSearchState + placeHolder: string } -export interface ExportState { - isExporting: boolean -} +// export interface ExportState { +// isExporting: boolean +// } const initialState: RuntimeState = { // avatar: UserAvatar, @@ -90,20 +89,21 @@ const initialState: RuntimeState = { // export: { // isExporting: false // }, - chat: { - isMultiSelectMode: false, - selectedMessageIds: [], - activeTopic: null, - activeAgentId: null, - activeTopicOrSession: 'topic', - activeSessionIdMap: {}, - renamingTopics: [], - newlyRenamedTopics: [], - sessionWaiting: {} - } + // chat: { + // isMultiSelectMode: false, + // selectedMessageIds: [], + // activeTopic: null, + // activeAgentId: null, + // activeTopicOrSession: 'topic', + // activeSessionIdMap: {}, + // renamingTopics: [], + // newlyRenamedTopics: [], + // sessionWaiting: {} + // } // websearch: { // activeSearches: {} // }, + placeHolder: '' } const runtimeSlice = createSlice({ @@ -163,16 +163,16 @@ const runtimeSlice = createSlice({ // @ts-ignore ts2589 false positive // state.chat.activeTopic = action.payload // }, - setActiveAgentId: (state, action: PayloadAction) => { - state.chat.activeAgentId = action.payload - }, - setActiveSessionIdAction: (state, action: PayloadAction<{ agentId: string; sessionId: string | null }>) => { - const { agentId, sessionId } = action.payload - state.chat.activeSessionIdMap[agentId] = sessionId - }, - setActiveTopicOrSessionAction: (state, action: PayloadAction<'topic' | 'session'>) => { - state.chat.activeTopicOrSession = action.payload - }, + // setActiveAgentId: (state, action: PayloadAction) => { + // state.chat.activeAgentId = action.payload + // }, + // setActiveSessionIdAction: (state, action: PayloadAction<{ agentId: string; sessionId: string | null }>) => { + // const { agentId, sessionId } = action.payload + // state.chat.activeSessionIdMap[agentId] = sessionId + // }, + // setActiveTopicOrSessionAction: (state, action: PayloadAction<'topic' | 'session'>) => { + // state.chat.activeTopicOrSession = action.payload + // }, // setRenamingTopics: (state, action: PayloadAction) => { // state.chat.renamingTopics = action.payload // }, @@ -191,9 +191,12 @@ const runtimeSlice = createSlice({ // state.websearch.activeSearches[requestId] = status // }, // setPlaceholder: (state, action: PayloadAction>) => {}, - setSessionWaitingAction: (state, action: PayloadAction<{ id: string; value: boolean }>) => { - const { id, value } = action.payload - state.chat.sessionWaiting[id] = value + // setSessionWaitingAction: (state, action: PayloadAction<{ id: string; value: boolean }>) => { + // const { id, value } = action.payload + // state.chat.sessionWaiting[id] = value + // } + setPlaceholder: (state, action: PayloadAction) => { + state.placeHolder = action.payload } } }) @@ -216,16 +219,16 @@ export const { // toggleMultiSelectMode, // setSelectedMessageIds, // setActiveTopic, - setActiveAgentId, - setActiveSessionIdAction, - setActiveTopicOrSessionAction, + // setActiveAgentId, + // setActiveSessionIdAction, + // setActiveTopicOrSessionAction, // setRenamingTopics, // setNewlyRenamedTopics, - setSessionWaitingAction + // setSessionWaitingAction // // WebSearch related actions // setActiveSearches, // setWebSearchStatus, - // setPlaceholder + setPlaceholder } = runtimeSlice.actions export default runtimeSlice.reducer