From 9a71a01b663f368f75cacd7c777fd82115c0bb84 Mon Sep 17 00:00:00 2001 From: icarus Date: Sun, 14 Sep 2025 06:43:24 +0800 Subject: [PATCH] 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 --- .../src/components/Popups/AddAgentModal.tsx | 13 +- src/renderer/src/hooks/useAgents.ts | 4 +- src/renderer/src/i18n/locales/en-us.json | 8 ++ src/renderer/src/i18n/locales/zh-cn.json | 5 + .../src/pages/home/Tabs/AssistantsTab.tsx | 39 ++--- .../pages/home/Tabs/components/AgentItem.tsx | 134 ++++++++++++++++++ 6 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 src/renderer/src/pages/home/Tabs/components/AgentItem.tsx diff --git a/src/renderer/src/components/Popups/AddAgentModal.tsx b/src/renderer/src/components/Popups/AddAgentModal.tsx index 8dab9835c7..6d0a502fac 100644 --- a/src/renderer/src/components/Popups/AddAgentModal.tsx +++ b/src/renderer/src/components/Popups/AddAgentModal.tsx @@ -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 = () => { ) } + +const getAvatar = (type: AgentType) => { + switch (type) { + case 'claude-code': + return ClaudeIcon + } + return undefined +} diff --git a/src/renderer/src/hooks/useAgents.ts b/src/renderer/src/hooks/useAgents.ts index 9d66c36890..205e579338 100644 --- a/src/renderer/src/hooks/useAgents.ts +++ b/src/renderer/src/hooks/useAgents.ts @@ -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_, diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 6dcf51aaeb..7283213640 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -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", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index e67f75c87d..d1773e13c3 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -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": "已禁用", diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 5bd07e9189..25bf063142 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -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 = ({ const { getGroupedAssistants, collapsedTags, toggleTagCollapse } = useTags() const { assistantsTabSortType = 'list', setAssistantsTabSortType } = useAssistantsTabSortType() const containerRef = useRef(null) + const { agents, setAgents, removeAgent } = useAgents() const onDelete = useCallback( (assistant: Assistant) => { @@ -48,6 +51,14 @@ const Assistants: FC = ({ [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 = ({ ) }, [onCreateAssistant, t]) - // const AddAgentButton = useCallback(() => { - // return ( - // - // - // - // - // {t('agent.add.title')} - // - // - // - // ) - // }, [onCreateAgent, t]) - if (assistantsTabSortType === 'tags') { return ( @@ -150,6 +148,15 @@ const Assistants: FC = ({ return ( + setDragging(true)} + onDragEnd={() => setDragging(false)}> + {(agent) => } + + {!dragging && } + = ({ )} {!dragging && renderAddAssistantButton} - {/* {!dragging && } */} - {!dragging && }
) diff --git a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx new file mode 100644 index 0000000000..e94c8b444c --- /dev/null +++ b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx @@ -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 = ({ 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 && } + {agent.name} + + ) + }, [agent.avatar, agent.name]) + + const handleClick = () => logger.debug('not implemented') + + return ( +
e.stopPropagation()}>{menu}
}> + + + + + +
+ ) +} + +// 提取创建菜单配置的函数 +function getMenuItems({ agent, t, onDelete }): MenuProps['items'] { + return [ + { + label: t('common.edit'), + key: 'edit', + icon: , + onClick: () => window.toast.info('not implemented') + }, + { + label: t('common.delete'), + key: 'delete', + icon: , + 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> = ({ className, ...props }) => ( +
+) + +const AssistantNameRow: React.FC> = ({ className, ...props }) => ( +
+) + +// const MenuButton: React.FC> = ({ className, ...props }) => ( +//
+// ) + +// const TopicCount: React.FC> = ({ className, ...props }) => ( +//
+// ) + +export default memo(AgentItem)