Files
ele-HeiXiu/src/pages/home/components/ModelSelector.tsx

123 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// ModelSelector — 模型选择器(垂直单选列表,按类别分组)
// 使用 useModelList() Hook 获取数据,组件仅负责渲染
// ============================================================
import { useMemo } from 'react';
import { Menu, Spin, Empty, Tag, Typography, theme as antTheme } from 'antd';
import { AppstoreOutlined } from '@ant-design/icons';
import { useModelList, MODEL_CATEGORY_LABELS } from '@/hooks/use-api';
import { useHomeContext } from '@/contexts/home-context';
import type { ModelCategory } from '@/services/modules';
const { Text } = Typography;
// ---------- 组件 ----------
export function ModelSelector() {
const { token } = antTheme.useToken();
const { selectedModel, setSelectedModel } = useHomeContext();
const { data: models, loading, groupedModels } = useModelList();
// 构建 Menu items分组标题 + 模型项
const menuItems = useMemo(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const items: any[] = [];
groupedModels.forEach((groupModels, category) => {
const categoryLabel = MODEL_CATEGORY_LABELS[category as ModelCategory] || category;
items.push({
key: `group-${category}`,
label: (
<Text strong style={{ fontSize: 12, color: token.colorTextSecondary }}>
{categoryLabel} ({groupModels.length})
</Text>
),
type: 'group' as const,
children: groupModels.map((model) => ({
key: model.id,
label: (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<Text
style={{
flex: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
fontSize: 13,
}}
title={model.name}
>
{model.name}
</Text>
{model.status !== 'active' && (
<Tag
color={model.status === 'inactive' ? 'default' : 'orange'}
style={{ fontSize: 10, lineHeight: '16px', padding: '0 4px' }}
>
{model.status === 'maintenance' ? '维护' : '停用'}
</Tag>
)}
</div>
),
})),
});
});
return items;
}, [groupedModels, token.colorTextSecondary]);
// 选中菜单项
const handleSelect = ({ key }: { key: string }) => {
const model = models?.find((m) => m.id === key);
if (model) {
setSelectedModel(model);
}
};
// 加载态
if (loading) {
return (
<div style={{ display: 'flex', justifyContent: 'center', padding: 24 }}>
<Spin size="small" />
</div>
);
}
// 空态
if (!models || models.length === 0) {
return <Empty description="暂无可用模型" image={Empty.PRESENTED_IMAGE_SIMPLE} />;
}
// 选中 key
const selectedKeys = selectedModel ? [selectedModel.id] : [];
return (
<div style={{ flex: 1, overflow: 'auto', minHeight: 0 }}>
<div
style={{
padding: '8px 12px',
borderBottom: `1px solid ${token.colorBorderSecondary}`,
display: 'flex',
alignItems: 'center',
gap: 6,
}}
>
<AppstoreOutlined style={{ color: token.colorPrimary, fontSize: 14 }} />
<Text strong style={{ fontSize: 13 }}></Text>
</div>
<Menu
mode="inline"
selectedKeys={selectedKeys}
onSelect={handleSelect}
items={menuItems}
style={{
borderInlineEnd: 'none',
background: 'transparent',
}}
/>
</div>
);
}