diff --git a/src/common/components/forms/useLLMSelect.tsx b/src/common/components/forms/useLLMSelect.tsx index 83a892a38..36ad066c9 100644 --- a/src/common/components/forms/useLLMSelect.tsx +++ b/src/common/components/forms/useLLMSelect.tsx @@ -17,6 +17,7 @@ import { FormLabelStart } from './FormLabelStart'; // configuration +const LLM_SELECT_REDUCE_OPTIONS = 10; // optimization: number of options over which only the selected is kept when closed (we'll have special notes for accessibility) const LLM_SELECT_SHOW_REASONING_ICON = false; @@ -46,6 +47,7 @@ const _slotProps = { } as const, } as const, button: { + 'aria-description': 'Options may be filtered when closed. Open dropdown to see all options.', sx: { // show the full name on the button whiteSpace: 'inherit', @@ -70,7 +72,7 @@ interface LLMSelectOptions { * * @param llmId (required) the LLM id * @param setLlmId (required) the function to set the LLM id - * @param options (optional) any arrray of options + * @param options (optional) any array of options */ export function useLLMSelect( llmId: undefined | DLLMId | null, // undefined: not set at all, null: has the meaning of no-llm-wanted here @@ -78,6 +80,9 @@ export function useLLMSelect( options: LLMSelectOptions, ): [DLLM | null, React.JSX.Element | null, React.FunctionComponent | undefined] { + // state + const [controlledOpen, setControlledOpen] = React.useState(false); + // external state const _filteredLLMs = useVisibleLLMs(llmId); @@ -89,11 +94,17 @@ export function useLLMSelect( // memo LLM Options + + const optimizeToSingleVisibleId = (!controlledOpen && _filteredLLMs.length > LLM_SELECT_REDUCE_OPTIONS) ? llmId : null; // id to keep visible when optimizing + const optionsArray = React.useMemo(() => { // create the option items let formerVendor: IModelVendor | null = null; return _filteredLLMs.reduce((acc, llm, _index) => { + if (optimizeToSingleVisibleId && llm.id !== optimizeToSingleVisibleId) + return acc; + const vendor = findModelVendor(llm.vId); const vendorChanged = vendor !== formerVendor; if (vendorChanged) @@ -101,7 +112,7 @@ export function useLLMSelect( // add separators if the vendor changed (and more than one vendor) const addSeparator = vendorChanged && formerVendor !== null; - if (addSeparator) + if (addSeparator && !optimizeToSingleVisibleId) acc.push({vendor?.name}); // the option component @@ -127,7 +138,7 @@ export function useLLMSelect( return acc; }, [] as React.JSX.Element[]); - }, [_filteredLLMs, noIcons]); + }, [_filteredLLMs, noIcons, optimizeToSingleVisibleId]); const onSelectChange = React.useCallback((_event: unknown, value: DLLMId | null) => value && setLlmId(value), [setLlmId]); @@ -143,6 +154,8 @@ export function useLLMSelect( size={larger ? undefined : 'sm'} disabled={disabled} onChange={onSelectChange} + listboxOpen={controlledOpen} + onListboxOpenChange={setControlledOpen} placeholder={placeholder} slotProps={_slotProps} endDecorator={autoRefreshDomain ? @@ -158,7 +171,7 @@ export function useLLMSelect( {/**/} - ), [autoRefreshDomain, disabled, isHorizontal, isReasoning, label, larger, llmId, onSelectChange, optionsArray, placeholder]); + ), [autoRefreshDomain, controlledOpen, disabled, isHorizontal, isReasoning, label, larger, llmId, onSelectChange, optionsArray, placeholder]); // Memo the vendor icon for the chat LLM const chatLLMVendorIconFC = React.useMemo(() => {