feat(agent): implement add agent popup functionality

add new AddAgentPopup component with type selection
move agent-related translations to dedicated section
update UI to use new popup instead of placeholder toast
This commit is contained in:
icarus 2025-09-14 01:38:34 +08:00
parent 943fccf655
commit dc9fb381f7
4 changed files with 187 additions and 6 deletions

View File

@ -0,0 +1,175 @@
import {
Avatar,
Button,
Form,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Select,
SelectedItemProps,
SelectedItems,
SelectItem
} from '@heroui/react'
import ClaudeCodeIcon from '@renderer/assets/images/models/claude.png'
import { TopView } from '@renderer/components/TopView'
import { useAgents } from '@renderer/hooks/useAgents'
import { useTimer } from '@renderer/hooks/useTimer'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { AgentEntity } from '@renderer/types'
import { uuid } from '@renderer/utils'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
interface Props {
resolve: (value: AgentEntity | undefined) => void
}
type AgentTypeOption = {
key: AgentEntity['type']
name: AgentEntity['name']
avatar: AgentEntity['avatar']
}
const PopupContainer: React.FC<Props> = ({ resolve }) => {
const [open, setOpen] = useState(true)
const { t } = useTranslation()
const loadingRef = useRef(false)
const { setTimeoutTimer } = useTimer()
const { addAgent } = useAgents()
const Option = useCallback(
({ option }: { option?: AgentTypeOption | null }) => {
if (!option) {
return (
<div className="flex gap-2">
<Avatar name="?" className="h-5 w-5" />
{t('common.invalid_value')}
</div>
)
}
return (
<div className="flex gap-2">
<Avatar src={option.avatar} className="h-5 w-5" />
{option.name}
</div>
)
},
[t]
)
const Item = useCallback(
({ item }: { item: SelectedItemProps<AgentTypeOption> }) => <Option option={item.data} />,
[Option]
)
const renderValue = useCallback(
(items: SelectedItems<AgentTypeOption>) => items.map((item) => <Item key={item.key} item={item} />),
[Item]
)
// add supported agents type here
const agentConfig = useMemo(
() =>
[
{
key: 'claude-code',
name: 'Claude Code',
avatar: ClaudeCodeIcon
}
] as const satisfies AgentTypeOption[],
[]
)
const agentOptions: AgentTypeOption[] = useMemo(
() =>
agentConfig.map(
(option) =>
({
...option,
rendered: <Option option={option} />
}) as const satisfies SelectedItemProps
),
[Option, agentConfig]
)
const onCreateAgent = useCallback(
async (option: AgentTypeOption) => {
if (loadingRef.current) {
return
}
loadingRef.current = true
// TODO: update redux state
const agent = {
id: uuid(),
type: option.key,
name: option.name,
created_at: '',
updated_at: '',
model: ''
} satisfies AgentEntity
setTimeoutTimer('onCreateAgent', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
resolve(agent)
setOpen(false)
},
[setTimeoutTimer, resolve]
)
const onClose = async () => {
setOpen(false)
AddAgentPopup.hide()
resolve(undefined)
}
const onSubmit = async () => {
window.toast.info('not implemented :(')
}
return (
<Modal isOpen={open} onClose={onClose}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader>{t('agent.add.title')}</ModalHeader>
<ModalBody>
<Form>
<Select
items={agentOptions}
label={t('agent.add.label')}
placeholder={t('agent.add.placeholder')}
renderValue={renderValue}>
{(option) => (
<SelectItem key={option.key}>
<Option option={option} />
</SelectItem>
)}
</Select>
</Form>
</ModalBody>
<ModalFooter>
<Button onPress={onClose}>{t('common.close')}</Button>
<Button color="primary" onPress={onSubmit}>
{t('common.add')}
</Button>
</ModalFooter>
</>
)}
</ModalContent>
</Modal>
)
}
export default class AddAgentPopup {
static topviewId = 0
static hide() {
TopView.hide('AddAgentPopup')
}
static show() {
return new Promise<AgentEntity | undefined>((resolve) => {
TopView.show(<PopupContainer resolve={resolve} />, 'AddAgentPopup')
})
}
}

View File

@ -1,4 +1,11 @@
{
"agent": {
"add": {
"label": "Agent 类型",
"placeholder": "选择 Agent 类型",
"title": "添加 Agent"
}
},
"agents": {
"add": {
"button": "添加到助手",
@ -262,9 +269,6 @@
},
"chat": {
"add": {
"agent": {
"title": "添加 Agent"
},
"assistant": {
"title": "添加助手"
},
@ -772,6 +776,7 @@
"go_to_settings": "前往设置",
"i_know": "我知道了",
"inspect": "检查",
"invalid_value": "无效值",
"knowledge_base": "知识库",
"language": "语言",
"loading": "加载中...",

View File

@ -90,8 +90,8 @@ const Assistants: FC<AssistantsTabProps> = ({
<AssistantAddItem onClick={onCreateAgent}>
<AddItemWrapper>
<Plus size={16} style={{ marginRight: 4, flexShrink: 0 }} />
<Typography.Text style={{ color: 'inherit' }} ellipsis={{ tooltip: t('chat.add.agent.title') }}>
{t('chat.add.agent.title')}
<Typography.Text style={{ color: 'inherit' }} ellipsis={{ tooltip: t('agent.add.title') }}>
{t('agent.add.title')}
</Typography.Text>
</AddItemWrapper>
</AssistantAddItem>

View File

@ -1,3 +1,4 @@
import AddAgentPopup from '@renderer/components/Popups/AddAgentPopup'
import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup'
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings'
@ -63,7 +64,7 @@ const HomeTabs: FC<Props> = ({
}
const onCreateAgent = async () => {
window.toast.info('Not implemented')
await AddAgentPopup.show()
}
const onCreateDefaultAssistant = () => {