chore(ui): make agent selector simpler

This commit is contained in:
Soumyadas15 2025-06-26 14:40:13 +05:30
parent 23fa2ba4e8
commit 06b0035b51
2 changed files with 210 additions and 196 deletions

View File

@ -209,7 +209,7 @@ export function DashboardContent() {
<h1 className="tracking-tight text-4xl text-muted-foreground leading-tight">
Hey, I am
</h1>
<h1 className="tracking-tight text-4xl font-semibold leading-tight text-primary">
<h1 className="ml-1 tracking-tight text-4xl font-semibold leading-tight text-primary">
{displayName}
{agentAvatar && (
<span className="text-muted-foreground ml-2">

View File

@ -1,28 +1,25 @@
'use client';
import React, { useState } from 'react';
import { Settings, ChevronRight, Bot, Presentation, Video, Code, FileSpreadsheet, Search, Plus, Star, User, Sparkles, Database, MessageSquare, Calculator, Palette, Zap } from 'lucide-react';
import React, { useState, useRef, useEffect } from 'react';
import { Settings, ChevronRight, Bot, Presentation, FileSpreadsheet, Search, Plus, User, Check, ChevronDown } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { ChatSettingsDialog } from './chat-settings-dialog';
import { SubscriptionStatus } from './_use-model-selection';
import { useAgents } from '@/hooks/react-query/agents/use-agents';
import { useFeatureFlag } from '@/lib/feature-flags';
import { ChatSettingsDialog } from './chat-settings-dialog';
import { useRouter } from 'next/navigation';
import { cn } from '@/lib/utils';
import { Label } from '@/components/ui/label';
interface PredefinedAgent {
id: string;
@ -35,16 +32,16 @@ interface PredefinedAgent {
const PREDEFINED_AGENTS: PredefinedAgent[] = [
{
id: 'slides',
name: 'Slides Pro',
name: 'Slides',
description: 'Create stunning presentations and slide decks',
icon: <Presentation className="h-4 w-4 mt-1" />,
icon: <Presentation className="h-4 w-4" />,
category: 'productivity'
},
{
id: 'sheets',
name: 'Data Analyst',
name: 'Sheets',
description: 'Spreadsheet and data analysis expert',
icon: <FileSpreadsheet className="h-4 w-4 mt-1" />,
icon: <FileSpreadsheet className="h-4 w-4" />,
category: 'productivity'
}
];
@ -55,14 +52,13 @@ interface ChatSettingsDropdownProps {
selectedModel: string;
onModelChange: (model: string) => void;
modelOptions: any[];
subscriptionStatus: SubscriptionStatus;
subscriptionStatus: any;
canAccessModel: (modelId: string) => boolean;
refreshCustomModels?: () => void;
disabled?: boolean;
className?: string;
}
export function ChatSettingsDropdown({
export const ChatSettingsDropdown: React.FC<ChatSettingsDropdownProps> = ({
selectedAgentId,
onAgentSelect,
selectedModel,
@ -72,202 +68,220 @@ export function ChatSettingsDropdown({
canAccessModel,
refreshCustomModels,
disabled = false,
className,
}: ChatSettingsDropdownProps) {
const [dropdownOpen, setDropdownOpen] = useState(false);
}) => {
const [isOpen, setIsOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
const [dialogOpen, setDialogOpen] = useState(false);
const searchInputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
// Check if custom agents feature is enabled
const { enabled: customAgentsEnabled, loading: flagsLoading } = useFeatureFlag('custom_agents');
const { data: agentsResponse, isLoading: agentsLoading } = useAgents();
const agents = agentsResponse?.agents || [];
// Fetch real agents from API only if feature is enabled
const { data: agentsResponse, isLoading: agentsLoading, refetch: loadAgents } = useAgents({
limit: 100,
sort_by: 'name',
sort_order: 'asc'
});
// Combine all agents
const allAgents = [
{
id: undefined,
name: 'Suna',
description: 'Your personal AI assistant',
type: 'default' as const,
icon: <User className="h-4 w-4" />
},
...PREDEFINED_AGENTS.map(agent => ({
...agent,
type: 'predefined' as const
})),
...agents.map((agent: any) => ({
...agent,
id: agent.agent_id,
type: 'custom' as const,
icon: agent.avatar || <Bot className="h-4 w-4" />
}))
];
const agents = (customAgentsEnabled && agentsResponse?.agents) || [];
const defaultAgent = agents.find(agent => agent.is_default);
// Filter agents based on search query
const filteredAgents = allAgents.filter((agent) =>
agent.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
agent.description?.toLowerCase().includes(searchQuery.toLowerCase())
);
// Find selected agent - could be from real agents or predefined
const selectedRealAgent = agents.find(a => a.agent_id === selectedAgentId);
const selectedPredefinedAgent = PREDEFINED_AGENTS.find(a => a.id === selectedAgentId);
const selectedAgent = selectedRealAgent || selectedPredefinedAgent;
const handleAgentSelect = (agentId: string | undefined) => {
onAgentSelect?.(agentId);
setDropdownOpen(false);
};
const handleMoreOptions = () => {
setDropdownOpen(false);
setDialogOpen(true);
};
const handleExploreAll = () => {
setDropdownOpen(false);
router.push('/agents');
};
useEffect(() => {
if (isOpen && searchInputRef.current) {
setTimeout(() => {
searchInputRef.current?.focus();
}, 50);
} else {
setSearchQuery('');
setHighlightedIndex(-1);
}
}, [isOpen]);
const getAgentDisplay = () => {
if (selectedRealAgent) {
const selectedAgent = allAgents.find(agent => agent.id === selectedAgentId);
if (selectedAgent) {
return {
name: selectedRealAgent.name,
icon: <Bot className="h-4 w-4" />,
avatar: selectedRealAgent.avatar
};
}
if (selectedPredefinedAgent) {
return {
name: selectedPredefinedAgent.name,
icon: selectedPredefinedAgent.icon,
avatar: null
name: selectedAgent.name,
icon: selectedAgent.icon
};
}
return {
name: 'Suna',
icon: <User className="h-4 w-4" />,
avatar: null
icon: <User className="h-4 w-4" />
};
};
const handleAgentSelect = (agentId: string | undefined) => {
onAgentSelect?.(agentId);
setIsOpen(false);
};
const handleSearchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
e.stopPropagation();
if (e.key === 'ArrowDown') {
e.preventDefault();
setHighlightedIndex((prev) =>
prev < filteredAgents.length - 1 ? prev + 1 : 0
);
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setHighlightedIndex((prev) =>
prev > 0 ? prev - 1 : filteredAgents.length - 1
);
} else if (e.key === 'Enter' && highlightedIndex >= 0) {
e.preventDefault();
const selectedAgent = filteredAgents[highlightedIndex];
if (selectedAgent) {
handleAgentSelect(selectedAgent.id);
}
}
};
const handleExploreAll = () => {
setIsOpen(false);
router.push('/agents');
};
const handleMoreOptions = () => {
setIsOpen(false);
setDialogOpen(true);
};
const agentDisplay = getAgentDisplay();
const AgentCard = ({ agent, isSelected, onClick, type }: {
agent: any;
isSelected: boolean;
onClick: () => void;
type: 'predefined' | 'custom' | 'default';
}) => (
<div
onClick={onClick}
className={cn(
"group relative rounded-lg p-1.5 cursor-pointer transition-all",
"hover:bg-accent/50",
isSelected ? "bg-accent border-accent-foreground/50" : ""
)}
>
<div className="flex items-start gap-2">
<div className="flex-shrink-0">
{type === 'default' ? (
<User className="h-4 w-4 mt-1" />
) : type === 'custom' ? (
agent.avatar
) : (
agent.icon
)}
</div>
<div className="flex-1 min-w-0">
<span className="font-medium text-sm truncate">{agent.name}</span>
</div>
</div>
</div>
);
return (
<>
<Popover open={dropdownOpen} onOpenChange={setDropdownOpen}>
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<PopoverTrigger asChild>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="sm"
className={cn(
'h-8 w-8 p-0 text-muted-foreground hover:text-foreground relative',
'rounded-lg',
className
)}
className="h-8 px-2 text-xs font-medium"
disabled={disabled}
>
<Settings className="h-4 w-4" />
{selectedAgentId && (
<div className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-primary" />
)}
</Button>
</PopoverTrigger>
</TooltipTrigger>
<TooltipContent side="top">
<p>
<div className="flex items-center gap-1.5">
{agentDisplay.icon}
<span className="hidden sm:inline-block truncate max-w-[80px]">
{agentDisplay.name}
{agentDisplay.avatar && ` ${agentDisplay.avatar}`}
</p>
</span>
<ChevronDown className="h-3 w-3 opacity-50" />
</div>
</Button>
</DropdownMenuTrigger>
</TooltipTrigger>
<TooltipContent>
<p>Select Agent</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<PopoverContent align="end" className="w-[480px] p-0" sideOffset={4}>
<div className="p-4">
<div className="mb-4">
<h3 className="text-sm font-medium">Choose Your Agent</h3>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<div className="flex items-center gap-2 mb-2">
<Label className="text-xs text-muted-foreground font-medium">Default Agents</Label>
</div>
<AgentCard
agent={{ name: 'Suna', description: 'Your personal AI assistant' }}
isSelected={!selectedAgentId}
onClick={() => handleAgentSelect(undefined)}
type="default"
<DropdownMenuContent align="end" className="w-80 p-0" sideOffset={4}>
<div className="p-3 border-b">
<div className="relative">
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<input
ref={searchInputRef}
type="text"
placeholder="Search agents..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={handleSearchInputKeyDown}
className="w-full pl-8 pr-3 py-2 text-sm bg-transparent border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
/>
<div className="space-y-1 max-h-[300px] overflow-y-auto">
{PREDEFINED_AGENTS.map((agent) => (
<AgentCard
key={agent.id}
agent={agent}
isSelected={selectedAgentId === agent.id}
onClick={() => handleAgentSelect(agent.id)}
type="predefined"
/>
))}
</div>
</div>
<div className="space-y-3">
<div className="flex items-center gap-2 mb-2">
<Label className="text-xs text-muted-foreground font-medium">Your Agents</Label>
</div>
<div className="max-h-80 overflow-y-auto">
{agentsLoading ? (
<div className="flex items-center justify-center py-8 text-sm text-muted-foreground">
Loading...
<div className="p-3 text-sm text-muted-foreground text-center">
Loading agents...
</div>
) : agents.length > 0 ? (
<div className="space-y-1 max-h-[300px] overflow-y-auto">
{agents.map((agent) => (
<AgentCard
key={agent.agent_id}
agent={agent}
isSelected={selectedAgentId === agent.agent_id}
onClick={() => handleAgentSelect(agent.agent_id)}
type="custom"
/>
))}
) : filteredAgents.length === 0 ? (
<div className="p-3 text-sm text-muted-foreground text-center">
No agents found
</div>
) : (
<div className="flex flex-col items-center justify-center py-8 text-center">
<Bot className="h-8 w-8 text-muted-foreground/50 mb-2" />
<p className="text-sm text-muted-foreground mb-2">No custom agents yet</p>
<Button
variant="outline"
size="sm"
onClick={handleExploreAll}
className="text-xs"
filteredAgents.map((agent, index) => {
const isSelected = agent.id === selectedAgentId;
const isHighlighted = index === highlightedIndex;
return (
<TooltipProvider key={agent.id || 'default'}>
<Tooltip>
<TooltipTrigger asChild>
<div className="w-full">
<DropdownMenuItem
className={cn(
"text-sm px-3 py-3 mx-2 my-0.5 flex items-center justify-between cursor-pointer",
isHighlighted && "bg-accent",
)}
onClick={() => handleAgentSelect(agent.id)}
onMouseEnter={() => setHighlightedIndex(index)}
>
<Plus className="h-3 w-3 mr-1" />
Get Started
</Button>
<div className="flex items-center gap-3">
<div className="flex-shrink-0">
{agent.icon}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-medium text-sm truncate">
{agent.name}
</span>
{agent.type === 'predefined' && (
<Badge variant="secondary" className="text-xs px-1 py-0 h-4">
Pro
</Badge>
)}
{agent.type === 'custom' && (
<Badge variant="outline" className="text-xs px-1 py-0 h-4">
Custom
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground line-clamp-1 mt-0.5">
{agent.description}
</p>
</div>
<Separator className="my-4" />
</div>
{isSelected && (
<Check className="h-4 w-4 text-blue-500 flex-shrink-0" />
)}
</DropdownMenuItem>
</div>
</TooltipTrigger>
<TooltipContent side="left" className="text-xs max-w-xs">
<p>{agent.description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
})
)}
</div>
<div className="border-t p-3">
<div className="flex items-center justify-between">
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
@ -277,22 +291,22 @@ export function ChatSettingsDropdown({
<Search className="h-3 w-3" />
Explore All
</Button>
</div>
<Button
variant="ghost"
variant="outline"
size="sm"
onClick={handleMoreOptions}
className="text-xs"
>
<Settings className="h-3 w-3" />
More Options
<ChevronRight className="h-3 w-3" />
<ChevronRight className="h-3 w-3 ml-1" />
</Button>
</div>
</div>
</PopoverContent>
</Popover>
</DropdownMenuContent>
</DropdownMenu>
<ChatSettingsDialog
open={dialogOpen}
onOpenChange={setDialogOpen}
@ -305,4 +319,4 @@ export function ChatSettingsDropdown({
/>
</>
);
}
};