mirror of https://github.com/kortix-ai/suna.git
feat: Add model provider icons and consolidate agent selection components
- Created model provider icon mapping utility with provider-specific SVG icons - Updated model selector to display provider icons (OpenAI, Anthropic, Google, xAI, MoonshotAI) - Increased icon sizes to 24px for better visibility across all components - Consolidated agent selection into single AgentSelector component - Removed duplicate AgentSelectionDropdown component - Updated trigger creation and schedule config to use new AgentSelector - Maintained UnifiedConfigMenu's integrated implementation for chat input - Aligned visual styling between agent avatars and model provider icons
This commit is contained in:
parent
0b29c8960c
commit
b319969e59
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="159" height="158" viewBox="0 0 159 158" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M88.5404 35.9707L122.851 122.03H141.667L107.356 35.9707H88.5404Z" fill="black"/>
|
||||||
|
<path d="M48.7354 87.9748L60.4754 57.7313L72.2155 87.9748H48.7354ZM50.6387 35.9707L16.3334 122.03H35.5148L42.5308 103.957H78.4211L85.4359 122.03H104.617L70.312 35.9707H50.6387Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 390 B |
|
@ -0,0 +1,10 @@
|
||||||
|
<svg width="159" height="158" viewBox="0 0 159 158" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1_9)">
|
||||||
|
<path d="M143 79.128C126.394 80.1471 110.733 87.2036 98.9683 98.9683C87.2036 110.733 80.1471 126.394 79.128 143H78.872C77.8546 126.393 70.7986 110.731 59.0336 98.9664C47.2686 87.2014 31.6071 80.1454 15 79.128L15 78.872C31.6071 77.8546 47.2686 70.7986 59.0336 59.0336C70.7986 47.2686 77.8546 31.6071 78.872 15L79.128 15C80.1471 31.6065 87.2036 47.2671 98.9683 59.0317C110.733 70.7964 126.394 77.8529 143 78.872V79.128Z" fill="black"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1_9">
|
||||||
|
<rect width="128" height="128" fill="white" transform="translate(15 15)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 706 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="159" height="158" viewBox="0 0 159 158" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M64.4348 97.1628L106.989 64.4024C109.075 62.7964 112.057 63.4228 113.051 65.9176C118.283 79.074 115.945 94.8848 105.536 105.74C95.127 116.596 80.6438 118.976 67.4059 113.554L52.9446 120.537C73.6863 135.322 98.8734 131.666 114.613 115.24C127.097 102.22 130.964 84.4732 127.348 68.4692L127.381 68.5032C122.138 44.9924 128.67 35.5948 142.05 16.3783C142.366 15.9227 142.683 15.467 143 15L125.393 33.3622V33.3052L64.4241 97.1744" fill="black"/>
|
||||||
|
<path d="M55.6535 105.125C40.7661 90.294 43.3328 67.3412 56.0356 54.1052C65.4288 44.3089 80.8187 40.3107 94.2531 46.1884L108.682 39.2399C106.082 37.2807 102.751 35.1734 98.928 33.6925C81.6489 26.277 60.9616 29.9677 46.9154 44.605C33.4043 58.6956 29.1555 80.3612 36.4517 98.8488C41.902 112.666 32.9674 122.439 23.9673 132.304C20.778 135.801 17.5777 139.298 15 143L55.6424 105.136" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 951 B |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,10 @@
|
||||||
|
<svg width="159" height="158" viewBox="0 0 159 158" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_1_10)">
|
||||||
|
<path d="M64.0937 61.5917V49.4317C64.0937 48.4075 64.4745 47.639 65.3621 47.1276L89.5925 33.0478C92.891 31.1279 96.8236 30.2323 100.882 30.2323C116.106 30.2323 125.747 42.1366 125.747 54.8081C125.747 55.7038 125.747 56.728 125.62 57.7521L100.502 42.9038C98.9798 42.0082 97.457 42.0082 95.9347 42.9038L64.0937 61.5917ZM120.672 108.953V79.8955C120.672 78.103 119.91 76.8231 118.388 75.9274L86.5474 57.2395L96.9497 51.2231C97.8374 50.7117 98.5992 50.7117 99.4867 51.2231L123.717 65.303C130.695 69.3995 135.387 78.103 135.387 86.5506C135.387 96.2782 129.68 105.238 120.672 108.951V108.953ZM56.6091 83.3521L46.2069 77.2085C45.3192 76.6971 44.9385 75.9287 44.9385 74.9045V46.7447C44.9385 33.049 55.3407 22.6803 69.4222 22.6803C74.7507 22.6803 79.6971 24.4729 83.8845 27.6726L58.8938 42.2651C57.3719 43.1608 56.6104 44.4407 56.6104 46.2333V83.3533L56.6091 83.3521ZM78.9998 96.4079L64.0936 87.9602V70.0407L78.9998 61.593L93.9047 70.0407V87.9602L78.9998 96.4079ZM88.5775 135.321C83.249 135.321 78.3026 133.528 74.1152 130.329L99.1055 115.735C100.628 114.84 101.389 113.56 101.389 111.768V74.6475L111.919 80.7911C112.807 81.3026 113.187 82.071 113.187 83.0952V111.255C113.187 124.95 102.657 135.32 88.5775 135.32V135.321ZM58.5118 106.777L34.2814 92.6968C27.3037 88.6001 22.6107 79.8968 22.6107 71.4491C22.6107 61.593 28.4462 52.7611 37.4526 49.0488V78.2327C37.4526 80.0252 38.2141 81.3051 39.736 82.2008L71.451 100.76L61.0488 106.777C60.1611 107.288 59.3994 107.288 58.5118 106.777ZM57.1172 127.769C42.7822 127.769 32.2526 116.888 32.2526 103.448C32.2526 102.424 32.3799 101.4 32.5061 100.376L57.4968 114.968C59.0187 115.864 60.5418 115.864 62.0637 114.968L93.9048 96.4092V108.569C93.9048 109.594 93.524 110.361 92.6363 110.873L68.406 124.953C65.1076 126.873 61.1748 127.769 57.116 127.769L57.1172 127.769ZM88.5775 143C103.927 143 116.739 131.992 119.658 117.4C133.866 113.687 143 100.247 143 86.5519C143 77.5916 139.194 68.8882 132.344 62.616C132.978 59.9278 133.359 57.2395 133.359 54.5525C133.359 36.2489 118.643 22.5519 101.644 22.5519C98.2197 22.5519 94.921 23.0634 91.6225 24.2161C85.9132 18.5838 78.0479 15 69.4222 15C54.0723 15 41.2604 26.0075 38.3415 40.5999C24.1339 44.3122 15 57.7522 15 71.4478C15 80.4082 18.8053 89.1115 25.6557 95.3837C25.0214 98.0719 24.6407 100.76 24.6407 103.447C24.6407 121.75 39.3563 135.447 56.3557 135.447C59.7802 135.447 63.0787 134.937 66.3771 133.784C72.0851 139.416 79.9505 143 88.5775 143Z" fill="black"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_1_10">
|
||||||
|
<rect width="128" height="128" fill="white" transform="translate(15 15)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { Search, Plus, Check, ChevronDown } from 'lucide-react';
|
import { Search, Plus, Check, ChevronDown, Loader2 } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
@ -16,7 +16,7 @@ import { cn } from '@/lib/utils';
|
||||||
import { AgentAvatar } from '@/components/thread/content/agent-avatar';
|
import { AgentAvatar } from '@/components/thread/content/agent-avatar';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
|
||||||
interface AgentSelectionDropdownProps {
|
interface AgentSelectorProps {
|
||||||
selectedAgentId?: string;
|
selectedAgentId?: string;
|
||||||
onAgentSelect: (agentId: string) => void;
|
onAgentSelect: (agentId: string) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -26,7 +26,7 @@ interface AgentSelectionDropdownProps {
|
||||||
variant?: 'default' | 'compact';
|
variant?: 'default' | 'compact';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AgentSelectionDropdown: React.FC<AgentSelectionDropdownProps> = ({
|
export const AgentSelector: React.FC<AgentSelectorProps> = ({
|
||||||
selectedAgentId,
|
selectedAgentId,
|
||||||
onAgentSelect,
|
onAgentSelect,
|
||||||
placeholder = "Choose an agent",
|
placeholder = "Choose an agent",
|
||||||
|
@ -37,33 +37,77 @@ export const AgentSelectionDropdown: React.FC<AgentSelectionDropdownProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState('');
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [allAgents, setAllAgents] = useState<any[]>([]);
|
||||||
const [showNewAgentDialog, setShowNewAgentDialog] = useState(false);
|
const [showNewAgentDialog, setShowNewAgentDialog] = useState(false);
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const { data: agentsResponse, isLoading } = useAgents();
|
// Debounce search query
|
||||||
const agents = agentsResponse?.agents || [];
|
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
|
// Fetch agents with pagination and search
|
||||||
const filteredAgents = agents.filter((agent: any) =>
|
const agentsParams = useMemo(() => ({
|
||||||
agent.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
page: currentPage,
|
||||||
agent.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
limit: 50,
|
||||||
);
|
search: debouncedSearchQuery || undefined,
|
||||||
|
}), [currentPage, debouncedSearchQuery]);
|
||||||
|
|
||||||
// Sort agents with selected first
|
const { data: agentsResponse, isLoading, isFetching } = useAgents(agentsParams);
|
||||||
const sortedAgents = React.useMemo(() => {
|
|
||||||
if (!selectedAgentId) return filteredAgents;
|
|
||||||
|
|
||||||
const selectedAgent = filteredAgents.find((agent: any) => agent.agent_id === selectedAgentId);
|
// Update agents list when data changes
|
||||||
const otherAgents = filteredAgents.filter((agent: any) => agent.agent_id !== selectedAgentId);
|
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;
|
const agents = allAgents;
|
||||||
}, [filteredAgents, selectedAgentId]);
|
|
||||||
|
// 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(() => {
|
useEffect(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
setTimeout(() => searchInputRef.current?.focus(), 50);
|
setTimeout(() => searchInputRef.current?.focus(), 50);
|
||||||
} else {
|
} else {
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
|
setDebouncedSearchQuery('');
|
||||||
|
setCurrentPage(1);
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
|
@ -85,7 +129,7 @@ export const AgentSelectionDropdown: React.FC<AgentSelectionDropdownProps> = ({
|
||||||
<div className="flex items-center gap-3 min-w-0 flex-1">
|
<div className="flex items-center gap-3 min-w-0 flex-1">
|
||||||
<AgentAvatar
|
<AgentAvatar
|
||||||
agentId={selectedAgent.agent_id}
|
agentId={selectedAgent.agent_id}
|
||||||
size={variant === 'compact' ? 16 : 20}
|
size={24}
|
||||||
className="flex-shrink-0"
|
className="flex-shrink-0"
|
||||||
fallbackName={selectedAgent.name}
|
fallbackName={selectedAgent.name}
|
||||||
/>
|
/>
|
||||||
|
@ -153,7 +197,7 @@ export const AgentSelectionDropdown: React.FC<AgentSelectionDropdownProps> = ({
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : sortedAgents.length === 0 ? (
|
) : orderedAgents.length === 0 ? (
|
||||||
<div className="px-3 py-6 text-center text-sm text-muted-foreground">
|
<div className="px-3 py-6 text-center text-sm text-muted-foreground">
|
||||||
<Search className="h-6 w-6 mx-auto mb-2 opacity-40" />
|
<Search className="h-6 w-6 mx-auto mb-2 opacity-40" />
|
||||||
<p>No agents found</p>
|
<p>No agents found</p>
|
||||||
|
@ -163,7 +207,7 @@ export const AgentSelectionDropdown: React.FC<AgentSelectionDropdownProps> = ({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-0.5 py-1">
|
<div className="space-y-0.5 py-1">
|
||||||
{sortedAgents.map((agent: any) => (
|
{orderedAgents.map((agent: any) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={agent.agent_id}
|
key={agent.agent_id}
|
||||||
className="flex items-center gap-3 px-3 py-2.5 mx-1 rounded-lg cursor-pointer"
|
className="flex items-center gap-3 px-3 py-2.5 mx-1 rounded-lg cursor-pointer"
|
||||||
|
@ -171,7 +215,7 @@ export const AgentSelectionDropdown: React.FC<AgentSelectionDropdownProps> = ({
|
||||||
>
|
>
|
||||||
<AgentAvatar
|
<AgentAvatar
|
||||||
agentId={agent.agent_id}
|
agentId={agent.agent_id}
|
||||||
size={20}
|
size={24}
|
||||||
className="flex-shrink-0"
|
className="flex-shrink-0"
|
||||||
fallbackName={agent.name}
|
fallbackName={agent.name}
|
||||||
/>
|
/>
|
||||||
|
@ -194,6 +238,28 @@ export const AgentSelectionDropdown: React.FC<AgentSelectionDropdownProps> = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Load More */}
|
||||||
|
{canLoadMore && (
|
||||||
|
<div className="px-1.5 pb-1">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full h-8 text-xs text-muted-foreground hover:text-foreground"
|
||||||
|
onClick={handleLoadMore}
|
||||||
|
disabled={isFetching}
|
||||||
|
>
|
||||||
|
{isFetching ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="h-3 w-3 animate-spin mr-1" />
|
||||||
|
Loading...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Load More'
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Create Agent Option */}
|
{/* Create Agent Option */}
|
||||||
{showCreateOption && (
|
{showCreateOption && (
|
||||||
<>
|
<>
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import React, { useState, useRef, useEffect, useMemo } from 'react';
|
import React, { useState, useRef, useEffect, useMemo } from 'react';
|
||||||
import { Check, Search, AlertTriangle, Crown, Cpu, Plus, Edit, Trash, KeyRound } from 'lucide-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 { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
@ -301,7 +302,8 @@ export function AgentModelSelector({
|
||||||
onClick={() => !disabled && handleSelect(model.id)}
|
onClick={() => !disabled && handleSelect(model.id)}
|
||||||
onMouseEnter={() => setHighlightedIndex(index)}
|
onMouseEnter={() => setHighlightedIndex(index)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center gap-3">
|
||||||
|
<ModelProviderIcon modelId={model.id} size={24} />
|
||||||
<span className="font-medium">{model.label}</span>
|
<span className="font-medium">{model.label}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -380,11 +382,11 @@ export function AgentModelSelector({
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<div className="relative flex items-center justify-center">
|
<ModelProviderIcon
|
||||||
<Cpu className="h-4 w-4" />
|
modelId={selectedModel}
|
||||||
{/* API models are quality controlled - no low quality warning needed */}
|
size={24}
|
||||||
</div>
|
/>
|
||||||
<span className="truncate">{selectedModelDisplay}</span>
|
<span className="truncate">{selectedModelDisplay}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
@ -406,10 +408,7 @@ export function AgentModelSelector({
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="relative flex items-center justify-center">
|
<ModelProviderIcon modelId={selectedModel} size={24} />
|
||||||
<Cpu className="h-4 w-4" />
|
|
||||||
{/* API models are quality controlled - no low quality warning needed */}
|
|
||||||
</div>
|
|
||||||
<span className="text-sm">{selectedModelDisplay}</span>
|
<span className="text-sm">{selectedModelDisplay}</span>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -509,7 +508,8 @@ export function AgentModelSelector({
|
||||||
)}
|
)}
|
||||||
onClick={() => handleSelect(model.id)}
|
onClick={() => handleSelect(model.id)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center gap-3">
|
||||||
|
<ModelProviderIcon modelId={model.id} size={24} />
|
||||||
<span className="font-medium">{model.label}</span>
|
<span className="font-medium">{model.label}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { format } from 'date-fns';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { TriggerProvider, ScheduleTriggerConfig } from '../types';
|
import { TriggerProvider, ScheduleTriggerConfig } from '../types';
|
||||||
import { useAgentWorkflows } from '@/hooks/react-query/agents/use-agent-workflows';
|
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 {
|
interface SimplifiedScheduleConfigProps {
|
||||||
provider: TriggerProvider;
|
provider: TriggerProvider;
|
||||||
|
@ -512,7 +512,7 @@ export const SimplifiedScheduleConfig: React.FC<SimplifiedScheduleConfigProps> =
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Agent</Label>
|
<Label>Agent</Label>
|
||||||
<AgentSelectionDropdown
|
<AgentSelector
|
||||||
selectedAgentId={selectedAgent}
|
selectedAgentId={selectedAgent}
|
||||||
onAgentSelect={onAgentSelect}
|
onAgentSelect={onAgentSelect}
|
||||||
placeholder="Choose an agent to handle this task"
|
placeholder="Choose an agent to handle this task"
|
||||||
|
|
|
@ -182,7 +182,7 @@ const LoggedInMenu: React.FC<UnifiedConfigMenuProps> = memo(function LoggedInMen
|
||||||
}, [selectedAgentId, displayAgent?.agent_id]);
|
}, [selectedAgentId, displayAgent?.agent_id]);
|
||||||
|
|
||||||
const renderAgentIcon = useCallback((agent: any) => {
|
const renderAgentIcon = useCallback((agent: any) => {
|
||||||
return <AgentAvatar agentId={agent?.agent_id} size={20} className="flex-shrink-0" fallbackName={agent?.name} />;
|
return <AgentAvatar agentId={agent?.agent_id} size={24} className="flex-shrink-0" fallbackName={agent?.name} />;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const currentAgentIdForPlaybooks = isLoggedIn ? displayAgent?.agent_id || '' : '';
|
const currentAgentIdForPlaybooks = isLoggedIn ? displayAgent?.agent_id || '' : '';
|
||||||
|
|
|
@ -790,7 +790,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="rounded-md flex items-center justify-center relative">
|
<div className="rounded-md flex items-center justify-center relative">
|
||||||
{groupAgentId ? (
|
{groupAgentId ? (
|
||||||
<AgentAvatar agentId={groupAgentId} size={20} className="h-5 w-5" />
|
<AgentAvatar agentId={groupAgentId} size={24} className="h-6 w-6" />
|
||||||
) : (
|
) : (
|
||||||
getAgentInfo().avatar
|
getAgentInfo().avatar
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { SimplifiedScheduleConfig } from '@/components/agents/triggers/providers
|
||||||
import { ScheduleTriggerConfig } from '@/components/agents/triggers/types';
|
import { ScheduleTriggerConfig } from '@/components/agents/triggers/types';
|
||||||
import { useCreateTrigger, useUpdateTrigger } from '@/hooks/react-query/triggers';
|
import { useCreateTrigger, useUpdateTrigger } from '@/hooks/react-query/triggers';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { AgentSelectionDropdown } from '@/components/agents/agent-selection-dropdown';
|
import { AgentSelector } from '@/components/agents/agent-selector';
|
||||||
|
|
||||||
interface TriggerCreationDialogProps {
|
interface TriggerCreationDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -148,7 +148,7 @@ export function TriggerCreationDialog({
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="space-y-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
<AgentSelectionDropdown
|
<AgentSelector
|
||||||
selectedAgentId={selectedAgent}
|
selectedAgentId={selectedAgent}
|
||||||
onAgentSelect={setSelectedAgent}
|
onAgentSelect={setSelectedAgent}
|
||||||
placeholder="Choose an agent"
|
placeholder="Choose an agent"
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { Cpu } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export type ModelProvider =
|
||||||
|
| 'openai'
|
||||||
|
| 'anthropic'
|
||||||
|
| 'google'
|
||||||
|
| 'xai'
|
||||||
|
| 'moonshotai'
|
||||||
|
| 'bedrock'
|
||||||
|
| 'openrouter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the provider from a model ID
|
||||||
|
*/
|
||||||
|
export function getModelProvider(modelId: string): ModelProvider {
|
||||||
|
if (modelId.includes('anthropic') || modelId.includes('claude')) {
|
||||||
|
return 'anthropic';
|
||||||
|
}
|
||||||
|
if (modelId.includes('openai') || modelId.includes('gpt')) {
|
||||||
|
return 'openai';
|
||||||
|
}
|
||||||
|
if (modelId.includes('google') || modelId.includes('gemini')) {
|
||||||
|
return 'google';
|
||||||
|
}
|
||||||
|
if (modelId.includes('xai') || modelId.includes('grok')) {
|
||||||
|
return 'xai';
|
||||||
|
}
|
||||||
|
if (modelId.includes('moonshotai') || modelId.includes('kimi')) {
|
||||||
|
return 'moonshotai';
|
||||||
|
}
|
||||||
|
if (modelId.includes('bedrock')) {
|
||||||
|
return 'bedrock';
|
||||||
|
}
|
||||||
|
if (modelId.includes('openrouter')) {
|
||||||
|
return 'openrouter';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default fallback - try to extract provider from model ID format "provider/model"
|
||||||
|
const parts = modelId.split('/');
|
||||||
|
if (parts.length > 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<ModelProvider, string> = {
|
||||||
|
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 (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center bg-muted border flex-shrink-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
style={{ width: size, height: size, ...borderRadiusStyle }}
|
||||||
|
>
|
||||||
|
<Cpu size={size * 0.6} className="text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center bg-background border flex-shrink-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
style={{ width: size, height: size, ...borderRadiusStyle }}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={iconSrc}
|
||||||
|
alt={`${provider} icon`}
|
||||||
|
width={size * 0.9} // Increase to 90% for maximum visibility
|
||||||
|
height={size * 0.9}
|
||||||
|
className="object-contain"
|
||||||
|
style={{ width: size * 0.9, height: size * 0.9 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the provider display name
|
||||||
|
*/
|
||||||
|
export function getModelProviderName(modelId: string): string {
|
||||||
|
const provider = getModelProvider(modelId);
|
||||||
|
|
||||||
|
const nameMap: Record<ModelProvider, string> = {
|
||||||
|
anthropic: 'Anthropic',
|
||||||
|
openai: 'OpenAI',
|
||||||
|
google: 'Google',
|
||||||
|
xai: 'xAI',
|
||||||
|
moonshotai: 'Moonshot AI',
|
||||||
|
bedrock: 'AWS Bedrock',
|
||||||
|
openrouter: 'OpenRouter',
|
||||||
|
};
|
||||||
|
|
||||||
|
return nameMap[provider] || 'Unknown';
|
||||||
|
}
|
Loading…
Reference in New Issue