feat: improve agent config modal UX with seamless agent switching

- Remove 'General' tab from agent configuration modal
- Add inline agent switcher integrated into dialog title area
- Fix name editing functionality to work with agent switching
- Display proper Suna icons in agent dropdown
- Remove redundant description text for cleaner UI
- Fix DialogTitle accessibility requirements
- Clean up duplicate close buttons

The agent switcher now seamlessly replaces the static title when onAgentChange is provided, creating a smooth dropdown experience that maintains visual hierarchy while enabling easy agent switching without closing the modal.
This commit is contained in:
marko-kraemer 2025-09-29 14:27:26 +02:00
parent bc6620569f
commit 2c2eaf61f8
5 changed files with 159 additions and 59 deletions

View File

@ -8,9 +8,14 @@ import {
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Tabs,
TabsContent,
@ -35,13 +40,15 @@ import {
Edit3,
Save,
Brain,
ChevronDown,
Search,
} from 'lucide-react';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import { KortixLogo } from '@/components/sidebar/kortix-logo';
import { useAgentVersionData } from '@/hooks/use-agent-version-data';
import { useUpdateAgent } from '@/hooks/react-query/agents/use-agents';
import { useUpdateAgent, useAgents } from '@/hooks/react-query/agents/use-agents';
import { useUpdateAgentMCPs } from '@/hooks/react-query/agents/use-update-agent-mcps';
import { useExportAgent } from '@/hooks/react-query/agents/use-agent-export-import';
import { ExpandableMarkdownEditor } from '@/components/ui/expandable-markdown-editor';
@ -62,6 +69,7 @@ interface AgentConfigurationDialogProps {
onOpenChange: (open: boolean) => void;
agentId: string;
initialTab?: 'instructions' | 'tools' | 'integrations' | 'knowledge' | 'playbooks' | 'triggers';
onAgentChange?: (agentId: string) => void;
}
export function AgentConfigurationDialog({
@ -69,12 +77,15 @@ export function AgentConfigurationDialog({
onOpenChange,
agentId,
initialTab = 'instructions',
onAgentChange,
}: AgentConfigurationDialogProps) {
const router = useRouter();
const searchParams = useSearchParams();
const queryClient = useQueryClient();
const { agent, versionData, isViewingOldVersion, isLoading, error } = useAgentVersionData({ agentId });
const { data: agentsResponse } = useAgents({}, { enabled: !!onAgentChange });
const agents = agentsResponse?.agents || [];
const updateAgentMutation = useUpdateAgent();
const updateAgentMCPsMutation = useUpdateAgentMCPs();
@ -341,8 +352,10 @@ export function AgentConfigurationDialog({
)}
</button>
<div>
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2">
{isEditingName ? (
// Name editing mode (takes priority over everything)
<div className="flex items-center gap-2">
<Input
ref={nameInputRef}
@ -379,7 +392,87 @@ export function AgentConfigurationDialog({
<X className="h-4 w-4" />
</Button>
</div>
) : onAgentChange ? (
// When agent switching is enabled, show a sleek inline agent selector
<div className="flex items-center gap-2 min-w-0 flex-1">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="flex items-center gap-2 hover:bg-muted/50 rounded-md px-2 py-1 transition-colors group">
<DialogTitle className="text-xl font-semibold truncate">
{isLoading ? 'Loading...' : formData.name || 'Agent'}
</DialogTitle>
<ChevronDown className="h-4 w-4 opacity-60 group-hover:opacity-100 transition-opacity flex-shrink-0" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-80 p-0"
align="start"
sideOffset={4}
>
<div className="p-3 border-b">
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
<Search className="h-4 w-4" />
Switch Agent
</div>
</div>
<div className="max-h-60 overflow-y-auto">
{agents.map((agent: any) => (
<DropdownMenuItem
key={agent.agent_id}
onClick={() => onAgentChange(agent.agent_id)}
className="p-3 flex items-center gap-3 cursor-pointer"
>
{agent.metadata?.is_suna_default ? (
<div className="w-6 h-6 rounded-lg bg-muted border flex items-center justify-center flex-shrink-0">
<KortixLogo size={12} />
</div>
) : (
<AgentIconAvatar
profileImageUrl={agent.profile_image_url}
iconName={agent.icon_name}
iconColor={agent.icon_color}
backgroundColor={agent.icon_background}
agentName={agent.name}
size={24}
className="flex-shrink-0"
/>
)}
<div className="flex-1 min-w-0">
<div className="font-medium truncate">{agent.name}</div>
{agent.description && (
<div className="text-xs text-muted-foreground truncate">
{agent.description}
</div>
)}
</div>
{agent.agent_id === agentId && (
<Check className="h-4 w-4 text-primary flex-shrink-0" />
)}
</DropdownMenuItem>
))}
</div>
</DropdownMenuContent>
</DropdownMenu>
{/* Add edit button for name editing when agent switching is enabled */}
{isNameEditable && (
<Button
size="icon"
variant="ghost"
className="h-6 w-6 flex-shrink-0"
onClick={() => {
setIsEditingName(true);
setTimeout(() => {
nameInputRef.current?.focus();
nameInputRef.current?.select();
}, 0);
}}
>
<Edit3 className="h-3 w-3" />
</Button>
)}
</div>
) : (
// Static title mode (no agent switching available)
<div className="flex items-center gap-2">
<DialogTitle className="text-xl font-semibold">
{isLoading ? 'Loading...' : formData.name || 'Agent'}
@ -402,9 +495,7 @@ export function AgentConfigurationDialog({
)}
</div>
)}
<DialogDescription>
Configure your agent's capabilities and behavior
</DialogDescription>
</div>
</div>
</div>

View File

@ -385,6 +385,9 @@ export const AgentsGrid: React.FC<AgentsGridProps> = ({
open={showConfigDialog}
onOpenChange={setShowConfigDialog}
agentId={configAgentId}
onAgentChange={(newAgentId) => {
setConfigAgentId(newAgentId);
}}
/>
)}
</>

View File

@ -433,6 +433,10 @@ export function DashboardContent() {
open={showConfigDialog}
onOpenChange={setShowConfigDialog}
agentId={configAgentId}
onAgentChange={(newAgentId) => {
setConfigAgentId(newAgentId);
setSelectedAgent(newAgentId);
}}
/>
)}
</>

View File

@ -667,6 +667,7 @@ export const ChatInput = memo(forwardRef<ChatInputHandles, ChatInputProps>(
onOpenChange={(open) => setAgentConfigDialog({ ...agentConfigDialog, open })}
agentId={selectedAgentId}
initialTab={agentConfigDialog.tab}
onAgentChange={onAgentSelect}
/>
)}
</div>

View File

@ -411,6 +411,7 @@ const LoggedInMenu: React.FC<UnifiedConfigMenuProps> = memo(function LoggedInMen
onOpenChange={(open) => setAgentConfigDialog({ ...agentConfigDialog, open })}
agentId={selectedAgentId || displayAgent?.agent_id}
initialTab={agentConfigDialog.tab}
onAgentChange={onAgentSelect}
/>
)}