♻️ refactor(ModelSelect): migrate from antd Select to LobeSelect (#11772)

* ♻️ refactor(ModelSelect): migrate from antd Select to LobeSelect

Resolves popover z-index issues by using LobeSelect component from @lobehub/ui which has proper z-index handling.

Changes:
- Replace Select with LobeSelect from @lobehub/ui
- Simplify styles by using popupClassName instead of classNames
- Set fixed popup width to 360px
- Remove unused TAG_CLASSNAME import and select styles

fix LOBE-4210

*  feat(ModelSelect): add initialWidth prop for dynamic width adjustment

- Introduced an `initialWidth` prop to the ModelSelect component to allow for dynamic width settings.
- Updated ProfileEditor and MemberProfile to utilize the new `initialWidth` feature for improved layout consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* 🔧 chore(package): update @lobehub/ui to version 4.29.0

- Bumped the version of @lobehub/ui in package.json to 4.29.0 for improved features and fixes.
- Enhanced ModelSelect component to include optional displayName and abilities properties for better data handling and rendering.

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2026-01-24 16:15:37 +08:00
committed by GitHub
parent e4345043d2
commit 73412d1a6c
4 changed files with 47 additions and 30 deletions

View File

@@ -205,7 +205,7 @@
"@lobehub/icons": "^4.0.2",
"@lobehub/market-sdk": "0.29.1",
"@lobehub/tts": "^4.0.2",
"@lobehub/ui": "^4.28.0",
"@lobehub/ui": "^4.29.0",
"@modelcontextprotocol/sdk": "^1.25.1",
"@napi-rs/canvas": "^0.1.88",
"@neondatabase/serverless": "^1.0.2",

View File

@@ -56,6 +56,7 @@ const ProfileEditor = memo(() => {
style={{ marginBottom: 12 }}
>
<ModelSelect
initialWidth
onChange={updateConfig}
value={{
model: config.model,

View File

@@ -114,6 +114,7 @@ const MemberProfile = memo(() => {
style={{ marginBottom: 12 }}
>
<ModelSelect
initialWidth
onChange={updateAgentConfig}
value={{
model: config?.model,

View File

@@ -1,47 +1,53 @@
import { Select, type SelectProps, TooltipGroup } from '@lobehub/ui';
import { LobeSelect, type LobeSelectProps, TooltipGroup } from '@lobehub/ui';
import { createStaticStyles } from 'antd-style';
import { memo, useMemo } from 'react';
import { type ReactNode, memo, useMemo } from 'react';
import { ModelItemRender, ProviderItemRender, TAG_CLASSNAME } from '@/components/ModelSelect';
import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
import { useEnabledChatModels } from '@/hooks/useEnabledChatModels';
import { type EnabledProviderWithModels } from '@/types/aiProvider';
const prefixCls = 'ant';
const styles = createStaticStyles(({ css }) => ({
popup: css`
&.${prefixCls}-select-dropdown .${prefixCls}-select-item-option-grouped {
padding-inline-start: 12px;
}
`,
select: css`
.${prefixCls}-select-selection-item {
.${TAG_CLASSNAME} {
display: none;
}
}
width: max(360px, var(--anchor-width));
`,
}));
type ModelAbilities = EnabledProviderWithModels['children'][number]['abilities'];
interface ModelOption {
label: any;
abilities?: ModelAbilities;
displayName?: string;
id: string;
label: ReactNode;
provider: string;
value: string;
}
interface ModelSelectProps extends Pick<SelectProps, 'loading' | 'size' | 'style' | 'variant'> {
interface ModelSelectProps extends Pick<LobeSelectProps, 'loading' | 'size' | 'style' | 'variant'> {
defaultValue?: { model: string; provider?: string };
initialWidth?: boolean;
onChange?: (props: { model: string; provider: string }) => void;
requiredAbilities?: (keyof EnabledProviderWithModels['children'][number]['abilities'])[];
showAbility?: boolean;
value?: { model: string; provider?: string };
}
const ModelSelect = memo<ModelSelectProps>(
({ value, onChange, showAbility = true, requiredAbilities, loading, size, style, variant }) => {
({
value,
onChange,
initialWidth = false,
showAbility = true,
requiredAbilities,
loading,
size,
style,
variant,
}) => {
const enabledList = useEnabledChatModels();
const options = useMemo<SelectProps['options']>(() => {
const options = useMemo<LobeSelectProps['options']>(() => {
const getChatModels = (provider: EnabledProviderWithModels) => {
const models =
requiredAbilities && requiredAbilities.length > 0
@@ -81,34 +87,43 @@ const ModelSelect = memo<ModelSelectProps>(
options: opts,
};
})
.filter(Boolean) as SelectProps['options'];
.filter(Boolean) as LobeSelectProps['options'];
}, [enabledList, requiredAbilities, showAbility]);
return (
<TooltipGroup>
<Select
className={styles.select}
classNames={{
popup: { root: styles.popup },
}}
<LobeSelect
defaultValue={`${value?.provider}/${value?.model}`}
loading={loading}
onChange={(value, option) => {
const model = value.split('/').slice(1).join('/');
if (!value) return;
const model = (value as string).split('/').slice(1).join('/');
onChange?.({ model, provider: (option as unknown as ModelOption).provider });
}}
optionRender={(option) => (
<ModelItemRender {...option.data} {...option.data.abilities} showInfoTag />
)}
optionRender={(option) => {
const data = option as unknown as ModelOption;
return (
<ModelItemRender
displayName={data.displayName}
id={data.id}
showInfoTag
{...data.abilities}
/>
);
}}
options={options}
popupClassName={styles.popup}
popupMatchSelectWidth={false}
selectedIndicatorVariant="bold"
size={size}
style={{
minWidth: 200,
width: initialWidth ? 'initial' : undefined,
...style,
}}
value={`${value?.provider}/${value?.model}`}
variant={variant}
virtual
/>
</TooltipGroup>
);