fix: AI profile display issues for Codex models

- Fix Codex profiles showing as 'Claude Sonnet' in UI
- Add proper Codex model display in profile cards and selectors
- Add useEffect to sync profile form data when editing profiles
- Update provider badges to show 'Codex' with OpenAI icon
- Enhance profile selection validation across feature dialogs
- Add getCodexModelLabel support to display functions

The issue was that profile display functions only handled Claude and Cursor
providers, causing Codex profiles to fallback to 'sonnet' display.
This commit is contained in:
DhanushSantosh
2026-01-08 17:13:45 +05:30
parent d253d494ba
commit 8a9715adef
9 changed files with 122 additions and 20 deletions

View File

@@ -368,11 +368,23 @@ export function AddFeatureDialog({
thinkingLevel: 'none', // Cursor handles thinking internally
});
} else {
// Claude profile
// Claude profile - ensure model is always set from profile
const profileModel = profile.model;
if (!profileModel || !['haiku', 'sonnet', 'opus'].includes(profileModel)) {
console.warn(
`[ProfileSelect] Invalid or missing model "${profileModel}" for profile "${profile.name}", defaulting to sonnet`
);
}
setNewFeature({
...newFeature,
model: profile.model || 'sonnet',
thinkingLevel: profile.thinkingLevel || 'none',
model:
profileModel && ['haiku', 'sonnet', 'opus'].includes(profileModel)
? profileModel
: 'sonnet',
thinkingLevel:
profile.thinkingLevel && profile.thinkingLevel !== 'none'
? profile.thinkingLevel
: 'none',
});
}
};

View File

@@ -256,11 +256,23 @@ export function EditFeatureDialog({
thinkingLevel: 'none', // Cursor handles thinking internally
});
} else {
// Claude profile
// Claude profile - ensure model is always set from profile
const profileModel = profile.model;
if (!profileModel || !['haiku', 'sonnet', 'opus'].includes(profileModel)) {
console.warn(
`[ProfileSelect] Invalid or missing model "${profileModel}" for profile "${profile.name}", defaulting to sonnet`
);
}
setEditingFeature({
...editingFeature,
model: profile.model || 'sonnet',
thinkingLevel: profile.thinkingLevel || 'none',
model:
profileModel && ['haiku', 'sonnet', 'opus'].includes(profileModel)
? profileModel
: 'sonnet',
thinkingLevel:
profile.thinkingLevel && profile.thinkingLevel !== 'none'
? profile.thinkingLevel
: 'none',
});
}
};

View File

@@ -2,6 +2,7 @@ import type { ModelAlias } from '@/store/app-store';
import type { ModelProvider, ThinkingLevel, ReasoningEffort } from '@automaker/types';
import { CURSOR_MODEL_MAP, CODEX_MODEL_MAP } from '@automaker/types';
import { Brain, Zap, Scale, Cpu, Rocket, Sparkles } from 'lucide-react';
import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon';
export type ModelOption = {
id: string; // Claude models use ModelAlias, Cursor models use "cursor-{id}"
@@ -142,4 +143,7 @@ export const PROFILE_ICONS: Record<string, React.ComponentType<{ className?: str
Cpu,
Rocket,
Sparkles,
Anthropic: AnthropicIcon,
Cursor: CursorIcon,
Codex: OpenAIIcon,
};

View File

@@ -2,7 +2,12 @@ import { Label } from '@/components/ui/label';
import { Brain, UserCircle, 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 } from '@automaker/types';
import {
CURSOR_MODEL_MAP,
profileHasThinking,
PROVIDER_PREFIXES,
getCodexModelLabel,
} from '@automaker/types';
import { PROFILE_ICONS } from './model-constants';
/**
@@ -14,6 +19,9 @@ function getProfileModelDisplay(profile: AIProfile): string {
const modelConfig = CURSOR_MODEL_MAP[cursorModel];
return modelConfig?.label || cursorModel;
}
if (profile.provider === 'codex') {
return getCodexModelLabel(profile.codexModel || 'gpt-5.2-codex');
}
// Claude
return profile.model || 'sonnet';
}
@@ -26,6 +34,10 @@ function getProfileThinkingDisplay(profile: AIProfile): string | null {
// 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;
}

View File

@@ -8,7 +8,12 @@ import {
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 } from '@automaker/types';
import {
CURSOR_MODEL_MAP,
profileHasThinking,
PROVIDER_PREFIXES,
getCodexModelLabel,
} from '@automaker/types';
import { PROFILE_ICONS } from './model-constants';
/**
@@ -20,6 +25,9 @@ function getProfileModelDisplay(profile: AIProfile): string {
const modelConfig = CURSOR_MODEL_MAP[cursorModel];
return modelConfig?.label || cursorModel;
}
if (profile.provider === 'codex') {
return getCodexModelLabel(profile.codexModel || 'gpt-5.2-codex');
}
// Claude
return profile.model || 'sonnet';
}
@@ -32,6 +40,10 @@ function getProfileThinkingDisplay(profile: AIProfile): string | null {
// 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;
}

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { HotkeyButton } from '@/components/ui/hotkey-button';
import { Input } from '@/components/ui/input';
@@ -53,15 +53,33 @@ export function ProfileForm({
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),
icon: profile.icon || 'Brain',
});
}, [profile]);
const supportsThinking = formData.provider === 'claude' && modelSupportsThinking(formData.model);
const handleProviderChange = (provider: ModelProvider) => {
setFormData({
...formData,
provider,
// Reset to defaults when switching providers
// 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 models when switching to that provider
cursorModel: provider === 'cursor' ? 'auto' : formData.cursorModel,
codexModel:
provider === 'codex' ? (CODEX_MODEL_MAP.gpt52Codex as CodexModelId) : formData.codexModel,
@@ -95,6 +113,15 @@ export function ProfileForm({
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(),
@@ -116,7 +143,7 @@ export function ProfileForm({
} else {
onSave({
...baseProfile,
model: formData.model,
model: finalModel as ModelAlias,
thinkingLevel: supportsThinking ? formData.thinkingLevel : 'none',
});
}

View File

@@ -1,11 +1,12 @@
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { GripVertical, Lock, Pencil, Trash2, Brain, Bot, Terminal } from 'lucide-react';
import { GripVertical, Lock, Pencil, Trash2 } from 'lucide-react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import type { AIProfile } from '@automaker/types';
import { CURSOR_MODEL_MAP, profileHasThinking } from '@automaker/types';
import { CURSOR_MODEL_MAP, profileHasThinking, getCodexModelLabel } from '@automaker/types';
import { PROFILE_ICONS } from '../constants';
import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon';
interface SortableProfileCardProps {
profile: AIProfile;
@@ -24,7 +25,13 @@ export function SortableProfileCard({ profile, onEdit, onDelete }: SortableProfi
opacity: isDragging ? 0.5 : 1,
};
const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : Brain;
const getDefaultIcon = () => {
if (profile.provider === 'cursor') return CursorIcon;
if (profile.provider === 'codex') return OpenAIIcon;
return AnthropicIcon;
};
const IconComponent = profile.icon ? PROFILE_ICONS[profile.icon] : getDefaultIcon();
return (
<div
@@ -72,11 +79,17 @@ export function SortableProfileCard({ profile, onEdit, onDelete }: SortableProfi
{/* Provider badge */}
<span className="text-xs px-2 py-0.5 rounded-full border border-border text-muted-foreground bg-muted/50 flex items-center gap-1">
{profile.provider === 'cursor' ? (
<Terminal className="w-3 h-3" />
<CursorIcon className="w-3 h-3" />
) : profile.provider === 'codex' ? (
<OpenAIIcon className="w-3 h-3" />
) : (
<Bot className="w-3 h-3" />
<AnthropicIcon className="w-3 h-3" />
)}
{profile.provider === 'cursor' ? 'Cursor' : 'Claude'}
{profile.provider === 'cursor'
? 'Cursor'
: profile.provider === 'codex'
? 'Codex'
: 'Claude'}
</span>
{/* Model badge */}
@@ -85,7 +98,9 @@ export function SortableProfileCard({ profile, onEdit, onDelete }: SortableProfi
? CURSOR_MODEL_MAP[profile.cursorModel || 'auto']?.label ||
profile.cursorModel ||
'auto'
: profile.model || 'sonnet'}
: profile.provider === 'codex'
? getCodexModelLabel(profile.codexModel || 'gpt-5.2-codex')
: profile.model || 'sonnet'}
</span>
{/* Thinking badge - works for both providers */}

View File

@@ -1,5 +1,6 @@
import { Brain, Zap, Scale, Cpu, Rocket, Sparkles } from 'lucide-react';
import type { ModelAlias, ThinkingLevel } from '@/store/app-store';
import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon';
// Icon mapping for profiles
export const PROFILE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {
@@ -9,6 +10,9 @@ export const PROFILE_ICONS: Record<string, React.ComponentType<{ className?: str
Cpu,
Rocket,
Sparkles,
Anthropic: AnthropicIcon,
Cursor: CursorIcon,
Codex: OpenAIIcon,
};
// Available icons for selection
@@ -19,6 +23,9 @@ export const ICON_OPTIONS = [
{ name: 'Cpu', icon: Cpu },
{ name: 'Rocket', icon: Rocket },
{ name: 'Sparkles', icon: Sparkles },
{ name: 'Anthropic', icon: AnthropicIcon },
{ name: 'Cursor', icon: CursorIcon },
{ name: 'Codex', icon: OpenAIIcon },
];
// Model options for the form