refactor: replace ContextMenu with Dropdown in AgentItem and SessionItem components for improved context menu handling

This commit is contained in:
kangfenmao 2025-11-06 10:56:38 +08:00
parent 9df38c7e83
commit 4e01210df4
4 changed files with 131 additions and 125 deletions

View File

@ -5,12 +5,12 @@ import AgentSettingsPopup from '@renderer/pages/settings/AgentSettings/AgentSett
import { AgentLabel } from '@renderer/pages/settings/AgentSettings/shared'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import type { AgentEntity } from '@renderer/types'
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu'
import { cn } from '@renderer/utils'
import { Tooltip } from 'antd'
import type { MenuProps } from 'antd'
import { Dropdown, Tooltip } from 'antd'
import { Bot } from 'lucide-react'
import type { FC } from 'react'
import { memo, useCallback } from 'react'
import { memo, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
// const logger = loggerService.withContext('AgentItem')
@ -37,45 +37,52 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete, onPress }) =
onPress()
}, [clickAssistantToShowTopic, topicPosition, onPress])
const menuItems: MenuProps['items'] = useMemo(
() => [
{
label: t('common.edit'),
key: 'edit',
icon: <EditIcon size={14} />,
onClick: () => AgentSettingsPopup.show({ agentId: agent.id })
},
{
label: t('common.delete'),
key: 'delete',
icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true,
onClick: () => {
window.modal.confirm({
title: t('agent.delete.title'),
content: t('agent.delete.content'),
centered: true,
okButtonProps: { danger: true },
onOk: () => onDelete(agent)
})
}
}
],
[t, agent, onDelete]
)
return (
<ContextMenu modal={false}>
<ContextMenuTrigger>
<Container onClick={handlePress} isActive={isActive}>
<AssistantNameRow className="name" title={agent.name ?? agent.id}>
<AgentNameWrapper>
<AgentLabel agent={agent} />
</AgentNameWrapper>
{isActive && (
<MenuButton>
<SessionCount>{sessions.length}</SessionCount>
</MenuButton>
)}
{!isActive && <BotIcon />}
</AssistantNameRow>
</Container>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem key="edit" onClick={() => AgentSettingsPopup.show({ agentId: agent.id })}>
<EditIcon size={14} />
{t('common.edit')}
</ContextMenuItem>
<ContextMenuItem
key="delete"
className="text-danger"
onClick={() => {
window.modal.confirm({
title: t('agent.delete.title'),
content: t('agent.delete.content'),
centered: true,
okButtonProps: { danger: true },
onOk: () => onDelete(agent)
})
}}>
<DeleteIcon size={14} className="lucide-custom text-danger" />
<span className="text-danger">{t('common.delete')}</span>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
<Dropdown
menu={{ items: menuItems }}
trigger={['contextMenu']}
popupRender={(menu) => <div onPointerDown={(e) => e.stopPropagation()}>{menu}</div>}>
<Container onClick={handlePress} isActive={isActive}>
<AssistantNameRow className="name" title={agent.name ?? agent.id}>
<AgentNameWrapper>
<AgentLabel agent={agent} />
</AgentNameWrapper>
{isActive && (
<MenuButton>
<SessionCount>{sessions.length}</SessionCount>
</MenuButton>
)}
{!isActive && <BotIcon />}
</AssistantNameRow>
</Container>
</Dropdown>
)
}

View File

@ -10,18 +10,10 @@ import { SessionLabel } from '@renderer/pages/settings/AgentSettings/shared'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { newMessagesActions } from '@renderer/store/newMessage'
import type { AgentSessionEntity } from '@renderer/types'
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuTrigger
} from '@renderer/ui/context-menu'
import { classNames } from '@renderer/utils'
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
import { Tooltip } from 'antd'
import type { MenuProps } from 'antd'
import { Dropdown, Tooltip } from 'antd'
import { MenuIcon, XIcon } from 'lucide-react'
import type { FC } from 'react'
import React, { memo, startTransition, useEffect, useMemo, useState } from 'react'
@ -111,80 +103,86 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, onDelete, onPress
const { topicPosition, setTopicPosition } = useSettings()
const singlealone = topicPosition === 'right'
const menuItems: MenuProps['items'] = useMemo(
() => [
{
label: t('common.edit'),
key: 'edit',
icon: <EditIcon size={14} />,
onClick: () => {
SessionSettingsPopup.show({
agentId,
sessionId: session.id
})
}
},
{
label: t('settings.topic.position.label'),
key: 'topic-position',
icon: <MenuIcon size={14} />,
children: [
{
label: t('settings.topic.position.left'),
key: 'left',
onClick: () => setTopicPosition('left')
},
{
label: t('settings.topic.position.right'),
key: 'right',
onClick: () => setTopicPosition('right')
}
]
},
{
label: t('common.delete'),
key: 'delete',
icon: <DeleteIcon size={14} className="lucide-custom" />,
danger: true,
onClick: () => {
onDelete()
}
}
],
[t, agentId, session.id, setTopicPosition, onDelete]
)
return (
<>
<ContextMenu modal={false}>
<ContextMenuTrigger>
<SessionListItem
className={classNames(isActive ? 'active' : '', singlealone ? 'singlealone' : '')}
onClick={isEditing ? undefined : onPress}
onDoubleClick={() => startEdit(session.name ?? '')}
title={session.name ?? session.id}
style={{
borderRadius: 'var(--list-item-border-radius)',
cursor: isEditing ? 'default' : 'pointer'
}}>
{isPending && !isActive && <PendingIndicator />}
{isFulfilled && !isActive && <FulfilledIndicator />}
<SessionNameContainer>
{isEditing ? (
<SessionEditInput
ref={inputRef}
value={editValue}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleValueChange(e.target.value)}
onKeyDown={handleKeyDown}
onClick={(e: React.MouseEvent) => e.stopPropagation()}
style={{ opacity: isSaving ? 0.5 : 1 }}
/>
) : (
<>
<SessionName>
<SessionLabel session={session} />
</SessionName>
<DeleteButton />
</>
)}
</SessionNameContainer>
</SessionListItem>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem
key="edit"
onClick={() => {
SessionSettingsPopup.show({
agentId,
sessionId: session.id
})
}}>
<EditIcon size={14} />
{t('common.edit')}
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger className="gap-2">
<MenuIcon size={14} />
{t('settings.topic.position.label')}
</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuItem key="left" onClick={() => setTopicPosition('left')}>
{t('settings.topic.position.left')}
</ContextMenuItem>
<ContextMenuItem key="right" onClick={() => setTopicPosition('right')}>
{t('settings.topic.position.right')}
</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuItem
key="delete"
className="text-danger"
onClick={() => {
onDelete()
}}>
<DeleteIcon size={14} className="lucide-custom text-danger" />
<span className="text-danger">{t('common.delete')}</span>
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</>
<Dropdown
menu={{ items: menuItems }}
trigger={['contextMenu']}
popupRender={(menu) => <div onPointerDown={(e) => e.stopPropagation()}>{menu}</div>}>
<SessionListItem
className={classNames(isActive ? 'active' : '', singlealone ? 'singlealone' : '')}
onClick={isEditing ? undefined : onPress}
onDoubleClick={() => startEdit(session.name ?? '')}
title={session.name ?? session.id}
style={{
borderRadius: 'var(--list-item-border-radius)',
cursor: isEditing ? 'default' : 'pointer'
}}>
{isPending && !isActive && <PendingIndicator />}
{isFulfilled && !isActive && <FulfilledIndicator />}
<SessionNameContainer>
{isEditing ? (
<SessionEditInput
ref={inputRef}
value={editValue}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleValueChange(e.target.value)}
onKeyDown={handleKeyDown}
onClick={(e: React.MouseEvent) => e.stopPropagation()}
style={{ opacity: isSaving ? 0.5 : 1 }}
/>
) : (
<>
<SessionName>
<SessionLabel session={session} />
</SessionName>
<DeleteButton />
</>
)}
</SessionNameContainer>
</SessionListItem>
</Dropdown>
)
}

View File

@ -130,6 +130,7 @@ const Container = styled(Scrollbar)`
display: flex;
flex-direction: column;
padding: 12px 10px;
overflow-x: hidden;
`
export default memo(Sessions)

View File

@ -57,7 +57,7 @@ const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant, setAct
}
return (
<div className="mb-[6px]">
<div className="-mt-[4px] mb-[6px]">
<AddButton onClick={handleAddButtonClick}>{t('chat.add.assistant.title')}</AddButton>
</div>
)