diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0b426c7..2688078 100755 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -21,6 +21,7 @@ "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle-group": "^1.1.11", "@tanstack/react-query": "^5.90.5", "@types/react-syntax-highlighter": "^15.5.13", "axios": "^1.13.1", @@ -4688,6 +4689,60 @@ } } }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index c079f2f..9ec2428 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,6 +35,7 @@ "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle-group": "^1.1.11", "@tanstack/react-query": "^5.90.5", "@types/react-syntax-highlighter": "^15.5.13", "axios": "^1.13.1", diff --git a/frontend/src/app/[locale]/(authenticated)/agents/page.tsx b/frontend/src/app/[locale]/(authenticated)/agents/page.tsx index 72b2f7d..e0a147a 100644 --- a/frontend/src/app/[locale]/(authenticated)/agents/page.tsx +++ b/frontend/src/app/[locale]/(authenticated)/agents/page.tsx @@ -1,8 +1,8 @@ /** * Agent Types List Page * - * Displays a list of agent types with search and filter functionality. - * Allows navigation to agent type detail and creation pages. + * Displays a list of agent types with search, status, and category filters. + * Supports grid and list view modes with user preference persistence. */ 'use client'; @@ -10,9 +10,10 @@ import { useState, useCallback, useMemo } from 'react'; import { useRouter } from '@/lib/i18n/routing'; import { toast } from 'sonner'; -import { AgentTypeList } from '@/components/agents'; +import { AgentTypeList, type ViewMode } from '@/components/agents'; import { useAgentTypes } from '@/lib/api/hooks/useAgentTypes'; import { useDebounce } from '@/lib/hooks/useDebounce'; +import type { AgentTypeCategory } from '@/lib/api/types/agentTypes'; export default function AgentTypesPage() { const router = useRouter(); @@ -20,6 +21,8 @@ export default function AgentTypesPage() { // Filter state const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); + const [categoryFilter, setCategoryFilter] = useState('all'); + const [viewMode, setViewMode] = useState('grid'); // Debounce search for API calls const debouncedSearch = useDebounce(searchQuery, 300); @@ -31,20 +34,24 @@ export default function AgentTypesPage() { return undefined; // 'all' returns undefined to not filter }, [statusFilter]); + // Determine category filter value + const categoryFilterValue = useMemo(() => { + if (categoryFilter === 'all') return undefined; + return categoryFilter as AgentTypeCategory; + }, [categoryFilter]); + // Fetch agent types const { data, isLoading, error } = useAgentTypes({ search: debouncedSearch || undefined, is_active: isActiveFilter, + category: categoryFilterValue, page: 1, limit: 50, }); - // Filter results client-side for 'all' status + // Get filtered agent types const filteredAgentTypes = useMemo(() => { if (!data?.data) return []; - - // When status is 'all', we need to fetch both and combine - // For now, the API returns based on is_active filter return data.data; }, [data?.data]); @@ -71,6 +78,16 @@ export default function AgentTypesPage() { setStatusFilter(status); }, []); + // Handle category filter change + const handleCategoryFilterChange = useCallback((category: string) => { + setCategoryFilter(category); + }, []); + + // Handle view mode change + const handleViewModeChange = useCallback((mode: ViewMode) => { + setViewMode(mode); + }, []); + // Show error toast if fetch fails if (error) { toast.error('Failed to load agent types', { @@ -87,6 +104,10 @@ export default function AgentTypesPage() { onSearchChange={handleSearchChange} statusFilter={statusFilter} onStatusFilterChange={handleStatusFilterChange} + categoryFilter={categoryFilter} + onCategoryFilterChange={handleCategoryFilterChange} + viewMode={viewMode} + onViewModeChange={handleViewModeChange} onSelect={handleSelect} onCreate={handleCreate} /> diff --git a/frontend/src/components/agents/AgentTypeDetail.tsx b/frontend/src/components/agents/AgentTypeDetail.tsx index 6e1da0b..d6c1507 100644 --- a/frontend/src/components/agents/AgentTypeDetail.tsx +++ b/frontend/src/components/agents/AgentTypeDetail.tsx @@ -2,7 +2,8 @@ * AgentTypeDetail Component * * Displays detailed information about a single agent type. - * Shows model configuration, permissions, personality, and instance stats. + * Features a hero header with icon/color, category, typical tasks, + * collaboration hints, model configuration, and instance stats. */ 'use client'; @@ -36,8 +37,13 @@ import { Cpu, CheckCircle2, AlertTriangle, + Sparkles, + Users, + Check, } from 'lucide-react'; -import type { AgentTypeResponse } from '@/lib/api/types/agentTypes'; +import { DynamicIcon } from '@/components/ui/dynamic-icon'; +import type { AgentTypeResponse, AgentTypeCategory } from '@/lib/api/types/agentTypes'; +import { CATEGORY_METADATA } from '@/lib/api/types/agentTypes'; import { AVAILABLE_MCP_SERVERS } from '@/lib/validations/agentType'; interface AgentTypeDetailProps { @@ -51,6 +57,30 @@ interface AgentTypeDetailProps { className?: string; } +/** + * Category badge with color + */ +function CategoryBadge({ category }: { category: AgentTypeCategory | null }) { + if (!category) return null; + + const meta = CATEGORY_METADATA[category]; + if (!meta) return null; + + return ( + + {meta.label} + + ); +} + /** * Status badge component for agent types */ @@ -81,11 +111,22 @@ function AgentTypeStatusBadge({ isActive }: { isActive: boolean }) { function AgentTypeDetailSkeleton() { return (
-
- -
- - + {/* Hero skeleton */} +
+
+ +
+ + +
+ + +
+
+
+ + +
@@ -161,57 +202,134 @@ export function AgentTypeDetail({ top_p?: number; }; + const agentColor = agentType.color || '#3B82F6'; + return (
- {/* Header */} -
- -
-
-

{agentType.name}

- + {/* Back button */} + + + {/* Hero Header */} +
+
+
+
+ {/* Icon */} +
+ +
+ + {/* Info */} +
+
+

{agentType.name}

+

+ {agentType.description || 'No description provided'} +

+
+
+ + + + Last updated:{' '} + {new Date(agentType.updated_at).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + })} + +
+
+ + {/* Actions */} +
+ + +
-

- Last modified:{' '} - {new Date(agentType.updated_at).toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric', - })} -

-
-
- -
{/* Main Content */}
- {/* Description Card */} - - - - - Description - - - -

- {agentType.description || 'No description provided'} -

-
-
+ {/* What This Agent Does Best */} + {agentType.typical_tasks.length > 0 && ( + + + + + What This Agent Does Best + + + +
    + {agentType.typical_tasks.map((task, index) => ( +
  • + + {task} +
  • + ))} +
+
+
+ )} + + {/* Works Well With */} + {agentType.collaboration_hints.length > 0 && ( + + + + + Works Well With + + + Agents that complement this type for effective collaboration + + + +
+ {agentType.collaboration_hints.map((hint, index) => ( + + {hint} + + ))} +
+
+
+ )} {/* Expertise Card */} @@ -355,7 +473,9 @@ export function AgentTypeDetail({
-

{agentType.instance_count}

+

+ {agentType.instance_count} +

Active instances