diff --git a/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx b/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx
index 27c1236cb2..6f73f76d79 100644
--- a/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx
+++ b/client/src/components/Chat/Menus/Endpoints/components/EndpointItem.tsx
@@ -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 (
+
+
+
+ );
+ }
+
+ const filteredModels = searchValue
+ ? filterModels(
+ endpoint,
+ (endpoint.models || []).map((model) => model.name),
+ searchValue,
+ agentsMap,
+ assistantsMap,
+ )
+ : null;
+
+ return (
+ <>
+ {endpointSpecs.map((spec: TModelSpec) => (
+
+ ))}
+ {filteredModels
+ ? renderEndpointModels(
+ endpoint,
+ endpoint.models || [],
+ selectedModel,
+ filteredModels,
+ endpointIndex,
+ )
+ : endpoint.models &&
+ renderEndpointModels(endpoint, endpoint.models, selectedModel, undefined, endpointIndex)}
+ >
+ );
+}
+
export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
const localize = useLocalize();
const {
- agentsMap,
- assistantsMap,
- modelSpecs,
selectedValues,
handleOpenKeyDialog,
handleSelectEndpoint,
@@ -93,19 +157,7 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
setEndpointSearchValue,
endpointRequiresUserKey,
} = useModelSelectorContext();
- const {
- 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 { endpoint: selectedEndpoint } = selectedValues;
const searchValue = endpointSearchValues[endpoint.value] || '';
const isUserProvided = useMemo(
@@ -130,15 +182,6 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
const isEndpointSelected = selectedEndpoint === endpoint.value;
if (endpoint.hasModels) {
- const filteredModels = searchValue
- ? filterModels(
- endpoint,
- (endpoint.models || []).map((model) => model.name),
- searchValue,
- agentsMap,
- assistantsMap,
- )
- : null;
const placeholder =
isAgentsEndpoint(endpoint.value) || isAssistantsEndpoint(endpoint.value)
? localize('com_endpoint_search_var', { 0: endpoint.label })
@@ -147,7 +190,6 @@ export function EndpointItem({ endpoint, endpointIndex }: EndpointItemProps) {
);
} else {