mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-30 07:39:06 +08:00
feat(agents): add agent management with delete confirmation
- Add agent deletion confirmation dialog with translations - Implement agent list display and drag-and-drop functionality - Include avatar support for agent types - Add success message for agent deletion
This commit is contained in:
parent
941a6666e6
commit
9a71a01b66
@ -20,7 +20,7 @@ import ClaudeIcon from '@renderer/assets/images/models/claude.png'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { useTimer } from '@renderer/hooks/useTimer'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { AgentEntity, isAgentType } from '@renderer/types'
|
||||
import { AgentEntity, AgentType, isAgentType } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { ChangeEvent, FormEvent, useCallback, useMemo, useRef, useState } from 'react'
|
||||
@ -204,7 +204,8 @@ export const AddAgentModal: React.FC = () => {
|
||||
instructions: form.instructions,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
model: form.model
|
||||
model: form.model,
|
||||
avatar: getAvatar(form.type)
|
||||
} satisfies AgentEntity
|
||||
logger.debug('Agent', agent)
|
||||
addAgent(agent)
|
||||
@ -282,3 +283,11 @@ export const AddAgentModal: React.FC = () => {
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
||||
const getAvatar = (type: AgentType) => {
|
||||
switch (type) {
|
||||
case 'claude-code':
|
||||
return ClaudeIcon
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { addAgent, removeAgent, setAgents, updateAgent } from '@renderer/store/agents'
|
||||
import { AgentEntity } from '@renderer/types'
|
||||
import { useCallback } from 'react'
|
||||
|
||||
export const useAgents = () => {
|
||||
const agents = useAppSelector((state) => state.agents.agentsNew)
|
||||
const dispatch = useAppDispatch()
|
||||
/**
|
||||
* Adds a new agent to the store
|
||||
@ -50,6 +51,7 @@ export const useAgents = () => {
|
||||
)
|
||||
|
||||
return {
|
||||
agents,
|
||||
addAgent: addAgent_,
|
||||
removeAgent: removeAgent_,
|
||||
updateAgent: updateAgent_,
|
||||
|
||||
@ -9,6 +9,13 @@
|
||||
"label": "Agent Type",
|
||||
"placeholder": "Select an agent type"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"content": "Deleting the agent will forcibly stop and delete all sessions associated with the agent. Are you sure?",
|
||||
"title": "Delete Agent"
|
||||
},
|
||||
"edit": {
|
||||
"title": "Edit Agent"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
@ -763,6 +770,7 @@
|
||||
"default": "Default",
|
||||
"delete": "Delete",
|
||||
"delete_confirm": "Are you sure you want to delete?",
|
||||
"delete_success": "Deleted successfully",
|
||||
"description": "Description",
|
||||
"detail": "Detail",
|
||||
"disabled": "Disabled",
|
||||
|
||||
@ -9,6 +9,10 @@
|
||||
"label": "Agent 类型",
|
||||
"placeholder": "选择 Agent 类型"
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"content": "删除该 Agent 将强制终止并删除该 Agent 下的所有会话。您确定吗?",
|
||||
"title": "删除 Agent"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
@ -763,6 +767,7 @@
|
||||
"default": "默认",
|
||||
"delete": "删除",
|
||||
"delete_confirm": "确定要删除吗?",
|
||||
"delete_success": "删除成功",
|
||||
"description": "描述",
|
||||
"detail": "详情",
|
||||
"disabled": "已禁用",
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
import { DownOutlined, RightOutlined } from '@ant-design/icons'
|
||||
import { Button } from '@heroui/react'
|
||||
import { Button, Divider } from '@heroui/react'
|
||||
import { DraggableList } from '@renderer/components/DraggableList'
|
||||
import { AddAgentModal } from '@renderer/components/Popups/AddAgentModal'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useAgents } from '@renderer/hooks/useAgents'
|
||||
import { useAssistants } from '@renderer/hooks/useAssistant'
|
||||
import { useAssistantPresets } from '@renderer/hooks/useAssistantPresets'
|
||||
import { useAssistantsTabSortType } from '@renderer/hooks/useStore'
|
||||
import { useTags } from '@renderer/hooks/useTags'
|
||||
import { Assistant, AssistantsSortType } from '@renderer/types'
|
||||
import { AgentEntity, Assistant, AssistantsSortType } from '@renderer/types'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { FC, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AgentItem from './components/AgentItem'
|
||||
import AssistantItem from './components/AssistantItem'
|
||||
|
||||
interface AssistantsTabProps {
|
||||
@ -35,6 +37,7 @@ const Assistants: FC<AssistantsTabProps> = ({
|
||||
const { getGroupedAssistants, collapsedTags, toggleTagCollapse } = useTags()
|
||||
const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const { agents, setAgents, removeAgent } = useAgents()
|
||||
|
||||
const onDelete = useCallback(
|
||||
(assistant: Assistant) => {
|
||||
@ -48,6 +51,14 @@ const Assistants: FC<AssistantsTabProps> = ({
|
||||
[activeAssistant, assistants, removeAssistant, setActiveAssistant, onCreateDefaultAssistant]
|
||||
)
|
||||
|
||||
const onDeleteAgent = useCallback(
|
||||
(agent: AgentEntity) => {
|
||||
removeAgent(agent.id)
|
||||
window.toast.success(t('common.delete_success'))
|
||||
},
|
||||
[removeAgent, t]
|
||||
)
|
||||
|
||||
const handleSortByChange = useCallback(
|
||||
(sortType: AssistantsSortType) => {
|
||||
setAssistantsTabSortType(sortType)
|
||||
@ -81,19 +92,6 @@ const Assistants: FC<AssistantsTabProps> = ({
|
||||
)
|
||||
}, [onCreateAssistant, t])
|
||||
|
||||
// const AddAgentButton = useCallback(() => {
|
||||
// return (
|
||||
// <AssistantAddItem onClick={onCreateAgent}>
|
||||
// <AddItemWrapper>
|
||||
// <Plus size={16} style={{ marginRight: 4, flexShrink: 0 }} />
|
||||
// <Typography.Text style={{ color: 'inherit' }} ellipsis={{ tooltip: t('agent.add.title') }}>
|
||||
// {t('agent.add.title')}
|
||||
// </Typography.Text>
|
||||
// </AddItemWrapper>
|
||||
// </AssistantAddItem>
|
||||
// )
|
||||
// }, [onCreateAgent, t])
|
||||
|
||||
if (assistantsTabSortType === 'tags') {
|
||||
return (
|
||||
<Container className="assistants-tab" ref={containerRef}>
|
||||
@ -150,6 +148,15 @@ const Assistants: FC<AssistantsTabProps> = ({
|
||||
|
||||
return (
|
||||
<Container className="assistants-tab" ref={containerRef}>
|
||||
<DraggableList
|
||||
list={agents}
|
||||
onUpdate={setAgents}
|
||||
onDragStart={() => setDragging(true)}
|
||||
onDragEnd={() => setDragging(false)}>
|
||||
{(agent) => <AgentItem agent={agent} isActive={false} onDelete={onDeleteAgent} />}
|
||||
</DraggableList>
|
||||
{!dragging && <AddAgentModal />}
|
||||
<Divider className="my-2" />
|
||||
<DraggableList
|
||||
list={assistants}
|
||||
onUpdate={updateAssistants}
|
||||
@ -171,8 +178,6 @@ const Assistants: FC<AssistantsTabProps> = ({
|
||||
)}
|
||||
</DraggableList>
|
||||
{!dragging && renderAddAssistantButton}
|
||||
{/* {!dragging && <AddAgentButton />} */}
|
||||
{!dragging && <AddAgentModal />}
|
||||
<div style={{ minHeight: 10 }}></div>
|
||||
</Container>
|
||||
)
|
||||
|
||||
134
src/renderer/src/pages/home/Tabs/components/AgentItem.tsx
Normal file
134
src/renderer/src/pages/home/Tabs/components/AgentItem.tsx
Normal file
@ -0,0 +1,134 @@
|
||||
import { Avatar, cn } from '@heroui/react'
|
||||
import { loggerService } from '@logger'
|
||||
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||
import { AgentEntity } from '@renderer/types'
|
||||
import { Dropdown, MenuProps } from 'antd'
|
||||
import { FC, memo, useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const logger = loggerService.withContext('AgentItem')
|
||||
|
||||
interface AgentItemProps {
|
||||
agent: AgentEntity
|
||||
isActive: boolean
|
||||
onDelete: (agent: AgentEntity) => void
|
||||
onTagClick?: (tag: string) => void
|
||||
}
|
||||
|
||||
const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete }) => {
|
||||
const { t } = useTranslation()
|
||||
// const { agents } = useAgents()
|
||||
|
||||
const menuItems = useMemo(
|
||||
() =>
|
||||
getMenuItems({
|
||||
agent,
|
||||
t,
|
||||
onDelete
|
||||
}),
|
||||
[agent, t, onDelete]
|
||||
)
|
||||
|
||||
const AgentLabel = useCallback(() => {
|
||||
return (
|
||||
<>
|
||||
{agent.avatar && <Avatar className="h-6 w-6" src={agent.avatar} />}
|
||||
<span className="text-sm">{agent.name}</span>
|
||||
</>
|
||||
)
|
||||
}, [agent.avatar, agent.name])
|
||||
|
||||
const handleClick = () => logger.debug('not implemented')
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{ items: menuItems }}
|
||||
trigger={['contextMenu']}
|
||||
popupRender={(menu) => <div onPointerDown={(e) => e.stopPropagation()}>{menu}</div>}>
|
||||
<Container onClick={handleClick} className={isActive ? 'active' : ''}>
|
||||
<AssistantNameRow className="name" title={agent.name}>
|
||||
<AgentLabel />
|
||||
</AssistantNameRow>
|
||||
</Container>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
|
||||
// 提取创建菜单配置的函数
|
||||
function getMenuItems({ agent, t, onDelete }): MenuProps['items'] {
|
||||
return [
|
||||
{
|
||||
label: t('common.edit'),
|
||||
key: 'edit',
|
||||
icon: <EditIcon size={14} />,
|
||||
onClick: () => window.toast.info('not implemented')
|
||||
},
|
||||
{
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const Container: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||
<div
|
||||
{...props}
|
||||
className={cn(
|
||||
'relative flex h-[37px] flex-row justify-between p-2',
|
||||
'rounded-[var(--list-item-border-radius)]',
|
||||
'border-[0.5px] border-transparent',
|
||||
'w-[calc(var(--assistants-width)_-_20px)]',
|
||||
'hover:bg-[var(--color-list-item-hover)]',
|
||||
className?.includes('active') && 'bg-[var(--color-list-item)] shadow-sm',
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
||||
const AssistantNameRow: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||
<div
|
||||
{...props}
|
||||
className={cn('text-[13px] text-[var(--color-text)]', 'flex flex-row items-center gap-2', className)}
|
||||
/>
|
||||
)
|
||||
|
||||
// const MenuButton: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||
// <div
|
||||
// {...props}
|
||||
// className={cn(
|
||||
// 'flex flex-row items-center justify-center',
|
||||
// 'h-[22px] min-h-[22px] min-w-[22px]',
|
||||
// 'absolute rounded-[11px]',
|
||||
// 'bg-[var(--color-background)]',
|
||||
// 'top-[6px] right-[9px]',
|
||||
// 'px-[5px]',
|
||||
// 'border-[0.5px] border-[var(--color-border)]',
|
||||
// className
|
||||
// )}
|
||||
// />
|
||||
// )
|
||||
|
||||
// const TopicCount: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||
// <div
|
||||
// {...props}
|
||||
// className={cn(
|
||||
// 'text-[10px] text-[var(--color-text)]',
|
||||
// 'rounded-[10px]',
|
||||
// 'flex flex-row items-center justify-center',
|
||||
// className
|
||||
// )}
|
||||
// />
|
||||
// )
|
||||
|
||||
export default memo(AgentItem)
|
||||
Loading…
Reference in New Issue
Block a user