claude-code-cchui/src/components/ui/ModelCardSelector.tsx
gaoziman 29b2d99a82 feat(ui): 新增设置相关 UI 组件
- FontSizePicker: 字体大小选择器,支持实时预览
- ModelCardSelector: 模型卡片选择组件(Haiku/Sonnet/Opus)
- UserMenu: 用户菜单弹出组件,支持主题切换和设置导航
2025-12-19 13:56:22 +08:00

109 lines
2.9 KiB
TypeScript

'use client';
import { cn } from '@/lib/utils';
// 模型卡片配置
const MODEL_CARDS = [
{
id: 'haiku',
name: 'Haiku',
description: 'Fast & efficient',
modelIdPattern: 'haiku',
},
{
id: 'sonnet',
name: 'Sonnet',
description: 'Balanced',
modelIdPattern: 'sonnet',
},
{
id: 'opus',
name: 'Opus',
description: 'Most capable',
modelIdPattern: 'opus',
},
];
interface ModelCardSelectorProps {
value: string;
onChange: (modelId: string) => void;
disabled?: boolean;
models?: { modelId: string; displayName: string }[];
}
export function ModelCardSelector({
value,
onChange,
disabled = false,
models = [],
}: ModelCardSelectorProps) {
// 根据当前选中的模型ID判断选中的卡片
const getSelectedCard = (modelId: string): string => {
const lowerModelId = modelId.toLowerCase();
if (lowerModelId.includes('haiku')) return 'haiku';
if (lowerModelId.includes('opus')) return 'opus';
return 'sonnet'; // 默认 sonnet
};
// 根据卡片类型找到对应的实际模型ID
const findModelIdByCard = (cardId: string): string => {
const model = models.find((m) =>
m.modelId.toLowerCase().includes(cardId)
);
return model?.modelId || value;
};
const selectedCard = getSelectedCard(value);
return (
<div className="flex gap-3">
{MODEL_CARDS.map((card) => {
const isSelected = selectedCard === card.id;
return (
<button
key={card.id}
onClick={() => {
if (!disabled) {
const newModelId = findModelIdByCard(card.id);
onChange(newModelId);
}
}}
disabled={disabled}
className={cn(
'flex flex-col items-center justify-center',
'w-[120px] h-[72px] rounded-md',
'border-2 transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[var(--color-primary)]',
isSelected
? 'bg-[var(--color-primary-light)] border-[var(--color-primary)]'
: 'bg-[var(--color-bg-tertiary)] border-transparent hover:border-[var(--color-border)]',
disabled && 'opacity-50 cursor-not-allowed'
)}
>
<span
className={cn(
'text-sm font-semibold',
isSelected
? 'text-[var(--color-primary)]'
: 'text-[var(--color-text-primary)]'
)}
>
{card.name}
</span>
<span
className={cn(
'text-xs mt-1',
isSelected
? 'text-[var(--color-primary)]'
: 'text-[var(--color-text-tertiary)]'
)}
>
{card.description}
</span>
</button>
);
})}
</div>
);
}