From 6b2602157ebc6019a637d0f28fa6487d84f8b26b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 17 Jul 2025 10:27:31 +0000 Subject: [PATCH] Prevent multiple agent creation and improve mutation handling Co-authored-by: markokraemer.mail --- frontend/src/app/(dashboard)/agents/page.tsx | 20 ++++++++++++--- .../components/agents/create-agent-dialog.tsx | 20 +++++++++------ .../thread/chat-input/agent-selector.tsx | 25 ++++++++++++++----- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/(dashboard)/agents/page.tsx b/frontend/src/app/(dashboard)/agents/page.tsx index acaec294..79b985bf 100644 --- a/frontend/src/app/(dashboard)/agents/page.tsx +++ b/frontend/src/app/(dashboard)/agents/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useMemo, useEffect } from 'react'; +import React, { useState, useMemo, useEffect, useCallback } from 'react'; import { toast } from 'sonner'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; @@ -82,6 +82,7 @@ export default function AgentsPage() { const [publishDialog, setPublishDialog] = useState(null); const [publishTags, setPublishTags] = useState([]); const [publishingAgentId, setPublishingAgentId] = useState(null); + const [isCreatingNewAgent, setIsCreatingNewAgent] = useState(false); const activeTab = useMemo(() => { return searchParams.get('tab') || 'my-agents'; @@ -250,9 +251,20 @@ export default function AgentsPage() { setEditDialogOpen(true); }; - const handleCreateNewAgent = () => { - createNewAgentMutation.mutate(); - }; + const handleCreateNewAgent = useCallback(() => { + if (isCreatingNewAgent || createNewAgentMutation.isPending) { + return; // Prevent multiple clicks + } + + setIsCreatingNewAgent(true); + + createNewAgentMutation.mutate(undefined, { + onSettled: () => { + // Reset the debounce state after mutation completes (success or error) + setTimeout(() => setIsCreatingNewAgent(false), 1000); + } + }); + }, [isCreatingNewAgent, createNewAgentMutation]); const handleInstallClick = (item: MarketplaceTemplate, e?: React.MouseEvent) => { if (e) { diff --git a/frontend/src/components/agents/create-agent-dialog.tsx b/frontend/src/components/agents/create-agent-dialog.tsx index eec1142b..18489953 100644 --- a/frontend/src/components/agents/create-agent-dialog.tsx +++ b/frontend/src/components/agents/create-agent-dialog.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; @@ -48,6 +48,7 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState('All'); const [formData, setFormData] = useState(initialFormData); + const [isSubmitting, setIsSubmitting] = useState(false); const createAgentMutation = useCreateAgent(); useEffect(() => { @@ -109,19 +110,24 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea return tools; }; - const handleSubmit = async () => { - if (!formData.name.trim()) { + const handleSubmit = useCallback(async () => { + if (!formData.name.trim() || isSubmitting || createAgentMutation.isPending) { return; } + setIsSubmitting(true); + try { await createAgentMutation.mutateAsync(formData); onOpenChange(false); onAgentCreated?.(); } catch (error) { console.error('Error creating agent:', error); + } finally { + // Reset the debounce state after a delay + setTimeout(() => setIsSubmitting(false), 1000); } - }; + }, [formData, isSubmitting, createAgentMutation, onOpenChange, onAgentCreated]); const handleCancel = () => { onOpenChange(false); @@ -306,16 +312,16 @@ export const CreateAgentDialog = ({ isOpen, onOpenChange, onAgentCreated }: Crea