mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Sort LLM Categories by names
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user