Merge branch 'v2' into feat/router-design

This commit is contained in:
MyPrototypeWhat 2025-12-10 11:35:45 +08:00 committed by GitHub
commit ec3c9db9ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 190 additions and 191 deletions

View File

@ -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<string, string | null>
'agent.session.waiting_id_map': Record<string, boolean>
}
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': {}
}
/**

View File

@ -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<TPath extends ConcreteApiPaths>([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<TPath extends ConcreteApiPaths>(
@ -139,6 +152,8 @@ export function useQuery<TPath extends ConcreteApiPaths>(
error?: Error
/** Function to manually refetch data */
refetch: () => void
/** SWR mutate function for optimistic updates */
mutate: KeyedMutator<ResponseForPath<TPath, 'GET'>>
} {
// Internal type conversion for SWR compatibility
const key = options?.enabled !== false ? buildSWRKey(path, options?.query as Record<string, any>) : null
@ -160,7 +175,8 @@ export function useQuery<TPath extends ConcreteApiPaths>(
data,
loading: isLoading || isValidating,
error: error as Error | undefined,
refetch
refetch,
mutate
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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]
)
/**

View File

@ -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<T> =
@ -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<Result<CreateAgentResponse>> => {
@ -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(

View File

@ -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,

View File

@ -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)
}

View File

@ -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> = (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)

View File

@ -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(() => {

View File

@ -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<InputbarInnerProps> = ({ 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(
() =>

View File

@ -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<Props> = 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()

View File

@ -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<AssistantsTabProps> = (props) => {
const containerRef = useRef<HTMLDivElement>(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

View File

@ -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<SessionsTabProps> = () => {
const { chat } = useRuntime()
const { activeAgentId } = chat
const [activeAgentId] = useCache('agent.active_id')
const { t } = useTranslation()
const { apiServer } = useSettings()

View File

@ -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> = (props) => {
const { chat } = useRuntime()
const { activeTopicOrSession } = chat
const [activeTopicOrSession] = useCache('chat.active_view')
if (activeTopicOrSession === 'topic') {
return <Topics {...props} />
}

View File

@ -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<AssistantItemProps> = ({
const { assistants, updateAssistants } = useAssistants()
const [isPending, setIsPending] = useState(false)
const dispatch = useAppDispatch()
useEffect(() => {
if (isActive) {
@ -145,8 +143,8 @@ const AssistantItem: FC<AssistantItemProps> = ({
}
}
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(

View File

@ -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<SessionItemProps> = ({ 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<AgentSessionEntity>(session)

View File

@ -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<SessionsProps> = ({ 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<SessionsProps> = ({ 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]

View File

@ -74,10 +74,10 @@ export const Topics: React.FC<Props> = ({ 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)'

View File

@ -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<UnifiedAddButtonProps> = ({ onCreateAssistant, setActiveAssistant, setActiveAgentId }) => {
const { t } = useTranslation()
const dispatch = useAppDispatch()
const { apiServerRunning, startApiServer } = useApiServer()
const afterCreate = useCallback(
@ -41,9 +39,9 @@ const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant, setAct
type: 'chat'
})
setActiveAgentId(a.id)
dispatch(setActiveTopicOrSessionAction('session'))
cacheService.set('chat.active_view', 'session')
},
[dispatch, setActiveAgentId, setActiveAssistant]
[setActiveAgentId, setActiveAssistant]
)
const handleAddButtonClick = async () => {

View File

@ -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 }

View File

@ -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<Props> = ({
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<Props> = ({
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<Props> = ({
const assistant = { ...defaultAssistant, id: uuid() }
addAssistant(assistant)
setActiveAssistant(assistant)
dispatch(setActiveAgentId(null))
dispatch(setActiveTopicOrSessionAction('topic'))
setActiveAgentId(null)
setActiveTopicOrSession('topic')
}
useEffect(() => {

View File

@ -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<Props> = ({ 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)

View File

@ -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<string, string | null>
/** 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<string, boolean>
}
// 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<string, string | null>
// /** 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<string, boolean>
// }
export interface WebSearchState {
activeSearches: Record<string, WebSearchStatus>
}
// export interface WebSearchState {
// activeSearches: Record<string, WebSearchStatus>
// }
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<string | null>) => {
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<string | null>) => {
// 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<string[]>) => {
// state.chat.renamingTopics = action.payload
// },
@ -191,9 +191,12 @@ const runtimeSlice = createSlice({
// state.websearch.activeSearches[requestId] = status
// },
// setPlaceholder: (state, action: PayloadAction<Partial<RuntimeState>>) => {},
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<string>) => {
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