mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 05:51:26 +08:00
feat(sessions): implement session management ui and state
- Rename Container to ButtonContainer for consistency - Add activeSessionId state to track active sessions per agent - Implement Sessions and SessionItem components with loading state - Add session selection and deletion functionality
This commit is contained in:
parent
b3ef6d4534
commit
f127150ea1
@ -36,11 +36,11 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete, onPress }) =
|
||||
<>
|
||||
<ContextMenu modal={false}>
|
||||
<ContextMenuTrigger>
|
||||
<Container onPress={onPress} className={isActive ? 'active' : ''}>
|
||||
<ButtonContainer onPress={onPress} className={isActive ? 'active' : ''}>
|
||||
<AssistantNameRow className="name" title={agent.name ?? agent.id}>
|
||||
<AgentLabel />
|
||||
</AssistantNameRow>
|
||||
</Container>
|
||||
</ButtonContainer>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem
|
||||
@ -73,7 +73,7 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete, onPress }) =
|
||||
)
|
||||
}
|
||||
|
||||
const Container: React.FC<React.ComponentProps<typeof Button>> = ({ className, children, ...props }) => (
|
||||
const ButtonContainer: React.FC<React.ComponentProps<typeof Button>> = ({ className, children, ...props }) => (
|
||||
<Button
|
||||
{...props}
|
||||
className={cn(
|
||||
|
||||
@ -1,45 +1,48 @@
|
||||
import { Button, cn, useDisclosure } from '@heroui/react'
|
||||
import { loggerService } from '@logger'
|
||||
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { AgentSessionEntity } from '@renderer/types'
|
||||
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu'
|
||||
import { FC, memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const logger = loggerService.withContext('AgentItem')
|
||||
// const logger = loggerService.withContext('AgentItem')
|
||||
|
||||
interface SessionItemProps {
|
||||
session: AgentSessionEntity
|
||||
isActive: boolean
|
||||
onDelete: (session: AgentSessionEntity) => void
|
||||
// use external agentId as SSOT, instead of session.agent_id
|
||||
agentId: string
|
||||
onDelete: () => void
|
||||
onPress: () => void
|
||||
}
|
||||
|
||||
const SessionItem: FC<SessionItemProps> = ({ session, isActive, onDelete, onPress }) => {
|
||||
const SessionItem: FC<SessionItemProps> = ({ session, agentId, onDelete, onPress }) => {
|
||||
const { t } = useTranslation()
|
||||
// const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const { onOpen } = useDisclosure()
|
||||
const { chat } = useRuntime()
|
||||
const activeSessionId = chat.activeSessionId[agentId]
|
||||
|
||||
const isActive = activeSessionId === session.id
|
||||
|
||||
const SessionLabel = useCallback(() => {
|
||||
const displayName = session.name ?? session.id
|
||||
return (
|
||||
<Button onPress={onPress}>
|
||||
<Button>
|
||||
<span className="text-sm">{displayName}</span>
|
||||
</Button>
|
||||
)
|
||||
}, [session.id, session.name, onPress])
|
||||
|
||||
const handleClick = () => logger.debug('not implemented')
|
||||
}, [session.id, session.name])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContextMenu modal={false}>
|
||||
<ContextMenuTrigger>
|
||||
<Container onClick={handleClick} className={isActive ? 'active' : ''}>
|
||||
<ButtonContainer onPress={onPress} className={isActive ? 'active' : ''}>
|
||||
<SessionLabelContainer className="name" title={session.name ?? session.id}>
|
||||
<SessionLabel />
|
||||
</SessionLabelContainer>
|
||||
</Container>
|
||||
</ButtonContainer>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent>
|
||||
<ContextMenuItem
|
||||
@ -59,7 +62,7 @@ const SessionItem: FC<SessionItemProps> = ({ session, isActive, onDelete, onPres
|
||||
content: t('agent.session.delete.content'),
|
||||
centered: true,
|
||||
okButtonProps: { danger: true },
|
||||
onOk: () => onDelete(session)
|
||||
onOk: () => onDelete()
|
||||
})
|
||||
}}>
|
||||
<DeleteIcon size={14} className="lucide-custom text-danger" />
|
||||
@ -72,20 +75,21 @@ const SessionItem: FC<SessionItemProps> = ({ session, isActive, onDelete, onPres
|
||||
)
|
||||
}
|
||||
|
||||
const Container: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||
<div
|
||||
const ButtonContainer: React.FC<React.ComponentProps<typeof Button>> = ({ className, children, ...props }) => (
|
||||
<Button
|
||||
{...props}
|
||||
className={cn(
|
||||
'relative flex h-[37px] flex-row justify-between p-2',
|
||||
'relative mb-2 flex h-[37px] flex-row justify-between p-2.5',
|
||||
'rounded-[var(--list-item-border-radius)]',
|
||||
'border-[0.5px] border-transparent',
|
||||
'w-[calc(var(--assistants-width)_-_20px)]',
|
||||
'hover:bg-[var(--color-list-item-hover)]',
|
||||
'bg-transparent hover:bg-[var(--color-list-item-hover)]',
|
||||
'cursor-pointer',
|
||||
className?.includes('active') && 'bg-[var(--color-list-item)] shadow-sm',
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)}>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
|
||||
const SessionLabelContainer: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||
|
||||
@ -1,23 +1,43 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { Spinner } from '@heroui/react'
|
||||
import { useSessions } from '@renderer/hooks/agents/useSessions'
|
||||
import { memo } from 'react'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setActiveSessionIdAction } from '@renderer/store/runtime'
|
||||
import { memo, useCallback } from 'react'
|
||||
|
||||
const logger = loggerService.withContext('SessionsTab')
|
||||
import SessionItem from './SessionItem'
|
||||
|
||||
// const logger = loggerService.withContext('SessionsTab')
|
||||
|
||||
interface SessionsProps {
|
||||
agentId: string
|
||||
}
|
||||
|
||||
const Sessions: React.FC<SessionsProps> = ({ agentId }) => {
|
||||
const { sessions } = useSessions(agentId)
|
||||
logger.debug('Sessions', sessions)
|
||||
const { sessions, isLoading, deleteSession } = useSessions(agentId)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const setActiveSessionId = useCallback(
|
||||
(agentId: string, sessionId: string | null) => {
|
||||
dispatch(setActiveSessionIdAction({ agentId, sessionId }))
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
if (isLoading) return <Spinner />
|
||||
|
||||
// if (error) return
|
||||
|
||||
return (
|
||||
<div className="agents-tab h-full w-full p-2">
|
||||
{/* TODO: Add session button */}
|
||||
Active Agent ID: {agentId}
|
||||
{sessions.map((session) => (
|
||||
<div key={session.id}>Not implemented</div>
|
||||
<SessionItem
|
||||
key={session.id}
|
||||
session={session}
|
||||
agentId={agentId}
|
||||
onDelete={() => deleteSession(session.id)}
|
||||
onPress={() => setActiveSessionId(agentId, session.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -7,8 +7,11 @@ export interface ChatState {
|
||||
isMultiSelectMode: boolean
|
||||
selectedMessageIds: string[]
|
||||
activeTopic: Topic | null
|
||||
/** UI state. null represents no active agent, may active assistant */
|
||||
/** UI state. null represents no active agent */
|
||||
activeAgentId: string | null
|
||||
/** UI state. Map agent id to active session id.
|
||||
* null represents no active session */
|
||||
activeSessionId: Record<string, string | null>
|
||||
/** topic ids that are currently being renamed */
|
||||
renamingTopics: string[]
|
||||
/** topic ids that are newly renamed */
|
||||
@ -81,6 +84,7 @@ const initialState: RuntimeState = {
|
||||
selectedMessageIds: [],
|
||||
activeTopic: null,
|
||||
activeAgentId: null,
|
||||
activeSessionId: {},
|
||||
renamingTopics: [],
|
||||
newlyRenamedTopics: []
|
||||
},
|
||||
@ -148,6 +152,10 @@ const runtimeSlice = createSlice({
|
||||
setActiveAgentId: (state, action: PayloadAction<string>) => {
|
||||
state.chat.activeAgentId = action.payload
|
||||
},
|
||||
setActiveSessionIdAction: (state, action: PayloadAction<{ agentId: string; sessionId: string | null }>) => {
|
||||
const { agentId, sessionId } = action.payload
|
||||
state.chat.activeSessionId[agentId] = sessionId
|
||||
},
|
||||
setRenamingTopics: (state, action: PayloadAction<string[]>) => {
|
||||
state.chat.renamingTopics = action.payload
|
||||
},
|
||||
@ -187,6 +195,7 @@ export const {
|
||||
setSelectedMessageIds,
|
||||
setActiveTopic,
|
||||
setActiveAgentId,
|
||||
setActiveSessionIdAction,
|
||||
setRenamingTopics,
|
||||
setNewlyRenamedTopics,
|
||||
// WebSearch related actions
|
||||
|
||||
Loading…
Reference in New Issue
Block a user