mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-01 13:50:19 +01:00
📳 refactor: Optimize Model Selector (#11787)
- Introduced a new `EndpointMenuContent` component to lazily render endpoint submenu content, improving performance by deferring expensive model-list rendering until the submenu is mounted. - Refactored `EndpointItem` to utilize the new component, simplifying the code and enhancing readability. - Removed redundant filtering logic and model specifications handling from `EndpointItem`, centralizing it within `EndpointMenuContent` for better maintainability.
This commit is contained in:
parent
dc489e7b25
commit
6cc6ee3207
1 changed files with 69 additions and 59 deletions
|
|
@ -80,12 +80,76 @@ const SettingsButton = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily-rendered content for an endpoint submenu. By extracting this into a
|
||||||
|
* separate component, the expensive model-list rendering (and per-item hooks
|
||||||
|
* such as MutationObservers in EndpointModelItem) only runs when the submenu
|
||||||
|
* is actually mounted — which Ariakit defers via `unmountOnHide`.
|
||||||
|
*/
|
||||||
|
function EndpointMenuContent({
|
||||||
|
endpoint,
|
||||||
|
endpointIndex,
|
||||||
|
}: {
|
||||||
|
endpoint: Endpoint;
|
||||||
|
endpointIndex: number;
|
||||||
|
}) {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { agentsMap, assistantsMap, modelSpecs, selectedValues, endpointSearchValues } =
|
||||||
|
useModelSelectorContext();
|
||||||
|
const { model: selectedModel, modelSpec: selectedSpec } = selectedValues;
|
||||||
|
const searchValue = endpointSearchValues[endpoint.value] || '';
|
||||||
|
|
||||||
|
const endpointSpecs = useMemo(() => {
|
||||||
|
if (!modelSpecs || !modelSpecs.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return modelSpecs.filter((spec: TModelSpec) => spec.group === endpoint.value);
|
||||||
|
}, [modelSpecs, endpoint.value]);
|
||||||
|
|
||||||
|
if (isAssistantsEndpoint(endpoint.value) && endpoint.models === undefined) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-center p-2"
|
||||||
|
role="status"
|
||||||
|
aria-label={localize('com_ui_loading')}
|
||||||
|
>
|
||||||
|
<Spinner aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredModels = searchValue
|
||||||
|
? filterModels(
|
||||||
|
endpoint,
|
||||||
|
(endpoint.models || []).map((model) => model.name),
|
||||||
|
searchValue,
|
||||||
|
agentsMap,
|
||||||
|
assistantsMap,
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{endpointSpecs.map((spec: TModelSpec) => (
|
||||||
|
<ModelSpecItem key={spec.name} spec={spec} isSelected={selectedSpec === spec.name} />
|
||||||
|
))}
|
||||||
|
{filteredModels
|
||||||
|
? renderEndpointModels(
|
||||||
|
endpoint,
|
||||||
|
endpoint.models || [],
|
||||||
|
selectedModel,
|
||||||
|
filteredModels,
|
||||||
|
endpointIndex,
|
||||||
|
)
|
||||||
|
: endpoint.models &&
|
||||||
|
renderEndpointModels(endpoint, endpoint.models, selectedModel, undefined, endpointIndex)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
|
export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const {
|
const {
|
||||||
agentsMap,
|
|
||||||
assistantsMap,
|
|
||||||
modelSpecs,
|
|
||||||
selectedValues,
|
selectedValues,
|
||||||
handleOpenKeyDialog,
|
handleOpenKeyDialog,
|
||||||
handleSelectEndpoint,
|
handleSelectEndpoint,
|
||||||
|
|
@ -93,19 +157,7 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
|
||||||
setEndpointSearchValue,
|
setEndpointSearchValue,
|
||||||
endpointRequiresUserKey,
|
endpointRequiresUserKey,
|
||||||
} = useModelSelectorContext();
|
} = useModelSelectorContext();
|
||||||
const {
|
const { endpoint: selectedEndpoint } = selectedValues;
|
||||||
model: selectedModel,
|
|
||||||
endpoint: selectedEndpoint,
|
|
||||||
modelSpec: selectedSpec,
|
|
||||||
} = selectedValues;
|
|
||||||
|
|
||||||
// Filter modelSpecs for this endpoint (by group matching endpoint value)
|
|
||||||
const endpointSpecs = useMemo(() => {
|
|
||||||
if (!modelSpecs || !modelSpecs.length) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return modelSpecs.filter((spec: TModelSpec) => spec.group === endpoint.value);
|
|
||||||
}, [modelSpecs, endpoint.value]);
|
|
||||||
|
|
||||||
const searchValue = endpointSearchValues[endpoint.value] || '';
|
const searchValue = endpointSearchValues[endpoint.value] || '';
|
||||||
const isUserProvided = useMemo(
|
const isUserProvided = useMemo(
|
||||||
|
|
@ -130,15 +182,6 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
|
||||||
const isEndpointSelected = selectedEndpoint === endpoint.value;
|
const isEndpointSelected = selectedEndpoint === endpoint.value;
|
||||||
|
|
||||||
if (endpoint.hasModels) {
|
if (endpoint.hasModels) {
|
||||||
const filteredModels = searchValue
|
|
||||||
? filterModels(
|
|
||||||
endpoint,
|
|
||||||
(endpoint.models || []).map((model) => model.name),
|
|
||||||
searchValue,
|
|
||||||
agentsMap,
|
|
||||||
assistantsMap,
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
const placeholder =
|
const placeholder =
|
||||||
isAgentsEndpoint(endpoint.value) || isAssistantsEndpoint(endpoint.value)
|
isAgentsEndpoint(endpoint.value) || isAssistantsEndpoint(endpoint.value)
|
||||||
? localize('com_endpoint_search_var', { 0: endpoint.label })
|
? localize('com_endpoint_search_var', { 0: endpoint.label })
|
||||||
|
|
@ -147,7 +190,6 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
|
||||||
<Menu
|
<Menu
|
||||||
id={`endpoint-${endpoint.value}-menu`}
|
id={`endpoint-${endpoint.value}-menu`}
|
||||||
key={`endpoint-${endpoint.value}-item`}
|
key={`endpoint-${endpoint.value}-item`}
|
||||||
defaultOpen={endpoint.value === selectedEndpoint}
|
|
||||||
searchValue={searchValue}
|
searchValue={searchValue}
|
||||||
onSearch={(value) => setEndpointSearchValue(endpoint.value, value)}
|
onSearch={(value) => setEndpointSearchValue(endpoint.value, value)}
|
||||||
combobox={<input placeholder=" " />}
|
combobox={<input placeholder=" " />}
|
||||||
|
|
@ -170,39 +212,7 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isAssistantsEndpoint(endpoint.value) && endpoint.models === undefined ? (
|
<EndpointMenuContent endpoint={endpoint} endpointIndex={endpointIndex} />
|
||||||
<div
|
|
||||||
className="flex items-center justify-center p-2"
|
|
||||||
role="status"
|
|
||||||
aria-label={localize('com_ui_loading')}
|
|
||||||
>
|
|
||||||
<Spinner aria-hidden="true" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{/* Render modelSpecs for this endpoint */}
|
|
||||||
{endpointSpecs.map((spec: TModelSpec) => (
|
|
||||||
<ModelSpecItem key={spec.name} spec={spec} isSelected={selectedSpec === spec.name} />
|
|
||||||
))}
|
|
||||||
{/* Render endpoint models */}
|
|
||||||
{filteredModels
|
|
||||||
? renderEndpointModels(
|
|
||||||
endpoint,
|
|
||||||
endpoint.models || [],
|
|
||||||
selectedModel,
|
|
||||||
filteredModels,
|
|
||||||
endpointIndex,
|
|
||||||
)
|
|
||||||
: endpoint.models &&
|
|
||||||
renderEndpointModels(
|
|
||||||
endpoint,
|
|
||||||
endpoint.models,
|
|
||||||
selectedModel,
|
|
||||||
undefined,
|
|
||||||
endpointIndex,
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue