From 725e166f3f1a5d29745a046c03d267e089b1c94b Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Thu, 15 Jan 2026 14:57:30 +0100 Subject: [PATCH] changes --- components/Settings/SettingsModal.tsx | 246 ++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 37 deletions(-) diff --git a/components/Settings/SettingsModal.tsx b/components/Settings/SettingsModal.tsx index 92d5de2..849045d 100644 --- a/components/Settings/SettingsModal.tsx +++ b/components/Settings/SettingsModal.tsx @@ -1,11 +1,15 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { IconAlertCircle, + IconBrain, + IconCode, IconDownload, + IconEye, IconPalette, IconRefresh, IconRobot, IconSearch, + IconTool, IconTrash, IconUser, IconX, @@ -17,6 +21,7 @@ import { Badge, Button, Card, + Chip, ColorSwatch, Divider, Group, @@ -97,6 +102,9 @@ export function SettingsModal({ const [modelSearch, setModelSearch] = useState(''); const [debouncedSearch] = useDebouncedValue(modelSearch, 200); + // Capability filter state + const [selectedCapabilities, setSelectedCapabilities] = useState([]); + // Pagination state const MODELS_PER_PAGE = 20; const [currentPage, setCurrentPage] = useState(1); @@ -111,13 +119,25 @@ export function SettingsModal({ [availableModels] ); - // Filter models based on debounced search + // Filter models based on debounced search and selected capabilities const filteredModels = useMemo( () => - modelNames.filter((name) => - name.toLowerCase().includes(debouncedSearch.toLowerCase().trim()) - ), - [modelNames, debouncedSearch] + modelNames.filter((name) => { + // Text search filter + const matchesSearch = name.toLowerCase().includes(debouncedSearch.toLowerCase().trim()); + if (!matchesSearch) { + return false; + } + + // Capability filter - if any capabilities selected, model must have ALL of them + if (selectedCapabilities.length > 0) { + const modelCaps = availableModels?.models[name]?.capabilities || []; + return selectedCapabilities.every((cap) => modelCaps.includes(cap)); + } + + return true; + }), + [modelNames, debouncedSearch, selectedCapabilities, availableModels] ); // Paginated models - only render what's visible @@ -127,10 +147,10 @@ export function SettingsModal({ return filteredModels.slice(start, start + MODELS_PER_PAGE); }, [filteredModels, currentPage]); - // Reset to page 1 when search changes + // Reset to page 1 when search or filter changes useEffect(() => { setCurrentPage(1); - }, [debouncedSearch]); + }, [debouncedSearch, selectedCapabilities]); // Fetch available models from the static JSON const fetchAvailableModels = useCallback(async (force = false) => { @@ -312,6 +332,12 @@ export function SettingsModal({ }); }; + // Get capabilities for an installed model by extracting base name + const getModelCapabilities = (fullModelName: string): string[] => { + const [baseName] = fullModelName.split(':'); + return availableModels?.models[baseName]?.capabilities || []; + }; + return ( ) : ( - {installedModels.map((model) => ( - - -
- - {model.name} - - - - {(model.size / 1024 / 1024 / 1024).toFixed(2)} GB - - - {model.details.parameter_size} - - - {model.details.quantization_level} - - -
- handleDeleteModel(model.name)} - > - - -
-
- ))} + {installedModels.map((model) => { + const capabilities = getModelCapabilities(model.name); + return ( + + +
+ + {model.name} + + + + {(model.size / 1024 / 1024 / 1024).toFixed(2)} GB + + + {model.details.parameter_size} + + + {model.details.quantization_level} + + + {capabilities.length > 0 && ( + + {capabilities.map((cap) => ( + + {cap} + + ))} + + )} +
+ handleDeleteModel(model.name)} + > + + +
+
+ ); + })}
)} @@ -519,9 +572,128 @@ export function SettingsModal({ leftSection={} value={modelSearch} onChange={(e) => setModelSearch(e.currentTarget.value)} - mb="sm" + mb="xs" /> + {/* Capability Filter */} + + + + + + Vision + + + + + + Tools + + + + + + Thinking + + + + + + Embedding + + + + + {loadingAvailable ? (