Files
automaker/apps/ui/src/components/views/settings-view/phase-models/phase-model-selector.tsx
Kacper 39f2c8c9ff feat(ui): Enhance AI model handling with Cursor support
- Refactor model handling to support both Claude and Cursor models across various components.
- Introduce `stripProviderPrefix` utility for consistent model ID processing.
- Update `CursorProvider` to utilize `isCursorModel` for model validation.
- Implement model override functionality in GitHub issue validation and enhancement routes.
- Add `useCursorStatusInit` hook to initialize Cursor CLI status on app startup.
- Update UI components to reflect changes in model selection and validation processes.

This update improves the flexibility of AI model usage and enhances user experience by allowing quick model overrides.
2025-12-30 04:01:56 +01:00

108 lines
3.5 KiB
TypeScript

import { cn } from '@/lib/utils';
import { useAppStore } from '@/store/app-store';
import type { ModelAlias, CursorModelId } from '@automaker/types';
import { stripProviderPrefix } from '@automaker/types';
import { CLAUDE_MODELS, CURSOR_MODELS } from '@/components/views/board-view/shared/model-constants';
interface PhaseModelSelectorProps {
label: string;
description: string;
value: ModelAlias | CursorModelId;
onChange: (model: ModelAlias | CursorModelId) => void;
}
export function PhaseModelSelector({
label,
description,
value,
onChange,
}: PhaseModelSelectorProps) {
const { enabledCursorModels } = useAppStore();
// Filter Cursor models to only show enabled ones
const availableCursorModels = CURSOR_MODELS.filter((model) => {
const cursorId = stripProviderPrefix(model.id) as CursorModelId;
return enabledCursorModels.includes(cursorId);
});
return (
<div
className={cn(
'p-4 rounded-xl',
'bg-accent/20 border border-border/30',
'hover:bg-accent/30 transition-colors'
)}
>
<div className="flex flex-col gap-3">
{/* Label and Description */}
<div>
<h4 className="text-sm font-medium text-foreground">{label}</h4>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
{/* Model Selection */}
<div className="flex flex-wrap gap-2">
{/* Claude Models */}
{CLAUDE_MODELS.map((model) => {
const isActive = value === model.id;
return (
<button
key={model.id}
onClick={() => onChange(model.id as ModelAlias)}
className={cn(
'px-3 py-1.5 rounded-lg text-xs font-medium',
'transition-all duration-150',
isActive
? ['bg-brand-500/20 text-brand-500', 'border border-brand-500/40', 'shadow-sm']
: [
'bg-accent/50 text-muted-foreground',
'border border-transparent',
'hover:bg-accent hover:text-foreground',
]
)}
>
{model.label}
</button>
);
})}
{/* Divider if there are Cursor models */}
{availableCursorModels.length > 0 && (
<div className="w-px h-6 bg-border/50 mx-1 self-center" />
)}
{/* Cursor Models */}
{availableCursorModels.map((model) => {
const cursorId = stripProviderPrefix(model.id) as CursorModelId;
const isActive = value === cursorId;
return (
<button
key={model.id}
onClick={() => onChange(cursorId)}
className={cn(
'px-3 py-1.5 rounded-lg text-xs font-medium',
'transition-all duration-150',
isActive
? [
'bg-purple-500/20 text-purple-400',
'border border-purple-500/40',
'shadow-sm',
]
: [
'bg-accent/50 text-muted-foreground',
'border border-transparent',
'hover:bg-accent hover:text-foreground',
]
)}
title={model.description}
>
{model.label}
</button>
);
})}
</div>
</div>
</div>
);
}