Sort LLM Categories by names

This commit is contained in:
Enrico Ros
2026-04-28 17:49:00 -07:00
parent a43b6a2cf5
commit ac69c62020
4 changed files with 47 additions and 9 deletions
@@ -15,6 +15,7 @@ import { KeyStroke } from '~/common/components/KeyStroke';
import { OptimaBarControlMethods, OptimaBarDropdownMemo, OptimaDropdownItems } from '~/common/layout/optima/bar/OptimaBarDropdown'; import { OptimaBarControlMethods, OptimaBarDropdownMemo, OptimaDropdownItems } from '~/common/layout/optima/bar/OptimaBarDropdown';
import { findModelsServiceOrNull } from '~/common/stores/llms/store-llms'; import { findModelsServiceOrNull } from '~/common/stores/llms/store-llms';
import { isDeepEqual } from '~/common/util/hooks/useDeep'; import { isDeepEqual } from '~/common/util/hooks/useDeep';
import { sortLLMsByServiceLabel } from '~/common/stores/llms/components/llms.dropdown.utils';
import { optimaActions, optimaOpenModels } from '~/common/layout/optima/useOptima'; import { optimaActions, optimaOpenModels } from '~/common/layout/optima/useOptima';
import { useAllLLMs } from '~/common/stores/llms/hooks/useAllLLMs'; import { useAllLLMs } from '~/common/stores/llms/hooks/useAllLLMs';
import { useModelDomain } from '~/common/stores/llms/hooks/useModelDomain'; import { useModelDomain } from '~/common/stores/llms/hooks/useModelDomain';
@@ -72,7 +73,10 @@ function LLMDropdown(props: {
return lcFilterString ? true : isLLMVisible(llm); return lcFilterString ? true : isLLMVisible(llm);
}); });
for (const llm of filteredLLMs) { // sort by service label so vendor groups appear alphabetically (groups remain contiguous because sort is stable on equal keys)
const sortedLLMs = sortLLMsByServiceLabel(filteredLLMs);
for (const llm of sortedLLMs) {
// add separators when changing services // add separators when changing services
if (!prevServiceId || llm.sId !== prevServiceId) { if (!prevServiceId || llm.sId !== prevServiceId) {
const vendor = findModelVendor(llm.vId); const vendor = findModelVendor(llm.vId);
+6 -2
View File
@@ -19,6 +19,7 @@ import { StarIconUnstyled, StarredNoXL2 } from '~/common/components/StarIcons';
import { TooltipOutlined } from '~/common/components/TooltipOutlined'; import { TooltipOutlined } from '~/common/components/TooltipOutlined';
import { findModelsServiceOrNull, getChatLLMId, llmsStoreActions } from '~/common/stores/llms/store-llms'; import { findModelsServiceOrNull, getChatLLMId, llmsStoreActions } from '~/common/stores/llms/store-llms';
import { optimaActions, optimaOpenModels } from '~/common/layout/optima/useOptima'; import { optimaActions, optimaOpenModels } from '~/common/layout/optima/useOptima';
import { sortLLMsByServiceLabel } from '~/common/stores/llms/components/llms.dropdown.utils';
import { useToggleableStringSet } from '~/common/util/hooks/useToggleableStringSet'; import { useToggleableStringSet } from '~/common/util/hooks/useToggleableStringSet';
import { useUIPreferencesStore } from '~/common/stores/store-ui'; import { useUIPreferencesStore } from '~/common/stores/store-ui';
import { useVisibleLLMs } from '~/common/stores/llms/llms.hooks'; import { useVisibleLLMs } from '~/common/stores/llms/llms.hooks';
@@ -202,12 +203,15 @@ export function useLLMSelect(
const optimizeToSingleVisibleId = (!controlledOpen && _filteredLLMs.length > LLM_SELECT_REDUCE_OPTIONS) ? llmId : null; // id to keep visible when optimizing const optimizeToSingleVisibleId = (!controlledOpen && _filteredLLMs.length > LLM_SELECT_REDUCE_OPTIONS) ? llmId : null; // id to keep visible when optimizing
const optionsArray = React.useMemo(() => { const optionsArray = React.useMemo(() => {
// sort LLMs alphabetically by service label so vendor groups appear in a stable order (groups remain contiguous because sort is stable on equal keys)
const sortedLLMs = sortLLMsByServiceLabel(_filteredLLMs);
// check if we have multiple services (to show collapsible headers) // check if we have multiple services (to show collapsible headers)
const hasMultipleServices = _filteredLLMs.some((llm, i, arr) => i > 0 && llm.sId !== arr[i - 1].sId); const hasMultipleServices = sortedLLMs.some((llm, i, arr) => i > 0 && llm.sId !== arr[i - 1].sId);
// create the option items // create the option items
let prevServiceId: DModelsServiceId | null = null; let prevServiceId: DModelsServiceId | null = null;
return _filteredLLMs.reduce((acc, llm, _index) => { return sortedLLMs.reduce((acc, llm, _index) => {
if (optimizeToSingleVisibleId && llm.id !== optimizeToSingleVisibleId) if (optimizeToSingleVisibleId && llm.id !== optimizeToSingleVisibleId)
return acc; return acc;
@@ -42,17 +42,44 @@ export interface LLMServiceGroup {
} }
/** /**
* Group LLMs by service, resolving service display labels. * Resolve display label for each unique service in the input.
* Fallback chain: service.label -> vendor.name -> service.id.
*/
function _resolveServiceLabels(llms: ReadonlyArray<DLLM>): Map<DModelsServiceId, string> {
const labelById = new Map<DModelsServiceId, string>();
for (const llm of llms) {
if (labelById.has(llm.sId)) continue;
const vendor = findModelVendor(llm.vId);
labelById.set(llm.sId, findModelsServiceOrNull(llm.sId)?.label || vendor?.name || llm.sId);
}
return labelById;
}
/**
* Stably sort LLMs by their service label (alphabetical, locale-aware).
* Preserves intra-service order (e.g. starred-first), since JS sort is stable.
*/
export function sortLLMsByServiceLabel<T extends DLLM>(llms: ReadonlyArray<T>): T[] {
if (llms.length < 2) return [...llms];
const labelById = _resolveServiceLabels(llms);
return [...llms].sort((a, b) => labelById.get(a.sId)!.localeCompare(labelById.get(b.sId)!));
}
/**
* Group LLMs by service, alphabetically sorted by service label.
* Preserves intra-service order.
*/ */
export function groupLLMsByService(llms: ReadonlyArray<DLLM>): LLMServiceGroup[] { export function groupLLMsByService(llms: ReadonlyArray<DLLM>): LLMServiceGroup[] {
const labelById = _resolveServiceLabels(llms);
if (llms.length >= 2)
llms = [...llms].sort((a, b) => labelById.get(a.sId)!.localeCompare(labelById.get(b.sId)!));
const groups: LLMServiceGroup[] = []; const groups: LLMServiceGroup[] = [];
let currentGroup: LLMServiceGroup | null = null; let currentGroup: LLMServiceGroup | null = null;
for (const llm of llms) { for (const llm of llms) {
if (!currentGroup || currentGroup.serviceId !== llm.sId) { if (!currentGroup || currentGroup.serviceId !== llm.sId) {
const vendor = findModelVendor(llm.vId); currentGroup = { serviceId: llm.sId, serviceLabel: labelById.get(llm.sId)!, models: [] };
const serviceLabel = findModelsServiceOrNull(llm.sId)?.label || vendor?.name || llm.sId;
currentGroup = { serviceId: llm.sId, serviceLabel, models: [] };
groups.push(currentGroup); groups.push(currentGroup);
} }
currentGroup.models.push(llm); currentGroup.models.push(llm);
+5 -2
View File
@@ -14,6 +14,7 @@ import { GoodTooltip } from '~/common/components/GoodTooltip';
import { PhGearSixIcon } from '~/common/components/icons/phosphor/PhGearSixIcon'; import { PhGearSixIcon } from '~/common/components/icons/phosphor/PhGearSixIcon';
import { STAR_EMOJI, StarredToggle, starredToggleStyle } from '~/common/components/StarIcons'; import { STAR_EMOJI, StarredToggle, starredToggleStyle } from '~/common/components/StarIcons';
import { findModelsServiceOrNull, llmsStoreActions } from '~/common/stores/llms/store-llms'; import { findModelsServiceOrNull, llmsStoreActions } from '~/common/stores/llms/store-llms';
import { sortLLMsByServiceLabel } from '~/common/stores/llms/components/llms.dropdown.utils';
import { useLLMsByService } from '~/common/stores/llms/llms.hooks'; import { useLLMsByService } from '~/common/stores/llms/llms.hooks';
import { useIsMobile } from '~/common/components/useMatchMedia'; import { useIsMobile } from '~/common/components/useMatchMedia';
import { useModelDomains } from '~/common/stores/llms/hooks/useModelDomains'; import { useModelDomains } from '~/common/stores/llms/hooks/useModelDomains';
@@ -283,7 +284,9 @@ export function ModelsList(props: {
// are we showing multiple services // are we showing multiple services
const showAllServices = !props.filterServiceId; const showAllServices = !props.filterServiceId;
const hasManyServices = llms.length >= 2 && llms.some(llm => llm.sId !== llms[0].sId); // sort by service label so vendor groups appear alphabetically when showing all services (single-service view keeps existing order)
const orderedLLMs = showAllServices ? sortLLMsByServiceLabel(llms) : llms;
const hasManyServices = orderedLLMs.length >= 2 && orderedLLMs.some(llm => llm.sId !== orderedLLMs[0].sId);
let lastGroupLabel = ''; let lastGroupLabel = '';
// derived // derived
@@ -293,7 +296,7 @@ export function ModelsList(props: {
// generate the list items, prepending headers when necessary // generate the list items, prepending headers when necessary
const items: React.JSX.Element[] = []; const items: React.JSX.Element[] = [];
for (const llm of llms) { for (const llm of orderedLLMs) {
// skip hidden models if requested // skip hidden models if requested
if (!props.showHiddenModels && isLLMHidden(llm)) if (!props.showHiddenModels && isLLMHidden(llm))