diff --git a/scripts/lint-ts.sh b/scripts/lint-ts.sh index 8581f3973d..ea32422cc2 100755 --- a/scripts/lint-ts.sh +++ b/scripts/lint-ts.sh @@ -2,4 +2,4 @@ set -o pipefail eslint src/ tests/ --fix --concurrency=auto --prune-suppressions -eslint src/ tests/ --concurrency=auto \ No newline at end of file +eslint src/ tests/ --concurrency=auto diff --git a/src/features/ModelSwitchPanel/components/List/index.tsx b/src/features/ModelSwitchPanel/components/List/index.tsx index 123501f0fb..455e3390f1 100644 --- a/src/features/ModelSwitchPanel/components/List/index.tsx +++ b/src/features/ModelSwitchPanel/components/List/index.tsx @@ -1,6 +1,6 @@ import { Flexbox } from '@lobehub/ui'; import { type FC, type ReactNode } from 'react'; -import { useMemo } from 'react'; +import { useCallback, useLayoutEffect, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useEnabledChatModels } from '@/hooks/useEnabledChatModels'; @@ -54,28 +54,69 @@ export const List: FC = ({ const activeKey = menuKey(provider, model); + // Set initial scroll position to keep active model centered + const listRef = useRef(null); + const activeNodeRef = useRef(null); + const hasInitializedPositionRef = useRef(false); + + const activeItemRef = useCallback((node: HTMLDivElement | null) => { + activeNodeRef.current = node; + }, []); + const listHeight = panelHeight - TOOLBAR_HEIGHT - FOOTER_HEIGHT; + useLayoutEffect(() => { + if (hasInitializedPositionRef.current) return; + + const container = listRef.current; + const activeNode = activeNodeRef.current; + if (!container || !activeNode) return; + + const targetScrollTop = + activeNode.offsetTop - (container.clientHeight - activeNode.offsetHeight) / 2; + container.scrollTop = Math.max(0, targetScrollTop); + hasInitializedPositionRef.current = true; + }, [listHeight, activeKey]); + return ( - - {listItems.map((item, index) => ( - - ))} + + {listItems.map((item, index) => { + const itemKey = menuKey( + 'provider' in item && item.provider ? item.provider.id : '', + 'model' in item && item.model + ? item.model.id + : 'data' in item && item.data + ? item.data.displayName + : `${item.type}-${index}`, + ); + const isActive = + (item.type === 'provider-model-item' && + menuKey(item.provider.id, item.model.id) === activeKey) || + (item.type === 'model-item-single' && + menuKey(item.data.providers[0].id, item.data.model.id) === activeKey) || + (item.type === 'model-item-multiple' && + item.data.providers.some((p) => menuKey(p.id, item.data.model.id) === activeKey)); + + const renderItem = (key?: string) => ( + + ); + + return isActive ? ( +
+ {renderItem()} +
+ ) : ( + renderItem(itemKey) + ); + })}
); };