avatar refactor fe, fix round agent avatar

:wq
This commit is contained in:
marko-kraemer 2025-09-29 17:33:12 +02:00
parent 8306a344a4
commit 0b29c8960c
11 changed files with 186 additions and 674 deletions

View File

@ -38,8 +38,7 @@ import Link from 'next/link';
import Image from 'next/image';
import { useTheme } from 'next-themes';
import ColorThief from 'colorthief';
import { KortixLogo } from '@/components/sidebar/kortix-logo';
import { DynamicIcon } from 'lucide-react/dynamic';
import { AgentAvatar } from '@/components/thread/content/agent-avatar';
interface MarketplaceTemplate {
template_id: string;
@ -357,13 +356,13 @@ export default function TemplateSharePage() {
const hasTools = customTools.length > 0 || agentpressTools.length > 0;
const getDefaultAvatar = () => {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD'];
const color = colors[Math.floor(Math.random() * colors.length)];
return (
<DynamicIcon
name={template.icon_name || 'bot' as any}
<AgentAvatar
iconName={template.icon_name}
iconColor={template.icon_color}
backgroundColor={template.icon_background}
agentName={template.name}
size={28}
color={color}
/>
);
};
@ -459,16 +458,13 @@ export default function TemplateSharePage() {
/>
)}
<div className="relative aspect-square w-full max-w-sm mx-auto lg:mx-0 rounded-2xl overflow-hidden bg-background">
{template.icon_name ? (
<>
<div
className="w-full h-full flex items-center justify-center"
style={{ backgroundColor: template.icon_background || '#e5e5e5' }}
>
<DynamicIcon
name={template.icon_name as any}
<div className="w-full h-full flex items-center justify-center">
<AgentAvatar
iconName={template.icon_name}
iconColor={template.icon_color}
backgroundColor={template.icon_background}
agentName={template.name}
size={120}
color={template.icon_color || '#000000'}
/>
</div>
<img
@ -479,10 +475,6 @@ export default function TemplateSharePage() {
crossOrigin="anonymous"
onLoad={() => setImageLoaded(true)}
/>
</>
) : (
getDefaultAvatar()
)}
</div>
</div>
<div className="space-y-4">

View File

@ -7,9 +7,8 @@ import { Badge } from '@/components/ui/badge';
import { useRouter } from 'next/navigation';
import { useCreateTemplate, useUnpublishTemplate } from '@/hooks/react-query/secure-mcp/use-secure-mcp';
import { toast } from 'sonner';
import { AgentCard } from './custom-agents-page/agent-card';
import { KortixLogo } from '../sidebar/kortix-logo';
import { DynamicIcon } from 'lucide-react/dynamic';
import { UnifiedAgentCard } from '@/components/ui/unified-agent-card';
import { AgentAvatar } from '../thread/content/agent-avatar';
import { AgentConfigurationDialog } from './agent-configuration-dialog';
import { isStagingMode } from '@/lib/config';
@ -98,27 +97,15 @@ const AgentModal: React.FC<AgentModalProps> = ({
<DialogTitle className="sr-only">Agent actions</DialogTitle>
<div className="relative">
<div className={`p-4 h-24 flex items-start justify-start relative`}>
{isSunaAgent ? (
<div className="p-6">
<KortixLogo size={48} />
</div>
) : agent.icon_name ? (
<div
className="h-16 w-16 rounded-xl flex items-center justify-center"
style={{ backgroundColor: agent.icon_background || '#F3F4F6' }}
>
<DynamicIcon
name={agent.icon_name as any}
size={32}
color={agent.icon_color || '#000000'}
<AgentAvatar
iconName={agent.icon_name}
iconColor={agent.icon_color}
backgroundColor={agent.icon_background}
agentName={agent.name}
isSunaDefault={isSunaAgent}
size={64}
/>
</div>
) : (
<div className="h-16 w-16 rounded-xl bg-muted flex items-center justify-center">
<span className="text-lg font-semibold">{agent.name.charAt(0).toUpperCase()}</span>
</div>
)}
</div>
<div className="p-4 space-y-2">
<div>
@ -296,11 +283,27 @@ export const AgentsGrid: React.FC<AgentsGridProps> = ({
)}
<div className={`transition-all duration-200 ${isDeleting ? 'opacity-60 scale-95' : ''}`}>
<AgentCard
mode="agent"
data={agentData}
styling={undefined}
onClick={() => !isDeleting && handleAgentClick(agent)}
<UnifiedAgentCard
variant="agent"
data={{
id: agent.agent_id,
name: agent.name,
tags: agent.tags,
created_at: agent.created_at,
agent_id: agent.agent_id,
is_default: agent.is_default,
is_public: agent.is_public,
marketplace_published_at: agent.marketplace_published_at,
download_count: agent.download_count,
current_version: agent.current_version,
metadata: agent.metadata,
icon_name: agent.icon_name,
icon_color: agent.icon_color,
icon_background: agent.icon_background,
}}
actions={{
onClick: () => !isDeleting && handleAgentClick(agent),
}}
/>
</div>
<div className={`absolute bottom-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity ${isDeleting ? 'pointer-events-none' : ''}`}>

View File

@ -1,503 +0,0 @@
'use client';
import React from 'react';
import { Download, CheckCircle, Loader2, Globe, GlobeLock, GitBranch, Trash2, MoreVertical, User } from 'lucide-react';
import { DynamicIcon } from 'lucide-react/dynamic';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { KortixLogo } from '@/components/sidebar/kortix-logo';
import { UnifiedAgentCard, type BaseAgentData, type AgentCardVariant } from '@/components/ui/unified-agent-card';
export type AgentCardMode = 'marketplace' | 'template' | 'agent';
interface LegacyBaseAgentData {
id: string;
name: string;
tags?: string[];
created_at: string;
}
interface MarketplaceData extends LegacyBaseAgentData {
creator_id: string;
is_kortix_team?: boolean;
download_count: number;
creator_name?: string;
marketplace_published_at?: string;
}
interface TemplateData extends LegacyBaseAgentData {
template_id: string;
is_public?: boolean;
download_count?: number;
}
interface AgentData extends LegacyBaseAgentData {
agent_id: string;
is_default?: boolean;
is_public?: boolean;
marketplace_published_at?: string;
download_count?: number;
current_version?: {
version_id: string;
version_name: string;
version_number: number;
};
metadata?: {
is_suna_default?: boolean;
centrally_managed?: boolean;
restrictions?: {
system_prompt_editable?: boolean;
tools_editable?: boolean;
name_editable?: boolean;
description_editable?: boolean;
mcps_editable?: boolean;
};
};
}
type AgentCardData = MarketplaceData | TemplateData | AgentData;
interface AgentCardProps {
mode: AgentCardMode;
data: AgentCardData;
styling?: {
color: string;
};
isActioning?: boolean;
onPrimaryAction?: (data: any, e?: React.MouseEvent) => void;
onSecondaryAction?: (data: any, e?: React.MouseEvent) => void;
onDeleteAction?: (data: any, e?: React.MouseEvent) => void;
onClick?: (data: any) => void;
currentUserId?: string;
}
const MarketplaceBadge: React.FC<{
isKortixTeam?: boolean;
isOwner?: boolean;
}> = ({ isKortixTeam, isOwner }) => {
return (
<div className="flex gap-1 flex-wrap">
{isKortixTeam && (
<Badge variant="secondary" className="bg-blue-100 text-blue-700 border-0 dark:bg-blue-950 dark:text-blue-300">
<CheckCircle className="h-3 w-3 mr-1" />
Kortix
</Badge>
)}
{isOwner && (
<Badge variant="secondary" className="bg-green-100 text-green-700 border-0 dark:bg-green-950 dark:text-green-300">
Owner
</Badge>
)}
</div>
);
};
const TemplateBadge: React.FC<{ isPublic?: boolean }> = ({ isPublic }) => {
if (isPublic) {
return (
<Badge variant="default" className="bg-green-100 text-green-700 border-0 dark:bg-green-950 dark:text-green-300">
<Globe className="h-3 w-3" />
Public
</Badge>
);
}
return (
<Badge variant="secondary" className="bg-gray-100 text-gray-700 border-0 dark:bg-gray-800 dark:text-gray-300">
<GlobeLock className="h-3 w-3" />
Private
</Badge>
);
};
const AgentBadges: React.FC<{ agent: AgentData, isSunaAgent: boolean }> = ({ agent, isSunaAgent }) => (
<div className="flex gap-1">
{!isSunaAgent && agent.current_version && (
<Badge variant="outline" className="text-xs">
<GitBranch className="h-3 w-3 mr-1" />
{agent.current_version.version_name}
</Badge>
)}
{!isSunaAgent && agent.is_public && (
<Badge variant="default" className="bg-green-100 text-green-700 border-0 dark:bg-green-950 dark:text-green-300 text-xs">
<Globe className="h-3 w-3 mr-1" />
Published
</Badge>
)}
</div>
);
const MarketplaceMetadata: React.FC<{ data: MarketplaceData }> = ({ data }) => (
<div className="flex items-center justify-between text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<User className="h-3 w-3" />
<span>{data.creator_name || 'Anonymous'}</span>
</div>
<div className="flex items-center gap-1">
<Download className="h-3 w-3" />
<span>{data.download_count} installs</span>
</div>
</div>
);
const TemplateMetadata: React.FC<{ data: TemplateData }> = ({ data }) => (
<div className="space-y-1 text-xs text-muted-foreground">
{data.is_public && data.download_count !== undefined && data.download_count > 0 && (
<div className="flex items-center gap-1">
<Download className="h-3 w-3" />
<span>{data.download_count} downloads</span>
</div>
)}
</div>
);
const AgentMetadata: React.FC<{ data: AgentData }> = ({ data }) => (
<div className="space-y-1 text-xs text-muted-foreground">
{data.is_public && data.marketplace_published_at && data.download_count != null && data.download_count > 0 && (
<div className="flex items-center gap-1">
<Download className="h-3 w-3" />
<span>{data.download_count} downloads</span>
</div>
)}
</div>
);
const MarketplaceActions: React.FC<{
onAction?: (data: any, e?: React.MouseEvent) => void;
onDeleteAction?: (data: any, e?: React.MouseEvent) => void;
isActioning?: boolean;
data: any;
currentUserId?: string;
}> = ({ onAction, onDeleteAction, isActioning, data, currentUserId }) => {
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
const isOwner = currentUserId && data.creator_id === currentUserId;
const handleDeleteClick = (e: React.MouseEvent) => {
e.stopPropagation();
setShowDeleteDialog(true);
};
const handleConfirmDelete = () => {
setShowDeleteDialog(false);
onDeleteAction?.(data);
};
return (
<>
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
<Button
onClick={(e) => {
e.stopPropagation();
onAction?.(data, e);
}}
disabled={isActioning}
className="flex-1"
size="sm"
>
{isActioning ? (
<>
<Loader2 className="h-4 w-4 animate-spin mr-2" />
Installing...
</>
) : (
<>
<Download className="h-4 w-4 mr-2" />
Install
</>
)}
</Button>
{isOwner && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="px-2"
disabled={isActioning}
onClick={(e) => e.stopPropagation()}
>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48">
<DropdownMenuItem
onClick={handleDeleteClick}
>
<Trash2 className="h-4 w-4" />
Delete Template
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Template</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete "<strong>{data.name}</strong>"? This will permanently remove it from the marketplace and cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={(e) => e.stopPropagation()}>
Cancel
</AlertDialogCancel>
<AlertDialogAction
onClick={(e) => {
e.stopPropagation();
handleConfirmDelete();
}}
className="bg-destructive hover:bg-destructive/90 text-white"
>
{isActioning ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Deleting...
</>
) : (
'Delete Template'
)}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
};
const TemplateActions: React.FC<{
data: TemplateData;
onPrimaryAction?: (data: any, e?: React.MouseEvent) => void;
onSecondaryAction?: (data: any, e?: React.MouseEvent) => void;
isActioning?: boolean;
}> = ({ data, onPrimaryAction, onSecondaryAction, isActioning }) => (
<div className="space-y-2">
{data.is_public ? (
<>
<Button
onClick={(e) => onPrimaryAction?.(data, e)}
disabled={isActioning}
variant="outline"
className="w-full"
size="sm"
>
{isActioning ? (
<>
<Loader2 className="h-3 w-3 animate-spin " />
Unpublishing...
</>
) : (
<>
<GlobeLock className="h-3 w-3 " />
Make Private
</>
)}
</Button>
</>
) : (
<Button
onClick={(e) => onPrimaryAction?.(data, e)}
disabled={isActioning}
variant="default"
className="w-full"
size="sm"
>
{isActioning ? (
<>
<Loader2 className="h-3 w-3 animate-spin " />
Publishing...
</>
) : (
<>
<Globe className="h-3 w-3 " />
Publish to Marketplace
</>
)}
</Button>
)}
</div>
);
const CardAvatar: React.FC<{
isSunaAgent?: boolean;
agentName?: string;
iconName?: string;
iconColor?: string;
iconBackground?: string;
}> = ({
isSunaAgent = false,
agentName,
iconName,
iconColor = '#000000',
iconBackground = '#F3F4F6'
}) => {
if (isSunaAgent) {
return (
<div className="h-14 w-14 bg-muted border flex items-center justify-center rounded-2xl">
<KortixLogo size={28} />
</div>
)
}
if (iconName) {
return (
<div
className="h-14 w-14 flex border items-center justify-center rounded-2xl"
style={{ backgroundColor: iconBackground }}
>
<DynamicIcon
name={iconName as any}
size={28}
color={iconColor}
/>
</div>
);
}
return (
<div className="h-14 w-14 border bg-muted flex items-center justify-center rounded-2xl">
<span className="text-lg font-semibold">{agentName?.charAt(0).toUpperCase() || '?'}</span>
</div>
)
};
const TagList: React.FC<{ tags?: string[] }> = ({ tags }) => {
return (
<div className="flex flex-wrap gap-1 min-h-[1.25rem]">
{tags && tags.length > 0 && (
<>
{tags.slice(0, 2).map(tag => (
<Badge key={tag} variant="outline" className="text-xs border-border/50">
{tag}
</Badge>
))}
{tags.length > 2 && (
<Badge variant="outline" className="text-xs border-border/50">
+{tags.length - 2}
</Badge>
)}
</>
)}
</div>
);
};
export const AgentCard: React.FC<AgentCardProps> = ({
mode,
data,
styling,
isActioning = false,
onPrimaryAction,
onSecondaryAction,
onDeleteAction,
onClick,
currentUserId
}) => {
// Convert legacy mode to new variant
const getVariant = (mode: AgentCardMode): AgentCardVariant => {
switch (mode) {
case 'marketplace':
return 'marketplace';
case 'template':
return 'template';
case 'agent':
return 'agent';
default:
return 'agent';
}
};
// Convert legacy data to BaseAgentData
const convertToBaseAgentData = (data: AgentCardData): BaseAgentData => {
const baseData: BaseAgentData = {
id: data.id,
name: data.name,
tags: data.tags,
created_at: data.created_at,
};
// Add mode-specific fields
if (mode === 'marketplace') {
const marketplaceData = data as MarketplaceData;
return {
...baseData,
creator_id: marketplaceData.creator_id,
creator_name: marketplaceData.creator_name,
is_kortix_team: marketplaceData.is_kortix_team,
download_count: marketplaceData.download_count,
marketplace_published_at: marketplaceData.marketplace_published_at,
icon_name: (data as any)?.icon_name,
icon_color: (data as any)?.icon_color,
icon_background: (data as any)?.icon_background,
};
}
if (mode === 'template') {
const templateData = data as TemplateData;
return {
...baseData,
template_id: templateData.template_id,
is_public: templateData.is_public,
download_count: templateData.download_count,
icon_name: (data as any)?.icon_name,
icon_color: (data as any)?.icon_color,
icon_background: (data as any)?.icon_background,
};
}
if (mode === 'agent') {
const agentData = data as AgentData;
return {
...baseData,
agent_id: agentData.agent_id,
is_default: agentData.is_default,
is_public: agentData.is_public,
marketplace_published_at: agentData.marketplace_published_at,
download_count: agentData.download_count,
current_version: agentData.current_version,
metadata: agentData.metadata,
icon_name: (data as any)?.icon_name,
icon_color: (data as any)?.icon_color,
icon_background: (data as any)?.icon_background,
};
}
return baseData;
};
return (
<UnifiedAgentCard
variant={getVariant(mode)}
data={convertToBaseAgentData(data)}
actions={{
onPrimaryAction,
onSecondaryAction,
onDeleteAction,
onClick,
}}
state={{
isActioning,
}}
currentUserId={currentUserId}
/>
);
};

View File

@ -4,7 +4,6 @@ export { SearchBar } from './search-bar';
export { MyAgentsTab } from './my-agents-tab';
export { MarketplaceTab } from './marketplace-tab';
export { MyTemplatesTab } from './my-templates-tab';
export { AgentCard, type AgentCardMode } from './agent-card';
export { MarketplaceSectionHeader } from './marketplace-section-header';
export { PublishDialog } from './publish-dialog';
export { LoadingSkeleton } from './loading-skeleton';

View File

@ -5,7 +5,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Skeleton } from '@/components/ui/skeleton';
import { SearchBar } from './search-bar';
import { MarketplaceSectionHeader } from './marketplace-section-header';
import { AgentCard } from './agent-card';
import { UnifiedAgentCard } from '@/components/ui/unified-agent-card';
import { Pagination } from '../pagination';
import type { MarketplaceTemplate } from '@/components/agents/installation/types';
@ -123,15 +123,31 @@ export const MarketplaceTab = ({
/> */}
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{allMarketplaceItems.map((item) => (
<AgentCard
<UnifiedAgentCard
key={item.id}
mode="marketplace"
data={item}
styling={getItemStyling(item)}
isActioning={installingItemId === item.id}
onPrimaryAction={onInstallClick}
onDeleteAction={onDeleteTemplate}
onClick={() => handleAgentClick(item)}
variant="marketplace"
data={{
id: item.id,
name: item.name,
tags: item.tags,
created_at: item.created_at,
creator_id: item.creator_id,
creator_name: item.creator_name,
is_kortix_team: item.is_kortix_team,
download_count: item.download_count,
marketplace_published_at: item.marketplace_published_at,
icon_name: item.icon_name,
icon_color: item.icon_color,
icon_background: item.icon_background,
}}
state={{
isActioning: installingItemId === item.id,
}}
actions={{
onPrimaryAction: () => onInstallClick(item),
onDeleteAction: () => onDeleteTemplate(item),
onClick: () => handleAgentClick(item),
}}
currentUserId={currentUserId}
/>
))}
@ -140,15 +156,31 @@ export const MarketplaceTab = ({
) : (
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{allMarketplaceItems.map((item) => (
<AgentCard
<UnifiedAgentCard
key={item.id}
mode="marketplace"
data={item}
styling={getItemStyling(item)}
isActioning={installingItemId === item.id}
onPrimaryAction={onInstallClick}
onDeleteAction={onDeleteTemplate}
onClick={() => handleAgentClick(item)}
variant="marketplace"
data={{
id: item.id,
name: item.name,
tags: item.tags,
created_at: item.created_at,
creator_id: item.creator_id,
creator_name: item.creator_name,
is_kortix_team: item.is_kortix_team,
download_count: item.download_count,
marketplace_published_at: item.marketplace_published_at,
icon_name: item.icon_name,
icon_color: item.icon_color,
icon_background: item.icon_background,
}}
state={{
isActioning: installingItemId === item.id,
}}
actions={{
onPrimaryAction: () => onInstallClick(item),
onDeleteAction: () => onDeleteTemplate(item),
onClick: () => handleAgentClick(item),
}}
currentUserId={currentUserId}
/>
))}

View File

@ -8,7 +8,7 @@ import { EmptyState } from '../empty-state';
import { AgentsGrid } from '../agents-grid';
import { LoadingState } from '../loading-state';
import { Pagination } from '../pagination';
import { AgentCard } from './agent-card';
import { UnifiedAgentCard } from '@/components/ui/unified-agent-card';
type AgentFilter = 'all' | 'templates';
@ -131,18 +131,30 @@ export const MyAgentsTab = ({
{myTemplates.map((template) => {
const isActioning = templatesActioningId === template.template_id;
return (
<AgentCard
<UnifiedAgentCard
key={template.template_id}
mode="template"
data={template}
styling={getTemplateStyling(template)}
isActioning={isActioning}
onPrimaryAction={
template.is_public
variant="template"
data={{
id: template.template_id,
name: template.name,
tags: template.tags,
created_at: template.created_at,
template_id: template.template_id,
is_public: template.is_public,
download_count: template.download_count,
icon_name: template.icon_name,
icon_color: template.icon_color,
icon_background: template.icon_background,
}}
state={{
isActioning: isActioning,
}}
actions={{
onPrimaryAction: template.is_public
? () => onUnpublish(template.template_id, template.name)
: () => onPublish(template)
}
onSecondaryAction={template.is_public ? () => {} : undefined}
: () => onPublish(template),
onSecondaryAction: template.is_public ? () => {} : undefined,
}}
/>
);
})}

View File

@ -5,7 +5,7 @@ import { Plus, Globe, AlertTriangle } from 'lucide-react';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import { AgentCard } from './agent-card';
import { UnifiedAgentCard } from '@/components/ui/unified-agent-card';
interface MyTemplatesTabProps {
templatesError: any;
@ -73,18 +73,30 @@ export const MyTemplatesTab = ({
{myTemplates?.map((template) => {
const isActioning = templatesActioningId === template.template_id;
return (
<AgentCard
<UnifiedAgentCard
key={template.template_id}
mode="template"
data={template}
styling={getTemplateStyling(template)}
isActioning={isActioning}
onPrimaryAction={
template.is_public
variant="template"
data={{
id: template.template_id,
name: template.name,
tags: template.tags,
created_at: template.created_at,
template_id: template.template_id,
is_public: template.is_public,
download_count: template.download_count,
icon_name: template.icon_name,
icon_color: template.icon_color,
icon_background: template.icon_background,
}}
state={{
isActioning: isActioning,
}}
actions={{
onPrimaryAction: template.is_public
? () => onUnpublish(template.template_id, template.name)
: () => onPublish(template)
}
onSecondaryAction={template.is_public ? onViewInMarketplace : undefined}
: () => onPublish(template),
onSecondaryAction: template.is_public ? onViewInMarketplace : undefined,
}}
/>
);
})}

View File

@ -163,7 +163,6 @@ export const MarketplaceAgentPreviewDialog: React.FC<MarketplaceAgentPreviewDial
backgroundColor={agent.icon_background}
agentName={agent.name}
size={80}
className="shadow-lg"
/>
</div>
</DialogHeader>

View File

@ -48,11 +48,17 @@ export const AgentAvatar: React.FC<AgentAvatarProps> = ({
const agentName = propAgentName ?? agent?.name ?? fallbackName;
const isSuna = propIsSunaDefault ?? agent?.metadata?.is_suna_default;
// Calculate responsive border radius - proportional to size
// Use a ratio that prevents full rounding while maintaining nice corners
const borderRadiusStyle = {
borderRadius: `${Math.min(size * 0.25, 16)}px` // 25% of size, max 16px
};
if (isLoading && agentId) {
return (
<div
className={cn("bg-muted animate-pulse rounded", className)}
style={{ width: size, height: size }}
className={cn("bg-muted animate-pulse", className)}
style={{ width: size, height: size, ...borderRadiusStyle }}
/>
);
}
@ -69,10 +75,10 @@ export const AgentAvatar: React.FC<AgentAvatarProps> = ({
return (
<div
className={cn(
"flex items-center justify-center rounded bg-muted border",
"flex items-center justify-center bg-muted border",
className
)}
style={{ width: size, height: size }}
style={{ width: size, height: size, ...borderRadiusStyle }}
>
<KortixLogo size={size * 0.6} />
</div>
@ -83,13 +89,14 @@ export const AgentAvatar: React.FC<AgentAvatarProps> = ({
return (
<div
className={cn(
"flex items-center justify-center rounded transition-all",
"flex items-center justify-center transition-all border",
className
)}
style={{
width: size,
height: size,
backgroundColor
backgroundColor,
...borderRadiusStyle
}}
>
<DynamicIcon
@ -105,10 +112,10 @@ export const AgentAvatar: React.FC<AgentAvatarProps> = ({
return (
<div
className={cn(
"flex items-center justify-center rounded bg-muted",
"flex items-center justify-center bg-muted border",
className
)}
style={{ width: size, height: size }}
style={{ width: size, height: size, ...borderRadiusStyle }}
>
<DynamicIcon
name="bot"

View File

@ -3,13 +3,9 @@ import {
Bot,
CheckCircle,
AlertTriangle,
Sparkles,
Settings,
Zap,
Calendar,
User,
Palette,
Shield
Sparkles,
User
} from 'lucide-react';
import { ToolViewProps } from '../types';
import { formatTimestamp, getToolTitle } from '../utils';
@ -20,38 +16,8 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { LoadingState } from '../shared/LoadingState';
import { Separator } from "@/components/ui/separator";
import { extractCreateNewAgentData } from './_utils';
import { DynamicIcon } from 'lucide-react/dynamic';
import { AgentAvatar } from '../../content/agent-avatar';
function IconPreview({
iconName,
iconColor,
iconBackground
}: {
iconName: string;
iconColor: string;
iconBackground: string;
}) {
const iconMap: { [key: string]: React.ComponentType<{ className?: string }> } = {
bot: Bot,
sparkles: Sparkles,
zap: Zap,
settings: Settings,
shield: Shield,
user: User,
palette: Palette,
};
const IconComponent = iconMap[iconName] || Bot;
return (
<div
className="w-12 h-12 rounded-xl flex items-center justify-center border"
style={{ backgroundColor: iconBackground }}
>
<DynamicIcon name={iconName as any} size={24} color={iconColor} />
</div>
);
}
export function CreateNewAgentToolView({
name = 'create-new-agent',
@ -150,17 +116,13 @@ export function CreateNewAgentToolView({
<div className="border rounded-xl p-4 space-y-4">
<div className="flex items-start justify-between">
<div className="flex items-center gap-3">
{icon_name && icon_color && icon_background ? (
<IconPreview
<AgentAvatar
iconName={icon_name}
iconColor={icon_color}
iconBackground={icon_background}
backgroundColor={icon_background}
agentName={agentName}
size={48}
/>
) : (
<div className="w-12 h-12 rounded-xl bg-muted/50 border flex items-center justify-center overflow-hidden">
<Bot className="w-6 h-6 text-blue-600 dark:text-blue-400" />
</div>
)}
<div>
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100">
{agentName}

View File

@ -136,12 +136,11 @@ const CardAvatar: React.FC<{
if (isSunaAgent) {
return (
<div className={cn(
"bg-muted border flex items-center justify-center rounded-2xl",
size <= 40 ? "h-10 w-10" : "h-14 w-14"
)}>
<KortixLogo size={size * 0.6} />
</div>
<AgentAvatar
isSunaDefault={true}
size={size}
className="border"
/>
);
}
@ -153,19 +152,17 @@ const CardAvatar: React.FC<{
backgroundColor={data.icon_background}
agentName={data.name}
size={size}
className="shadow-sm"
/>
);
}
// Fallback avatar
return (
<div className={cn(
"border bg-muted flex items-center justify-center rounded-2xl",
size <= 40 ? "h-10 w-10" : "h-14 w-14"
)}>
<span className="text-lg font-semibold">{data.name?.charAt(0).toUpperCase() || '?'}</span>
</div>
<AgentAvatar
agentName={data.name}
size={size}
className="border"
/>
);
};
@ -376,7 +373,7 @@ export const UnifiedAgentCard: React.FC<UnifiedAgentCardProps> = ({
>
<Card
className={cn(
'cursor-pointer transition-all duration-300 hover:shadow-lg',
'cursor-pointer transition-all duration-300',
isSelected
? 'ring-2 ring-primary bg-primary/5'
: 'hover:border-primary/50',
@ -432,7 +429,7 @@ export const UnifiedAgentCard: React.FC<UnifiedAgentCardProps> = ({
const renderStandardCard = () => {
const cardClassName = cn(
'group relative bg-card rounded-2xl overflow-hidden shadow-sm transition-all duration-300 border cursor-pointer flex flex-col border-border/50 hover:border-primary/20',
'group relative bg-card rounded-2xl overflow-hidden transition-all duration-300 border cursor-pointer flex flex-col border-border/50 hover:border-primary/20',
className
);