Models List: show free only

This commit is contained in:
Enrico Ros
2026-02-25 23:50:08 -08:00
parent 6ae2195d10
commit acd8430d51
4 changed files with 44 additions and 52 deletions
+10 -1
View File
@@ -1,7 +1,8 @@
import { useShallow } from 'zustand/react/shallow';
import { DLLM, DLLMId, isLLMVisible } from './llms.types';
import type { DModelsServiceId } from './llms.service.types';
import { DLLM, DLLMId, isLLMVisible } from './llms.types';
import { isLLMChatFree_cached } from './llms.pricing';
import { useModelsStore } from './store-llms';
@@ -59,6 +60,14 @@ export function useHasLLMs(): boolean {
return useModelsStore(state => !!state.llms.length);
}
export function useHasFreeLLMs(serviceId: false | DModelsServiceId | null): boolean {
return useModelsStore(state => {
if (serviceId === null) return false; // explicitly no service, so no free llms
const llms = !serviceId ? state.llms : state.llms.filter(llm => llm.sId === serviceId);
return llms.some(isLLMChatFree_cached);
});
}
export function useModelsServices() {
return useModelsStore(useShallow(state => ({
modelsServices: state.sources,
@@ -16,11 +16,13 @@ import type { DModelsService, DModelsServiceId } from '~/common/stores/llms/llms
import { AppBreadcrumbs } from '~/common/components/AppBreadcrumbs';
import { ConfirmationModal } from '~/common/components/modals/ConfirmationModal';
import { GoodModal } from '~/common/components/modals/GoodModal';
import { llmsStoreActions } from '~/common/stores/llms/store-llms';
import { PhGift } from '~/common/components/icons/phosphor/PhGift';
import { isLLMChatFree_cached } from '~/common/stores/llms/llms.pricing';
import { llmsStoreActions, llmsStoreState } from '~/common/stores/llms/store-llms';
import { optimaActions } from '~/common/layout/optima/useOptima';
import { themeZIndexOverMobileDrawer } from '~/common/app.theme';
import { useAllServicesDCStatus } from '~/common/stores/llms/hooks/useModelServiceClientSideFetch';
import { useHasLLMs } from '~/common/stores/llms/llms.hooks';
import { useHasFreeLLMs, useHasLLMs } from '~/common/stores/llms/llms.hooks';
import { useIsMobile } from '~/common/components/useMatchMedia';
import { useModelsZeroState } from '~/common/stores/llms/hooks/useModelsZeroState';
import { useOverlayComponents } from '~/common/layout/overlays/useOverlayComponents';
@@ -76,7 +78,7 @@ export function ModelsConfiguratorModal(props: {
// active service with fallback to the last added service
const activeServiceId = confServiceId
const activeServiceId: string | null = confServiceId
?? modelsServices[modelsServices.length - 1]?.id
?? null;
@@ -86,6 +88,7 @@ export function ModelsConfiguratorModal(props: {
const hasAnyServices = !!modelsServices.length;
const isTabWizard = tab === 'wizard';
const isTabSetup = tab === 'setup';
const activeHasFreeLLMs = useHasFreeLLMs(activeServiceId);
// const isTabDefaults = tab === 'defaults';
@@ -166,6 +169,20 @@ export function ModelsConfiguratorModal(props: {
llmsStoreActions().setServiceModelsHidden(activeServiceId, false);
}, [activeServiceId]);
const handleShowOnlyFree = React.useCallback(() => {
const updates = llmsStoreState().llms
.filter(llm => llm.sId === activeServiceId)
.map(llm => ({ id: llm.id, partial: { userHidden: !isLLMChatFree_cached(llm) } }));
llmsStoreActions().updateLLMs(updates);
}, [activeServiceId]);
const handleShowOnlyPaid = React.useCallback(() => {
const updates = llmsStoreState().llms
.filter(llm => llm.sId === activeServiceId)
.map(llm => ({ id: llm.id, partial: { userHidden: isLLMChatFree_cached(llm) } }));
llmsStoreActions().updateLLMs(updates);
}, [activeServiceId]);
const handleRemoveClones = React.useCallback(() => {
showPromisedOverlay('llms-remove-clones', {}, ({ onResolve, onUserReject }) =>
<ConfirmationModal
@@ -298,6 +315,16 @@ export function ModelsConfiguratorModal(props: {
<ListItemDecorator><VisibilityOffIcon /></ListItemDecorator>
Hide All
</MenuItem>
{activeHasFreeLLMs && <ListDivider />}
{activeHasFreeLLMs && <MenuItem onClick={handleShowOnlyFree}>
<ListItemDecorator><PhGift /></ListItemDecorator>
Only Free
</MenuItem>}
{activeHasFreeLLMs && <MenuItem onClick={handleShowOnlyPaid}>
<ListItemDecorator />
Only Paid
</MenuItem>}
<ListDivider />
<MenuItem onClick={handleResetVisibility}>
<ListItemDecorator><RestoreIcon /></ListItemDecorator>
Reset Default Visibility
@@ -308,7 +335,7 @@ export function ModelsConfiguratorModal(props: {
);
return undefined;
}, [activeService?.label, dcAllEnabled, dcHasEligible, dcMenuAnchor, dcNoneEnabled, dcStatus.eligible, dcStatus.enabled, handleDisableAllDC, handleEnableAllDC, handleHideAllModels, handleMainMenuOpenChange, handleRefreshModels, handleRemoveClones, handleResetAllParameters, handleResetVisibility, handleShowAllModels, handleShowWizard, hasAnyServices, hasLLMs, isMobile, isRefreshing, isTabSetup, isTabWizard, mainMenuOpen, setShowModelsHidden, setStarredOnTop, showModelsHidden, starredOnTop, visMenuAnchor]);
}, [activeHasFreeLLMs, activeService?.label, dcAllEnabled, dcHasEligible, dcMenuAnchor, dcNoneEnabled, dcStatus.eligible, dcStatus.enabled, handleDisableAllDC, handleEnableAllDC, handleHideAllModels, handleMainMenuOpenChange, handleRefreshModels, handleRemoveClones, handleResetAllParameters, handleResetVisibility, handleShowAllModels, handleShowOnlyFree, handleShowOnlyPaid, handleShowWizard, hasAnyServices, hasLLMs, isMobile, isRefreshing, isTabSetup, isTabWizard, mainMenuOpen, setShowModelsHidden, setStarredOnTop, showModelsHidden, starredOnTop, visMenuAnchor]);
// custom done button for wizard mode (combines start and close buttons)
@@ -359,7 +386,7 @@ export function ModelsConfiguratorModal(props: {
setShowExplainer(true);
}, []);
const handleDismissExplainer = React.useCallback((event: React.BaseSyntheticEvent, reason: 'backdropClick' | 'escapeKeyDown' | 'closeClick') => {
const handleDismissExplainer = React.useCallback((_event: React.BaseSyntheticEvent, reason: 'backdropClick' | 'escapeKeyDown' | 'closeClick') => {
// hide for both the 'x' button and close
setShowExplainer(false);
@@ -2,13 +2,10 @@ import * as React from 'react';
import { Box, Button, Typography } from '@mui/joy';
import LaunchIcon from '@mui/icons-material/Launch';
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import type { DModelsServiceId } from '~/common/stores/llms/llms.service.types';
import { AlreadySet } from '~/common/components/AlreadySet';
import { FormInputKey } from '~/common/components/forms/FormInputKey';
import { isLLMChatFree_cached } from '~/common/stores/llms/llms.pricing';
import { InlineError } from '~/common/components/InlineError';
import { Link } from '~/common/components/Link';
import { PhGift } from '~/common/components/icons/phosphor/PhGift';
@@ -16,7 +13,6 @@ import { FormSwitchControl } from '~/common/components/forms/FormSwitchControl';
import { SetupFormClientSideToggle } from '~/common/components/forms/SetupFormClientSideToggle';
import { SetupFormRefetchButton } from '~/common/components/forms/SetupFormRefetchButton';
import { getCallbackUrl } from '~/common/app.routes';
import { llmsStoreActions, llmsStoreState } from '~/common/stores/llms/store-llms';
import { useToggleableBoolean } from '~/common/util/hooks/useToggleableBoolean';
import { ApproximateCosts } from '../ApproximateCosts';
@@ -29,7 +25,7 @@ import { isValidOpenRouterKey, ModelVendorOpenRouter } from './openrouter.vendor
export function OpenRouterServiceSetup(props: { serviceId: DModelsServiceId }) {
// external state
const { service, serviceAccess, serviceHasCloudTenantConfig, serviceHasLLMs, serviceHasVisibleLLMs, updateSettings } =
const { service, serviceAccess, serviceHasCloudTenantConfig, serviceHasLLMs, updateSettings } =
useServiceSetup(props.serviceId, ModelVendorOpenRouter);
// derived state
@@ -58,27 +54,6 @@ export function OpenRouterServiceSetup(props: { serviceId: DModelsServiceId }) {
// ...bye / see you soon at the callback location...
};
const handleHIdeNonFreeLLMs = () => {
const { llms } = llmsStoreState();
const { updateLLMs } = llmsStoreActions();
const updates = llms
.filter(llm => llm.sId === props.serviceId)
.map(llm => {
const isFree = isLLMChatFree_cached(llm);
return { id: llm.id, partial: { userHidden: !isFree } };
});
updateLLMs(updates);
};
const handleSetVisibilityAll = React.useCallback((visible: boolean) => {
const { llms } = llmsStoreState();
const { updateLLMs } = llmsStoreActions();
const updates = llms
.filter(llm => llm.sId === props.serviceId)
.map(llm => ({ id: llm.id, partial: { userHidden: !visible } }));
updateLLMs(updates);
}, [props.serviceId]);
return <>
<ApproximateCosts serviceId={service?.id} />
@@ -148,23 +123,6 @@ export function OpenRouterServiceSetup(props: { serviceId: DModelsServiceId }) {
<SetupFormRefetchButton
refetch={refetch} disabled={!shallFetchSucceed || isFetching} loading={isFetching} error={isError} advanced={advanced}
leftButton={
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Button
color='neutral' variant='outlined' size='sm'
onClick={handleHIdeNonFreeLLMs}
>
Only Free <PhGift sx={{ ml: 1, color: 'success.softColor' }} />
</Button>
<Button
color='neutral' variant='outlined' size='sm'
onClick={() => handleSetVisibilityAll(!serviceHasVisibleLLMs)}
endDecorator={serviceHasVisibleLLMs ? <VisibilityOffOutlinedIcon /> : <VisibilityOutlinedIcon />}
>
{serviceHasVisibleLLMs ? 'Hide' : 'Show'} All
</Button>
</Box>
}
/>
{isError && <InlineError error={error} />}
+1 -3
View File
@@ -2,7 +2,7 @@ import * as React from 'react';
import { useShallow } from 'zustand/react/shallow';
import type { DModelsService, DModelsServiceId } from '~/common/stores/llms/llms.service.types';
import { DLLM, isLLMVisible } from '~/common/stores/llms/llms.types';
import type { DLLM } from '~/common/stores/llms/llms.types';
import { useShallowStabilizer } from '~/common/util/hooks/useShallowObject';
import { useModelsStore } from '~/common/stores/llms/store-llms';
@@ -21,7 +21,6 @@ export function useServiceSetup<TServiceSettings extends object, TAccess>(servic
serviceHasCloudTenantConfig: boolean;
serviceHasLLMs: boolean;
serviceHasVisibleLLMs: boolean;
serviceSetupValid: boolean;
updateLabel: (label: string, allowEmpty?: boolean) => void;
@@ -50,7 +49,6 @@ export function useServiceSetup<TServiceSettings extends object, TAccess>(servic
serviceHasCloudTenantConfig: vendorHasBackendCap(vendor),
serviceHasLLMs: !!serviceLLms.length,
serviceHasVisibleLLMs: serviceLLms.some(isLLMVisible),
serviceSetupValid: serviceSetupValid,
partialSettings: service?.setup ?? null, // NOTE: do not use - prefer ACCESS; only used in 1 edge case now