feature/codex-cli

This commit is contained in:
DhanushSantosh
2026-01-06 04:52:25 +05:30
parent 4d4025ca06
commit a57dcc170d
54 changed files with 5562 additions and 91 deletions

View File

@@ -7,7 +7,8 @@ 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, Bot, Terminal } from 'lucide-react';
import { Brain } from 'lucide-react';
import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon';
import { toast } from 'sonner';
import type {
AIProfile,
@@ -15,8 +16,9 @@ import type {
ThinkingLevel,
ModelProvider,
CursorModelId,
CodexModelId,
} from '@automaker/types';
import { CURSOR_MODEL_MAP, cursorModelHasThinking } from '@automaker/types';
import { CURSOR_MODEL_MAP, cursorModelHasThinking, CODEX_MODEL_MAP } from '@automaker/types';
import { useAppStore } from '@/store/app-store';
import { CLAUDE_MODELS, THINKING_LEVELS, ICON_OPTIONS } from '../constants';
@@ -46,6 +48,8 @@ export function ProfileForm({
thinkingLevel: profile.thinkingLevel || ('none' as ThinkingLevel),
// Cursor-specific
cursorModel: profile.cursorModel || ('auto' as CursorModelId),
// Codex-specific
codexModel: profile.codexModel || ('gpt-5.2' as CodexModelId),
icon: profile.icon || 'Brain',
});
@@ -59,6 +63,7 @@ export function ProfileForm({
model: provider === 'claude' ? 'sonnet' : formData.model,
thinkingLevel: provider === 'claude' ? 'none' : formData.thinkingLevel,
cursorModel: provider === 'cursor' ? 'auto' : formData.cursorModel,
codexModel: provider === 'codex' ? 'gpt-5.2' : formData.codexModel,
});
};
@@ -76,6 +81,13 @@ export function ProfileForm({
});
};
const handleCodexModelChange = (codexModel: CodexModelId) => {
setFormData({
...formData,
codexModel,
});
};
const handleSubmit = () => {
if (!formData.name.trim()) {
toast.error('Please enter a profile name');
@@ -95,6 +107,11 @@ export function ProfileForm({
...baseProfile,
cursorModel: formData.cursorModel,
});
} else if (formData.provider === 'codex') {
onSave({
...baseProfile,
codexModel: formData.codexModel,
});
} else {
onSave({
...baseProfile,
@@ -158,34 +175,48 @@ export function ProfileForm({
{/* Provider Selection */}
<div className="space-y-2">
<Label>AI Provider</Label>
<div className="flex gap-2">
<div className="grid grid-cols-3 gap-2">
<button
type="button"
onClick={() => handleProviderChange('claude')}
className={cn(
'flex-1 px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-center gap-2',
'px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-center gap-2',
formData.provider === 'claude'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background hover:bg-accent border-border'
)}
data-testid="provider-select-claude"
>
<Bot className="w-4 h-4" />
<AnthropicIcon className="w-4 h-4" />
Claude
</button>
<button
type="button"
onClick={() => handleProviderChange('cursor')}
className={cn(
'flex-1 px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-center gap-2',
'px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-center gap-2',
formData.provider === 'cursor'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background hover:bg-accent border-border'
)}
data-testid="provider-select-cursor"
>
<Terminal className="w-4 h-4" />
Cursor CLI
<CursorIcon className="w-4 h-4" />
Cursor
</button>
<button
type="button"
onClick={() => handleProviderChange('codex')}
className={cn(
'px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-center gap-2',
formData.provider === 'codex'
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background hover:bg-accent border-border'
)}
data-testid="provider-select-codex"
>
<OpenAIIcon className="w-4 h-4" />
Codex
</button>
</div>
</div>
@@ -222,7 +253,7 @@ export function ProfileForm({
{formData.provider === 'cursor' && (
<div className="space-y-2">
<Label className="flex items-center gap-2">
<Terminal className="w-4 h-4 text-primary" />
<CursorIcon className="w-4 h-4 text-primary" />
Cursor Model
</Label>
<div className="flex flex-col gap-2">
@@ -283,6 +314,77 @@ export function ProfileForm({
</div>
)}
{/* Codex Model Selection */}
{formData.provider === 'codex' && (
<div className="space-y-2">
<Label className="flex items-center gap-2">
<OpenAIIcon className="w-4 h-4 text-primary" />
Codex Model
</Label>
<div className="flex flex-col gap-2">
{Object.entries(CODEX_MODEL_MAP).map(([key, modelId]) => {
const modelConfig = {
gpt52Codex: { label: 'GPT-5.2-Codex', badge: 'Premium', hasReasoning: true },
gpt52: { label: 'GPT-5.2', badge: 'Premium', hasReasoning: true },
gpt51CodexMax: {
label: 'GPT-5.1-Codex-Max',
badge: 'Premium',
hasReasoning: true,
},
gpt51Codex: { label: 'GPT-5.1-Codex', badge: 'Balanced' },
gpt51CodexMini: { label: 'GPT-5.1-Codex-Mini', badge: 'Speed' },
gpt51: { label: 'GPT-5.1', badge: 'Standard' },
o3Mini: { label: 'o3-mini', badge: 'Reasoning', hasReasoning: true },
o4Mini: { label: 'o4-mini', badge: 'Reasoning', hasReasoning: true },
}[key as keyof typeof CODEX_MODEL_MAP] || { label: modelId, badge: 'Standard' };
return (
<button
key={modelId}
type="button"
onClick={() => handleCodexModelChange(modelId)}
className={cn(
'w-full px-3 py-2 rounded-md border text-sm font-medium transition-colors flex items-center justify-between',
formData.codexModel === modelId
? 'bg-primary text-primary-foreground border-primary'
: 'bg-background hover:bg-accent border-border'
)}
data-testid={`codex-model-select-${modelId}`}
>
<span>{modelConfig.label}</span>
<div className="flex gap-1">
{modelConfig.hasReasoning && (
<Badge
variant="outline"
className={cn(
'text-xs',
formData.codexModel === modelId
? 'border-primary-foreground/50 text-primary-foreground'
: 'border-amber-500/50 text-amber-600 dark:text-amber-400'
)}
>
Reasoning
</Badge>
)}
<Badge
variant="outline"
className={cn(
'text-xs',
formData.codexModel === modelId
? 'border-primary-foreground/50 text-primary-foreground'
: 'border-muted-foreground/50 text-muted-foreground'
)}
>
{modelConfig.badge}
</Badge>
</div>
</button>
);
})}
</div>
</div>
)}
{/* Claude Thinking Level */}
{formData.provider === 'claude' && supportsThinking && (
<div className="space-y-2">