From 843f7a50aa5aba7048c01aed09556798e5471197 Mon Sep 17 00:00:00 2001 From: Ari Webb Date: Thu, 18 Dec 2025 16:51:48 -0800 Subject: [PATCH] add text search for all models (#310) --- src/cli/components/ModelSelector.tsx | 54 ++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/cli/components/ModelSelector.tsx b/src/cli/components/ModelSelector.tsx index c8467d5..58888de 100644 --- a/src/cli/components/ModelSelector.tsx +++ b/src/cli/components/ModelSelector.tsx @@ -51,6 +51,7 @@ export function ModelSelector({ const [error, setError] = useState(null); const [isCached, setIsCached] = useState(false); const [refreshing, setRefreshing] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); const mountedRef = useRef(true); useEffect(() => { @@ -111,8 +112,13 @@ export function ModelSelector({ // All other models: API handles not in models.json const otherModelHandles = useMemo(() => { - return allApiHandles.filter((handle) => !staticModelHandles.has(handle)); - }, [allApiHandles, staticModelHandles]); + const filtered = allApiHandles.filter( + (handle) => !staticModelHandles.has(handle), + ); + if (!searchQuery) return filtered; + const query = searchQuery.toLowerCase(); + return filtered.filter((handle) => handle.toLowerCase().includes(query)); + }, [allApiHandles, staticModelHandles, searchQuery]); // Get the list for current category const currentList: UiModel[] = useMemo(() => { @@ -149,6 +155,7 @@ export function ModelSelector({ }); setCurrentPage(0); setSelectedIndex(0); + setSearchQuery(""); }, []); // Set initial selection to current model on mount @@ -172,14 +179,20 @@ export function ModelSelector({ useInput( (input, key) => { - // Allow ESC even while loading + // Handle ESC: clear search first if active, otherwise cancel if (key.escape) { - onCancel(); + if (searchQuery) { + setSearchQuery(""); + setCurrentPage(0); + setSelectedIndex(0); + } else { + onCancel(); + } return; } // Allow 'r' to refresh even while loading (but not while already refreshing) - if (input === "r" && !refreshing) { + if (input === "r" && !refreshing && !searchQuery) { loadModels.current(true); return; } @@ -189,6 +202,16 @@ export function ModelSelector({ return; } + // Handle backspace for search + if (key.backspace || key.delete) { + if (searchQuery) { + setSearchQuery((prev) => prev.slice(0, -1)); + setCurrentPage(0); + setSelectedIndex(0); + } + return; + } + // Disable other inputs while loading if (isLoading || refreshing || visibleModels.length === 0) { return; @@ -223,6 +246,11 @@ export function ModelSelector({ if (selectedModel) { onSelect(selectedModel.id); } + } else if (category === "all" && input && input.length === 1) { + // Capture text input for search (only in "all" category) + setSearchQuery((prev) => prev + input); + setCurrentPage(0); + setSelectedIndex(0); } }, // Keep active so ESC and 'r' work while loading. @@ -238,7 +266,8 @@ export function ModelSelector({ - Select Model (↑↓ navigate, ←→/jk page, Enter select, ESC cancel) + Select Model (↑↓ navigate, ←→/jk page, Tab category, Enter select, ESC + cancel) {!isLoading && !refreshing && ( @@ -262,10 +291,15 @@ export function ModelSelector({ )} {!isLoading && !refreshing && ( - - Page {currentPage + 1}/{totalPages} - {isCached ? " · cached" : ""} · 'r' to refresh - + + + Page {currentPage + 1}/{totalPages} + {isCached ? " · cached" : ""} · 'r' to refresh + + {category === "all" && ( + Search: {searchQuery || "(type to search)"} + )} + )}