From 8e10f522c022744d04d8189a7ca86ffadd6466a3 Mon Sep 17 00:00:00 2001 From: Kacper Date: Mon, 29 Dec 2025 22:17:07 +0100 Subject: [PATCH] feat: Enhance Cursor model selection and profile handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated AddFeatureDialog to support both Cursor and Claude profiles, allowing for dynamic model and thinking level selection based on the chosen profile. - Modified ModelSelector to filter available Cursor models based on global settings and display a warning if the Cursor CLI is not available. - Enhanced ProfileQuickSelect to handle both profile types and improve selection logic for Cursor profiles. - Refactored CursorSettingsTab to manage global settings for enabled Cursor models and default model selection, streamlining the configuration process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .../board-view/dialogs/add-feature-dialog.tsx | 24 +++- .../board-view/shared/model-selector.tsx | 122 +++++++++++------- .../shared/profile-quick-select.tsx | 46 +++++-- .../providers/cursor-settings-tab.tsx | 111 +++------------- apps/ui/src/store/app-store.ts | 25 ++++ libs/types/src/settings.ts | 10 +- 6 files changed, 184 insertions(+), 154 deletions(-) diff --git a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx index 77e866b1..80e13114 100644 --- a/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/add-feature-dialog.tsx @@ -327,12 +327,23 @@ export function AddFeatureDialog({ }); }; - const handleProfileSelect = (model: AgentModel, thinkingLevel: ThinkingLevel) => { - setNewFeature({ - ...newFeature, - model, - thinkingLevel, - }); + const handleProfileSelect = (profile: AIProfile) => { + if (profile.provider === 'cursor') { + // Cursor profile - set cursor model + const cursorModel = `cursor-${profile.cursorModel || 'auto'}`; + setNewFeature({ + ...newFeature, + model: cursorModel as AgentModel, + thinkingLevel: 'none', // Cursor handles thinking internally + }); + } else { + // Claude profile + setNewFeature({ + ...newFeature, + model: profile.model || 'sonnet', + thinkingLevel: profile.thinkingLevel || 'none', + }); + } }; // Cursor models handle thinking internally, so only show thinking selector for Claude models @@ -529,6 +540,7 @@ export function AddFeatureDialog({ profiles={aiProfiles} selectedModel={newFeature.model} selectedThinkingLevel={newFeature.thinkingLevel} + selectedCursorModel={isCursorModel ? newFeature.model : undefined} onSelect={handleProfileSelect} showManageLink onManageLinkClick={() => { diff --git a/apps/ui/src/components/views/board-view/shared/model-selector.tsx b/apps/ui/src/components/views/board-view/shared/model-selector.tsx index 764aae33..70c6e802 100644 --- a/apps/ui/src/components/views/board-view/shared/model-selector.tsx +++ b/apps/ui/src/components/views/board-view/shared/model-selector.tsx @@ -1,8 +1,10 @@ import { Label } from '@/components/ui/label'; import { Badge } from '@/components/ui/badge'; -import { Brain, Bot, Terminal } from 'lucide-react'; +import { Brain, Bot, Terminal, AlertTriangle } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { AgentModel } from '@/store/app-store'; +import { useAppStore } from '@/store/app-store'; +import { useSetupStore } from '@/store/setup-store'; import type { ModelProvider } from '@automaker/types'; import { CLAUDE_MODELS, CURSOR_MODELS, ModelOption } from './model-constants'; @@ -27,12 +29,25 @@ export function ModelSelector({ onModelSelect, testIdPrefix = 'model-select', }: ModelSelectorProps) { + const { enabledCursorModels, cursorDefaultModel } = useAppStore(); + const { cursorCliStatus } = useSetupStore(); + const selectedProvider = getProviderFromModelString(selectedModel); + // Check if Cursor CLI is available + const isCursorAvailable = cursorCliStatus?.installed && cursorCliStatus?.auth?.authenticated; + + // Filter Cursor models based on enabled models from global settings + const filteredCursorModels = CURSOR_MODELS.filter((model) => { + // Extract the cursor model ID from the prefixed ID (e.g., "cursor-auto" -> "auto") + const cursorModelId = model.id.replace('cursor-', ''); + return enabledCursorModels.includes(cursorModelId as any); + }); + const handleProviderChange = (provider: ModelProvider) => { if (provider === 'cursor' && selectedProvider !== 'cursor') { - // Switch to Cursor's default model - onModelSelect('cursor-auto'); + // Switch to Cursor's default model (from global settings) + onModelSelect(`cursor-${cursorDefaultModel}`); } else if (provider === 'claude' && selectedProvider !== 'claude') { // Switch to Claude's default model onModelSelect('sonnet'); @@ -117,6 +132,17 @@ export function ModelSelector({ {/* Cursor Models */} {selectedProvider === 'cursor' && (
+ {/* Warning when Cursor CLI is not available */} + {!isCursorAvailable && ( +
+ +
+ Cursor CLI is not installed or authenticated. Configure it in Settings → AI + Providers. +
+
+ )} +
- {CURSOR_MODELS.map((option) => { - const isSelected = selectedModel === option.id; - return ( -
- - ); - })} + data-testid={`${testIdPrefix}-${option.id}`} + > + {option.label} +
+ {option.hasThinking && ( + + Thinking + + )} + {option.tier && ( + + {option.tier} + + )} +
+ + ); + }) + )}
)} diff --git a/apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx b/apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx index 54bcc392..e86a7071 100644 --- a/apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx +++ b/apps/ui/src/components/views/board-view/shared/profile-quick-select.tsx @@ -1,5 +1,5 @@ import { Label } from '@/components/ui/label'; -import { Brain, UserCircle } from 'lucide-react'; +import { Brain, UserCircle, Terminal } from 'lucide-react'; import { cn } from '@/lib/utils'; import type { AgentModel, ThinkingLevel, AIProfile } from '@automaker/types'; import { CURSOR_MODEL_MAP, profileHasThinking } from '@automaker/types'; @@ -34,7 +34,8 @@ interface ProfileQuickSelectProps { profiles: AIProfile[]; selectedModel: AgentModel; selectedThinkingLevel: ThinkingLevel; - onSelect: (model: AgentModel, thinkingLevel: ThinkingLevel) => void; + selectedCursorModel?: string; // For detecting cursor profile selection + onSelect: (profile: AIProfile) => void; // Changed to pass full profile testIdPrefix?: string; showManageLink?: boolean; onManageLinkClick?: () => void; @@ -44,19 +45,30 @@ export function ProfileQuickSelect({ profiles, selectedModel, selectedThinkingLevel, + selectedCursorModel, onSelect, testIdPrefix = 'profile-quick-select', showManageLink = false, onManageLinkClick, }: ProfileQuickSelectProps) { - // Filter to only Claude profiles for now - Cursor profiles will be supported - // when features support provider selection (Phase 9) - const claudeProfiles = profiles.filter((p) => p.provider === 'claude'); + // Show both Claude and Cursor profiles + const allProfiles = profiles; - if (claudeProfiles.length === 0) { + if (allProfiles.length === 0) { return null; } + // Check if a profile is selected + const isProfileSelected = (profile: AIProfile): boolean => { + if (profile.provider === 'cursor') { + // For cursor profiles, check if cursor model matches + const profileCursorModel = `cursor-${profile.cursorModel || 'auto'}`; + return selectedCursorModel === profileCursorModel; + } + // For Claude profiles + return selectedModel === profile.model && selectedThinkingLevel === profile.thinkingLevel; + }; + return (
@@ -69,15 +81,16 @@ export function ProfileQuickSelect({
- {claudeProfiles.slice(0, 6).map((profile) => { + {allProfiles.slice(0, 6).map((profile) => { const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain; - const isSelected = - selectedModel === profile.model && selectedThinkingLevel === profile.thinkingLevel; + const isSelected = isProfileSelected(profile); + const isCursorProfile = profile.provider === 'cursor'; + return (