import React, { useState, useMemo } from "react"; import { Box, Text, Stack, Button, TextInput, Group, Tooltip, Collapse, ActionIcon } from "@mantine/core"; import { useTranslation } from "react-i18next"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import SearchIcon from "@mui/icons-material/Search"; import { baseToolRegistry } from "../../data/toolRegistry"; import "./ToolPicker.css"; type Tool = { icon: React.ReactNode; name: string; description: string; }; type ToolRegistry = { [id: string]: Tool; }; interface ToolPickerProps { selectedToolKey: string | null; onSelect: (id: string) => void; toolRegistry: ToolRegistry; } interface GroupedTools { [category: string]: { [subcategory: string]: Array<{ id: string; tool: Tool }>; }; } const ToolPicker = ({ selectedToolKey, onSelect, toolRegistry }: ToolPickerProps) => { const { t } = useTranslation(); const [search, setSearch] = useState(""); const [expandedCategories, setExpandedCategories] = useState>(new Set()); // Group tools by category and subcategory in a single pass - O(n) const groupedTools = useMemo(() => { const grouped: GroupedTools = {}; Object.entries(toolRegistry).forEach(([id, tool]) => { // Get category and subcategory from the base registry const baseTool = baseToolRegistry[id as keyof typeof baseToolRegistry]; const category = baseTool?.category || "Other"; const subcategory = baseTool?.subcategory || "General"; if (!grouped[category]) { grouped[category] = {}; } if (!grouped[category][subcategory]) { grouped[category][subcategory] = []; } grouped[category][subcategory].push({ id, tool }); }); return grouped; }, [toolRegistry]); // Sort categories in custom order and subcategories alphabetically - O(c * s * log(s)) const sortedCategories = useMemo(() => { const categoryOrder = ['RECOMMENDED TOOLS', 'STANDARD TOOLS', 'ADVANCED TOOLS']; return Object.entries(groupedTools) .map(([category, subcategories]) => ({ category, subcategories: Object.entries(subcategories) .sort(([a], [b]) => a.localeCompare(b)) // Sort subcategories alphabetically .map(([subcategory, tools]) => ({ subcategory, tools: tools.sort((a, b) => a.tool.name.localeCompare(b.tool.name)) // Sort tools alphabetically })) })) .sort((a, b) => { const aIndex = categoryOrder.indexOf(a.category.toUpperCase()); const bIndex = categoryOrder.indexOf(b.category.toUpperCase()); return aIndex - bIndex; }); }, [groupedTools, t]); // Filter tools based on search - O(n) const filteredCategories = useMemo(() => { if (!search.trim()) return sortedCategories; return sortedCategories.map(({ category, subcategories }) => ({ category, subcategories: subcategories.map(({ subcategory, tools }) => ({ subcategory, tools: tools.filter(({ tool }) => tool.name.toLowerCase().includes(search.toLowerCase()) || tool.description.toLowerCase().includes(search.toLowerCase()) ) })).filter(({ tools }) => tools.length > 0) })).filter(({ subcategories }) => subcategories.length > 0); }, [sortedCategories, search, t]); const toggleCategory = (category: string) => { setExpandedCategories(prev => { const newSet = new Set(prev); if (newSet.has(category)) { newSet.delete(category); } else { newSet.add(category); } return newSet; }); }; const renderToolButton = (id: string, tool: Tool, index: number) => ( ); return ( setSearch(e.currentTarget.value)} autoComplete="off" className="search-input rounded-lg" leftSection={} /> {filteredCategories.length === 0 ? ( {t("toolPicker.noToolsFound", "No tools found")} ) : ( filteredCategories.map(({ category, subcategories }) => ( {/* Category Header */} {/* Subcategories */} {subcategories.map(({ subcategory, tools }) => ( {/* Subcategory Header (only show if there are multiple subcategories) */} {subcategories.length > 1 && ( {subcategory} )} {/* Tools in this subcategory */} {tools.map(({ id, tool }, index) => renderToolButton(id, tool, index) )} ))} )) )} ); }; export default ToolPicker;