mirror of https://github.com/kortix-ai/suna.git
parent
8306a344a4
commit
0b29c8960c
|
@ -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,30 +458,23 @@ 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}
|
||||
size={120}
|
||||
color={template.icon_color || '#000000'}
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
ref={imageRef}
|
||||
src={""}
|
||||
alt={template.name}
|
||||
className="w-full h-full object-cover"
|
||||
crossOrigin="anonymous"
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
getDefaultAvatar()
|
||||
)}
|
||||
<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}
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
ref={imageRef}
|
||||
src={""}
|
||||
alt={template.name}
|
||||
className="w-full h-full object-cover"
|
||||
crossOrigin="anonymous"
|
||||
onLoad={() => setImageLoaded(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
|
|
|
@ -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,26 +97,14 @@ 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'}
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
<AgentAvatar
|
||||
iconName={agent.icon_name}
|
||||
iconColor={agent.icon_color}
|
||||
backgroundColor={agent.icon_background}
|
||||
agentName={agent.name}
|
||||
isSunaDefault={isSunaAgent}
|
||||
size={64}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-4 space-y-2">
|
||||
|
@ -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' : ''}`}>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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';
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -163,7 +163,6 @@ export const MarketplaceAgentPreviewDialog: React.FC<MarketplaceAgentPreviewDial
|
|||
backgroundColor={agent.icon_background}
|
||||
agentName={agent.name}
|
||||
size={80}
|
||||
className="shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
iconName={icon_name}
|
||||
iconColor={icon_color}
|
||||
iconBackground={icon_background}
|
||||
/>
|
||||
) : (
|
||||
<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>
|
||||
)}
|
||||
<AgentAvatar
|
||||
iconName={icon_name}
|
||||
iconColor={icon_color}
|
||||
backgroundColor={icon_background}
|
||||
agentName={agentName}
|
||||
size={48}
|
||||
/>
|
||||
<div>
|
||||
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100">
|
||||
{agentName}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue