import { CATEGORIES, MODALITIES, type Modality, TOOLS } from "@snapotter/shared"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "@/contexts/i18n-context"; import { useFuseSearch } from "@/hooks/use-fuse-search"; import { ICON_MAP } from "@/lib/icon-map"; import { getCategoryName, getModalityName } from "@/lib/tool-i18n"; import { cn } from "@/lib/utils"; import { useFeaturesStore } from "@/stores/features-store"; import { useSettingsStore } from "@/stores/settings-store"; import { SearchBar } from "../common/search-bar"; import { ToolCard } from "../common/tool-card"; type ModalityFilter = "all" | Modality; export function ToolPanel() { const { t } = useTranslation(); const [search, setSearch] = useState(""); const [selectedModality, setSelectedModality] = useState("all"); const { disabledTools, experimentalEnabled, loaded, fetch } = useSettingsStore(); const fetchFeatures = useFeaturesStore((s) => s.fetch); useEffect(() => { fetch(); }, [fetch]); useEffect(() => { fetchFeatures(); }, [fetchFeatures]); const visibleTools = useMemo(() => { if (!loaded) return []; return TOOLS.filter((t) => { if (disabledTools.includes(t.id)) return false; if (t.experimental && !experimentalEnabled) return false; return true; }); }, [disabledTools, experimentalEnabled, loaded]); const modalityFilteredTools = useMemo(() => { if (selectedModality === "all") return visibleTools; return visibleTools.filter((tool) => tool.modality === selectedModality); }, [visibleTools, selectedModality]); const filteredTools = useFuseSearch(modalityFilteredTools, search); const groupedByModality = useMemo(() => { const byModality = new Map>(); for (const tool of filteredTools) { // In "all" mode, merge file-modality tools into the document section; // when a specific tab is active, keep the original modality key. const key = selectedModality === "all" && tool.modality === "file" ? "document" : tool.modality; const cats = byModality.get(key) ?? new Map(); const list = cats.get(tool.category) ?? []; list.push(tool); cats.set(tool.category, list); byModality.set(key, cats); } return byModality; }, [filteredTools, selectedModality]); return (
{/* Modality filter tabs */}
{MODALITIES.map((m) => { const isActive = selectedModality === m.id; const short = m.id === "document" ? "Docs" : m.id === "file" ? "Files" : m.name; return ( ); })}
{MODALITIES.filter((m) => { // In "all" mode, file-modality tools are merged into the document // section so skip the standalone file entry. When the file tab is // active, keep it. if (selectedModality === "all" && m.id === "file") return false; return groupedByModality.has(m.id); }).map((modality, idx, arr) => { const ModalityIcon = ICON_MAP[modality.icon] as React.ComponentType<{ className?: string; }>; const categoryMap = groupedByModality.get(modality.id); if (!categoryMap) return null; const isLast = idx === arr.length - 1; return (
{idx > 0 &&
}
{ModalityIcon && ( )}

{getModalityName( t, modality.id, modality.id === "document" && selectedModality === "all" ? "Documents & Files" : modality.name, )}

{CATEGORIES.filter((cat) => categoryMap.has(cat.id)).map((category) => (

{getCategoryName(t, category.id, category.name)}

{categoryMap.get(category.id)?.map((tool) => ( ))}
))}
); })} {filteredTools.length === 0 && (

No tools found

)}
); }