diff --git a/frontend/public/images/models/Anthropic.svg b/frontend/public/images/models/Anthropic.svg new file mode 100644 index 00000000..c0004ba5 --- /dev/null +++ b/frontend/public/images/models/Anthropic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/public/images/models/Gemini.svg b/frontend/public/images/models/Gemini.svg new file mode 100644 index 00000000..3fa8d820 --- /dev/null +++ b/frontend/public/images/models/Gemini.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/public/images/models/Grok.svg b/frontend/public/images/models/Grok.svg new file mode 100644 index 00000000..e0e7a389 --- /dev/null +++ b/frontend/public/images/models/Grok.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/public/images/models/Moonshot.svg b/frontend/public/images/models/Moonshot.svg new file mode 100644 index 00000000..263af204 --- /dev/null +++ b/frontend/public/images/models/Moonshot.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/public/images/models/OAI.svg b/frontend/public/images/models/OAI.svg new file mode 100644 index 00000000..c423d4d8 --- /dev/null +++ b/frontend/public/images/models/OAI.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/src/components/agents/agent-selection-dropdown.tsx b/frontend/src/components/agents/agent-selector.tsx similarity index 67% rename from frontend/src/components/agents/agent-selection-dropdown.tsx rename to frontend/src/components/agents/agent-selector.tsx index ffb3a15b..06ad90c7 100644 --- a/frontend/src/components/agents/agent-selection-dropdown.tsx +++ b/frontend/src/components/agents/agent-selector.tsx @@ -1,7 +1,7 @@ 'use client'; -import React, { useState, useRef, useEffect } from 'react'; -import { Search, Plus, Check, ChevronDown } from 'lucide-react'; +import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react'; +import { Search, Plus, Check, ChevronDown, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, @@ -16,7 +16,7 @@ import { cn } from '@/lib/utils'; import { AgentAvatar } from '@/components/thread/content/agent-avatar'; import { Skeleton } from '@/components/ui/skeleton'; -interface AgentSelectionDropdownProps { +interface AgentSelectorProps { selectedAgentId?: string; onAgentSelect: (agentId: string) => void; placeholder?: string; @@ -26,7 +26,7 @@ interface AgentSelectionDropdownProps { variant?: 'default' | 'compact'; } -export const AgentSelectionDropdown: React.FC = ({ +export const AgentSelector: React.FC = ({ selectedAgentId, onAgentSelect, placeholder = "Choose an agent", @@ -37,33 +37,77 @@ export const AgentSelectionDropdown: React.FC = ({ }) => { const [isOpen, setIsOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); + const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(''); + const [currentPage, setCurrentPage] = useState(1); + const [allAgents, setAllAgents] = useState([]); const [showNewAgentDialog, setShowNewAgentDialog] = useState(false); const searchInputRef = useRef(null); - const { data: agentsResponse, isLoading } = useAgents(); - const agents = agentsResponse?.agents || []; + // Debounce search query + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearchQuery(searchQuery); + setCurrentPage(1); // Reset to first page when searching + }, 300); + return () => clearTimeout(timer); + }, [searchQuery]); - // Filter agents based on search query - const filteredAgents = agents.filter((agent: any) => - agent.name.toLowerCase().includes(searchQuery.toLowerCase()) || - agent.description?.toLowerCase().includes(searchQuery.toLowerCase()) - ); + // Fetch agents with pagination and search + const agentsParams = useMemo(() => ({ + page: currentPage, + limit: 50, + search: debouncedSearchQuery || undefined, + }), [currentPage, debouncedSearchQuery]); - // Sort agents with selected first - const sortedAgents = React.useMemo(() => { - if (!selectedAgentId) return filteredAgents; + const { data: agentsResponse, isLoading, isFetching } = useAgents(agentsParams); - const selectedAgent = filteredAgents.find((agent: any) => agent.agent_id === selectedAgentId); - const otherAgents = filteredAgents.filter((agent: any) => agent.agent_id !== selectedAgentId); + // Update agents list when data changes + useEffect(() => { + if (agentsResponse?.agents) { + if (currentPage === 1 || debouncedSearchQuery) { + // First page or new search - replace all agents + setAllAgents(agentsResponse.agents); + } else { + // Subsequent pages - append to existing agents + setAllAgents(prev => [...prev, ...agentsResponse.agents]); + } + } + }, [agentsResponse, currentPage, debouncedSearchQuery]); - return selectedAgent ? [selectedAgent, ...otherAgents] : filteredAgents; - }, [filteredAgents, selectedAgentId]); + const agents = allAgents; + + // Check if we can load more + const canLoadMore = useMemo(() => { + if (!agentsResponse?.pagination) return false; + return agentsResponse.pagination.current_page < agentsResponse.pagination.total_pages; + }, [agentsResponse?.pagination]); + + const handleLoadMore = useCallback(() => { + if (canLoadMore && !isFetching) { + setCurrentPage(prev => prev + 1); + } + }, [canLoadMore, isFetching]); + + // Order agents with selected first + const orderedAgents = useMemo(() => { + const list = [...agents]; + if (!selectedAgentId) return list; + + const selectedIndex = list.findIndex(a => a.agent_id === selectedAgentId); + if (selectedIndex > 0) { + const [selected] = list.splice(selectedIndex, 1); + list.unshift(selected); + } + return list; + }, [agents, selectedAgentId]); useEffect(() => { if (isOpen) { setTimeout(() => searchInputRef.current?.focus(), 50); } else { setSearchQuery(''); + setDebouncedSearchQuery(''); + setCurrentPage(1); } }, [isOpen]); @@ -85,7 +129,7 @@ export const AgentSelectionDropdown: React.FC = ({
@@ -153,7 +197,7 @@ export const AgentSelectionDropdown: React.FC = ({
))} - ) : sortedAgents.length === 0 ? ( + ) : orderedAgents.length === 0 ? (

No agents found

@@ -163,7 +207,7 @@ export const AgentSelectionDropdown: React.FC = ({
) : (
- {sortedAgents.map((agent: any) => ( + {orderedAgents.map((agent: any) => ( = ({ > @@ -194,6 +238,28 @@ export const AgentSelectionDropdown: React.FC = ({ )}
+ {/* Load More */} + {canLoadMore && ( +
+ +
+ )} + {/* Create Agent Option */} {showCreateOption && ( <> diff --git a/frontend/src/components/agents/config/model-selector.tsx b/frontend/src/components/agents/config/model-selector.tsx index f9d82b09..d5a5956d 100644 --- a/frontend/src/components/agents/config/model-selector.tsx +++ b/frontend/src/components/agents/config/model-selector.tsx @@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect, useMemo } from 'react'; import { Check, Search, AlertTriangle, Crown, Cpu, Plus, Edit, Trash, KeyRound } from 'lucide-react'; +import { ModelProviderIcon } from '@/lib/model-provider-icons'; import { Button } from '@/components/ui/button'; import { DropdownMenu, @@ -301,7 +302,8 @@ export function AgentModelSelector({ onClick={() => !disabled && handleSelect(model.id)} onMouseEnter={() => setHighlightedIndex(index)} > -
+
+ {model.label}
@@ -380,11 +382,11 @@ export function AgentModelSelector({ className )} > -
-
- - {/* API models are quality controlled - no low quality warning needed */} -
+
+ {selectedModelDisplay}
@@ -406,10 +408,7 @@ export function AgentModelSelector({ className )} > -
- - {/* API models are quality controlled - no low quality warning needed */} -
+ {selectedModelDisplay} )} @@ -509,7 +508,8 @@ export function AgentModelSelector({ )} onClick={() => handleSelect(model.id)} > -
+
+ {model.label}
diff --git a/frontend/src/components/agents/triggers/providers/simplified-schedule-config.tsx b/frontend/src/components/agents/triggers/providers/simplified-schedule-config.tsx index a27d2cc6..9cc68bc1 100644 --- a/frontend/src/components/agents/triggers/providers/simplified-schedule-config.tsx +++ b/frontend/src/components/agents/triggers/providers/simplified-schedule-config.tsx @@ -34,7 +34,7 @@ import { format } from 'date-fns'; import { cn } from '@/lib/utils'; import { TriggerProvider, ScheduleTriggerConfig } from '../types'; import { useAgentWorkflows } from '@/hooks/react-query/agents/use-agent-workflows'; -import { AgentSelectionDropdown } from '@/components/agents/agent-selection-dropdown'; +import { AgentSelector } from '@/components/agents/agent-selector'; interface SimplifiedScheduleConfigProps { provider: TriggerProvider; @@ -512,7 +512,7 @@ export const SimplifiedScheduleConfig: React.FC =
- = memo(function LoggedInMen }, [selectedAgentId, displayAgent?.agent_id]); const renderAgentIcon = useCallback((agent: any) => { - return ; + return ; }, []); const currentAgentIdForPlaybooks = isLoggedIn ? displayAgent?.agent_id || '' : ''; diff --git a/frontend/src/components/thread/content/ThreadContent.tsx b/frontend/src/components/thread/content/ThreadContent.tsx index b9f60750..df1d1c7f 100644 --- a/frontend/src/components/thread/content/ThreadContent.tsx +++ b/frontend/src/components/thread/content/ThreadContent.tsx @@ -790,7 +790,7 @@ export const ThreadContent: React.FC = ({
{groupAgentId ? ( - + ) : ( getAgentInfo().avatar )} diff --git a/frontend/src/components/triggers/trigger-creation-dialog.tsx b/frontend/src/components/triggers/trigger-creation-dialog.tsx index a02fbee6..801d432b 100644 --- a/frontend/src/components/triggers/trigger-creation-dialog.tsx +++ b/frontend/src/components/triggers/trigger-creation-dialog.tsx @@ -15,7 +15,7 @@ import { SimplifiedScheduleConfig } from '@/components/agents/triggers/providers import { ScheduleTriggerConfig } from '@/components/agents/triggers/types'; import { useCreateTrigger, useUpdateTrigger } from '@/hooks/react-query/triggers'; import { toast } from 'sonner'; -import { AgentSelectionDropdown } from '@/components/agents/agent-selection-dropdown'; +import { AgentSelector } from '@/components/agents/agent-selector'; interface TriggerCreationDialogProps { open: boolean; @@ -148,7 +148,7 @@ export function TriggerCreationDialog({
- 1) { + const provider = parts[0].toLowerCase(); + if (['openai', 'anthropic', 'google', 'xai', 'moonshotai', 'bedrock', 'openrouter'].includes(provider)) { + return provider as ModelProvider; + } + } + + return 'openai'; // Default fallback +} + +/** + * Component to render the model provider icon + */ +interface ModelProviderIconProps { + modelId: string; + size?: number; + className?: string; + variant?: 'default' | 'compact'; +} + +export function ModelProviderIcon({ + modelId, + size = 24, // Default to 24px for better visibility + className = '', + variant = 'default' +}: ModelProviderIconProps) { + const provider = getModelProvider(modelId); + + const iconMap: Record = { + anthropic: '/images/models/Anthropic.svg', + openai: '/images/models/OAI.svg', + google: '/images/models/Gemini.svg', + xai: '/images/models/Grok.svg', + moonshotai: '/images/models/Moonshot.svg', + bedrock: '/images/models/Anthropic.svg', // Bedrock uses Anthropic models primarily + openrouter: '/images/models/OAI.svg', // Default to OpenAI icon for OpenRouter + }; + + const iconSrc = iconMap[provider]; + + // Calculate responsive border radius - proportional to size (matching AgentAvatar) + const borderRadiusStyle = { + borderRadius: `${Math.min(size * 0.25, 16)}px` // 25% of size, max 16px + }; + + if (!iconSrc) { + return ( +
+ +
+ ); + } + + return ( +
+ {`${provider} +
+ ); +} + +/** + * Get the provider display name + */ +export function getModelProviderName(modelId: string): string { + const provider = getModelProvider(modelId); + + const nameMap: Record = { + anthropic: 'Anthropic', + openai: 'OpenAI', + google: 'Google', + xai: 'xAI', + moonshotai: 'Moonshot AI', + bedrock: 'AWS Bedrock', + openrouter: 'OpenRouter', + }; + + return nameMap[provider] || 'Unknown'; +}