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 { AgentLabel } from '@renderer/pages/settings/AgentSettings/shared'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import type { AgentEntity } from '@renderer/types' import type { AgentEntity } from '@renderer/types'
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu'
import { cn } from '@renderer/utils' 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 { Bot } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import { memo, useCallback } from 'react' import { memo, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
// const logger = loggerService.withContext('AgentItem') // const logger = loggerService.withContext('AgentItem')
@ -37,45 +37,52 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete, onPress }) =
onPress() onPress()
}, [clickAssistantToShowTopic, topicPosition, 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 ( return (
<ContextMenu modal={false}> <Dropdown
<ContextMenuTrigger> menu={{ items: menuItems }}
<Container onClick={handlePress} isActive={isActive}> trigger={['contextMenu']}
<AssistantNameRow className="name" title={agent.name ?? agent.id}> popupRender={(menu) => <div onPointerDown={(e) => e.stopPropagation()}>{menu}</div>}>
<AgentNameWrapper> <Container onClick={handlePress} isActive={isActive}>
<AgentLabel agent={agent} /> <AssistantNameRow className="name" title={agent.name ?? agent.id}>
</AgentNameWrapper> <AgentNameWrapper>
{isActive && ( <AgentLabel agent={agent} />
<MenuButton> </AgentNameWrapper>
<SessionCount>{sessions.length}</SessionCount> {isActive && (
</MenuButton> <MenuButton>
)} <SessionCount>{sessions.length}</SessionCount>
{!isActive && <BotIcon />} </MenuButton>
</AssistantNameRow> )}
</Container> {!isActive && <BotIcon />}
</ContextMenuTrigger> </AssistantNameRow>
<ContextMenuContent> </Container>
<ContextMenuItem key="edit" onClick={() => AgentSettingsPopup.show({ agentId: agent.id })}> </Dropdown>
<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>
) )
} }

View File

@ -10,18 +10,10 @@ import { SessionLabel } from '@renderer/pages/settings/AgentSettings/shared'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { newMessagesActions } from '@renderer/store/newMessage' import { newMessagesActions } from '@renderer/store/newMessage'
import type { AgentSessionEntity } from '@renderer/types' 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 { classNames } from '@renderer/utils'
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession' 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 { MenuIcon, XIcon } from 'lucide-react'
import type { FC } from 'react' import type { FC } from 'react'
import React, { memo, startTransition, useEffect, useMemo, useState } 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 { topicPosition, setTopicPosition } = useSettings()
const singlealone = topicPosition === 'right' 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 ( return (
<> <Dropdown
<ContextMenu modal={false}> menu={{ items: menuItems }}
<ContextMenuTrigger> trigger={['contextMenu']}
<SessionListItem popupRender={(menu) => <div onPointerDown={(e) => e.stopPropagation()}>{menu}</div>}>
className={classNames(isActive ? 'active' : '', singlealone ? 'singlealone' : '')} <SessionListItem
onClick={isEditing ? undefined : onPress} className={classNames(isActive ? 'active' : '', singlealone ? 'singlealone' : '')}
onDoubleClick={() => startEdit(session.name ?? '')} onClick={isEditing ? undefined : onPress}
title={session.name ?? session.id} onDoubleClick={() => startEdit(session.name ?? '')}
style={{ title={session.name ?? session.id}
borderRadius: 'var(--list-item-border-radius)', style={{
cursor: isEditing ? 'default' : 'pointer' borderRadius: 'var(--list-item-border-radius)',
}}> cursor: isEditing ? 'default' : 'pointer'
{isPending && !isActive && <PendingIndicator />} }}>
{isFulfilled && !isActive && <FulfilledIndicator />} {isPending && !isActive && <PendingIndicator />}
<SessionNameContainer> {isFulfilled && !isActive && <FulfilledIndicator />}
{isEditing ? ( <SessionNameContainer>
<SessionEditInput {isEditing ? (
ref={inputRef} <SessionEditInput
value={editValue} ref={inputRef}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleValueChange(e.target.value)} value={editValue}
onKeyDown={handleKeyDown} onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleValueChange(e.target.value)}
onClick={(e: React.MouseEvent) => e.stopPropagation()} onKeyDown={handleKeyDown}
style={{ opacity: isSaving ? 0.5 : 1 }} onClick={(e: React.MouseEvent) => e.stopPropagation()}
/> style={{ opacity: isSaving ? 0.5 : 1 }}
) : ( />
<> ) : (
<SessionName> <>
<SessionLabel session={session} /> <SessionName>
</SessionName> <SessionLabel session={session} />
<DeleteButton /> </SessionName>
</> <DeleteButton />
)} </>
</SessionNameContainer> )}
</SessionListItem> </SessionNameContainer>
</ContextMenuTrigger> </SessionListItem>
<ContextMenuContent> </Dropdown>
<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>
</>
) )
} }

View File

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

View File

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