diff --git a/apps/server/src/services/settings-service.ts b/apps/server/src/services/settings-service.ts index 7acd2ed18..9270b3fbd 100644 --- a/apps/server/src/services/settings-service.ts +++ b/apps/server/src/services/settings-service.ts @@ -22,7 +22,6 @@ import type { Credentials, ProjectSettings, KeyboardShortcuts, - AIProfile, ProjectRef, TrashedProjectRef, BoardBackgroundSettings, @@ -299,7 +298,6 @@ export class SettingsService { ignoreEmptyArrayOverwrite('trashedProjects'); ignoreEmptyArrayOverwrite('projectHistory'); ignoreEmptyArrayOverwrite('recentFolders'); - ignoreEmptyArrayOverwrite('aiProfiles'); ignoreEmptyArrayOverwrite('mcpServers'); ignoreEmptyArrayOverwrite('enabledCursorModels'); @@ -617,18 +615,15 @@ export class SettingsService { : false, useWorktrees: appState.useWorktrees !== undefined ? (appState.useWorktrees as boolean) : true, - showProfilesOnly: (appState.showProfilesOnly as boolean) || false, defaultPlanningMode: (appState.defaultPlanningMode as GlobalSettings['defaultPlanningMode']) || 'skip', defaultRequirePlanApproval: (appState.defaultRequirePlanApproval as boolean) || false, - defaultAIProfileId: (appState.defaultAIProfileId as string | null) || null, muteDoneSound: (appState.muteDoneSound as boolean) || false, enhancementModel: (appState.enhancementModel as GlobalSettings['enhancementModel']) || 'sonnet', keyboardShortcuts: (appState.keyboardShortcuts as KeyboardShortcuts) || DEFAULT_GLOBAL_SETTINGS.keyboardShortcuts, - aiProfiles: (appState.aiProfiles as AIProfile[]) || [], projects: (appState.projects as ProjectRef[]) || [], trashedProjects: (appState.trashedProjects as TrashedProjectRef[]) || [], projectHistory: (appState.projectHistory as string[]) || [], diff --git a/apps/server/src/types/settings.ts b/apps/server/src/types/settings.ts index a92e706e4..e785f8ea4 100644 --- a/apps/server/src/types/settings.ts +++ b/apps/server/src/types/settings.ts @@ -13,7 +13,6 @@ export type { ThinkingLevel, ModelProvider, KeyboardShortcuts, - AIProfile, ProjectRef, TrashedProjectRef, ChatSessionRef, diff --git a/apps/ui/scripts/setup-e2e-fixtures.mjs b/apps/ui/scripts/setup-e2e-fixtures.mjs index e6009fd48..26488791f 100644 --- a/apps/ui/scripts/setup-e2e-fixtures.mjs +++ b/apps/ui/scripts/setup-e2e-fixtures.mjs @@ -47,10 +47,8 @@ const E2E_SETTINGS = { enableDependencyBlocking: true, skipVerificationInAutoMode: false, useWorktrees: true, - showProfilesOnly: false, defaultPlanningMode: 'skip', defaultRequirePlanApproval: false, - defaultAIProfileId: null, muteDoneSound: false, phaseModels: { enhancementModel: { model: 'sonnet' }, @@ -73,7 +71,6 @@ const E2E_SETTINGS = { spec: 'D', context: 'C', settings: 'S', - profiles: 'M', terminal: 'T', toggleSidebar: '`', addFeature: 'N', @@ -84,7 +81,6 @@ const E2E_SETTINGS = { projectPicker: 'P', cyclePrevProject: 'Q', cycleNextProject: 'E', - addProfile: 'N', splitTerminalRight: 'Alt+D', splitTerminalDown: 'Alt+S', closeTerminal: 'Alt+W', @@ -94,48 +90,6 @@ const E2E_SETTINGS = { githubPrs: 'R', newTerminalTab: 'Alt+T', }, - aiProfiles: [ - { - id: 'profile-heavy-task', - name: 'Heavy Task', - description: - 'Claude Opus with Ultrathink for complex architecture, migrations, or deep debugging.', - model: 'opus', - thinkingLevel: 'ultrathink', - provider: 'claude', - isBuiltIn: true, - icon: 'Brain', - }, - { - id: 'profile-balanced', - name: 'Balanced', - description: 'Claude Sonnet with medium thinking for typical development tasks.', - model: 'sonnet', - thinkingLevel: 'medium', - provider: 'claude', - isBuiltIn: true, - icon: 'Scale', - }, - { - id: 'profile-quick-edit', - name: 'Quick Edit', - description: 'Claude Haiku for fast, simple edits and minor fixes.', - model: 'haiku', - thinkingLevel: 'none', - provider: 'claude', - isBuiltIn: true, - icon: 'Zap', - }, - { - id: 'profile-cursor-refactoring', - name: 'Cursor Refactoring', - description: 'Cursor Composer 1 for refactoring tasks.', - provider: 'cursor', - cursorModel: 'composer-1', - isBuiltIn: true, - icon: 'Sparkles', - }, - ], // Default test project using the fixture path - tests can override via route mocking if needed projects: [ { diff --git a/apps/ui/src/components/layout/sidebar.tsx b/apps/ui/src/components/layout/sidebar.tsx index a1d03e87a..98535bdac 100644 --- a/apps/ui/src/components/layout/sidebar.tsx +++ b/apps/ui/src/components/layout/sidebar.tsx @@ -59,7 +59,7 @@ export function Sidebar() { } = useAppStore(); // Environment variable flags for hiding sidebar items - const { hideTerminal, hideWiki, hideRunningAgents, hideContext, hideSpecEditor, hideAiProfiles } = + const { hideTerminal, hideWiki, hideRunningAgents, hideContext, hideSpecEditor } = SIDEBAR_FEATURE_FLAGS; // Get customizable keyboard shortcuts @@ -232,7 +232,6 @@ export function Sidebar() { hideSpecEditor, hideContext, hideTerminal, - hideAiProfiles, currentProject, projects, projectHistory, diff --git a/apps/ui/src/components/layout/sidebar/constants.ts b/apps/ui/src/components/layout/sidebar/constants.ts index 4beca9530..0ca501728 100644 --- a/apps/ui/src/components/layout/sidebar/constants.ts +++ b/apps/ui/src/components/layout/sidebar/constants.ts @@ -20,5 +20,4 @@ export const SIDEBAR_FEATURE_FLAGS = { hideRunningAgents: import.meta.env.VITE_HIDE_RUNNING_AGENTS === 'true', hideContext: import.meta.env.VITE_HIDE_CONTEXT === 'true', hideSpecEditor: import.meta.env.VITE_HIDE_SPEC_EDITOR === 'true', - hideAiProfiles: import.meta.env.VITE_HIDE_AI_PROFILES === 'true', } as const; diff --git a/apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts b/apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts index 350bd2f89..9ea163d54 100644 --- a/apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts +++ b/apps/ui/src/components/layout/sidebar/hooks/use-navigation.ts @@ -5,11 +5,9 @@ import { LayoutGrid, Bot, BookOpen, - UserCircle, Terminal, CircleDot, GitPullRequest, - Zap, Lightbulb, } from 'lucide-react'; import type { NavSection, NavItem } from '../types'; @@ -26,7 +24,6 @@ interface UseNavigationProps { cycleNextProject: string; spec: string; context: string; - profiles: string; board: string; agent: string; terminal: string; @@ -38,7 +35,6 @@ interface UseNavigationProps { hideSpecEditor: boolean; hideContext: boolean; hideTerminal: boolean; - hideAiProfiles: boolean; currentProject: Project | null; projects: Project[]; projectHistory: string[]; @@ -57,7 +53,6 @@ export function useNavigation({ hideSpecEditor, hideContext, hideTerminal, - hideAiProfiles, currentProject, projects, projectHistory, @@ -114,12 +109,6 @@ export function useNavigation({ icon: BookOpen, shortcut: shortcuts.context, }, - { - id: 'profiles', - label: 'AI Profiles', - icon: UserCircle, - shortcut: shortcuts.profiles, - }, ]; // Filter out hidden items @@ -130,9 +119,6 @@ export function useNavigation({ if (item.id === 'context' && hideContext) { return false; } - if (item.id === 'profiles' && hideAiProfiles) { - return false; - } return true; }); @@ -201,7 +187,6 @@ export function useNavigation({ hideSpecEditor, hideContext, hideTerminal, - hideAiProfiles, hasGitHubRemote, unviewedValidationsCount, ]); diff --git a/apps/ui/src/components/ui/keyboard-map.tsx b/apps/ui/src/components/ui/keyboard-map.tsx index 2e00c1e29..0b2fe5415 100644 --- a/apps/ui/src/components/ui/keyboard-map.tsx +++ b/apps/ui/src/components/ui/keyboard-map.tsx @@ -88,7 +88,6 @@ const SHORTCUT_LABELS: Record = { spec: 'Spec Editor', context: 'Context', settings: 'Settings', - profiles: 'AI Profiles', terminal: 'Terminal', ideation: 'Ideation', githubIssues: 'GitHub Issues', @@ -102,7 +101,6 @@ const SHORTCUT_LABELS: Record = { projectPicker: 'Project Picker', cyclePrevProject: 'Prev Project', cycleNextProject: 'Next Project', - addProfile: 'Add Profile', splitTerminalRight: 'Split Right', splitTerminalDown: 'Split Down', closeTerminal: 'Close Terminal', @@ -116,7 +114,6 @@ const SHORTCUT_CATEGORIES: Record setShowMassEditDialog(false)} selectedFeatures={selectedFeatures} onApply={handleBulkUpdate} - showProfilesOnly={showProfilesOnly} - aiProfiles={aiProfiles} /> {/* Board Background Modal */} @@ -1348,8 +1344,6 @@ export function BoardView() { defaultBranch={selectedWorktreeBranch} currentBranch={currentWorktreeBranch || undefined} isMaximized={isMaximized} - showProfilesOnly={showProfilesOnly} - aiProfiles={aiProfiles} parentFeature={spawnParentFeature} allFeatures={hookFeatures} /> @@ -1364,8 +1358,6 @@ export function BoardView() { branchCardCounts={branchCardCounts} currentBranch={currentWorktreeBranch || undefined} isMaximized={isMaximized} - showProfilesOnly={showProfilesOnly} - aiProfiles={aiProfiles} allFeatures={hookFeatures} /> 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 bae7ce50b..bab34522e 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 @@ -32,24 +32,17 @@ import { ModelAlias, ThinkingLevel, FeatureImage, - AIProfile, PlanningMode, Feature, } from '@/store/app-store'; import type { ReasoningEffort, PhaseModelEntry } from '@automaker/types'; -import { - supportsReasoningEffort, - PROVIDER_PREFIXES, - isCursorModel, - isClaudeModel, -} from '@automaker/types'; +import { supportsReasoningEffort, isClaudeModel } from '@automaker/types'; import { TestingTabContent, PrioritySelector, WorkModeSelector, PlanningModeSelect, AncestorContextSection, - ProfileTypeahead, } from '../shared'; import type { WorkMode } from '../shared'; import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector'; @@ -60,7 +53,6 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { useNavigate } from '@tanstack/react-router'; import { getAncestors, formatAncestorContextForPrompt, @@ -100,8 +92,6 @@ interface AddFeatureDialogProps { defaultBranch?: string; currentBranch?: string; isMaximized: boolean; - showProfilesOnly: boolean; - aiProfiles: AIProfile[]; parentFeature?: Feature | null; allFeatures?: Feature[]; } @@ -118,13 +108,10 @@ export function AddFeatureDialog({ defaultBranch = 'main', currentBranch, isMaximized, - showProfilesOnly, - aiProfiles, parentFeature = null, allFeatures = [], }: AddFeatureDialogProps) { const isSpawnMode = !!parentFeature; - const navigate = useNavigate(); const [workMode, setWorkMode] = useState('current'); // Form state @@ -139,7 +126,6 @@ export function AddFeatureDialog({ const [priority, setPriority] = useState(2); // Model selection state - const [selectedProfileId, setSelectedProfileId] = useState(); const [modelEntry, setModelEntry] = useState({ model: 'opus' }); // Check if current model supports planning mode (Claude/Anthropic only) @@ -163,7 +149,7 @@ export function AddFeatureDialog({ const [selectedAncestorIds, setSelectedAncestorIds] = useState>(new Set()); // Get defaults from store - const { defaultPlanningMode, defaultRequirePlanApproval, defaultAIProfileId } = useAppStore(); + const { defaultPlanningMode, defaultRequirePlanApproval } = useAppStore(); // Enhancement model override const enhancementOverride = useModelOverride({ phase: 'enhancementModel' }); @@ -177,24 +163,12 @@ export function AddFeatureDialog({ wasOpenRef.current = open; if (justOpened) { - const defaultProfile = defaultAIProfileId - ? aiProfiles.find((p) => p.id === defaultAIProfileId) - : null; - setSkipTests(defaultSkipTests); setBranchName(defaultBranch || ''); setWorkMode('current'); setPlanningMode(defaultPlanningMode); setRequirePlanApproval(defaultRequirePlanApproval); - - // Set model from default profile or fallback - if (defaultProfile) { - setSelectedProfileId(defaultProfile.id); - applyProfileToModel(defaultProfile); - } else { - setSelectedProfileId(undefined); - setModelEntry({ model: 'opus' }); - } + setModelEntry({ model: 'opus' }); // Initialize ancestors for spawn mode if (parentFeature) { @@ -212,41 +186,12 @@ export function AddFeatureDialog({ defaultBranch, defaultPlanningMode, defaultRequirePlanApproval, - defaultAIProfileId, - aiProfiles, parentFeature, allFeatures, ]); - const applyProfileToModel = (profile: AIProfile) => { - if (profile.provider === 'cursor') { - const cursorModel = `${PROVIDER_PREFIXES.cursor}${profile.cursorModel || 'auto'}`; - setModelEntry({ model: cursorModel as ModelAlias }); - } else if (profile.provider === 'codex') { - setModelEntry({ - model: profile.codexModel || 'codex-gpt-5.2-codex', - reasoningEffort: 'none', - }); - } else if (profile.provider === 'opencode') { - setModelEntry({ model: profile.opencodeModel || 'opencode/big-pickle' }); - } else { - // Claude - setModelEntry({ - model: profile.model || 'sonnet', - thinkingLevel: profile.thinkingLevel || 'none', - }); - } - }; - - const handleProfileSelect = (profile: AIProfile) => { - setSelectedProfileId(profile.id); - applyProfileToModel(profile); - }; - const handleModelChange = (entry: PhaseModelEntry) => { setModelEntry(entry); - // Clear profile selection when manually changing model - setSelectedProfileId(undefined); }; const buildFeatureData = (): FeatureData | null => { @@ -327,7 +272,6 @@ export function AddFeatureDialog({ setSkipTests(defaultSkipTests); setBranchName(''); setPriority(2); - setSelectedProfileId(undefined); setModelEntry({ model: 'opus' }); setWorkMode('current'); setPlanningMode(defaultPlanningMode); @@ -538,31 +482,14 @@ export function AddFeatureDialog({ AI & Execution -
-
- - { - onOpenChange(false); - navigate({ to: '/profiles' }); - }} - testIdPrefix="add-feature-profile" - /> -
-
- - -
+
+ +
; // Map of branch name to unarchived card count currentBranch?: string; isMaximized: boolean; - showProfilesOnly: boolean; - aiProfiles: AIProfile[]; allFeatures: Feature[]; } @@ -113,11 +97,8 @@ export function EditFeatureDialog({ branchCardCounts, currentBranch, isMaximized, - showProfilesOnly, - aiProfiles, allFeatures, }: EditFeatureDialogProps) { - const navigate = useNavigate(); const [editingFeature, setEditingFeature] = useState(feature); // Derive initial workMode from feature's branchName const [workMode, setWorkMode] = useState(() => { @@ -140,7 +121,6 @@ export function EditFeatureDialog({ ); // Model selection state - const [selectedProfileId, setSelectedProfileId] = useState(); const [modelEntry, setModelEntry] = useState(() => ({ model: (feature?.model as ModelAlias) || 'opus', thinkingLevel: feature?.thinkingLevel || 'none', @@ -180,7 +160,6 @@ export function EditFeatureDialog({ thinkingLevel: feature.thinkingLevel || 'none', reasoningEffort: feature.reasoningEffort || 'none', }); - setSelectedProfileId(undefined); } else { setEditFeaturePreviewMap(new Map()); setDescriptionChangeSource(null); @@ -188,35 +167,8 @@ export function EditFeatureDialog({ } }, [feature]); - const applyProfileToModel = (profile: AIProfile) => { - if (profile.provider === 'cursor') { - const cursorModel = `${PROVIDER_PREFIXES.cursor}${profile.cursorModel || 'auto'}`; - setModelEntry({ model: cursorModel as ModelAlias }); - } else if (profile.provider === 'codex') { - setModelEntry({ - model: profile.codexModel || 'codex-gpt-5.2-codex', - reasoningEffort: 'none', - }); - } else if (profile.provider === 'opencode') { - setModelEntry({ model: profile.opencodeModel || 'opencode/big-pickle' }); - } else { - // Claude - setModelEntry({ - model: profile.model || 'sonnet', - thinkingLevel: profile.thinkingLevel || 'none', - }); - } - }; - - const handleProfileSelect = (profile: AIProfile) => { - setSelectedProfileId(profile.id); - applyProfileToModel(profile); - }; - const handleModelChange = (entry: PhaseModelEntry) => { setModelEntry(entry); - // Clear profile selection when manually changing model - setSelectedProfileId(undefined); }; const handleUpdate = () => { @@ -554,31 +506,14 @@ export function EditFeatureDialog({ AI & Execution
-
-
- - { - onClose(); - navigate({ to: '/profiles' }); - }} - testIdPrefix="edit-feature-profile" - /> -
-
- - -
+
+ +
void; selectedFeatures: Feature[]; onApply: (updates: Partial) => Promise; - showProfilesOnly: boolean; - aiProfiles: AIProfile[]; } interface ApplyState { @@ -98,14 +96,7 @@ function FieldWrapper({ label, isMixed, willApply, onApplyChange, children }: Fi ); } -export function MassEditDialog({ - open, - onClose, - selectedFeatures, - onApply, - showProfilesOnly, - aiProfiles, -}: MassEditDialogProps) { +export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: MassEditDialogProps) { const [isApplying, setIsApplying] = useState(false); // Track which fields to apply @@ -149,26 +140,6 @@ export function MassEditDialog({ } }, [open, selectedFeatures]); - const handleModelSelect = (newModel: string) => { - const isCursor = isCursorModel(newModel); - setModel(newModel as ModelAlias); - if (isCursor || !modelSupportsThinking(newModel)) { - setThinkingLevel('none'); - } - }; - - const handleProfileSelect = (profile: AIProfile) => { - if (profile.provider === 'cursor') { - const cursorModel = `${PROVIDER_PREFIXES.cursor}${profile.cursorModel || 'auto'}`; - setModel(cursorModel as ModelAlias); - setThinkingLevel('none'); - } else { - setModel((profile.model || 'sonnet') as ModelAlias); - setThinkingLevel(profile.thinkingLevel || 'none'); - } - setApplyState((prev) => ({ ...prev, model: true, thinkingLevel: true })); - }; - const handleApply = async () => { const updates: Partial = {}; @@ -208,29 +179,11 @@ export function MassEditDialog({
- {/* Quick Select Profile Section */} - {aiProfiles.length > 0 && ( -
- -

- Selecting a profile will automatically enable model settings -

- -
- )} - {/* Model Selector */}

- Or select a specific model configuration + Select a specific model configuration

void; // Changed to pass full profile - testIdPrefix?: string; - showManageLink?: boolean; - onManageLinkClick?: () => void; -} - -export function ProfileQuickSelect({ - profiles, - selectedModel, - selectedThinkingLevel, - selectedCursorModel, - onSelect, - testIdPrefix = 'profile-quick-select', - showManageLink = false, - onManageLinkClick, -}: ProfileQuickSelectProps) { - // Show both Claude and Cursor profiles - const allProfiles = profiles; - - 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 = `${PROVIDER_PREFIXES.cursor}${profile.cursorModel || 'auto'}`; - return selectedCursorModel === profileCursorModel; - } - // For Claude profiles - return selectedModel === profile.model && selectedThinkingLevel === profile.thinkingLevel; - }; - - return ( -
-
- - - Presets - -
-
- {allProfiles.slice(0, 6).map((profile) => { - const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain; - const isSelected = isProfileSelected(profile); - const isCursorProfile = profile.provider === 'cursor'; - - return ( - - ); - })} -
-

- Or customize below. - {showManageLink && onManageLinkClick && ( - <> - {' '} - Manage profiles in{' '} - - - )} -

-
- ); -} diff --git a/apps/ui/src/components/views/board-view/shared/profile-select.tsx b/apps/ui/src/components/views/board-view/shared/profile-select.tsx deleted file mode 100644 index c3c68a1c2..000000000 --- a/apps/ui/src/components/views/board-view/shared/profile-select.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { Brain, Terminal } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import type { ModelAlias, ThinkingLevel, AIProfile, CursorModelId } from '@automaker/types'; -import { - CURSOR_MODEL_MAP, - profileHasThinking, - PROVIDER_PREFIXES, - getCodexModelLabel, -} from '@automaker/types'; -import { PROFILE_ICONS } from './model-constants'; - -/** - * Get display string for a profile's model configuration - */ -function getProfileModelDisplay(profile: AIProfile): string { - if (profile.provider === 'cursor') { - const cursorModel = profile.cursorModel || 'auto'; - const modelConfig = CURSOR_MODEL_MAP[cursorModel]; - return modelConfig?.label || cursorModel; - } - if (profile.provider === 'codex') { - return getCodexModelLabel(profile.codexModel || 'codex-gpt-5.2-codex'); - } - // Claude - return profile.model || 'sonnet'; -} - -/** - * Get display string for a profile's thinking configuration - */ -function getProfileThinkingDisplay(profile: AIProfile): string | null { - if (profile.provider === 'cursor') { - // For Cursor, thinking is embedded in the model - return profileHasThinking(profile) ? 'thinking' : null; - } - if (profile.provider === 'codex') { - // For Codex, thinking is embedded in the model - return profileHasThinking(profile) ? 'thinking' : null; - } - // Claude - return profile.thinkingLevel && profile.thinkingLevel !== 'none' ? profile.thinkingLevel : null; -} - -interface ProfileSelectProps { - profiles: AIProfile[]; - selectedModel: ModelAlias | CursorModelId; - selectedThinkingLevel: ThinkingLevel; - selectedCursorModel?: string; // For detecting cursor profile selection - onSelect: (profile: AIProfile) => void; - testIdPrefix?: string; - className?: string; - disabled?: boolean; -} - -/** - * ProfileSelect - Compact dropdown selector for AI profiles - * - * A lightweight alternative to ProfileQuickSelect for contexts where - * space is limited (e.g., mass edit, bulk operations). - * - * Shows icon + profile name in dropdown, with model details below. - * - * @example - * ```tsx - * - * ``` - */ -export function ProfileSelect({ - profiles, - selectedModel, - selectedThinkingLevel, - selectedCursorModel, - onSelect, - testIdPrefix = 'profile-select', - className, - disabled = false, -}: ProfileSelectProps) { - if (profiles.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 = `${PROVIDER_PREFIXES.cursor}${profile.cursorModel || 'auto'}`; - return selectedCursorModel === profileCursorModel; - } - // For Claude profiles - return selectedModel === profile.model && selectedThinkingLevel === profile.thinkingLevel; - }; - - const selectedProfile = profiles.find(isProfileSelected); - - return ( -
- - {selectedProfile && ( -

- {getProfileModelDisplay(selectedProfile)} - {getProfileThinkingDisplay(selectedProfile) && - ` + ${getProfileThinkingDisplay(selectedProfile)}`} -

- )} -
- ); -} diff --git a/apps/ui/src/components/views/board-view/shared/profile-typeahead.tsx b/apps/ui/src/components/views/board-view/shared/profile-typeahead.tsx deleted file mode 100644 index 4080676cf..000000000 --- a/apps/ui/src/components/views/board-view/shared/profile-typeahead.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import * as React from 'react'; -import { Check, ChevronsUpDown, UserCircle, Settings2 } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import { Button } from '@/components/ui/button'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, -} from '@/components/ui/command'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { Badge } from '@/components/ui/badge'; -import type { AIProfile } from '@automaker/types'; -import { CURSOR_MODEL_MAP, profileHasThinking, getCodexModelLabel } from '@automaker/types'; -import { PROVIDER_ICON_COMPONENTS } from '@/components/ui/provider-icon'; - -/** - * Get display string for a profile's model configuration - */ -function getProfileModelDisplay(profile: AIProfile): string { - if (profile.provider === 'cursor') { - const cursorModel = profile.cursorModel || 'auto'; - const modelConfig = CURSOR_MODEL_MAP[cursorModel]; - return modelConfig?.label || cursorModel; - } - if (profile.provider === 'codex') { - return getCodexModelLabel(profile.codexModel || 'codex-gpt-5.2-codex'); - } - if (profile.provider === 'opencode') { - // Extract a short label from the opencode model - const modelId = profile.opencodeModel || ''; - if (modelId.includes('/')) { - const parts = modelId.split('/'); - return parts[parts.length - 1].split('.')[0] || modelId; - } - return modelId; - } - // Claude - return profile.model || 'sonnet'; -} - -/** - * Get display string for a profile's thinking configuration - */ -function getProfileThinkingDisplay(profile: AIProfile): string | null { - if (profile.provider === 'cursor' || profile.provider === 'codex') { - return profileHasThinking(profile) ? 'thinking' : null; - } - // Claude - return profile.thinkingLevel && profile.thinkingLevel !== 'none' ? profile.thinkingLevel : null; -} - -interface ProfileTypeaheadProps { - profiles: AIProfile[]; - selectedProfileId?: string; - onSelect: (profile: AIProfile) => void; - placeholder?: string; - className?: string; - disabled?: boolean; - showManageLink?: boolean; - onManageLinkClick?: () => void; - testIdPrefix?: string; -} - -export function ProfileTypeahead({ - profiles, - selectedProfileId, - onSelect, - placeholder = 'Select profile...', - className, - disabled = false, - showManageLink = false, - onManageLinkClick, - testIdPrefix = 'profile-typeahead', -}: ProfileTypeaheadProps) { - const [open, setOpen] = React.useState(false); - const [inputValue, setInputValue] = React.useState(''); - const [triggerWidth, setTriggerWidth] = React.useState(0); - const triggerRef = React.useRef(null); - - const selectedProfile = React.useMemo( - () => profiles.find((p) => p.id === selectedProfileId), - [profiles, selectedProfileId] - ); - - // Update trigger width when component mounts or value changes - React.useEffect(() => { - if (triggerRef.current) { - const updateWidth = () => { - setTriggerWidth(triggerRef.current?.offsetWidth || 0); - }; - updateWidth(); - const resizeObserver = new ResizeObserver(updateWidth); - resizeObserver.observe(triggerRef.current); - return () => { - resizeObserver.disconnect(); - }; - } - }, [selectedProfileId]); - - // Filter profiles based on input - const filteredProfiles = React.useMemo(() => { - if (!inputValue) return profiles; - const lower = inputValue.toLowerCase(); - return profiles.filter( - (p) => - p.name.toLowerCase().includes(lower) || - p.description?.toLowerCase().includes(lower) || - p.provider.toLowerCase().includes(lower) - ); - }, [profiles, inputValue]); - - const handleSelect = (profile: AIProfile) => { - onSelect(profile); - setInputValue(''); - setOpen(false); - }; - - return ( - - - - - - - - - No profile found. - - {filteredProfiles.map((profile) => { - const ProviderIcon = PROVIDER_ICON_COMPONENTS[profile.provider]; - const isSelected = profile.id === selectedProfileId; - const modelDisplay = getProfileModelDisplay(profile); - const thinkingDisplay = getProfileThinkingDisplay(profile); - - return ( - handleSelect(profile)} - className="flex items-center gap-2 py-2" - data-testid={`${testIdPrefix}-option-${profile.id}`} - > -
- {ProviderIcon ? ( - - ) : ( - - )} -
- {profile.name} - - {modelDisplay} - {thinkingDisplay && ( - + {thinkingDisplay} - )} - -
-
-
- {profile.isBuiltIn && ( - - Built-in - - )} - -
-
- ); - })} -
- {showManageLink && onManageLinkClick && ( - <> - - - { - setOpen(false); - onManageLinkClick(); - }} - className="text-muted-foreground" - data-testid={`${testIdPrefix}-manage-link`} - > - - Manage AI Profiles - - - - )} -
-
-
-
- ); -} diff --git a/apps/ui/src/components/views/github-issues-view.tsx b/apps/ui/src/components/views/github-issues-view.tsx index e8e5536b8..e1e09cade 100644 --- a/apps/ui/src/components/views/github-issues-view.tsx +++ b/apps/ui/src/components/views/github-issues-view.tsx @@ -26,8 +26,7 @@ export function GitHubIssuesView() { const [pendingRevalidateOptions, setPendingRevalidateOptions] = useState(null); - const { currentProject, defaultAIProfileId, aiProfiles, getCurrentWorktree, worktreesByProject } = - useAppStore(); + const { currentProject, getCurrentWorktree, worktreesByProject } = useAppStore(); // Model override for validation const validationModelOverride = useModelOverride({ phase: 'validationModel' }); @@ -45,12 +44,6 @@ export function GitHubIssuesView() { onShowValidationDialogChange: setShowValidationDialog, }); - // Get default AI profile for task creation - const defaultProfile = useMemo(() => { - if (!defaultAIProfileId) return null; - return aiProfiles.find((p) => p.id === defaultAIProfileId) ?? null; - }, [defaultAIProfileId, aiProfiles]); - // Get current branch from selected worktree const currentBranch = useMemo(() => { if (!currentProject?.path) return ''; @@ -99,9 +92,6 @@ export function GitHubIssuesView() { .filter(Boolean) .join('\n'); - // Use profile default model - const featureModel = defaultProfile?.model ?? 'opus'; - const feature = { id: `issue-${issue.number}-${crypto.randomUUID()}`, title: issue.title, @@ -110,8 +100,8 @@ export function GitHubIssuesView() { status: 'backlog' as const, passes: false, priority: getFeaturePriority(validation.estimatedComplexity), - model: featureModel, - thinkingLevel: defaultProfile?.thinkingLevel ?? 'none', + model: 'opus', + thinkingLevel: 'none' as const, branchName: currentBranch, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), @@ -129,7 +119,7 @@ export function GitHubIssuesView() { toast.error(err instanceof Error ? err.message : 'Failed to create task'); } }, - [currentProject?.path, defaultProfile, currentBranch] + [currentProject?.path, currentBranch] ); if (loading) { diff --git a/apps/ui/src/components/views/profiles-view.tsx b/apps/ui/src/components/views/profiles-view.tsx deleted file mode 100644 index e11ec8b63..000000000 --- a/apps/ui/src/components/views/profiles-view.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import { useState, useMemo, useCallback } from 'react'; -import { useAppStore, AIProfile } from '@/store/app-store'; -import { - useKeyboardShortcuts, - useKeyboardShortcutsConfig, - KeyboardShortcut, -} from '@/hooks/use-keyboard-shortcuts'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Sparkles } from 'lucide-react'; -import { toast } from 'sonner'; -import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog'; -import { - DndContext, - DragEndEvent, - PointerSensor, - useSensor, - useSensors, - closestCenter, -} from '@dnd-kit/core'; -import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable'; -import { SortableProfileCard, ProfileForm, ProfilesHeader } from './profiles-view/components'; - -export function ProfilesView() { - const { - aiProfiles, - addAIProfile, - updateAIProfile, - removeAIProfile, - reorderAIProfiles, - resetAIProfiles, - } = useAppStore(); - const shortcuts = useKeyboardShortcutsConfig(); - - const [showAddDialog, setShowAddDialog] = useState(false); - const [editingProfile, setEditingProfile] = useState(null); - const [profileToDelete, setProfileToDelete] = useState(null); - - // Sensors for drag-and-drop - const sensors = useSensors( - useSensor(PointerSensor, { - activationConstraint: { - distance: 5, - }, - }) - ); - - // Separate built-in and custom profiles - const builtInProfiles = useMemo(() => aiProfiles.filter((p) => p.isBuiltIn), [aiProfiles]); - const customProfiles = useMemo(() => aiProfiles.filter((p) => !p.isBuiltIn), [aiProfiles]); - - const handleDragEnd = useCallback( - (event: DragEndEvent) => { - const { active, over } = event; - - if (over && active.id !== over.id) { - const oldIndex = aiProfiles.findIndex((p) => p.id === active.id); - const newIndex = aiProfiles.findIndex((p) => p.id === over.id); - - if (oldIndex !== -1 && newIndex !== -1) { - reorderAIProfiles(oldIndex, newIndex); - } - } - }, - [aiProfiles, reorderAIProfiles] - ); - - const handleAddProfile = (profile: Omit) => { - addAIProfile(profile); - setShowAddDialog(false); - toast.success('Profile created', { - description: `Created "${profile.name}" profile`, - }); - }; - - const handleUpdateProfile = (profile: Omit) => { - if (editingProfile) { - updateAIProfile(editingProfile.id, profile); - setEditingProfile(null); - toast.success('Profile updated', { - description: `Updated "${profile.name}" profile`, - }); - } - }; - - const confirmDeleteProfile = () => { - if (!profileToDelete) return; - - removeAIProfile(profileToDelete.id); - toast.success('Profile deleted', { - description: `Deleted "${profileToDelete.name}" profile`, - }); - setProfileToDelete(null); - }; - - const handleResetProfiles = () => { - resetAIProfiles(); - toast.success('Profiles refreshed', { - description: 'Default profiles have been updated to the latest version', - }); - }; - - // Build keyboard shortcuts for profiles view - const profilesShortcuts: KeyboardShortcut[] = useMemo(() => { - const shortcutsList: KeyboardShortcut[] = []; - - // Add profile shortcut - when in profiles view - shortcutsList.push({ - key: shortcuts.addProfile, - action: () => setShowAddDialog(true), - description: 'Create new profile', - }); - - return shortcutsList; - }, [shortcuts]); - - // Register keyboard shortcuts for profiles view - useKeyboardShortcuts(profilesShortcuts); - - return ( -
- {/* Header Section */} - setShowAddDialog(true)} - addProfileHotkey={shortcuts.addProfile} - /> - - {/* Content */} -
-
- {/* Custom Profiles Section */} -
-
-

Custom Profiles

- - {customProfiles.length} - -
- {customProfiles.length === 0 ? ( -
setShowAddDialog(true)} - > - -

- No custom profiles yet. Create one to get started! -

-
- ) : ( - - p.id)} - strategy={verticalListSortingStrategy} - > -
- {customProfiles.map((profile) => ( - setEditingProfile(profile)} - onDelete={() => setProfileToDelete(profile)} - /> - ))} -
-
-
- )} -
- - {/* Built-in Profiles Section */} -
-
-

Built-in Profiles

- - {builtInProfiles.length} - -
-

- Pre-configured profiles for common use cases. These cannot be edited or deleted. -

- - p.id)} - strategy={verticalListSortingStrategy} - > -
- {builtInProfiles.map((profile) => ( - {}} - onDelete={() => {}} - /> - ))} -
-
-
-
-
-
- - {/* Add Profile Dialog */} - - - - Create New Profile - Define a reusable model configuration preset. - - setShowAddDialog(false)} - isEditing={false} - hotkeyActive={showAddDialog} - /> - - - - {/* Edit Profile Dialog */} - setEditingProfile(null)}> - - - Edit Profile - Modify your profile settings. - - {editingProfile && ( - setEditingProfile(null)} - isEditing={true} - hotkeyActive={!!editingProfile} - /> - )} - - - - {/* Delete Confirmation Dialog */} - !open && setProfileToDelete(null)} - onConfirm={confirmDeleteProfile} - title="Delete Profile" - description={ - profileToDelete - ? `Are you sure you want to delete "${profileToDelete.name}"? This action cannot be undone.` - : '' - } - confirmText="Delete Profile" - testId="delete-profile-confirm-dialog" - confirmTestId="confirm-delete-profile-button" - /> -
- ); -} diff --git a/apps/ui/src/components/views/profiles-view/components/index.ts b/apps/ui/src/components/views/profiles-view/components/index.ts deleted file mode 100644 index 729626a2b..000000000 --- a/apps/ui/src/components/views/profiles-view/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { SortableProfileCard } from './sortable-profile-card'; -export { ProfileForm } from './profile-form'; -export { ProfilesHeader } from './profiles-header'; diff --git a/apps/ui/src/components/views/profiles-view/components/profile-form.tsx b/apps/ui/src/components/views/profiles-view/components/profile-form.tsx deleted file mode 100644 index 1e7090d81..000000000 --- a/apps/ui/src/components/views/profiles-view/components/profile-form.tsx +++ /dev/null @@ -1,560 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Button } from '@/components/ui/button'; -import { HotkeyButton } from '@/components/ui/hotkey-button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; -import { Badge } from '@/components/ui/badge'; -import { cn, modelSupportsThinking } from '@/lib/utils'; -import { DialogFooter } from '@/components/ui/dialog'; -import { Brain } from 'lucide-react'; -import { AnthropicIcon, CursorIcon, OpenAIIcon, OpenCodeIcon } from '@/components/ui/provider-icon'; -import { toast } from 'sonner'; -import type { - AIProfile, - ModelAlias, - ThinkingLevel, - ModelProvider, - CursorModelId, - CodexModelId, - OpencodeModelId, -} from '@automaker/types'; -import { - CURSOR_MODEL_MAP, - cursorModelHasThinking, - CODEX_MODEL_MAP, - OPENCODE_MODELS, - DEFAULT_OPENCODE_MODEL, -} from '@automaker/types'; -import { useAppStore } from '@/store/app-store'; -import { CLAUDE_MODELS, THINKING_LEVELS, ICON_OPTIONS } from '../constants'; - -interface ProfileFormProps { - profile: Partial; - onSave: (profile: Omit) => void; - onCancel: () => void; - isEditing: boolean; - hotkeyActive: boolean; -} - -export function ProfileForm({ - profile, - onSave, - onCancel, - isEditing, - hotkeyActive, -}: ProfileFormProps) { - const { enabledCursorModels } = useAppStore(); - - const [formData, setFormData] = useState({ - name: profile.name || '', - description: profile.description || '', - provider: (profile.provider || 'claude') as ModelProvider, - // Claude-specific - model: profile.model || ('sonnet' as ModelAlias), - thinkingLevel: profile.thinkingLevel || ('none' as ThinkingLevel), - // Cursor-specific - cursorModel: profile.cursorModel || ('auto' as CursorModelId), - // Codex-specific - use a valid CodexModelId from CODEX_MODEL_MAP - codexModel: profile.codexModel || (CODEX_MODEL_MAP.gpt52Codex as CodexModelId), - // OpenCode-specific - opencodeModel: profile.opencodeModel || (DEFAULT_OPENCODE_MODEL as OpencodeModelId), - icon: profile.icon || 'Brain', - }); - - // Sync formData with profile prop when it changes - useEffect(() => { - setFormData({ - name: profile.name || '', - description: profile.description || '', - provider: (profile.provider || 'claude') as ModelProvider, - // Claude-specific - model: profile.model || ('sonnet' as ModelAlias), - thinkingLevel: profile.thinkingLevel || ('none' as ThinkingLevel), - // Cursor-specific - cursorModel: profile.cursorModel || ('auto' as CursorModelId), - // Codex-specific - use a valid CodexModelId from CODEX_MODEL_MAP - codexModel: profile.codexModel || (CODEX_MODEL_MAP.gpt52Codex as CodexModelId), - // OpenCode-specific - opencodeModel: profile.opencodeModel || (DEFAULT_OPENCODE_MODEL as OpencodeModelId), - icon: profile.icon || 'Brain', - }); - }, [profile]); - - const supportsThinking = formData.provider === 'claude' && modelSupportsThinking(formData.model); - - const handleProviderChange = (provider: ModelProvider) => { - setFormData({ - ...formData, - provider, - // Only reset Claude fields when switching TO Claude; preserve otherwise - model: provider === 'claude' ? 'sonnet' : formData.model, - thinkingLevel: provider === 'claude' ? 'none' : formData.thinkingLevel, - // Reset cursor/codex/opencode models when switching to that provider - cursorModel: provider === 'cursor' ? 'auto' : formData.cursorModel, - codexModel: - provider === 'codex' ? (CODEX_MODEL_MAP.gpt52Codex as CodexModelId) : formData.codexModel, - opencodeModel: - provider === 'opencode' - ? (DEFAULT_OPENCODE_MODEL as OpencodeModelId) - : formData.opencodeModel, - }); - }; - - const handleModelChange = (model: ModelAlias) => { - setFormData({ - ...formData, - model, - }); - }; - - const handleCursorModelChange = (cursorModel: CursorModelId) => { - setFormData({ - ...formData, - cursorModel, - }); - }; - - const handleCodexModelChange = (codexModel: CodexModelId) => { - setFormData({ - ...formData, - codexModel, - }); - }; - - const handleOpencodeModelChange = (opencodeModel: OpencodeModelId) => { - setFormData({ - ...formData, - opencodeModel, - }); - }; - - const handleSubmit = () => { - if (!formData.name.trim()) { - toast.error('Please enter a profile name'); - return; - } - - // Ensure model is always set for Claude profiles - const validModels: ModelAlias[] = ['haiku', 'sonnet', 'opus']; - const finalModel = - formData.provider === 'claude' - ? validModels.includes(formData.model) - ? formData.model - : 'sonnet' - : undefined; - - const baseProfile = { - name: formData.name.trim(), - description: formData.description.trim(), - provider: formData.provider, - isBuiltIn: false, - icon: formData.icon, - }; - - if (formData.provider === 'cursor') { - onSave({ - ...baseProfile, - cursorModel: formData.cursorModel, - }); - } else if (formData.provider === 'codex') { - onSave({ - ...baseProfile, - codexModel: formData.codexModel, - }); - } else if (formData.provider === 'opencode') { - onSave({ - ...baseProfile, - opencodeModel: formData.opencodeModel, - }); - } else { - onSave({ - ...baseProfile, - model: finalModel as ModelAlias, - thinkingLevel: supportsThinking ? formData.thinkingLevel : 'none', - }); - } - }; - - return ( - <> -
- {/* Name */} -
- - setFormData({ ...formData, name: e.target.value })} - placeholder="e.g., Heavy Task, Quick Fix" - data-testid="profile-name-input" - /> -
- - {/* Description */} -
- -