refactor: update AgentModal to use TopView for improved modal management and enhance form structure

This commit is contained in:
kangfenmao 2025-11-05 12:06:58 +08:00
parent b334a2c5be
commit 720284262f
2 changed files with 367 additions and 226 deletions

View File

@ -1,21 +1,7 @@
import type { SelectedItemProps } from '@heroui/react'
import {
Button,
Form,
Input,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Select,
SelectItem,
Textarea,
useDisclosure
} from '@heroui/react'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import type { Selection } from '@react-types/shared'
import ClaudeIcon from '@renderer/assets/images/models/claude.png' import ClaudeIcon from '@renderer/assets/images/models/claude.png'
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
import { TopView } from '@renderer/components/TopView'
import { permissionModeCards } from '@renderer/config/agent' import { permissionModeCards } from '@renderer/config/agent'
import { agentModelFilter, getModelLogoById } from '@renderer/config/models' import { agentModelFilter, getModelLogoById } from '@renderer/config/models'
import { useAgents } from '@renderer/hooks/agents/useAgents' import { useAgents } from '@renderer/hooks/agents/useAgents'
@ -31,14 +17,16 @@ import type {
UpdateAgentForm UpdateAgentForm
} from '@renderer/types' } from '@renderer/types'
import { AgentConfigurationSchema, isAgentType } from '@renderer/types' import { AgentConfigurationSchema, isAgentType } from '@renderer/types'
import { Avatar, Button, Input, Modal, Select } from 'antd'
import { AlertTriangleIcon } from 'lucide-react' import { AlertTriangleIcon } from 'lucide-react'
import type { ChangeEvent, FormEvent } from 'react' import type { ChangeEvent, FormEvent } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { ErrorBoundary } from '../../ErrorBoundary' import type { BaseOption } from './shared'
import type { BaseOption, ModelOption } from './shared'
import { Option, renderOption } from './shared' const { TextArea } = Input
const logger = loggerService.withContext('AddAgentPopup') const logger = loggerService.withContext('AddAgentPopup')
@ -48,8 +36,6 @@ interface AgentTypeOption extends BaseOption {
name: AgentEntity['name'] name: AgentEntity['name']
} }
type Option = AgentTypeOption | ModelOption
type AgentWithTools = AgentEntity & { tools?: Tool[] } type AgentWithTools = AgentEntity & { tools?: Tool[] }
const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({ const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({
@ -64,58 +50,38 @@ const buildAgentForm = (existing?: AgentWithTools): BaseAgentForm => ({
configuration: AgentConfigurationSchema.parse(existing?.configuration ?? {}) configuration: AgentConfigurationSchema.parse(existing?.configuration ?? {})
}) })
type Props = { interface ShowParams {
agent?: AgentWithTools agent?: AgentWithTools
isOpen: boolean
onClose: () => void
afterSubmit?: (a: AgentEntity) => void afterSubmit?: (a: AgentEntity) => void
} }
/** interface Props extends ShowParams {
* Modal component for creating or editing an agent. resolve: (data: any) => void
* }
* Either trigger or isOpen and onClose is given.
* @param agent - Optional agent entity for editing mode. const PopupContainer: React.FC<Props> = ({ agent, afterSubmit, resolve }) => {
* @param isOpen - Optional controlled modal open state. From useDisclosure.
* @param onClose - Optional callback when modal closes. From useDisclosure.
* @returns Modal component for agent creation/editing
*/
export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _onClose, afterSubmit }) => {
const { isOpen, onClose } = useDisclosure({ isOpen: _isOpen, onClose: _onClose })
const { t } = useTranslation() const { t } = useTranslation()
const [open, setOpen] = useState(true)
const loadingRef = useRef(false) const loadingRef = useRef(false)
// const { setTimeoutTimer } = useTimer()
const { addAgent } = useAgents() const { addAgent } = useAgents()
const { updateAgent } = useUpdateAgent() const { updateAgent } = useUpdateAgent()
// hard-coded. We only support anthropic for now.
const { models } = useApiModels({ providerType: 'anthropic' }) const { models } = useApiModels({ providerType: 'anthropic' })
const isEditing = (agent?: AgentWithTools) => agent !== undefined const isEditing = (agent?: AgentWithTools) => agent !== undefined
const [form, setForm] = useState<BaseAgentForm>(() => buildAgentForm(agent)) const [form, setForm] = useState<BaseAgentForm>(() => buildAgentForm(agent))
useEffect(() => { useEffect(() => {
if (isOpen) { if (open) {
setForm(buildAgentForm(agent)) setForm(buildAgentForm(agent))
} }
}, [agent, isOpen]) }, [agent, open])
const selectedPermissionMode = form.configuration?.permission_mode ?? 'default' const selectedPermissionMode = form.configuration?.permission_mode ?? 'default'
const onPermissionModeChange = useCallback((keys: Selection) => { const onPermissionModeChange = useCallback((value: PermissionMode) => {
if (keys === 'all') {
return
}
const [first] = Array.from(keys)
if (!first) {
return
}
setForm((prev) => { setForm((prev) => {
const parsedConfiguration = AgentConfigurationSchema.parse(prev.configuration ?? {}) const parsedConfiguration = AgentConfigurationSchema.parse(prev.configuration ?? {})
const nextMode = first as PermissionMode if (parsedConfiguration.permission_mode === value) {
if (parsedConfiguration.permission_mode === nextMode) {
if (!prev.configuration) { if (!prev.configuration) {
return { return {
...prev, ...prev,
@ -129,7 +95,7 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
...prev, ...prev,
configuration: { configuration: {
...parsedConfiguration, ...parsedConfiguration,
permission_mode: nextMode permission_mode: value
} }
} }
}) })
@ -150,55 +116,57 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
[] []
) )
const agentOptions: AgentTypeOption[] = useMemo( const agentOptions = useMemo(
() => () =>
agentConfig.map( agentConfig.map((option) => ({
(option) => value: option.key,
({ label: (
...option, <OptionWrapper>
rendered: <Option option={option} /> <Avatar src={option.avatar} size={24} />
}) as const satisfies SelectedItemProps <span>{option.label}</span>
), </OptionWrapper>
)
})),
[agentConfig] [agentConfig]
) )
const onAgentTypeChange = useCallback( const onAgentTypeChange = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => { (value: AgentType) => {
const prevConfig = agentConfig.find((config) => config.key === form.type) const prevConfig = agentConfig.find((config) => config.key === form.type)
let newName: string | undefined = form.name let newName: string | undefined = form.name
if (prevConfig && prevConfig.name === form.name) { if (prevConfig && prevConfig.name === form.name) {
const newConfig = agentConfig.find((config) => config.key === e.target.value) const newConfig = agentConfig.find((config) => config.key === value)
if (newConfig) { if (newConfig) {
newName = newConfig.name newName = newConfig.name
} }
} }
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
type: e.target.value as AgentType, type: value,
name: newName name: newName
})) }))
}, },
[agentConfig, form.name, form.type] [agentConfig, form.name, form.type]
) )
const onNameChange = useCallback((name: string) => { const onNameChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
name name: e.target.value
})) }))
}, []) }, [])
const onDescChange = useCallback((description: string) => { const onDescChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
description description: e.target.value
})) }))
}, []) }, [])
const onInstChange = useCallback((instructions: string) => { const onInstChange = useCallback((e: ChangeEvent<HTMLTextAreaElement>) => {
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
instructions instructions: e.target.value
})) }))
}, []) }, [])
@ -232,7 +200,6 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
}, []) }, [])
const modelOptions = useMemo(() => { const modelOptions = useMemo(() => {
// mocked data. not final version
return (models ?? []) return (models ?? [])
.filter((m) => .filter((m) =>
agentModelFilter({ agentModelFilter({
@ -243,22 +210,31 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
}) })
) )
.map((model) => ({ .map((model) => ({
type: 'model', value: model.id,
key: model.id, label: (
label: model.name, <OptionWrapper>
avatar: getModelLogoById(model.id), <Avatar src={getModelLogoById(model.id)} size={24} />
providerId: model.provider, <span>{model.name}</span>
providerName: model.provider_name </OptionWrapper>
})) satisfies ModelOption[] )
}))
}, [models]) }, [models])
const onModelChange = useCallback((e: ChangeEvent<HTMLSelectElement>) => { const onModelChange = useCallback((value: string) => {
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
model: e.target.value model: value
})) }))
}, []) }, [])
const onCancel = () => {
setOpen(false)
}
const onClose = () => {
resolve({})
}
const onSubmit = useCallback( const onSubmit = useCallback(
async (e: FormEvent<HTMLFormElement>) => { async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault() e.preventDefault()
@ -330,9 +306,7 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
afterSubmit?.(result.data) afterSubmit?.(result.data)
} }
loadingRef.current = false loadingRef.current = false
setOpen(false)
// setTimeoutTimer('onCreateAgent', () => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
onClose()
}, },
[ [
form.type, form.type,
@ -344,7 +318,6 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
form.allowed_tools, form.allowed_tools,
form.configuration, form.configuration,
agent, agent,
onClose,
t, t,
updateAgent, updateAgent,
afterSubmit, afterSubmit,
@ -352,138 +325,309 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
] ]
) )
AgentModalPopup.hide = onCancel
return ( return (
<ErrorBoundary> <ErrorBoundary>
<Modal <Modal
isOpen={isOpen} title={isEditing(agent) ? t('agent.edit.title') : t('agent.add.title')}
onClose={onClose} open={open}
classNames={{ onCancel={onCancel}
base: 'max-h-[90vh]', afterClose={onClose}
wrapper: 'overflow-hidden' transitionName="animation-move-down"
}}> centered
<ModalContent> width={680}
{(onClose) => ( footer={null}>
<> <StyledForm onSubmit={onSubmit}>
<ModalHeader>{isEditing(agent) ? t('agent.edit.title') : t('agent.add.title')}</ModalHeader> <FormContent>
<Form onSubmit={onSubmit} className="min-h-0 w-full shrink overflow-auto"> <FormRow>
<ModalBody className="min-h-0 w-full flex-1 shrink overflow-auto"> <FormItem style={{ flex: 1 }}>
<div className="flex gap-2"> <Label>{t('agent.type.label')}</Label>
<Select <Select
isRequired value={form.type}
isDisabled={isEditing(agent)} onChange={onAgentTypeChange}
selectionMode="single" options={agentOptions}
selectedKeys={[form.type]} disabled={isEditing(agent)}
disallowEmptySelection style={{ width: '100%' }}
onChange={onAgentTypeChange} />
items={agentOptions} </FormItem>
label={t('agent.type.label')} <FormItem style={{ flex: 1 }}>
placeholder={t('agent.add.type.placeholder')} <Label>
renderValue={renderOption}> {t('common.name')} <RequiredMark>*</RequiredMark>
{(option) => ( </Label>
<SelectItem key={option.key} textValue={option.label}> <Input value={form.name} onChange={onNameChange} required />
<Option option={option} /> </FormItem>
</SelectItem> </FormRow>
)}
</Select> <FormItem>
<Input isRequired value={form.name} onValueChange={onNameChange} label={t('common.name')} /> <Label>
</div> {t('common.model')} <RequiredMark>*</RequiredMark>
<Select </Label>
isRequired <Select
selectionMode="single" value={form.model || undefined}
selectedKeys={form.model ? [form.model] : []} onChange={onModelChange}
disallowEmptySelection options={modelOptions}
onChange={onModelChange} placeholder={t('common.placeholders.select.model')}
items={modelOptions} style={{ width: '100%' }}
label={t('common.model')} showSearch
placeholder={t('common.placeholders.select.model')} filterOption={(input, option) => {
renderValue={renderOption}> const label = option?.label as any
{(option) => ( return label?.props?.children?.[1]?.toLowerCase().includes(input.toLowerCase()) || false
<SelectItem key={option.key} textValue={option.label}> }}
<Option option={option} /> />
</SelectItem> </FormItem>
)}
</Select> <FormItem>
<Select <Label>
isRequired {t('agent.settings.tooling.permissionMode.title', 'Permission mode')} <RequiredMark>*</RequiredMark>
selectionMode="single" </Label>
selectedKeys={[selectedPermissionMode]} <Select
onSelectionChange={onPermissionModeChange} value={selectedPermissionMode}
label={t('agent.settings.tooling.permissionMode.title', 'Permission mode')} onChange={onPermissionModeChange}
placeholder={t('agent.settings.tooling.permissionMode.placeholder', 'Select permission mode')} style={{ width: '100%' }}
description={t( placeholder={t('agent.settings.tooling.permissionMode.placeholder', 'Select permission mode')}
'agent.settings.tooling.permissionMode.helper', dropdownStyle={{ minWidth: '500px' }}
'Choose how the agent handles tool approvals.' optionLabelProp="label">
)} {permissionModeCards.map((item) => (
items={permissionModeCards}> <Select.Option key={item.mode} value={item.mode} label={t(item.titleKey, item.titleFallback)}>
{(item) => ( <PermissionOptionWrapper>
<SelectItem key={item.mode} textValue={t(item.titleKey, item.titleFallback)}> <div className="title">{t(item.titleKey, item.titleFallback)}</div>
<div className="flex flex-col gap-1"> <div className="description">{t(item.descriptionKey, item.descriptionFallback)}</div>
<span className="font-medium text-sm">{t(item.titleKey, item.titleFallback)}</span> <div className="behavior">{t(item.behaviorKey, item.behaviorFallback)}</div>
<span className="text-foreground-500 text-xs"> {item.caution && (
{t(item.descriptionKey, item.descriptionFallback)} <div className="caution">
</span> <AlertTriangleIcon size={12} />
<span className="text-foreground-400 text-xs"> {t(
{t(item.behaviorKey, item.behaviorFallback)} 'agent.settings.tooling.permissionMode.bypassPermissions.warning',
</span> 'Use with caution — all tools will run without asking for approval.'
{item.caution ? ( )}
<span className="flex items-center gap-1 text-danger-500 text-xs">
<AlertTriangleIcon size={12} className="text-danger" />
{t(
'agent.settings.tooling.permissionMode.bypassPermissions.warning',
'Use with caution — all tools will run without asking for approval.'
)}
</span>
) : null}
</div> </div>
</SelectItem> )}
)} </PermissionOptionWrapper>
</Select> </Select.Option>
<div className="space-y-2"> ))}
<div className="flex items-center justify-between"> </Select>
<span className="font-medium text-foreground text-sm"> <HelpText>
{t('agent.session.accessible_paths.label')} {t('agent.settings.tooling.permissionMode.helper', 'Choose how the agent handles tool approvals.')}
</span> </HelpText>
<Button size="sm" variant="flat" onPress={addAccessiblePath}> </FormItem>
{t('agent.session.accessible_paths.add')}
<FormItem>
<LabelWithButton>
<Label>
{t('agent.session.accessible_paths.label')} <RequiredMark>*</RequiredMark>
</Label>
<Button size="small" onClick={addAccessiblePath}>
{t('agent.session.accessible_paths.add')}
</Button>
</LabelWithButton>
{form.accessible_paths.length > 0 ? (
<PathList>
{form.accessible_paths.map((path) => (
<PathItem key={path}>
<PathText title={path}>{path}</PathText>
<Button size="small" danger onClick={() => removeAccessiblePath(path)}>
{t('common.delete')}
</Button> </Button>
</div> </PathItem>
{form.accessible_paths.length > 0 ? ( ))}
<div className="space-y-2"> </PathList>
{form.accessible_paths.map((path) => ( ) : (
<div <EmptyText>{t('agent.session.accessible_paths.empty')}</EmptyText>
key={path} )}
className="flex items-center justify-between gap-2 rounded-medium border border-default-200 px-3 py-2"> </FormItem>
<span className="truncate text-sm" title={path}>
{path} <FormItem>
</span> <Label>{t('common.prompt')}</Label>
<Button size="sm" variant="light" color="danger" onPress={() => removeAccessiblePath(path)}> <TextArea rows={3} value={form.instructions ?? ''} onChange={onInstChange} />
{t('common.delete')} </FormItem>
</Button>
</div> <FormItem>
))} <Label>{t('common.description')}</Label>
</div> <TextArea rows={2} value={form.description ?? ''} onChange={onDescChange} />
) : ( </FormItem>
<p className="text-foreground-400 text-sm">{t('agent.session.accessible_paths.empty')}</p> </FormContent>
)}
</div> <FormFooter>
<Textarea label={t('common.prompt')} value={form.instructions ?? ''} onValueChange={onInstChange} /> <Button onClick={onCancel}>{t('common.close')}</Button>
<Textarea <Button type="primary" htmlType="submit" loading={loadingRef.current}>
label={t('common.description')} {isEditing(agent) ? t('common.confirm') : t('common.add')}
value={form.description ?? ''} </Button>
onValueChange={onDescChange} </FormFooter>
/> </StyledForm>
</ModalBody>
<ModalFooter className="w-full">
<Button onPress={onClose}>{t('common.close')}</Button>
<Button color="primary" type="submit" isLoading={loadingRef.current}>
{isEditing(agent) ? t('common.confirm') : t('common.add')}
</Button>
</ModalFooter>
</Form>
</>
)}
</ModalContent>
</Modal> </Modal>
</ErrorBoundary> </ErrorBoundary>
) )
} }
const TopViewKey = 'AgentModalPopup'
export default class AgentModalPopup {
static topviewId = 0
static hide() {
TopView.hide(TopViewKey)
}
static show(props: ShowParams) {
return new Promise<any>((resolve) => {
TopView.show(
<PopupContainer
{...props}
resolve={(v) => {
resolve(v)
TopView.hide(TopViewKey)
}}
/>,
TopViewKey
)
})
}
}
// Keep the old export for backward compatibility during migration
export const AgentModal = AgentModalPopup
const StyledForm = styled.form`
display: flex;
flex-direction: column;
gap: 16px;
`
const FormContent = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
max-height: 60vh;
overflow-y: auto;
padding-right: 8px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background-color: var(--color-border);
border-radius: 3px;
}
`
const FormRow = styled.div`
display: flex;
gap: 12px;
`
const FormItem = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`
const Label = styled.label`
font-size: 14px;
color: var(--color-text-1);
font-weight: 500;
`
const RequiredMark = styled.span`
color: #ff4d4f;
margin-left: 4px;
`
const HelpText = styled.div`
font-size: 12px;
color: var(--color-text-3);
`
const LabelWithButton = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`
const PathList = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`
const PathItem = styled.div`
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 12px;
border: 1px solid var(--color-border);
border-radius: 6px;
background-color: var(--color-bg-1);
`
const PathText = styled.span`
flex: 1;
font-size: 13px;
color: var(--color-text-2);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`
const EmptyText = styled.p`
font-size: 13px;
color: var(--color-text-3);
margin: 0;
`
const FormFooter = styled.div`
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
border-top: 1px solid var(--color-border);
`
const OptionWrapper = styled.div`
display: flex;
align-items: center;
gap: 8px;
`
const PermissionOptionWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 6px;
padding: 8px 0;
.title {
font-size: 14px;
font-weight: 600;
color: var(--color-text-1);
margin-bottom: 2px;
}
.description {
font-size: 12px;
color: var(--color-text-2);
line-height: 1.4;
}
.behavior {
font-size: 12px;
color: var(--color-text-3);
line-height: 1.4;
}
.caution {
display: flex;
align-items: flex-start;
gap: 6px;
font-size: 12px;
color: #ff4d4f;
margin-top: 4px;
padding: 6px 8px;
background-color: rgba(255, 77, 79, 0.1);
border-radius: 4px;
svg {
flex-shrink: 0;
margin-top: 2px;
}
}
`

View File

@ -1,6 +1,5 @@
import { useDisclosure } from '@heroui/react'
import AddAssistantOrAgentPopup from '@renderer/components/Popups/AddAssistantOrAgentPopup' import AddAssistantOrAgentPopup from '@renderer/components/Popups/AddAssistantOrAgentPopup'
import { AgentModal } from '@renderer/components/Popups/agent/AgentModal' import AgentModalPopup from '@renderer/components/Popups/agent/AgentModal'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setActiveTopicOrSessionAction } from '@renderer/store/runtime' import { setActiveTopicOrSessionAction } from '@renderer/store/runtime'
import type { AgentEntity, Assistant, Topic } from '@renderer/types' import type { AgentEntity, Assistant, Topic } from '@renderer/types'
@ -18,21 +17,8 @@ interface UnifiedAddButtonProps {
const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant, setActiveAssistant, setActiveAgentId }) => { const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant, setActiveAssistant, setActiveAgentId }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { isOpen: isAgentModalOpen, onOpen: onAgentModalOpen, onClose: onAgentModalClose } = useDisclosure()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const handleAddButtonClick = () => {
AddAssistantOrAgentPopup.show({
onSelect: (type) => {
if (type === 'assistant') {
onCreateAssistant()
} else if (type === 'agent') {
onAgentModalOpen()
}
}
})
}
const afterCreate = useCallback( const afterCreate = useCallback(
(a: AgentEntity) => { (a: AgentEntity) => {
// TODO: should allow it to be null // TODO: should allow it to be null
@ -58,10 +44,21 @@ const UnifiedAddButton: FC<UnifiedAddButtonProps> = ({ onCreateAssistant, setAct
[dispatch, setActiveAgentId, setActiveAssistant] [dispatch, setActiveAgentId, setActiveAssistant]
) )
const handleAddButtonClick = () => {
AddAssistantOrAgentPopup.show({
onSelect: (type) => {
if (type === 'assistant') {
onCreateAssistant()
} else if (type === 'agent') {
AgentModalPopup.show({ afterSubmit: afterCreate })
}
}
})
}
return ( return (
<div className="mb-1"> <div className="mb-1">
<AddButton onClick={handleAddButtonClick}>{t('chat.add.assistant.title')}</AddButton> <AddButton onClick={handleAddButtonClick}>{t('chat.add.assistant.title')}</AddButton>
<AgentModal isOpen={isAgentModalOpen} onClose={onAgentModalClose} afterSubmit={afterCreate} />
</div> </div>
) )
} }