fix(chat): handle agent and session state changes properly

update activeAgentId type to allow null and reset state when deleting agent
add validation for missing agent/session in UI components
This commit is contained in:
icarus 2025-09-26 14:31:56 +08:00
parent c52cc5a94f
commit 0be2177937
4 changed files with 53 additions and 24 deletions

View File

@ -1,9 +1,12 @@
import { useAppDispatch } from '@renderer/store'
import { setActiveAgentId, setActiveSessionIdAction } from '@renderer/store/runtime'
import { AddAgentForm } from '@renderer/types'
import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import useSWR from 'swr'
import { useRuntime } from '../useRuntime'
import { useAgentClient } from './useAgentClient'
export const useAgents = () => {
@ -16,6 +19,9 @@ export const useAgents = () => {
return result.data
}, [client])
const { data, error, isLoading, mutate } = useSWR(key, fetcher)
const { chat } = useRuntime()
const { activeAgentId } = chat
const dispatch = useAppDispatch()
const addAgent = useCallback(
async (form: AddAgentForm) => {
@ -34,6 +40,15 @@ export const useAgents = () => {
async (id: string) => {
try {
await client.deleteAgent(id)
dispatch(setActiveSessionIdAction({ agentId: id, sessionId: null }))
if (activeAgentId === id) {
const newId = data?.filter((a) => a.id !== id).find(() => true)?.id
if (newId) {
dispatch(setActiveAgentId(newId))
} else {
dispatch(setActiveAgentId(null))
}
}
mutate((prev) => prev?.filter((a) => a.id !== id) ?? [])
window.toast.success(t('common.delete_success'))
} catch (error) {

View File

@ -18,7 +18,7 @@ import { classNames } from '@renderer/utils'
import { Flex } from 'antd'
import { debounce } from 'lodash'
import { AnimatePresence, motion } from 'motion/react'
import React, { FC, useMemo, useState } from 'react'
import React, { FC, useCallback, useMemo, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -172,6 +172,27 @@ const Chat: FC<Props> = (props) => {
return () => <AgentSessionInputbar agentId={activeAgentId} sessionId={sessionId} />
}, [activeAgentId, activeSessionId])
// TODO: more info
const AgentInvalid = useCallback(() => {
return (
<div className="flex h-full w-full items-center justify-center">
<div>
<Alert color="warning" title="Select an agent" />
</div>
</div>
)
}, [])
// TODO: more info
const SessionInvalid = useCallback(() => {
return (
<div className="flex h-full w-full items-center justify-center">
<div>
<Alert color="warning" title="Create a session" />
</div>
</div>
)
}, [])
return (
<Container id="chat" className={classNames([messageStyle, { 'multi-select-mode': isMultiSelectMode }])}>
{isTopNavbar && (
@ -213,7 +234,11 @@ const Chat: FC<Props> = (props) => {
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} topic={props.activeTopic} />
</>
)}
{activeTopicOrSession === 'session' && (
{activeTopicOrSession === 'session' && !activeAgentId && <AgentInvalid />}
{activeTopicOrSession === 'session' && activeAgentId && !activeSessionId[activeAgentId] && (
<SessionInvalid />
)}
{activeTopicOrSession === 'session' && activeAgentId && activeSessionId[activeAgentId] && (
<>
<SessionMessages />
<SessionInputBar />

View File

@ -1,4 +1,4 @@
import { Alert, cn, Spinner } from '@heroui/react'
import { Alert, cn } from '@heroui/react'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { AnimatePresence, motion } from 'framer-motion'
@ -24,6 +24,14 @@ const SessionsTab: FC<SessionsTabProps> = () => {
)
}
if (!activeAgentId) {
return (
<div>
<Alert color="warning" title={'Select an agent'} />
</div>
)
}
return (
<AnimatePresence mode="wait">
<motion.div
@ -35,26 +43,7 @@ const SessionsTab: FC<SessionsTabProps> = () => {
'overflow-hidden',
topicPosition === 'right' && navbarPosition === 'top' ? 'rounded-l-2xl border-t border-b border-l' : undefined
)}>
{!activeAgentId ? (
<motion.div
key="loading"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
className="flex h-full flex-col items-center justify-center gap-3">
<Spinner size="lg" color="primary" />
<motion.p
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.3 }}
className="text-foreground-500 text-sm">
{t('common.loading')}...
</motion.p>
</motion.div>
) : (
<Sessions agentId={activeAgentId} />
)}
<Sessions agentId={activeAgentId} />
</motion.div>
</AnimatePresence>
)

View File

@ -158,7 +158,7 @@ const runtimeSlice = createSlice({
// @ts-ignore ts2589 false positive
state.chat.activeTopic = action.payload
},
setActiveAgentId: (state, action: PayloadAction<string>) => {
setActiveAgentId: (state, action: PayloadAction<string | null>) => {
state.chat.activeAgentId = action.payload
},
setActiveSessionIdAction: (state, action: PayloadAction<{ agentId: string; sessionId: string | null }>) => {