diff --git a/web/features/agent-v2/roster/components/__tests__/create-agent-dialog.spec.tsx b/web/features/agent-v2/roster/components/__tests__/create-agent-dialog.spec.tsx new file mode 100644 index 00000000000..5a3ca1e7812 --- /dev/null +++ b/web/features/agent-v2/roster/components/__tests__/create-agent-dialog.spec.tsx @@ -0,0 +1,59 @@ +import { render, screen, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { CreateAgentDialog } from '../create-agent-dialog' + +const mutationMock = vi.hoisted(() => ({ + isPending: false, + mutate: vi.fn(), +})) + +vi.mock('@tanstack/react-query', () => ({ + useMutation: () => ({ + isPending: mutationMock.isPending, + mutate: mutationMock.mutate, + }), +})) + +vi.mock('@/service/client', () => ({ + consoleQuery: { + agents: { + post: { + mutationOptions: vi.fn(() => ({})), + }, + }, + }, +})) + +describe('CreateAgentDialog', () => { + beforeEach(() => { + vi.clearAllMocks() + mutationMock.isPending = false + }) + + it('submits roster role and default icon fields when creating an agent', async () => { + const user = userEvent.setup() + render() + + await user.click(screen.getByRole('button', { name: /agentV2\.roster\.createAgent/ })) + + const dialog = await screen.findByRole('dialog', { name: 'agentV2.roster.createDialog.title' }) + await user.type(within(dialog).getByRole('textbox', { name: 'agentV2.roster.createForm.nameLabel' }), ' Research Agent ') + await user.type(within(dialog).getByRole('textbox', { name: 'agentV2.roster.createForm.roleLabel' }), ' Researcher ') + await user.type(within(dialog).getByPlaceholderText('agentV2.roster.createForm.descriptionPlaceholder'), ' Find and summarize market materials. ') + await user.click(within(dialog).getByRole('button', { name: 'common.operation.create' })) + + expect(mutationMock.mutate).toHaveBeenCalledWith({ + body: { + name: 'Research Agent', + description: 'Find and summarize market materials.', + role: 'Researcher', + icon_type: 'emoji', + icon: '🧸', + icon_background: '#F5F3FF', + }, + }, expect.objectContaining({ + onError: expect.any(Function), + onSuccess: expect.any(Function), + })) + }) +}) diff --git a/web/features/agent-v2/roster/components/create-agent-dialog.tsx b/web/features/agent-v2/roster/components/create-agent-dialog.tsx index 8314b0e5373..00c955bd608 100644 --- a/web/features/agent-v2/roster/components/create-agent-dialog.tsx +++ b/web/features/agent-v2/roster/components/create-agent-dialog.tsx @@ -1,5 +1,6 @@ 'use client' +import type { AppIconSelection } from '@/app/components/base/app-icon-picker' import { Button } from '@langgenius/dify-ui/button' import { Dialog, DialogCloseButton, DialogContent, DialogDescription, DialogTitle, DialogTrigger } from '@langgenius/dify-ui/dialog' import { FieldControl, FieldError, FieldLabel, FieldRoot } from '@langgenius/dify-ui/field' @@ -9,6 +10,8 @@ import { toast } from '@langgenius/dify-ui/toast' import { useMutation } from '@tanstack/react-query' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import AppIcon from '@/app/components/base/app-icon' +import AppIconPicker from '@/app/components/base/app-icon-picker' import { consoleQuery } from '@/service/client' type AgentFormValues = { @@ -17,12 +20,33 @@ type AgentFormValues = { role?: string } +const defaultAgentIcon = { + type: 'emoji', + icon: '🧸', + background: '#F5F3FF', +} satisfies AppIconSelection + export function CreateAgentDialog() { const { t } = useTranslation('agentV2') const { t: tCommon } = useTranslation('common') const [open, setOpen] = useState(false) + const [formKey, setFormKey] = useState(0) + const [iconPickerOpen, setIconPickerOpen] = useState(false) + const [agentIcon, setAgentIcon] = useState(defaultAgentIcon) const createAgentMutation = useMutation(consoleQuery.agents.post.mutationOptions()) + const resetForm = () => { + setFormKey(key => key + 1) + setAgentIcon(defaultAgentIcon) + setIconPickerOpen(false) + } + + const handleOpenChange = (nextOpen: boolean) => { + setOpen(nextOpen) + if (!nextOpen) + resetForm() + } + const handleSubmit = (formValues: AgentFormValues) => { const trimmedName = formValues.name?.trim() ?? '' if (!trimmedName || createAgentMutation.isPending) @@ -33,11 +57,14 @@ export function CreateAgentDialog() { name: trimmedName, description: formValues.description?.trim() ?? '', role: formValues.role?.trim() ?? '', + icon_type: agentIcon.type, + icon: agentIcon.type === 'image' ? agentIcon.fileId : agentIcon.icon, + icon_background: agentIcon.type === 'emoji' ? agentIcon.background : undefined, }, }, { onSuccess: () => { toast.success(t('roster.createSuccess')) - setOpen(false) + handleOpenChange(false) }, onError: () => { toast.error(t('roster.createFailed')) @@ -46,79 +73,119 @@ export function CreateAgentDialog() { } return ( - - - )} - > - - {t('roster.createAgent')} - - - - - {t('roster.createDialog.title')} - - - {t('roster.createDialog.description')} - - - className="mt-5 space-y-4" - onFormSubmit={handleSubmit} - > - - - {t('roster.createForm.nameLabel')} - - - - {t('roster.createForm.nameRequired')} - - - - - {t('roster.createForm.descriptionLabel')} - -